Agent pane: a failed subagent or sub-session offers Retry, and shows acpmux's retry while it waits
Leo, 2026-10-26: "this needs to retry :("
Two Claude Task subagents ended with "Subagent failed: …" after a transient gateway 503 (no account with capacity). Nothing on the row could run them again. This is the webview half. The daemon half (automatic retry with backoff, the retry state, _acpmux/retry, the subagent_retry records) has landed in acpmux (#25232, #25264, #25234, #25296, #25381). This PR reads those real fields and gates on acpmux's capabilities.
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)say where a row stands:retrying(a retry waits),settling(acpmux decides, starts or runs a sub-session's retry,retryDeciding),failedwith the Retry it offers and the retries acpmux gave up on, ornone(not failed, nested, or a staleretry). The end line, the summary list, the running tray and the sub-session card all read that and nothing else;retryKeyis the one cache-key part for it. - Adapter.
readRetryreadsretry: {attempt, of, reason, nextAt}|nullfrom each_meta.acpmux.subagentsentry and from the session row (session/list, withlane.errorandretryDeciding). The fold keepsretryableanderror. "Gave up after N tries" comes only from asubagent_retryrecord with outcomeexhausted. A clearedretryis never read as giving up, since acpmux also clears it when a retry fires. - Retrying state. While a retry waits, the end line reads "Retrying (2/3) after a capacity error" with
<Spinner loading />, never a dot. The sub-session card, the summary list and the running tray show the same; the tray lists a retrying subagent or chat with the running ones. A sub-session retry that waits on a parent run slot shows the existing "Waiting for a slot". - Retry action, on a failed or lost line of the chat's own, never nested and never while a retry waits or settles. It's an inline button, and the same single action sits in the row's ⋯ menu beside Restart. Menus open on the click.
- A Task subagent with
retryablegets_acpmux/retry {sessionId: chat, subagentId}when acpmux namessubagent-retry-v1. Otherwise it gets "Ask parent to retry", and so does a daemon that answers -32601 orretry.subagent_unsupported. - A sub-session gets
_acpmux/retry {sessionId: child}when acpmux namesturn-retry-v1, and no Retry otherwise. Only acpmux can retry a chat's turn, so it never asks the parent. - A daemon that lists no capabilities gets the method tried.
- Refusals show as a short inline note:
retry.busy;retry.nothing_failedby itswhy;retry.stopped;retry.unknown_subagent.
- A Task subagent with
- No double fire. The client keeps no in-flight state. A Retry that went stays disabled until the row changes. acpmux refuses a second retry of its own as
retry.busy, which also covers an attempt that already fired. Retry uses the same reach checks as Restart, checked again before the fallback send: never in a View Only chat or a read-only pane, and never on the next chat during a switch. - Retry continuations (provenance
auto_resumeorsubagent_retry) don't draw as person bubbles. - 15 new strings in
Localizable.xcstrings, in every shipped language (non-English markedneeds_review).
Follow-up (daemon): after acpmux fires an attempt it clears retry, so the row reads failed with Retry until the subagent runs; a click meanwhile gets "Busy, try again shortly". A per-subagent "attempt in flight" field from acpmux would let the row keep its spinner. The webview won't guess it (rounds 6 to 8 of review showed each client-side guess breaking).
No new timers, observers or loops, and no countdown to nextAt. Refusal notes are React state keyed to the row's version.
Gallery: agent-pane.subagent-retry
Synthetic rows named after Leo's two (no private data), Linux Chromium, w720, at head 3a4fd56.
| State | Dark | Light |
|---|---|---|
| Before: the base line, failed, with no way to run it again at rest | ![]() | ![]() |
| After (A, picked): failed (capacity), inline Retry | ![]() | ![]() |
| Retrying (2/4) and (1/4), turning ring | ![]() | ![]() |
| Gave up after 4 tries, Retry once more | ![]() | ![]() |
| Task subagent acpmux can't retry: Ask parent to retry | ![]() | ![]() |
| The ⋯ menu (on hover), opened on the click: the row's one action | ![]() | ![]() |
| Version B: link after the time | ![]() | ![]() |
| Version C: ⋯ menu only (opened) | ![]() | ![]() |
The failed row's Retry was built three ways: A an inline outlined button (picked), B a link after the time, C in the ⋯ menu only. A is the pick: a failure gets a visible way out at rest, and it reads apart from the sentence. B blends into the line's text. C shows nothing until hover, which is how the base already hid Restart. B and C stay in the gallery (RetryAffordanceContext).
Risk
Low to medium, contained to the subagent end line, the sub-session card, the summary list and the tray. A failed line always shows a small Retry now. A real Retry prompts the parent chat (acpmux does it), with the same reach as Restart. About 500 of the ~1,075 lines are the test file, the gallery and strings; the rest is one feature across the line, card, tray and summary, so it lands as one PR.
Tests
subagents/retryState.test.ts(new): the adapter on the daemon's shapes; the fold keeping retry, retryable, error and the exhausted step (fired/scheduled clear it, a cleared retry alone doesn't set it); the session list'sretry; which rows get which action under each capability set; the ask's one line; each phase fromsubagentRetry/sessionRetry(retrying, settling, failed, none for stale or nested rows); the tray placing retrying rows with the running ones; RPC params, refusals and theirwhy, the -32601 andsubagent_unsupportedfallbacks (a subagent asks, a sub-session never does); the strings in the catalog.- On huge-blue at the head:
bunx tsc --noEmit -p .clean,bunx vp fmtno diff,bunx vp lint0 errors,bun test src/agent-session/acpmux/subagents/ src/agent-session/acpmux/summary/ test/subagent-tray.test.tsx116 pass.test/gallery-coverage.test.tsfails the same way on base (entries across the pane that aren't in this PR).
Agent session: agent session
🤖 Generated with Claude Code















