Agent pane: retry state for failed subagents and sub-sessions (part 1 of 2)
Leo, 2026-10-26: "this needs to retry :("
Part 1 of 2. This PR holds the state, the adapter and the session rows. Part 2 (stacked on this one, opened next) adds the subagent end line's Retry and ⋯ menu, the running tray, the gallery and their strings. #25226 was the two combined, and it was split to keep each PR under 800 lines.
Two Claude Task subagents ended with "Subagent failed: …" after a transient gateway 503. acpmux now retries them itself and lets a person retry them (#25232, #25264, #25234, #25296, #25381). This is the webview half.
Can our side retry a failed Claude Task subagent?
Not through ACP or the Claude Code SDK directly. Only the parent agent can, and acpmux asks it to. claude-agent-acp (0.94) exposes only Query.stopTask / _session/async_task/stop for a task, and nothing that re-runs one. The SDK's Query has no retry or restart for a task either. A failed Agent-tool call is the parent's tool result, so a new run has to come from the parent: another Agent call, or a SendMessage to the subagent's agentId, which resumes it from its transcript. acpmux's _acpmux/retry {sessionId, subagentId} does the second one: it prompts the parent, in the same session, to resume that subagent by its agent id. So a Task subagent that acpmux marks retryable gets a real Retry. "Ask parent to retry" (one line naming the task and its description, sent through the composer) is the fallback when retryable is false or absent, or the daemon doesn't serve the capability.
What changes
- One owner for a row's retry (
subagents/retryState.ts).subagentRetry(agent, capabilities, child?)andsessionRetry(session)give each row one state:retrying: a retry waits.settling: acpmux is deciding, starting or running a sub-session's retry (retryDeciding).failed: carries the Retry the row offers and the retries acpmux gave up on.none: the row isn't failed, is nested, or itsretryis stale.
retryKeyis the one cache-key part for it. Part 2's surfaces read the same functions.
- Adapter.
readRetryreadsretry: {attempt, of, reason, nextAt}|nullfrom_meta.acpmux.subagentsentries and from the session row, along withretryDecidingandlane.error. The fold keepsretryableanderror. "Gave up" comes only from asubagent_retrystep with outcomeexhausted. A clearedretrynever means "gave up", since acpmux also clears it when a retry fires. - runRetry runs the one Retry a row offers:
- A Task subagent with
retryablegets_acpmux/retry {sessionId: chat, subagentId}when acpmux namessubagent-retry-v1. Otherwise it gets the parent ask, and so does a daemon that answers -32601 orretry.subagent_unsupported. - A sub-session gets
{sessionId: child}underturn-retry-v1. It never falls back to asking the parent. - Refusals map to short notes:
retry.busy;retry.nothing_failedby itswhy;retry.stopped;retry.unknown_subagent. - runRetry keeps no state, and acpmux's
retry.busyanswers a second Retry.
- A Task subagent with
- Session rows show "Retrying (2/4) after a capacity error" with
<Spinner loading />(never a dot) while acpmux waits to retry. This covers the sub-session card, the summary list and its counts, and the Inspector's started-chat rows. - Retry continuations (provenance
auto_resumeorsubagent_retry) don't draw as person bubbles.direct.tsreads acpmux's capabilities and addsretry()for the RPC. - 11 strings in
Localizable.xcstrings, in every shipped language (non-English markedneeds_review).
No UI action ships in this part: nothing calls runRetry until part 2. The visible change is the retrying label on session rows. It has no timers, observers or loops, and no countdown to nextAt.
Risk
Low. The new reads are of fields acpmux already sends, and the only visible change is the retrying label on session rows. Part 2 carries the before and after captures (the Retry affordance).
Tests
subagents/retryState.test.ts(new), 23 tests:- the adapter on the daemon's shapes
- each state from
subagentRetryandsessionRetry - the fold's exhausted step
- which rows get which action under each capability set
- RPC params, refusals and their
why, and the fallbacks - the started-chat rows and counts
- the strings
- On huge-blue:
tscclean,vp fmtno diff,vp lint0 errors.retryState.test.tspasses 23/23 run alone. A wholesrc/agent-session/acpmux/run fails base's 67 tests (other files mock i18n) and 5 of these for the same reason.
Agent session: agent session
🤖 Generated with Claude Code
![View with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3-3x.png)
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Need help on this PR? Tag @codesmith-bot with what you need.