acpmux: a turn the person did not start never pings, and says whether it is quiet
Leo, about a chat message that said only "Nothing new for you yet.": "why do you guys keep sending me this/get pinged to send this". Offered "a turn you didn't start that has nothing new to tell you shouldn't show up as a message or ping you", he said "yeah".
What happens today
Claude Code runs turns acpmux never prompted: when a background task or subagent ends (a task notification), on a scheduled wakeup or /loop tick, or for a hook. The agent answers, often with a no-op line. In a real coordinator chat on cosmic-gray, 115 such turns ran against 13 prompted ones; "Nothing new for you yet." was one of them. acpmux logged their words as ordinary rows, and each one became the chat's preview / lastMessage, which is the sidebar line and the body of the next "finished" notification.
What changes (acpmux, Rust)
New hub/background_turn.rs. A system/init that comes while no acpmux turn runs, followed by model work (a message, thought, tool call or plan), is a background turn. acpmux brackets it with two mux background_turn records:
{state: "started", id, trigger}, wheretriggeristask_notification,scheduled(acommand_lifecyclestart) orsystem(anything else, such as a hook);{state: "ended", id, trigger, quiet, reason}, wherereasonisresult,prompt(a prompt started an acpmux turn while it ran, and the rest belongs to that turn) orexit.
Such a turn never pings:
- it never marks the chat unread;
- it never ends an acpmux turn, so
lastTurnand the cmux-tui-core notices built from it don't move. This was already true before this PR, and the test now pins it; - new: its words stay off
preview/lastMessage, so the sidebar line and notification bodies keep the person's last real reply.
The rule for "worth seeing", decided here only: quiet is true when the reply is at most one short line (160 characters) with no question mark and no link (://). The webview draws a quiet turn as one muted line that opens on a click, and draws any other background turn in full. Neither kind pings. Turns the person started, and inbox messages from other agents (which are acpmux prompts), behave exactly as before.
A system/init with no work after it starts nothing, so Claude Code's init at startup is not a turn. The record is documented in docs/acpmux-schema.json.
Pairs with #30205 (the webview, which draws these records). Older logs have no records and render as before.
Tests
- Unit tests (
hub::background_turn::tests, 6): the quiet rule; the bracket and trigger for a task notification, a scheduled wakeup and the system case; an acpmux turn is never a background turn; an init with no work is no turn; a prompt ends a running one. - Integration tests (
hub_integration background_turns, 2), using a newown-turn: FIFO :: TEXTscenario infake_claude.pythat runs Claude Code's own turn for a task notification while the chat is idle and hidden:- "Nothing new for you yet." gives one quiet bracketed turn.
unreadstays false,lastTurnis unchanged andlastMessageis unchanged. - A two-line reply is not quiet and still never pings.
- "Nothing new for you yet." gives one quiet bracketed turn.
- Fleet runs: all passed on linux-pool job b2b4cd507a5aab68e0e63b40; clippy
--all-targetsprinted no warnings.
Risk: the tap now logs one extra mux record per background turn and checks one small mutex per own update. The hold, turn lock and unread paths are untouched.
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.
Summary by cubic
acpmux now brackets turns Claude Code runs on its own ā when a background task or subagent ends, on a scheduled wakeup or /loop tick, or in a hook ā with mux background_turn records, and keeps their words off the chat's preview and lastMessage. Previously such a turn's reply became the sidebar line and the next notification's body, pinging the person even when it said nothing new. These turns also never mark the chat unread or move lastTurn.
- A turn is
quietwhen its reply is one short line (ā¤160 characters) with no question mark and no link. The webview draws a quiet turn as one muted line that opens on a click, and any other background turn in full; neither kind pings. - The
startedrecord carries thetrigger(task_notification,scheduled, orsystem); theendedrecord addsquietandreason(result,prompt, orexit). The schema inacpmux-schema.jsondocuments both. - A
system/initwith no model work after it starts no turn, so Claude Code's startup init is unaffected. Turns the person started and inbox messages from other agents behave as before, and older logs render as before. - Adds unit tests for the quiet rule and bracketing, plus integration tests that run Claude Code's own turn over a FIFO and verify the chat stays unread with
lastTurnandlastMessageunchanged.