Skip to content

fix(auto-routing): survive container rollouts and make scheduler limits admin-configurable - #7614

Merged
iscekic merged 4 commits into
mainfrom
feat/auto-routing-admin-scheduler-config
Oct 16, 2026
Merged

iscekic merged 4 commits into
mainfrom
feat/auto-routing-admin-scheduler-config

Conversation

@iscekic

@iscekic iscekic commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Two benchmark robustness problems, fixed together.

1. Every worker deploy killed ~15 measurement lanes

Every deploy from the pipeline (including unrelated packages/** changes, which redeploy every worker) rolls the benchmark containers over. Axiom (cloudflare-logpush, ScriptName auto-routing-benchmark) shows the sequence: Container error: Runtime signalled the container to exit due to a new version rollout, then Network connection lost / Container port connection closed unexpectedly / The container just exited on the in-flight /run proxy, then benchmark_scheduler_wait reason=ambiguous_execution.

The container's SIGTERM drain finished the in-flight CLI case and wrote the response to a connection that no longer existed, so the output was lost even though the case had been paid for. On the retry, the paid-execution ledger still had the row, no pending output existed, and the scheduler killed the whole (model, variant, rep) lane.

  • The container refuses a case it has not started when SIGTERM arrives (503), so a rollout never pays for work it interrupts, and drains the case it already accepted.
  • A drained case whose caller disappeared is posted to the worker's new /container/case-result route, which stores the outcome as recoverable pending output. That route is authenticated by the paid-execution claim token (which a container can hold, and the backend secret cannot) and only accepts an outcome for a claim token that opened an execution. A not_started outcome releases the ledger row, so the case is retried without a paid call.
  • A connection lost in flight no longer records a lane failure: the chunk keeps its slot lease, so the existing quarantine still prevents provider oversubscription, and lease recovery re-dispatches it and reuses the delivered output.
  • A case that reaches the ledger with no recoverable output by recovery time is still genuinely ambiguous: it fails its lane rather than repeating a paid call. The no-duplicate-paid-inference guarantee is unchanged for genuinely ambiguous cases.
  • Mid-run connection drops without a rollout follow the same path, so they recover the same way when the container finishes and delivers.

2. Scheduler limits are admin-configurable

Benchmark evals are bottlenecked by the per-model chunk cap: one model lane can only run a few chunks at once, so a single large lane drains slowly while the shared container pool sits idle.

  • Raise the default modelConcurrency from 4 to 12. The other scheduler defaults are unchanged.
  • Let admins change slots, modelConcurrency, providerConcurrency, and unknownProviderConcurrency at runtime, without a deploy.
    • The limits are global to the worker's scheduler pool. They are not per router, and they are not part of BenchmarkConfig or /admin/config.
    • Overrides are stored in a new single-row D1 table, scheduler_settings, generated with pnpm db:generate. The migration only adds a table.
    • dispatchScheduler reads the row once per pass by primary key, resolves the effective config, and passes it to claimScheduledChunks as config. Nothing reads the row per chunk.
    • Precedence for each limit: admin override, then BENCHMARK_SCHEDULER_CONFIG, then the default. modelLimits, providerLimits, and shares still come only from the env config.
  • GET /admin/scheduler-config returns each limit's effective value, its source (admin, env, or default), the override, and the fallback used without one.
  • PUT /admin/scheduler-config replaces the whole override set. A missing or null limit clears its override. The request is rejected with a 400 for any of these:
    • a value that is not an integer
    • a value below 1
    • a value above BENCHMARK_CONTAINER_BUDGET
    • an unknown key
  • Both endpoints use the existing /admin bearer auth and the x-updated-by convention.
  • apps/web gets a Scheduler card on the auto-routing admin Benchmarks tab. It is backed by /admin/api/auto-routing/benchmark-scheduler-config and the shared admin client, and the request/response contracts live in @kilocode/auto-routing-contracts.

Verification

services/auto-routing-benchmark on a disposable VM:

  • pnpm typecheck, pnpm lint (oxlint, 0 errors), pnpm test (677 tests), pnpm smoke:scheduler
  • The container rollout regression test fails on the pre-fix container server (4 of its cases) and passes with it
  • End to end: wrangler dev with local D1 and every migration applied, then GET /admin/scheduler-config (401 unauthenticated; modelConcurrency 12 with source default), PUT {"modelConcurrency":8} (source flips to admin, and the next GET returns it), and invalid PUTs (modelConcurrency: 0, unknown key) rejected with 400

Rollout tests cover:

  • a running case drained after SIGTERM whose client disconnected is delivered to the worker as completed output, and a queued case that never started is delivered as not_started
  • a queued case is refused (503) once shutdown begins, so no paid call starts after SIGTERM
  • the worker records a container-posted outcome as pending output, releases the ledger row for not_started, and rejects an outcome whose claim token does not match an open execution
  • a lost paid connection quarantines the chunk without a lane failure, while a ledger row with no recoverable output at recovery time still fails its lane

The scheduler-configuration commit, as reported on this PR before it was combined:

  • packages/auto-routing-contracts: pnpm typecheck, pnpm lint, pnpm test
  • packages/web-shared: pnpm typecheck
  • apps/web: pnpm typecheck, oxlint on the changed admin paths, jest for the auto-routing admin page and routes and for the benchmark admin client
  • The Scheduler card was not loaded in a running admin panel. It is covered only by typecheck and lint.

Residual risks

  • Container rollout grace is a platform behaviour, not ours: if the runtime kills a draining container before it finishes the CLI case, that case is still genuinely ambiguous and fails its lane. This change removes every rollout loss where the drain completes.
  • A container killed mid-flight (no drain, no delivery) is still ambiguous by design, because repeating it could duplicate a paid call.

Visual Changes

Raise the default per-model benchmark concurrency from 4 to 12 and let admins override slots, model, provider and unknown-provider concurrency at runtime through a global scheduler_settings D1 row, exposed as GET/PUT /admin/scheduler-config and a Scheduler card on the admin Benchmarks tab.
@iscekic iscekic self-assigned this Oct 15, 2026
@kilo-code-bot

kilo-code-bot Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental commit classifies container outcomes by error type so only a genuinely never-started case is reported as not_started, and gates result delivery on recoverable outcomes; the previously reported ledger-safety concern is now resolved and the new regression test exercises the changed branch.

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 0
Files Reviewed (2 files in incremental diff)
  • services/auto-routing-benchmark/container/server.mjs - no new issues; prior not_started classification finding is fixed (non-NotStartedError now yields failed, and delivery requires a recoverable outcome)
  • services/auto-routing-benchmark/src/container-auth.test.ts - no new issues; the added test asserts a post-spawn failure returns 500 and posts no outcome

Notes (non-blocking, no action required):

  • The non-recoverable failed outcome is no longer delivered, which matches acceptContainerCaseResult, whose schema only accepts completed and not_started.
  • The HTTP response for a post-spawn failure is unchanged from before (500 + message); only the /container/case-result delivery changed, so worker-side retry behavior is unaffected.
Previous Review Summaries (3 snapshots, latest commit fac1ed2)

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

Previous review (commit fac1ed2)

Status: 1 Issue Found | Recommendation: Address before merge

Executive Summary

The incremental changes since the last review are formatting-only (oxfmt reflow) plus cast removals in cli-runner.ts and container-auth.test.ts that do not alter behavior; the previously reported ledger-safety concern in the container's rollout-orphan delivery path remains present and unresolved.

Overview

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

SUGGESTION

File Line Issue
services/auto-routing-benchmark/container/server.mjs 313 outcome is set to not_started for every error, but only NotStartedError guarantees no paid call started. Latent today, but a post-spawn rejection would release the paid-execution ledger row and permit a duplicate paid call on recovery.
Files Reviewed (4 files in incremental diff, plus prior review scope)
  • services/auto-routing-benchmark/container/server.mjs - 1 issue (carried forward, unchanged)
  • services/auto-routing-benchmark/src/cli-runner.ts - no new issues
  • services/auto-routing-benchmark/src/container-auth.test.ts - no new issues
  • services/auto-routing-benchmark/src/scheduler.ts - no new issues

Fix these issues in Kilo Cloud

Previous review (commit 150163d)

Status: 1 Issue Found | Recommendation: Address before merge

Executive Summary

Incremental review of fix(auto-routing): survive benchmark container rollouts found one non-blocking robustness issue in the container's rollout-orphan delivery path: the delivered outcome is classified as not_started for every error, not only the pre-spawn NotStartedError.

Overview

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

SUGGESTION

File Line Issue
services/auto-routing-benchmark/container/server.mjs 318 outcome is set to not_started for every error, but only NotStartedError guarantees no paid call started. Latent today, but a post-spawn rejection would release the paid-execution ledger row and permit a duplicate paid call on recovery.
Files Reviewed (11 files)
  • services/auto-routing-benchmark/container/server.mjs - 1 suggestion
  • services/auto-routing-benchmark/src/run.ts
  • services/auto-routing-benchmark/src/scheduler.ts
  • services/auto-routing-benchmark/src/cli-runner.ts
  • services/auto-routing-benchmark/src/index.ts
  • services/auto-routing-benchmark/src/container-auth.test.ts
  • services/auto-routing-benchmark/src/run-process-job.test.ts
  • services/auto-routing-benchmark/README.md
  • services/auto-routing-benchmark/.dev.vars.example
  • services/auto-routing-benchmark/wrangler.jsonc
  • services/auto-routing-benchmark/worker-configuration.d.ts (generated)

Fix these issues in Kilo Cloud

Previous review (commit 50b3e7e)

Status: No Issues Found | Recommendation: Merge

Executive Summary

Reviewed the admin-tunable benchmark scheduler limits change (new single-row scheduler_settings D1 table, resolveSchedulerConfig precedence, /admin/scheduler-config GET/PUT, shared contracts, and the admin Scheduler card); no high-confidence bugs, security issues, or memory leaks found in the changed code.

Files Reviewed (15 files)
  • apps/web/src/app/admin/api/auto-routing/benchmark-scheduler-config/route.ts
  • apps/web/src/app/admin/auto-routing/BenchmarksSection.tsx
  • apps/web/src/app/admin/auto-routing/SchedulerConfigCard.tsx
  • packages/auto-routing-contracts/src/benchmark.ts
  • packages/web-shared/src/lib/ai-gateway/auto-routing-benchmark-admin-client.ts
  • services/auto-routing-benchmark/README.md
  • services/auto-routing-benchmark/migrations/0021_puzzling_sersi.sql
  • services/auto-routing-benchmark/migrations/meta/0021_snapshot.json (generated)
  • services/auto-routing-benchmark/migrations/meta/_journal.json
  • services/auto-routing-benchmark/src/admin.ts
  • services/auto-routing-benchmark/src/db-schema.ts
  • services/auto-routing-benchmark/src/run.ts
  • services/auto-routing-benchmark/src/scheduler-settings.test.ts
  • services/auto-routing-benchmark/src/scheduler.test.ts
  • services/auto-routing-benchmark/src/scheduler.ts

Notes (non-blocking, no action required):

  • The new /admin/scheduler-config routes sit behind the existing global authMiddleware and /admin/* router guard, matching the /admin/config convention.
  • The migration adds a new table only (no table-locking DDL on populated tables, no NOT NULL-without-default on existing columns). 0021_snapshot.json prevId matches 0020's id and includes scheduler_settings.
  • Operationally, dispatch now reads scheduler_settings each pass, so migration 0021 must be applied for the deploy; the PR description already calls this out.

Reviewed by deepseek-v4.1-flash · Input: 49.3K · Output: 10.5K · Cached: 310.3K

Review guidance: REVIEW.md from base branch main

@iscekic
iscekic enabled auto-merge October 15, 2026 21:37
@iscekic
iscekic disabled auto-merge October 15, 2026 21:59
A worker deploy rolls every container over, which tears down the caller's
connection while a benchmark case is in flight. The container drained the CLI
child and wrote the output to a socket nobody was reading, so the retry found the
paid-execution ledger entry without pending output and killed the lane.

The container now refuses cases it has not started when SIGTERM arrives, so a
rollout cannot pay for work it interrupts, and posts the outcome of a case it did
drain back to the worker's /container/case-result route. That route is
authenticated by the paid-execution claim token, which a container can hold and
the backend secret it cannot, and only accepts outcomes for a claim token that
opened the execution: a completed run becomes recoverable pending output, and a
case that never started releases its ledger entry.

A connection lost in flight keeps quarantining the slot with the chunk's lease
instead of recording a lane failure, so lease recovery re-dispatches the chunk and
reuses the delivered output. Only a case that reaches the ledger with no
recoverable output by recovery time remains ambiguous and still fails its lane
rather than repeating a paid call.
@iscekic iscekic changed the title feat(auto-routing): admin-tunable benchmark scheduler limits fix(auto-routing): survive container rollouts and make scheduler limits admin-configurable Oct 15, 2026
A container that lost its caller posted every case failure as not_started,
which releases the paid-execution ledger row. A failure after the CLI spawned
can have made a paid call, so that row must survive for ambiguity quarantine.
@iscekic

iscekic commented Oct 15, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Admin Scheduler card verified end to end in a browser against a local benchmark worker and local D1 (no production contact): default state, save an override, reload persists, clear back to default, and an API-rejected value shows an error toast with the card intact.

Default state

Override persisted after reload

API rejection toast

@iscekic
iscekic enabled auto-merge October 15, 2026 23:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants