Chief: no permission request waits on an answer nobody can give

Merged
LA
16d ago
+945-85

Summary

The Chief must never wait on a permission answer that nobody can give. acpmux accepts an allow only from the person's cmux app. The app shows no tab for a Chief turn session. A subagent's prompt is shown only in its agent tab. A cloud peer refuses an allow that comes from this Mac. Before this PR, several paths left a request open with nobody told. The turn then waited until its 3-hour limit ("Using optchat: control · 34m").

  1. Relayed allow (typed "allow" or "1" that the Chief relays with control answer). When acpmux refuses the allow:
    • If the app shows a working Allow button (a local subagent with a workspace), the prompt stays open. The person gets one line in the chat, once per prompt: "a1 waits for your approval to run make deploy: use the Allow button in its workspace."
    • Otherwise the host denies the prompt and tells the person.
    • The note and the control description no longer tell the model to allow. chief agents allow says what still works.
    • Approval cards are gone since #22747. No path posts a question part, and the tests check this.
  2. Watchdog.
    • A request of a turn session that the host does not allow is denied at once, in every turn (the owner's turn included). Before, the host "left it to its harness" and nobody could answer it. The same applies to a turn session that no turn runs on any more (kept, adopted or ended).
    • The host watches every request it leaves open: a subagent or child prompt with no working button, or a deny that acpmux did not accept. Each one is denied after approval::PERMISSION_WAIT (120 s, on the brain's injected clock). The reason goes to host.log and to the trace (approver: "no approver", reason).
    • The turn idle watchdog now treats a turn that waits on a permission request as idle (TurnFold::awaits_permission). Before, it skipped every turn that had a running tool.
  3. Loop stop. These denials now count toward REPEATS_BEFORE_STOP: a deny after a refused allow, a turn-gate refusal of a Chief tool, and an overdue deny. The turn's stop line gives the reason.
  4. Codex. Codex has a per-MCP-tool approval_mode = "approve". But acpmux gives a Codex turn no optchat MCP server, so Chief tools run as chief <verb> shell commands. No per-tool setting covers those. The reason is documented on codex_turn_config, and the deny stays.

Tests

rbx --lane chief-no-hang --cwd Native/OptChat/optchat-chief -- cargo test --locked --test approval_gates: 17 passed. 9 of them are new and test behaviour at the brain/host boundary with the fake acpmux:

  • a relayed allow tells the person once and keeps the prompt
  • a relayed allow for a prompt the app does not show denies it
  • a prompt that waits 2 minutes is denied, with a trace entry
  • a prompt the app shows waits for the person, also when its workspace opens after the request
  • a turn session's request is refused at once, also for a stale turn session
  • denies after refused allows stop the turn
  • turn-gate refusals stop the turn
  • a turn that waits on a permission counts as idle

The idle test was checked against the old code: it fails ("no cancel 1").

cargo fmt --check: OK. cargo clippy --locked --all-targets -- -D warnings: clean. Full cargo test --locked: every test passes except turn_seal_binding::a_subagent_cannot_reach_owner_controls_on_the_shared_socket. That test also fails on unchanged origin/feat-cmux-next (63bd306ac662) on the same builder, so this PR did not cause it.

Observed / Not run

  • Not run: a live Chief on a tagged build. Nothing here was observed against a real acpmux daemon or the app.
  • The immediate deny of a turn session's request overrides a person's own "ask" rule on the Chief's turn sessions. No app surface shows those sessions; check again if #22900 lands.
  • Not fixed: a request raised while the host was away, for example across a host restart. acpmux sends no notification for it, so only the turn idle watchdog covers it, and only for the turn session. A child or subagent prompt raised then gets only the reconcile note.

Changelog

  • Chief: a permission request nobody can answer no longer holds a turn; a relayed allow tells you where to approve, and unanswerable prompts are denied after 2 minutes.