Story-2992 Walkthrough

Change: the localhost eval viewer (uv run lab web) becomes an agent-structure mirror. Four founder-review asks from 2026-07-26 land together: real persona avatars, an agent card per brain, a Models lens answering "which models, which route, what cost", and Input/Output Schema lenses scoped to the selected surface. A CI conformance gate makes the standard agent layout enforceable: an agent that breaks it shows a red STRUCTURE GAP banner in the viewer and fails tests/test_agent_structure_conformance.py.

Same PR, separate decision: Chore-2993 promotes kimi-coding:k3 to a production-eligible route (founder call 2026-07-26) and fills the model-pricing.yaml gaps the Models lens exposed. The lens is where the founder reads that decision back; the two changes review together.

1 · Start the viewer

uv run lab web
# prints: lab web: http://127.0.0.1:<port>/?token=<per-launch token>

Every check below is a GET on that URL. The token is per-launch; nothing here writes anything.

2 · Avatars and agent cards (Overview)

ClickExpect
Top navCircular portrait before each persona name — Atlas, Blake, Vega, Indigo, Mira. Blake and Indigo portraits are new (agents/registrar/assets/blake.png, agents/indigo/assets/indigo.png). context-judge stays text-only (no persona, by design).
Any agent → 01 OverviewAgent card: portrait, role line, and a 1–2 paragraph description of what the agent does, sourced from that agent's identity.py (description field).

Portraits serve from each agent's own assets/ dir via a token-gated /avatars/<agent> route; an agent without a portrait file falls back to a monogram tile. The viewer is identity-driven: it loads agents/<key>/identity.py directly — no duplicate name/portrait maps inside evalkit/lab.py.

3 · Models lens (new page 05)

ClickExpect
Mira → conversational → 05 ModelsOnly conversational seats: converse leg 1 kimi-coding:k3 (direct · kimi-coding (plan), $0.05 / $0.5 per 1M), leg 2 gemini-flash-lite (OpenRouter gateway), leg 3 gpt-5.6-luna (direct · openai). Judges listed separately — eval infrastructure, shown on every surface.
Mira → command → 05 ModelsCommand seats only (command_mapper, proposer, critic, synthesizer). No conversational seats mixed in.
Vega / Indigo / Blake → 05 ModelsSame table, parsed generically from each agent's llm.yaml — scalar seats, fallback chains, and Blake's nested primary/fallbacks all resolve. No per-agent code in the viewer.

Columns: seat, model (alias-resolved via model-registry.yaml), provider path, $in/$out/$cache-read per 1M tokens (from model-pricing.yaml), INT (INR per 1M), cache policy, price note. Env-placeholder seats (Atlas) render as deploy-env resolved instead of a fake price.

4 · Input Schema + Output Schema (pages 06 and 07)

ClickExpect
Mira → conversational → 06 Input SchemaMiraConverseInput only: matter_id, current_frame, counsel_message required; types, defaults, constraints per field. No command contracts.
Mira → command → 06 Input SchemaMiraCommandInput, MiraMapCommandInput, PanelInput. No conversational contract.
Mira → command → 07 Output SchemaFrameOutput (the Brief), full field table.
Mira → conversational → 07 Output SchemaHonest note: converse streams plain markdown — no typed output contract. Shared supporting shapes listed separately.

Contracts are read from the agent's own models.py / panel_models.py (pramaan-docs ADR-117 — the type IS the contract), deduped by class name.

5 · Surfaces you didn't configure don't appear

The surface rail shows only surfaces that have suites — Mira shows COMMAND and CONVERSATIONAL; AMBIENT is gone (previously visible-but-disabled under Story-2973 D35; reversed on founder review). Every lens — Datasets, Runs, Prompts, Models, both Schema pages — is scoped to the selected surface. Selecting conversational never shows command content, and vice versa.

6 · Structure conformance gate

uv run pytest tests/test_agent_structure_conformance.py -q
3 passed

The gate asserts, for every agent dir: identity.py complete (5 fields, key == directory), avatar file present (context_judge exempt — no persona), <key>.yaml, llm.yaml, prompt source. Atlas was brought to full structure as part of this change (new agents/atlas/llm.yaml; config.py fails closed when a catalog placeholder is undeclared). Break any one and every page for that agent renders a red STRUCTURE GAP banner naming the missing piece — the viewer never papers over agent drift.

7 · kimi-coding production promotion (Chore-2993)

uv run pytest tests/test_provider_routes.py -q   # guard coverage kept
uv run pytest agents/mira/tests -q               # 257 passed
uv run pytest agents/atlas/tests -q              # 516 passed

agents/_shared/provider_routes.py: kimi-coding is now production_allowed — the Models lens shows it as direct · kimi-coding (plan) with no non-prod marker. Fallback chains behind kimi-coding:k3 are retained as availability insurance; production_lookup still fails closed for any future non-production route. Pricing rows verified against the live OpenRouter catalog 2026-07-26.

Gate evidence

uv run pytest tests/ -q          # 423 passed (governance + lab web + conformance)
uv run ruff check agents/ evalkit/ tests/ scripts/   # all checks passed

Known follow-ups (not this PR)

ItemWhy deferred
Case-table visual tightening (drop uniform columns, clamp descriptions, attack-class badges)Proposal on the table; founder has not picked an option.
Runs lens: Logfire trace/span deep-linksNeeds logfire.url_from_eval design.
Prompts lens: honest-label vs effective-stackOpen question from Story-2973 handoff.
Registrar provider.py: kimi-coding 90s wire boundNo Blake seat uses that route today; flagged in Chore-2993.
Indigo judge-fallback docstrings (moonshot vs anthropic-sonnet)Comment drift only; flagged in Chore-2993.