acpmux: a background task tells its chat when it ends; bg_* results lead with a summary line

Merged
TE
2d ago
+526-19

Leo's words (2026-11-03): "hmm claude code desktop's summarization of tool calls/parsing of them seem kinda good? we should bea bel to do that AND have condensed tools that are callable in acpmux/cmux-next..."

What

The acpmux MCP server's background-task tools become condensed tools, and a background task wakes its chat when it ends.

Contract (every condensed tool): an optional description argument (a few words: what the call is for; the transcript titles the row with it), and a result whose first text line is a one-line outcome, also returned as structuredContent.summary, with structuredContent.status = ok | failed | running.

  • bg_start takes description (kept with the task; bg_list, bg_output and the end message name the task by it) and notify.
  • With notify, when the task exits, is stopped, is killed or is lost, the daemon sends ONE message into that chat: a tool notice from acpmux titled with the summary line (Background command finished: <description or command> (exit 0)), with the task id, its command and its last 20 lines of output. It is never steered into a running turn; it waits behind it, like a lane report.
  • notify defaults to true in the MCP tool (false at the raw _acpmux/bg/start RPC). The tool is the agent-facing path, and the PreToolUse hook sends Claude's run_in_background shells there. Those shells already tell the agent when they finish, so with the default on, routing them to the daemon loses nothing. An agent that wants silence passes notify: false.
  • Every bg_* result's first line is the summary: bg_start gives running; bg_output, bg_stop and each listed task give running/ok/failed by state and exit code (ok only for exit 0); bg_list gives a count (3 background tasks: 1 running, 1 ok, 1 failed), running while any task runs, else failed if any failed.

How (no polling)

  • hub/bg_notify.rs: for a notify task the daemon starts a thread that blocks in flock on the runner's liveness lock. The runner holds that lock until it has written its exit record, and the kernel frees it if the runner dies (lost). The thread then hands off to the runtime, which sends the notice.
  • Exactly once: the prompt id is bg-done-<task>, sent with resend, so a second send (for example, a crash between the send and the mark) is answered from the chat's log and never runs again. On acceptance, <task>.notified is written next to the record. At startup the daemon (bg_notify_resume) watches only notify tasks with no mark, and sends at once for a task that ended while no daemon ran.
  • No message goes to a stopped, closing or purged chat, or from a daemon that is shutting down, or for a record of another folder generation. A watcher holds only a Weak hub.
  • description/notify are stored in the runner's record (bg_run::Spec/Record, serde-defaulted, so older records still read).

Tests

  • hub_integration::bg_tasks::a_task_with_notify_tells_its_chat_once_when_it_ends: one notice on exit, with the summary line, exit code and the last 20 lines; none for notify: false; the summary/status fields.
  • hub_integration::bg_tasks::a_task_end_is_sent_once_across_a_restart: an end that is already sent is not sent again by the next daemon, and an end that came while no daemon ran is sent once by the next one.
  • Unit: bg_run::tests::a_tasks_outcome_is_one_line_and_a_status, acpmux_mcp_bg::tests::a_lists_outcome_counts_its_tasks, hub::bg_notify::tests::a_notice_body_carries_the_last_lines.

Observed / Not run

  • Ran at head 732c85c287a43afe88228c2b77c347e79f568d00 on the fleet (fast-checks run 41601847615): cargo test -p acpmux --test hub_integration bg_tasks passed 13/13, including the two new tests; cargo test -p acpmux --lib acpmux_mcp passed 36/36; cargo clippy -p acpmux --all-targets -- -D warnings was clean.
  • Not run: the whole acpmux suite, and a live agent chat getting the notice in the app (only the hub's fake agent).

Changelog

  • Added: an agent's background command (acpmux bg_start) now tells its chat once when it ends, and background-task tool results start with a one-line summary.

Agent session: agent session

šŸ¤– Generated with Claude Code