Skip to content

Project Retrospective — V1.0

Date: 2026-08-03 Release: v1.0.0 (tag 4fff4ba, commit d8b95e6)

Project objective

Build a provider-aware Claude Code statusline from observed local behavior, not assumptions: show the actual provider/model a request resolved to (e.g. opencode/big-pickle) instead of the requested label (auto/cheap), while never displaying a value the statusline cannot source.

Major milestones

Milestone Date Result
Phase 1A — Internet research 2026-08-01 Raw research archived with provenance
Phase 1B — Local investigation 2026-08-01/02 Payload + OmniRoute topology + transcript facts verified
Phase 2 — Architecture 2026-08-02 Transcript-first design, ADR-0004–0008
Phase 3 — Design 2026-08-02 6 deliverables, 16 acceptance cases, ADR-0009
Phase 4 — Implementation 2026-08-03 Modular statusline, 29/29 tests, deployed live (5c0973b)
Phase 5 — Validation 2026-08-03 Live route + model-switch + terminal-width validated
Phase 6 — Documentation & maintenance 2026-08-03 README, ADR-0010, ops notes, maintenance plan
V1.0 release 2026-08-03 Merged to main, tagged v1.0.0

Key architectural decisions

  • Transcript-first route read (ADR-0004/0005): read the resolved model from the local session transcript (sub-millisecond, no Docker dependency) rather than querying OmniRoute SQLite at statusline time. Provider enrichment is off by default.
  • Empirical model→provider mapping (ADR-0006): a versioned JSON heuristic derived from call_logs, refreshed out-of-band by a maintenance script — never at statusline read time.
  • Read-only at runtime (ADR-0005/0008): the statusline only reads files; it never writes, never calls OmniRoute, never makes network requests. OmniRoute-down is therefore non-blocking (ADR-0010).
  • Bounded tail read (ADR-0009 change request): the transcript read is bounded; the window size is an implementation constant (256 KB), not an architectural invariant.
  • Failure invariant: every failure path degrades toward the requested label or empty string; no error, undefined, or null ever reaches output.
  • 5-minute staleness + [stale] marker, and a 32-char route bound — both tunable constants.

Biggest discoveries

  1. The statusline payload carries the requested model, not the resolved route. model.display_name is auto/cheap even when the request resolved elsewhere.
  2. The transcript carries the resolved model on every assistant message (message.model), matching call_logs.model (78/78 in Phase 2) — a reliable, local, per-session source. The field drifted from top-level to message.model between phases (schema/version drift).
  3. combo_name is the reliable filter for auto-routed traffic in call_logs; requested_model='auto/cheap' only matches blocked/classifier-gated rows.
  4. Auto-routing is request-weight dependent: a light session resolved to opencode/mimo-v2.5-free while a heavy session stayed opencode/big-pickle.
  5. Assistant messages stream into the transcript progressively with write-time timestamps (thinking → text → tool_use as separate flushed lines). This is why the [stale] marker clears at processing start — correct per design, not a bug.
  6. The auto/cheap safety classifier gates non-allowlisted Bash in Claude Code, and OmniRoute runs its own classifier probes (the 499 rows). The statusline's route path uses only plain fs reads, so it is immune.

What worked well

  • Investigate-first discipline: the engineering principle held — no implementation until data flows, interfaces, and failure modes were locally verified.
  • Gate-based roadmap: explicit phase gates with ADRs meant each phase was approved on evidence before code moved forward.
  • Read-only verification: Phase 2 proved the transcript was the right source before a line of implementation was written.
  • Contract test suite: 29 tests encoding all 16 acceptance cases + invariants caught regressions and made "failure behavior is deliberate" testable.
  • Worktree isolation: the implementation ran in a dedicated worktree, keeping the user's working copy untouched until release.
  • Graceful degradation as a product value: the requested-label fallback and [stale] marker turned every failure mode into a readable state instead of a crash.

What we'd do differently next time

  • Provision the Bash allowlist earlier. The auto/cheap classifier outage stalled deployment work; a pre-authorized allowlist for the few commands the project needs would have saved wall-clock time.
  • Capture the light-vs-heavy routing comparison sooner. It was a live, cheap observation that materially strengthened validation; worth scheduling during Phase 5 rather than discovering it opportunistically.
  • Consider pinning the OmniRoute image tag. The image is latest (unpinned); the transcript schema drift we hit suggests pinning would reduce future surprises.

Lessons learned

  • Verify local behavior before trusting documentation — the resolved model is not in the statusline payload, and the transcript field moved between versions.
  • Heuristic data should degrade gracefully: a missing mapping entry renders model-only, never a guess.
  • A read-only runtime dependency (local files only) is a strong safety and availability property — it made OmniRoute-down and classifier-down scenarios non-events.
  • "Most recent route" is the honest contract: the statusline shows the last response's resolution, re-read per event, not a stable per-session history.
  • The empirical mapping is a snapshot, not a contract (ADR-0006) — plan for refresh.
  • Test both the producer and the consumer of a generated artifact. A passing consumer test does not prove the producer generated the expected artifact — V2's runtime was fully tested and correct, but the regeneration script dropped the context-window column, and the feature silently no-op'd until the producer was fixed and covered (commit ca4a9ec; findings doc Part 4).

Why V1.0 is considered complete

  • Phases 1–6 all closed with recorded gates (ADR-0003, 0004–0008, 0009, 0010).
  • The implementation is deployed and live: 29/29 contract tests pass; real transcripts and the generated mapping produce opencode/big-pickle; failure paths degrade correctly.
  • Validation covered 6 of 7 roadmap scenarios live; the 7th (OmniRoute unavailable) is accepted as an opportunistic observation because the statusline has no coupling to OmniRoute (ADR-0010).
  • Documentation is complete: README, repository map, ADRs, operational notes, troubleshooting, and a maintenance plan.

Suggested starting point for V2 (visual statusline improvements)

Treat V2 as a separate project on the V1.0 baseline (ADR-0010). Starting points:

  • Extension surface: Statusline/lib/ — the progress bar and context-display replacement are new segments, so the route core (transcript.js, mapping.js, route.js, staleness.js) should stay intact; add new lib modules and wire them in Statusline/statusline.js.
  • Start with a design update: revise Statusline/Design/01-display-requirements.md (line structure) and 06-test-cases-acceptance.md before code — the existing contract suite is the regression net.
  • Config: Statusline/lib/config.js holds the tunable constants (staleness, truncation); visual limits belong there.
  • Re-deploy: Scripts/deploy-statusline.js and the mapping maintenance script are reusable as-is.
  • Baseline guard: main + v1.0.0 are the stable release; branch V2 off main.