Skip to content

feat(auto-routing): owner-configurable Efficient model pools - #4846

Open
iscekic wants to merge 20 commits into
mainfrom
efficient-custom-pool-e6e3
Open

feat(auto-routing): owner-configurable Efficient model pools#4846
iscekic wants to merge 20 commits into
mainfrom
efficient-custom-pool-e6e3

Conversation

@iscekic

@iscekic iscekic commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Summary

What. kilo-auto/efficient can now be customized per owner: a personal user or an organization configures 1–10 exact (managed model, canonical catalog variant) pairs on the existing Auto routing card. Missing exact pairs are benchmarked on demand through the existing benchmark Worker; routing then decides only among ready, compatible entries and keeps the balanced fallback whenever none are usable.

Why. Owners want the Efficient auto model constrained to the exact models and variants they trust (same-model xhigh vs max stay distinct end-to-end) without losing the platform default when their pool cannot serve.

How.

  • Shared contracts for Pool entries / Benchmark profiles with exact-pair identity (packages/auto-routing-contracts), ADR 0002 updates, canonical terms in CONTEXT.md.
  • Global benchmark profile registry in the benchmark Worker: atomic all-or-nothing D1 admission (10 new/retried pairs per owner per rolling 24h, globally deduplicated), exact-pair composite keys with a rebuild-safe migration, profile-purpose runs on the existing single decider slot, and sparse custom routing tables assembled from ready/current profiles only.
  • Auto-routing Worker: pools stored beside mode on the existing routing-settings Durable Object (mixed put/delete commits in one storage transaction), custom decide path failing closed on inactive/missing capability rows, org → personal → platform inheritance resolved independently per field.
  • Gateway resolution serves the exact catalog variant (full settings, not effort-only mapping); a removed variant or unusable pool falls back to balanced. Legacy reasoningEffort decisions stay parseable.
  • Web: validated settings API (enabled/visible/managed catalog entries only; virtual, experiment, hidden, denied, BYOK-only rejected) and the pool editor on the existing Auto routing card for personal and organization settings (owners/billing managers edit; members read-only). Deploy-order safe during the web-before-worker window: the card falls back to the legacy mode route (poolSupported: false hides pool controls; mode saves stay mode-only at every worker version; a mid-session rollback answers a retryable 503 instead of mis-saving). Malformed worker settings responses degrade to a graceful 502.

Verification

  • Local E2E (full local stack: nextjs + auto-routing + auto-routing-benchmark + D1, Chromium + curl + wrangler d1 + psql) — PASSED, all 10 acceptance criteria:
    1. Personal/org pools save, clear, and inherit independently (org override served claude-haiku-4.5; clearing restored personal claude-sonnet-4.5, instrumented proof; mode and pool saves independent).
    2. Member read-only (disabled controls, PUT → 401); billing_manager flip restores full edit.
    3. Validation rejects hidden/virtual/missing-variant/unknown ids with specific reasons at API and UI.
    4. Same-model xhigh + max persist and render as distinct rows and distinct D1 PKs.
    5. Benchmarking state with 15s polling that transitions to Ready and stops at terminal.
    6. Failed → Retry re-admits (ledger +1); org-denied entry renders Unavailable with no retry and is removable; 10/24h quota rejects with retryAt copy, atomically (zero partial rows).
    7. Empty/inherited copy verified verbatim for personal and org cards.
    8. Load and save failures are retryable with Try again CTAs and recover.
    9. Real routing assertion (real kilo-auto/efficient calls only): seeded ready pool constrained the decision to pool entries (custom table, 18 routes, threshold-winner xhigh); stale profiles → balanced fallback with uncharged re-admission; no pool → default platform path.
    10. 19 state screenshots at 375px + desktop (personal + org variants of ready, benchmarking, failed/retry, unavailable, quota, empty, load-error, member read-only).
    • Deploy-order fallback matrix skipped as structurally unproducible in a single-tree local stack; covered by automated route/card tests including the rollback-503 path.
  • Automated coverage on the latest head: contracts 115 tests, benchmark 235, auto-routing 177, web focused 119 + admin-client 36; typecheck, lint, format, dependency-cycle, changes-only repo typecheck all clean; fresh cumulative review over the whole diff: no findings.

Visual Changes

Before After
Auto routing card with ranking-mode controls only Auto routing card with Efficient model pool editor: entry rows with model + canonical variant pickers, per-entry status (Ready / Benchmarking / Failed with Retry / Unavailable with remove), 1–10 entry cap, Clear pool, quota messaging — for personal and organization settings; organization cards explain inheritance ("No organization override…"); pool controls hidden with a rollout note while backends update. States captured at 375px and desktop during E2E (see Verification).

Reviewer Notes

  • Focus: exact-pair identity at every hop (picker → contracts → D1 composite keys → assembly → decide/sticky → gateway getModelVariants); admission vs save atomicity across the two workers (quota 429 with retryAt, all-or-nothing D1 admission, one DO storage transaction); the deploy-order fallback matrix (old web + new worker, new web + old worker, rollback mid-session).
  • D1 migration 0005 rebuilds the profile tables with backfill — verify the generated SQL preserves legacy rows and exact-pair keys; 0006/0007 are additive.
  • Accepted residuals: same-owner concurrent quota-boundary racing (documented in-code; quota is throttling, per-pair charge integrity is atomic); deploy-window pool saves answer a retryable 503 by design rather than risking a silent pool clear or resurrection.

Follow-ups noted during review (non-blocking)

  • createWorkerAdminFetch keeps the throw-on-malformed convention for the sibling admin routes; the settings client now sets the safer safeParse→502 precedent those can adopt.
  • The new 502 mapping reports intermediary non-JSON failures (edge HTML error pages) as 502 rather than the upstream status; genuine worker error responses are always JSON, so this only affects edge cases that surface as the same retryable load/save error either way.
  • Degrade paths have no log/Sentry signal yet (consistent with the route's pre-existing 502 branches).

iscekic added 18 commits July 28, 2026 16:18
A bare session target intermittently resolves to a same-named window and
fails with "index 0 in use". Use the session: colon form and record the
learning.
… contracts

Add Pool entry / Efficient model pool schemas with a collision-safe
poolEntryKey, optional canonical variant on routing candidates and
benchmark decisions with legacy reasoningEffort compatibility, the sparse
CustomRoutingTableSchema, benchmark profile registry wire types, and owner
pool settings contracts. Extend ADR 0002 with owner-pool invariants and
record Auto Routing scope and canonical terms in CONTEXT.md.
…results

Composite (model, variant) primary keys across run_models, model_summaries,
and case_results with a generated migration that backfills variant from
reasoning_effort; exact-pair queue messages, container lanes, carry matching,
and summaries; routing-table candidates self-describe their variant. The
published platform routing table JSON is unchanged in shape. Bounded contract
extensions: optional variant on BenchmarkModelSummary and
BenchmarkDeciderModel with legacy reasoningEffort compatibility.
…-e6e3

# Conflicts:
#	.kilo_workflow/dispatch-role.sh
When kilo-auto/efficient decisions carry a canonical variant, apply the
full getModelVariants settings (reasoning + verbosity). Missing catalog
keys fall back to balanced; legacy effort-only decisions stay unchanged.
Store Efficient model pools on the routing-mode DO, admit profiles
before settings PUT, and resolve custom tables with exact-pair sticky
and custom-only isActive fail-closed.
…atomic admission

Add benchmark_profiles and profile_request_events D1 tables, one
all-or-nothing admission batch with 10-per-owner-per-24h quota and
global dedupe, concurrency-safe guarded upserts and conditional
charges, and the internal register/status endpoints.
Cover the charged-event, register-upsert, and status-insert builders
against node:sqlite so column-count or guard regressions fail the suite
behind the drizzle mock.
Validate Efficient pool entries against the owner's effective managed
catalog, proxy worker settings, annotate unavailable saved entries, and
preserve benchmark quota 429 retryAt for the UI.
…, sparse custom tables

Wire startRun(purpose, entries), terminal profile transitions, pending-batch
drain on the existing one-active-decider slot with platform priority,
POST /admin/custom-routing-table with provenance-bound assembly, and ops
docs. Platform artifact path unchanged.
Extend AutoRoutingModeCard to manage mode + 1-10 pool entries via
/api/auto-routing/settings, with benchmark status, polling, retry,
and org billing-manager edit permission isolation.
…-e6e3

# Conflicts:
#	.kilo_workflow/dispatch-role.sh
A mixed save (mode set + pool cleared, or the reverse) fanned out to
independent multi-key put and delete ops via Promise.all; one succeeding
while the other threw diverged mode and pool despite the 'commit or fail
together' contract. Wrap the mutation in ctx.storage.transaction and cover
rollback with an honest transactional storage stub.
Production promotes web before workers, so every merge runs new web
against workers that lack /admin/routing-settings (JSON 404). Load now
falls back to the legacy mode route and reports poolSupported: false,
hiding pool controls instead of failing the card; mode saves from that
state use the legacy mode endpoint, which never touches pool keys at any
worker version. A settings PUT that 404s (rollback mid-session from a
supported UI) answers a retryable 503 rather than silently mis-saving.
…gration truth

ADR 0002 and the benchmark README now match the shipped behavior: owner
mode+pool commit in one DO storage transaction; the web-before-worker
deploy window serves the legacy-synthesis fallback (poolSupported: false,
mode-only legacy saves, retryable 503 on settings PUT 404); admission's
DO-after-admit semantics are written down; and the rollback section stops
claiming the D1 schema is additive now that 0005 rebuilds profile tables
with backfill.
@iscekic iscekic self-assigned this Jul 28, 2026
Comment thread apps/web/src/lib/ai-gateway/auto-routing-admin-client.ts Outdated
Comment thread apps/web/src/app/api/auto-routing/settings/route.ts
Comment thread services/auto-routing-benchmark/src/profiles.ts
@kilo-code-bot

kilo-code-bot Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

This incremental review confirms the prior admin-client/route.ts graceful-502 findings are fixed (JSON parse errors and schema-mismatched 2xx bodies now safely degrade to 502 instead of throwing), and the profiles.ts quota-race finding is unchanged, out of scope for this diff, and recorded as an accepted residual risk by the author.

Files Reviewed (4 files)
  • apps/web/src/lib/ai-gateway/auto-routing-admin-client.ts - fix verified (safeParse + guarded response.json())
  • apps/web/src/lib/ai-gateway/auto-routing-admin-client.test.ts - new regression tests for the fix
  • .kilo_workflow/learnings/auto-routing-benchmark-d1-fixtures.md - documentation only
  • .kilo_workflow/learnings/web-gateway-org-context-and-fake-login.md - documentation only
Previous Review Summary (commit 608e3d5)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 608e3d5)

Status: 3 Issues Found | Recommendation: Address before merge

Executive Summary

The most notable risk is in services/auto-routing-benchmark/src/profiles.ts, where the per-owner benchmark-admission quota check is not atomic with the event insert, letting concurrent requests exceed the documented 10-per-24h cap; two additional findings show the intended graceful-502 fallback for a stale/rolled-back auto-routing worker response is actually unreachable because the admin client throws first.

Overview

Severity Count
CRITICAL 0
WARNING 3
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/web/src/lib/ai-gateway/auto-routing-admin-client.ts 136 .parse() on a 2xx worker body throws instead of degrading gracefully
apps/web/src/app/api/auto-routing/settings/route.ts 120 Graceful-502 fallback for malformed worker responses is unreachable
services/auto-routing-benchmark/src/profiles.ts 416 TOCTOU race in per-owner benchmark-admission quota check
Files Reviewed (60 files)
  • .kilo_workflow/learnings/tmux-new-window-index-in-use-name-prefix-collision.md
  • CONTEXT.md
  • apps/web/src/app/api/auto-routing/settings/route.ts - 1 issue
  • apps/web/src/app/api/auto-routing/settings/route.test.ts
  • apps/web/src/components/auto-routing/AutoRoutingModeCard.tsx
  • apps/web/src/components/auto-routing/AutoRoutingModeCard.test.ts
  • apps/web/src/components/organizations/providers-and-models/OrganizationProvidersAndModelsPage.tsx
  • apps/web/src/lib/ai-gateway/auto-model/resolution.ts
  • apps/web/src/lib/ai-gateway/auto-model/resolution.test.ts
  • apps/web/src/lib/ai-gateway/auto-routing-admin-client.ts - 1 issue
  • apps/web/src/lib/ai-gateway/auto-routing-admin-client.test.ts
  • apps/web/src/lib/ai-gateway/auto-routing-pool-validation.ts
  • apps/web/src/lib/ai-gateway/auto-routing-pool-validation.test.ts
  • docs/adr/0002-auto-routing-efficient.md
  • packages/auto-routing-contracts/src/benchmark.ts
  • packages/auto-routing-contracts/src/contracts.test.ts
  • packages/auto-routing-contracts/src/index.ts
  • packages/auto-routing-contracts/src/routing-table.test.ts
  • packages/auto-routing-contracts/src/routing-table.ts
  • services/auto-routing-benchmark/README.md
  • services/auto-routing-benchmark/migrations/0005_fuzzy_senator_kelly.sql
  • services/auto-routing-benchmark/migrations/0006_hard_blizzard.sql
  • services/auto-routing-benchmark/migrations/0007_complex_makkari.sql
  • services/auto-routing-benchmark/migrations/meta/0005_snapshot.json
  • services/auto-routing-benchmark/migrations/meta/0006_snapshot.json
  • services/auto-routing-benchmark/migrations/meta/0007_snapshot.json
  • services/auto-routing-benchmark/migrations/meta/_journal.json
  • services/auto-routing-benchmark/src/admin.ts
  • services/auto-routing-benchmark/src/admin.test.ts
  • services/auto-routing-benchmark/src/auto-decider-sync.ts
  • services/auto-routing-benchmark/src/auto-decider-sync.test.ts
  • services/auto-routing-benchmark/src/cli-runner.ts
  • services/auto-routing-benchmark/src/cli-runner.test.ts
  • services/auto-routing-benchmark/src/custom-routing-table.ts
  • services/auto-routing-benchmark/src/custom-routing-table.test.ts
  • services/auto-routing-benchmark/src/db-replace-summaries.test.ts
  • services/auto-routing-benchmark/src/db-save-routing-table.test.ts
  • services/auto-routing-benchmark/src/db-schema.ts
  • services/auto-routing-benchmark/src/db.test.ts
  • services/auto-routing-benchmark/src/db.ts
  • services/auto-routing-benchmark/src/profile-runs.test.ts
  • services/auto-routing-benchmark/src/profile-transition-sql.test.ts
  • services/auto-routing-benchmark/src/profiles-sql.test.ts
  • services/auto-routing-benchmark/src/profiles.test.ts
  • services/auto-routing-benchmark/src/profiles.ts - 1 issue
  • services/auto-routing-benchmark/src/reasoning-effort.ts
  • services/auto-routing-benchmark/src/routing-table-builder.ts
  • services/auto-routing-benchmark/src/routing-table-builder.test.ts
  • services/auto-routing-benchmark/src/run-process-job.test.ts
  • services/auto-routing-benchmark/src/run.test.ts
  • services/auto-routing-benchmark/src/run.ts
  • services/auto-routing/src/admin-routing-settings.ts
  • services/auto-routing/src/benchmark-origin.ts
  • services/auto-routing/src/decide.ts
  • services/auto-routing/src/decision-cache.ts
  • services/auto-routing/src/decision-cache.test.ts
  • services/auto-routing/src/decision-engine.ts
  • services/auto-routing/src/decision-engine.test.ts
  • services/auto-routing/src/index.ts
  • services/auto-routing/src/index.test.ts
  • services/auto-routing/src/model-capabilities.ts
  • services/auto-routing/src/model-capabilities.test.ts
  • services/auto-routing/src/routing-mode.ts
  • services/auto-routing/src/routing-mode.test.ts

Fix these issues in Kilo Cloud


Reviewed by claude-sonnet-5 · Input: 20 · Output: 5K · Cached: 419.7K

Review guidance: REVIEW.md from base branch main

iscekic added 2 commits July 29, 2026 01:08
Verifier-authored from the owner-pool E2E run: deterministic benchmark
profile D1 fixture facts (engine identity, switch_cost_factor floor,
config prerequisites, provenance binding, uncharged stale re-admission)
and local gateway/auth facts (org-context bearer path, balance top-up
counter, fake-login cookie behavior, org deny-list fixture lever).
fetchAutoRoutingSettingsAdmin parsed success bodies with .parse(), so an
unexpected-shape or non-JSON 2xx (old/rolled-back worker, proxy error
page) threw through the route and surfaced as an unhandled 500 instead of
the intended graceful 502. safeParse plus a guarded .json() now return
the 502 result the route already maps, making the deploy-order safety net
reachable. Addresses two Kilobot review threads on PR #4846.
@iscekic

iscekic commented Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

(bot) @kilocode-bot please review the latest head 3239b9d — it addresses your two malformed-response findings from the previous head; the third thread was replied to with rationale and resolved.

@kilo-code-bot

kilo-code-bot Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Follow-up review at 3239b9d78 (diff vs 608e3d5)

Scope of the delta: apps/web/src/lib/ai-gateway/auto-routing-admin-client.ts (+21/-2), its test file (+50), and two .kilo_workflow/learnings/*.md notes. Nothing else changed.

Finding 1 — malformed JSON from worker: fixed

fetchAutoRoutingSettingsAdmin now wraps response.json() in try/catch and returns { status: 502, body: { error: 'Invalid worker settings response' } } instead of letting the SyntaxError escape into route.ts. Regression test: "returns 502 for 2xx non-JSON bodies without throwing" (exercised through updateAutoRoutingSettings).

Finding 2 — schema-mismatched 2xx body: fixed

The AutoRoutingSettingsResponseSchema.parse(body) throw site is replaced with safeParse + a 502 degrade, matching the existing 502 contract in workerResultResponse. Regression tests: "returns 502 for 2xx bodies that fail settings schema validation" plus "passes through non-2xx worker error bodies", which guards the 404 status passthrough that GET relies on for the legacy-mode fallback. Coverage looks adequate; CI (test, typecheck, lint, format-check) is green on this head.

Finding 3 — quota race in services/auto-routing-benchmark/src/profiles.ts: unchanged, as expected

git diff 608e3d5..3239b9d78 -- services/auto-routing-benchmark/ is empty, so the accepted-residual-risk decision was not silently altered by the fix. No further comment.

New observations (non-blocking)

  1. Non-JSON non-2xx responses now lose the upstream status. The try/catch sits before the !response.ok branch, so any non-2xx whose body isn't JSON (edge/CF HTML error page, empty body, plain-text 429/503 from an intermediary) is reported as 502 Invalid worker settings response rather than the real status. This is strictly better than the previous throw, but it bypasses three status-specific paths in route.ts: the GET 404 → getAutoRoutingMode legacy fallback, the PUT 404 → 503 pool_temporarily_unavailable mapping, and the 429 quota-body passthrough. The worker's own createNotFoundHandler returns JSON { error: 'Not found' }, so genuine worker 404s are unaffected — only intermediary/edge failures hit this. If you want it tightened: on parse failure, preserve response.status for non-2xx (body: { error: \Request failed: ${response.status}` }`) and reserve 502 for 2xx only. There is also no test for the "non-2xx + non-JSON body" combination, which is exactly this case.
  2. No observability on either degrade path. Both 502 returns are silent, so worker contract drift is indistinguishable from an unparseable/edge body in logs. A console.error/Sentry breadcrumb carrying response.status and the zod issue paths (no body contents, per the logging baseline) would make this diagnosable. The pre-existing 502 branches in route.ts have the same gap, so this is consistent rather than a regression.
  3. The shared helper still has the original bug shape. worker-admin-fetch.ts (createWorkerAdminFetch) still does bare await response.json() and schema.parse(body), so sibling callers in this same module — getAutoRoutingMode (used by the GET legacy fallback), updateAutoRoutingMode, and the classifier endpoints — can still throw on malformed or mismatched worker responses. Pre-existing and out of scope for this diff, but worth a follow-up now that the settings path sets the safer precedent.

No blocking issues; both reported findings are genuinely fixed.

@kilo-code-bot

kilo-code-bot Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review already posted at #4846 (comment) — both findings confirmed fixed, third thread confirmed unchanged/out of scope. No further action needed from my side; the non-blocking observations (404-body edge case, missing logging, worker-admin-fetch.ts follow-up) are informational only and don't block merge.

@iscekic iscekic added the human-ready The PR is ready for human review. label Jul 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

human-ready The PR is ready for human review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant