Story-2925 Walkthrough

Change: Anthropic joins the agent fleet. Claude Opus 5 launched 2026-07-24 and Sonnet 5 is on introductory pricing, which moved the seat economics enough to crown Anthropic into four seats and re-point one shared alias. Chasing the eval evidence for those seats then uncovered Bug-2926: Indigo has been silently running a two-model panel while paying for a three-model one. Both land here.

Read the second commit as the real story. The seat changes are routine config. The bug they exposed is not: it had been degrading every document Indigo ever read, and it never raised, never alarmed, and never failed a test.

Part 1 — the seats (Story-2925)

Agent · seatWasNowProven?
Indigo judge — fallback legmoonshot-judgeanthropic-sonnet✅ forced probe: salvaged 28, dropped 0
Indigo eval_candidates.judge[moonshot-judge, gpt-5.6-sol][anthropic-opus, gpt-5.6-sol]⚪ config only
Mira synthesizerminimax-midCHAIN [kimi-coding:k3 → minimax-mid]⚪ not live-sampled
Vega judges.second_lensx-ai/grok-4.3anthropic/claude-sonnet-5⚪ eval path only
context_judge judge — fallbackopenai-frontieranthropic-sonnet⚪ needs a forced probe
google-flash alias (3 seats)gemini-3.5-flashgemini-3.6-flash✅ survived every pass of 4 live runs

Why Anthropic rides the gateway, not a direct door

Every other vendor is provider-DIRECT. Anthropic cannot be, today: build_model's direct branch constructs an OpenAIChatModel, and Anthropic's API is not OpenAI-wire. A bare OpenRouter slug gets the gateway plus ZDR pinning with zero code change, so that is the path taken. Anthropic is now the second gateway vendor after the Google exception — for a different reason, and the registry says so.

⚠️ The tokenizer tax — the number that changed the answer

Anthropic's pricing page states Claude 4.7+ use a newer tokenizer emitting ~30% more tokens for the same text. Token price is therefore not comparable across that line, and our documents are the input:

Modellist $ineffective $ per 1M tokens of text
claude-sonnet-5 (today)2.00≈ 2.60 — still under kimi-k3 ($3.00)
claude-sonnet-5 (from 2026-09-01)3.00≈ 3.90 — now OVER kimi-k3
claude-opus-55.00≈ 6.50

This is why Sonnet, not Opus, took the Indigo judge fallback: Indigo runs uncached (founder call, "only a 1-time job"), so on a 100k-token document Sonnet costs ~$0.26 and fits the max_run_cost_usd: 0.45 cap, while Opus costs ~$0.65 and would breach it mid-run.

A list means two different things (mira/panel.py)

Mira's synthesizer could not take kimi-coding:k3 as pure config: that route is production_allowed=False and _build_agent called build_model() without allow_nonproduction, so the seat failed closed at build time. The factory is now chain-aware — the same FallbackModel shape _converse_agent already used, lifted into the shared factory so every panel role inherits it.

The trap this walked into, and the guard for it. In llm.yaml a LIST means two different things: for proposer it is a parallel roster of diverse drafters; for every other role it is an ordered chain. The first version wrapped the roster in a FallbackModel too, turning 3 independent drafters into 1 primary + 2 never-used fallbacks. Nothing errors. The panel still returns answers. It just quietly stops being a panel. Pinned by test_proposer_roster_is_never_collapsed_into_a_fallback_chain.

Part 2 — the stopwatch was broken (Bug-2926)

Indigo lost ~1 of its 3 deep_read proposers to TimeoutError on essentially every run, and the victim rotated by run rather than by model — sonnet-5, deepseek-v4-flash, grok-4.20 and grok-4.5 were all observed dying. That is the signature of a broken stopwatch, not a slow model.

Why nobody noticed: the pass only REQUIRES 2 survivors. Losing one is not an error anyone sees — the run reports success. status=partial was the only hint.

ROOT CAUSE — one clock, measuring the wrong thing
  await asyncio.wait_for(_run_seat_streamed(agent, prompt), timeout=remaining)

THE FIX — chunks reset the clock; only silence kills
  chunks = stream.stream_responses(debounce_by=None).__aiter__()
  while True:
      chunk = await asyncio.wait_for(anext(chunks, _STREAM_DONE), timeout=silence_s)
      if chunk is _STREAM_DONE: break

Indigo had already adopted the hard half of the streaming doctrine (run_stream) but kept a wall-clock box — contradicting docs/notes/streamed-provider-calls-2026-07-24.md: "a route read-cap measures inter-chunk silence, never total call duration. Slow is not dead."

stream_responses and not stream_output: it yields raw provider chunks, so a model emitting reasoning tokens counts as alive. stream_output validates partial output per chunk and can stay quiet through exactly the long thinking phase we must not kill.

Why this works without buying more wall-clock

invoke_budget_s cannot be raised from this repo — all four passes share one invoke and the budget is bounded by the broker timeout, which is pramaan-functions-owned (RFD-DOC-007 §4.5). That constraint made the fix better. A wedged seat used to consume its ENTIRE box, so remaining <= 0 made the bounded retry unreachable and the seat was simply lost:

before:  wedged seat burns all 180s  →  remaining <= 0  →  retry unreachable  →  seat lost
after:   wedged seat dies at 45s     →  135s left       →  retry runs         →  seat kept

45s vs the 8.4s worst healthy inter-chunk gap measured at 132k input tokens — a ~5× margin.

⚠️ This is HALF the fix, and the other half is not in this repo

Live runs on the same document and roster went 2-of-3 → 3-of-3 → 2-of-3 again. The recurrence reports error_kind=TimeoutError, not indigo_seat_went_silent — the seat streamed continuously and still exceeded the 180s box. So deep_read has at least two causes:

CauseSignatureStatus
Wedged streamindigo_seat_went_silent✅ fixed — caught at 45s, retry recovers it
Genuinely slow seatTimeoutError at 180s❌ not fixed — needs a bigger box (broker-owned)
The diagnostic is what proved this. Before the change every death read TimeoutError and the two causes were indistinguishable even in hindsight. The new error_kind separated them on the very first repeat run — and disproved the author's own "these were all stalls" conclusion.

Verification

CheckResult
Offline suites (indigo · mira · framer · context_judge · registrar · governance)1,513 passed
ruff checkclean
Indigo offline gatesproposed=80 rejected=0
Live: gemini-3.6-flashsurvived every pass, 4 runs
Live: sonnet-5 judge (forced probe)salvaged 28, dropped 0
Live: deep_read 3-of-31 of 3 post-fix runs — intermittent, see above

Not included, and why