acpmux: live progress for Claude workflows and background agents, and stop
Summary
Claude's dynamic workflows and background agents now report live progress through acpmux, and the app can stop one. This is the acpmux half of the GUI work for workflows and background tasks. The web half comes in a separate PR.
- Workflows are subagents. A
Workflowtool call becomes a background subagent of kindworkflow. Its tool result only marks the launch, carrying the run's name, summary, task id and transcript dir. A failed script check ends it as failed, and itstask_notificationends it. - Live progress. Claude's
task_progresslines becomesubagent_progressupdates in the subagent model (_meta.acpmux.subagents). Each carries tokens, tool uses, elapsed ms and the current action: a workflow's step, otherwise the agent's progress summary or last tool. A workflow also getsagents {done, started}, counted from itsjournal.jsonl. That read caps at 4 MiB and runs off the stdout loop. - Stop.
_acpmux/subagent_stop {sessionId, subagentSessionId}sends Claude'sstop_taskcontrol request for the subagent's task, and Claude's answer is the reply. The task'stask_notificationthen ends the subagent as cancelled. - Who may stop. The app's agent pane connects over the WebSocket listener as
LocalApp, and unhandled session methods used to reach the harness only from the unix socket.remote_guard::forwardsnow letsLocalApppass exactly_acpmux/subagent_stop. Web and peer connections are still refused. The person gate (harness_forward_check) is unchanged, andhub/person.rsis not touched.
Security notes (relay authorization)
- Local command or content execution: none. The only params the method reads are
subagentSessionId, which is looked up in the translator's own map. Claude receives only a task id the translator already holds, so caller params never reach Claude. - Unowned objects: the hub resolves
sessionIdand rewrites it to that session's agent session, so a stop can reach only a subagent of that session. - Local state exposure: none; the reply is
{}or Claude's error text. - Origins: Web is refused, and so is a peer. LocalApp may pass this one method and no other unhandled method.
tests/remote_guard.rscovers all three cases.
Testing
claude_stdio/workflow_tests.rs(hostedverify-cmux-tui-hosted --filter workflow_tests):- a workflow as a background subagent with progress;
- a failed script check;
- a background agent's progress;
- progress in the subagent model;
- stop: the
stop_taskline, the success reply, Claude's refusal, and an unknown subagent. - Red on 7eb9b65d (test commit); green on this head (run in progress, results to follow).
tests/remote_guard.rsan_unhandled_session_method_reaches_the_harness_only_from_the_unix_socket, extended to check that Web and peer are refused and only LocalApp may stop. Red on a branch with the test and no gate change; green on this head (runs in progress).- God-file check:
requests.rsdoes not grow (net 0). Crash ratchet passes. - An Opus review found no blockers. Its should-fixes (guard tests, journal read off the stdout loop) and nits are addressed in de305b2b.
Changelog
- Added: Claude workflows and background agents report live progress (tokens, tool uses, current step, agents done), and the app can stop one.
🤖 Generated with Claude Code
Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Claude's dynamic workflows and background agents report live progress through acpmux, and the app can stop one.
A Workflow tool call runs as a background subagent of kind workflow: its launch names the run, a failed script check ends it as failed, and its task_notification ends it. Claude's task_progress lines become subagent_progress updates carrying tokens, tool uses, elapsed ms, and the current action (a workflow's step, else the agent's progress summary or last tool); a workflow also gets agents {done, started}, counted from its journal.jsonl. That read caps at 4 MiB and runs off the stdout loop.
_acpmux/subagent_stop sends Claude's stop_task for a subagent's task and answers with Claude's reply. The app connection may pass this one unhandled method; Web and peer connections are still refused.
Relay authorization
- The only parameter read is the subagent id, looked up in the translator's own maps; no caller parameter reaches Claude.
- The hub resolves
sessionIdto that session's agent session, so a stop reaches only a subagent of that session. tests/remote_guard.rscovers the unix socket, the app, and the Web and peer refusals.