SIGN IN SIGN UP

feat: show a running agent's real model in /workflows (#167)

While a subagent was RUNNING, /workflows displayed the MAIN session model
instead of the model the subagent actually runs on. The display only became
correct once the agent finished.

Root cause: agent() seeds `displayModel = modelSpec ?? options.mainModel` and
emits it on onAgentStart, which necessarily happens before agentRunner.run().
The real model is resolved later, inside WorkflowAgent.run(), and reported via
onModelResolved — whose handler only mutated the closure-local `displayModel`,
so the corrected value first reached the snapshot at onAgentEnd. agent.model is
assigned in exactly two places (WorkflowManager's onAgentStart and onAgentEnd
handlers), so the snapshot field was physically unwritable mid-run.

That guess is wrong for two common shapes, not just an edge case:

  - a `tier`-tagged agent — agent() deliberately passes modelSpec: undefined
    when opts.tier is set, so displayModel always falls back to mainModel;
  - ANY untagged agent once ~/.pi/workflows/model-tiers.json exists, because
    resolveAgentModelSpec implicitly routes untagged agents through the
    "medium" tier.

A second, worse variant: the resume/journal-replay branch emits onAgentStart
and onAgentEnd back-to-back with the never-resolved value, so replayed rows
showed the main model permanently — they never converged, even after the run
finished.

Fix: push the resolved id out on a new optional `onAgentModel` callback, fired
from the existing onModelResolved hook with the same per-call id as
onAgentStart/onAgentEnd. WorkflowManager writes it into the live snapshot and
re-emits it as "agentModel" so the task panel and navigator repaint. For the
replay path, JournalEntry/PersistedJournalEntry gain an optional `model`
recorded at onAgentJournal; entries without it degrade to the previous
behaviour. The field is deliberately NOT part of hashAgentCall, so no cached
agent is invalidated by the change.

Rejected alternatives: resolving the spec eagerly in workflow.ts duplicates the
precedence ladder, cannot canonicalize a fuzzy spec without the async registry,
and would lie in the onModelFallback degrade path; emitting onAgentStart after
resolution would never create a row at all on a fresh install, where
onModelResolved does not fire.

Side benefit: an agent aborted mid-run never reaches onAgentEnd, so its stale
model used to be persisted forever; it is now already correct when the abort
lands.
Y
Yuanpeng Li committed
581334e93809f6bc631566e63c21cb3ee36bc9ce
Parent: 9f4ec57
Committed by GitHub <noreply@github.com> on 8/27/2026, 6:02:33 AM