Repository navigation
acpmux: turn cap, so a session taking many agents' messages can end its turn - #27520
Conversation
…red) The turn cap's setting (SessionMeta.turnCapMs, _acpmux/set_turn_cap, acpmux session turn-cap) and its tests, with the cap not yet applied to prompts: the tests past the cap fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-1288bbb2b5d2f5c6b66a
…next turn A session with a turn cap whose running turn is older than the cap takes no other agent's steer: their messages wait in the queue and the next turn takes them together. The person's own steers still go in. The hub steers one notice into that turn, once, when an agent message waits, and never cancels the turn itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-1288bbb2b5d2f5c6b66a
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-1288bbb2b5d2f5c6b66a
|
cmux status (one comment, edited in place; each line is updated by the job that owns it) Review App of this head (app.zip, kept 1 day)
Gallery before/after for
States
Pick other states with a red: cmux-next checks (god files, concurrency, crash safety, l10n); base red: the same failure on the base 1c243fe; does not block |
|
Owned by an agent session · session |
|
Bead |
|
Found 6 test failures on Blacksmith runners: Failures
|
The cap's sender lookup and refusal text lived in run_prompt_joined, whose future grew past the debug test thread's stack in steer_now::send_now_for_a_queued_prompt_steers_it_in_at_once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-1288bbb2b5d2f5c6b66a
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-1288bbb2b5d2f5c6b66a
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
…, restarts, stale callers - The queue's Send now of a held agent message still steers in past the cap. - An agent's interrupt held past the cap waits without stopping the turn. - The notice goes only into the turn it names, checked before it is logged and again before it is written. - The turn the notice went into is saved in the session's meta, so a turn a restart adopts gets no second notice. - A local connection with a caller token this daemon does not know cannot write the cap. - The timer test checks "nothing before the cap" only while the cap is ahead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-1288bbb2b5d2f5c6b66a
The prompt future sits near a debug test thread's stack limit on base; the turn cap's additions pushed two hub_integration tests past it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-1288bbb2b5d2f5c6b66a
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
…-cap # Conflicts: # cmux-tui/crates/acpmux/docs/acpmux-schema.json
…is written The notice's turn is checked again under the input handoff, which every prompt's write takes, so no later turn's prompt is written between the check and the notice. A notice logged but never written leaves steer_withdrawn in the log. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-1288bbb2b5d2f5c6b66a
…dependent test - The notice marker is set only while the turn it names still runs, read under the meta lock, so an attempt for an ended turn never replaces a later turn's marker. - The timer test reads only logged notices before the cap: a notice may be logged and still on its way on a slow machine. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-1288bbb2b5d2f5c6b66a
… the notice's log dedupe - An agent message queued as the running turn crosses its cap re-checks the cap after it is queued, so the timer's earlier look at the queue cannot leave the turn without its notice. - The notice goes with resend: a notice the log already holds (its meta marker's save was lost) is answered from the log, never sent again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-1288bbb2b5d2f5c6b66a
teamleaderleo
left a comment
There was a problem hiding this comment.
Review (codex gpt-6-astra high fast, independent review): SHIP at a405b91
Five rounds. Rounds 1 and 2 were BLOCK (the notice could reach a later turn; an agent's interrupt past the cap could stop the turn), and both were fixed. Rounds 3, 4 and 5 were SHIP.
Round 5's two should-fixes are left for a follow-up. Each can lose only the notice, never a held message:
- An internal agent prompt that has no acceptance callback and crosses the deadline doesn't re-check the cap.
- A crash between saving the notice marker and logging the notice leaves that turn without one.
Reds at this head: only "No new SwiftUI" inside cmux-next checks. It flags CmuxNextHome/ChatColors/HomeChatColors{Panel,View}.swift and fails identically on base 1c243fe. This PR changes no Swift.
Tests: https://github.com/manaflow-ai/cmux/actions/runs/40960102893
- hub_integration fails the same 2 tests as base.
- durable_agents passes 124/124, as on base.
- The turn cap suite passes 3x.
|
Shipped in:
|
- tests/agent_tools.rs: the background-shell hook test starts acpmux through daemon_env (hermetic helper); #26447 tripped hermetic_daemon_guard. - hub/turn_cap.rs: its tests gate turns with libc::mkfifo, so they are Unix-only; test (windows) compiles again (#27520). - 12 new JSON index writes/reads and one slice (#26447, #27512, #26108) use JsonSet::set / Value::get / slice::get; panic ratchet back to baseline 697.
![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light-3x.png)
Why
Leo, 2026-10-29: "What are we gonna do with these 200 fing messages? You never end a fing turn. As soon as it comes in, it fing, like, occupies you for another fing turn." and "you can't you just deal with this? Like ACPmux can't be this stuff. How do swarms work?"
A coordinator session gets messages from 30+ lanes. Each one steered into its running turn, so the turn never ended (one ran about 17 h). This is the turn cap from the swarm-inbox plan (P3): past the cap, a turn stops taking other agents' steers, so it can finish.
What
SessionMeta.turnCapMs(ms;0turns it off; absent means the default)._acpmux/set_turn_cap {sessionId, turnCapMs?}. A number sets it, null clears it, and leaving it out reads it. The reply is{sessionId, turnCapMs, effectiveTurnCapMs}.acpmux session turn-cap NAME [--minutes N | --off | --clear].hub::turn_cap::default_turn_cap_ms(&SessionMeta)is the single place that links the coordinator setting to a default cap (COORDINATOR_TURN_CAP_MS, 20 min). It returnsNoneuntil thecoordinatorsetting (P2) lands; that PR changes the body of this one function.hub/turn_cap.rs, wired inturns.rsrun_prompt_joined):digest::agent_sendercounts as another agent's:agent_message, sub-session messages, lane reports, or a proven caller.digest.rs).interruptwaits like the rest and stops nothing.steerOnlyagent message can't wait, so it is refusedsteer.unavailablewithdata.turnCap: true.PromptOptions::send_now, also for a held agent message), forum pushes, offer results and auto-resume.{kind: "turn_cap", turnId, capMs}, hub-set only.turn_cap::notice_reacheschecks this insteer_turnbefore the notice is logged.agent_prompt_hook(bound) checks again while it holdsinput_handoff, which every prompt write takes, so no later turn's prompt can be written between the check and the notice.steer_withdrawnin the log.turnCapNoticed). It is set only while that turn still runs, so an attempt for a turn that has ended never replaces a later turn's marker.resend, so the log also dedupes it: a turn adopted after a restart gets no second notice, even if the marker's save was lost.startedAt. Once the turn-boundary change (P0, harnessend_turnends the daemon turn) lands, the cap covers only one genuinely long harness turn. This PR doesn't touch P0's functions.Timer: stop condition and cleanup
arm_turn_capwhen a turn starts, and only when the session has a cap.AbortHandlelives inSession::turn_cap_timer. The next turn'sarm_turn_capaborts the previous timer before it arms a new one.Weak, so a closed session or a dropped hub makes it a no-op.startedAt.Stack headroom
run_prompt_joinednow awaitssteer_turnthroughBox::pin. On base, the prompt future is already close to a debug test thread's stack limit. The turn cap's few extra lines pushed twohub_integrationtests (steer_now::send_now_for_a_queued_prompt_steers_it_in_at_once,fork_through::btw::a_codex_side_session_runs_sandboxed_read_only_and_the_adapter_is_refused) into a stack overflow. Boxing the steer's future makes the prompt future smaller than it is on base. The cost is one allocation per steer.Wire changes
docs/acpmux-schema.json:_acpmux/set_turn_cap, and theturn_capprovenance kind onuser_message.spec/acpmux-protocol.jsonandwebviews/.../generated/methods.ts: regenerated withACPMUX_PROTOCOL_BLESS=1andbun run acpmux-protocol:codegen._acpmux/mesh_control, which base's committed IR was missing.protocol_ir_is_currentfails that way on base too.Tests (red first)
hub/turn_cap_tests.rsuses the fake agent steering the way Claude Code does:interruptstops nothing.copy_seamtest seam), whose turn ends and the next one starts meanwhile, is refused, never written, and leavessteer_withdrawn.Runs (fast checks on Blacksmith 32 vCPU Linux):
a405b91(merged with base 1c243fe, which carries acpmux: run a prompt's turn on the heap so awaiting it cannot overflow a stack #27577's boxing ofrun_prompt_joined): https://github.com/manaflow-ai/cmux/actions/runs/40960102893-D warningsis clean.durable_agentspasses 124/124, the same as base: https://github.com/manaflow-ai/cmux/actions/runs/40957537973. Its panics atlog_load.rs:148andhub/mod.rsare the debug-build test seamACPMUX_TEST_PANIC_IN_LOAD_LOGSand the thread scope that re-raises it, and they show on base too.send_steer,lane_reportsandsubsessions_deliverypass, and the full--libsuite passes (1949, run 40958992811).hub_integration: 553 passed, 2 failed. Both failures fail the same way on base:auto_resume::retry::stop_before_a_retrys_turn_starts_prevents_it("the turn was stopped before this prompt reached the agent") andtranscript_events::orchestration_fields_reach_subagent_events_and_session_summary(left Null, right 1234). Base runs: https://github.com/manaflow-ai/cmux/actions/runs/40947262818 and https://github.com/manaflow-ai/cmux/actions/runs/40936124287.held_queue::agent_messages_joined_to_a_send_whose_agent_cannot_start_are_not_heldfailed once under full-suite load at 040cb78. It passes 6/6 on its own both here and on base (run 40960162193), and passes in the head's full run.No capture: daemon-only change (the acpmux turn cap). Nothing the app draws changes; the generated webview method list gains one untyped entry.
Risk
steer_turnis boxed inside the prompt future (see above), and base's acpmux: run a prompt's turn on the heap so awaiting it cannot overflow a stack #27577 boxesrun_prompt_joineditself. No session has one by default, so behaviour changes only after the person setsturnCapMs(or after P2 turns it on for coordinators).This PR is about 800 changed lines: one setting, its RPC and CLI, the hold, and its tests.
Agent session: agent session
🤖 Generated with Claude Code