Report the model each role actually ran, not the configured one - #39
Merged
Merged
Conversation
6 tasks
Every surface named a stage's models from configuration. A role with no configured model is invoked without --model and the harness chooses for itself, so "harness default" was as much as any of them could say, and an alias like sonnet never showed the version behind it. After a run finished there was no way to answer which model it had used. Each attempt now records what its harness reports running. Claude names its model on the init event of its stream, the same event the session identity already comes from, so the adapter reads both in one pass. The backend records it per role beside the session, the observation carries it, and the engine writes it onto the attempt, preferring a reported model over a configured one because it is the more precise of the two: it names what an unset role resolved to and the version behind an alias. The TUI stage view and the dashboard stage panel show what ran, falling back to the configured value when a harness reports nothing rather than guessing. Codex documents no model on its events, so its adapter reads one only if a stream provides it under the usual key and otherwise reports nothing, which leaves those surfaces exactly as they were. Also fixes the dashboard timeline, which tested whether either role had a model and then always printed the worker's: a stage with a supervisor model and no worker model announced "with harness default", naming the wrong role. It now names each role that reported one, and the supervisor appears there at all for the first time. Refs #38 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Owner
Author
|
Rebased onto Heads-up for merge order: this PR and #44 both rewrite the same line in the dashboard's Conversation/Chat panel — this one to show the model that actually ran, #44 to lead the panel with blocking diagnostics. They don't conflict with |
sam-bretz
force-pushed
the
issue-38-resolved-models
branch
from
September 16, 2026 17:00
413c370 to
8479a9b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #38.
What changed
Every surface named a stage's models from configuration. A role with no configured model is invoked without
--modeland the harness picks for itself, soharness defaultwas as much as any surface could say — and an alias likesonnetnever showed the version behind it. After a run finished, "which model did this use?" had no answer.Each attempt now records what its harness reports running, and the surfaces prefer that over the configured value.
claude-sonnet-5-20260115claude-sonnet-5-20260115 (harness default)sonnetclaude-sonnet-5-20260115claude-sonnet-5-20260115 (configured sonnet)opusopusopusopusopusHow
Claude names its model on the
initevent of its stream — the same event the session identity already comes from — so the adapter reads both in one pass (claudeInit).agent.HarnessgainsModel(stream) string; the backend records it per role beside the session;engine.Observationcarries it; the engine writes it ontoAttempt.Models, preferring a reported model because it is the more precise of the two.Display:
internal/tui/model.go(stage view),internal/web/view.go+app.js(stage panel and timeline).Acceptance criteria
The timeline defect
The issue noted this line:
It tested whether either role had a model and then always printed the worker's, so a stage with a supervisor model and no worker model announced
with harness default— naming the wrong role. It now names each role that reported one, and the supervisor appears in the timeline at all for the first time.A limitation worth knowing
Codex reports nothing. Its documented events carry no model, and I did not want to invent a schema, so
Codex.Modelreads a top-levelmodelkey only if a stream happens to provide one. For Codex runs these surfaces behave exactly as before, falling back to the configured value. If someone knows the real Codex field, that adapter is a two-line change.Claude's
init.modelfield is likewise read defensively: a stream that says nothing leaves the configured value showing rather than a guess.Testing
gofmt,go vet ./..., fullgo test ./...pass. New tests cover the adapter parse (including a stream that says nothing), the engine merge (unset role, alias→version, and that an unchanged attempt is not rewritten), the TUI formatter's five cases, and the web view reportingran_workerwithout inventing a supervisor.Not exercised against a live harness — the
init.modelfield is read from Claude's documented stream shape, so that is worth one real run before merge.🤖 Generated with Claude Code