Chief: trusted agent sessions may tell and spawn its own subagents

Merged
TE
2d ago
+455-6

When another agent session sends the Chief a message through cmux chief (a coordinator routing work, for example), the Chief's turn takes the ask floor. That floor refused tell and spawn to the Chief's own subagents with "refused: this turn carries another person's text". So the Chief could not pass on a coordinator's routing to its own subagents, even when the owner wanted it to.

Leo, 2026-11-02, on that refusal: "that seems kinda lame? why shouldn't you be able to do that? i think you should."

What changes

  • New Chief settings:
    • relay.trustAgents (default true).
    • relay.trustedAgents (default ["*-coord"]). This is a list of acpmux session names, each an exact name or a pattern with one *. A bare * is refused.
    • The configured contact session is trusted too.
    • Set with optchat-chief settings set relay.trustedAgents "a-coord, lead-*" or ... relay.trustAgents false. Both work with the host running or stopped.
  • A turn whose only other-person text comes from trusted sessions may:
    • tell any of the Chief's own subagents. The subagent sees the message prefixed with [relayed from NAME].
    • spawn subagents with the owner's policy, instead of the ask floor.
  • Everything else stays refused in that turn, and its own session keeps policy ask. That covers:
    • Bash and file writes
    • tell to session: or chief: ids
    • remember, cloud_workspace, watch commands and attachments
    • acpmux_send
  • The relay is dropped, and the whole floor applies, when:
    • any message in the turn is untrusted, whether it opened the turn or was steered in;
    • an agent's message is steered into an owner's turn;
    • a turn is resumed after a restart.
  • The turn's framing line names the sender. It says what the turn may do, and that the Chief should not ask the user for more access.

Trust model. A sender's name is read only from a message the owner's own CLI posted (agent_sender_as). A teammate's forged marker names nobody, and another_persons_forged_agent_marker_names_nobody still passes. Anything that can post as the owner could already post unmarked owner text. So the list is a convenience scope, not a new boundary.

Tests. Red first: the test commit fails on the old host. These pass with the fix:

  • tests/approval_gates.rs:
    • a trusted coordinator may tell and spawn, and the tell shows the sender, while Bash, sessions, Chiefs, memory, Cloud, watch and attachments stay refused;
    • an untrusted agent in the same turn keeps the whole floor;
    • the switch turns it off;
    • the list and the contact decide who is trusted.
  • Unit tests in chief_settings.rs cover matching, parsing and the round trip.
  • The full optchat-chief test suite and clippy (-D warnings) pass on the Linux pool.

Risk. Chief code (access policy). The change opens only tell and spawn to the Chief's own subagents, and only for owner-trusted sessions. The person can turn it off.

Follow-ups (from review):

  • tests for a mid-turn steer of an untrusted message, a late tell after a relay turn, and a resumed relay turn;
  • a contact match that also checks the host;
  • show the sender on a spawned task too.

Agent session: agent session

🤖 Generated with Claude Code


Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Allows the Chief to relay trusted agent message turns to its own subagents: when only trusted sessions' text opened the turn, tell and spawn are allowed, with the tell prefixed [relayed from NAME] and spawn running under the owner's policy. Previously such turns refused both as "this turn carries another person's text". Everything else—Bash, file writes, sessions, Chiefs, memory, Cloud, watch, attachments, acpmux_send—stays refused, and an untrusted message in the same turn drops the relay.

The trust scope is configured with two new settings, relay.trustAgents (default true) and relay.trustedAgents (default ["*-coord"], plus the configured contact session); a bare * is refused.

Trust model

  • Sender names come only from messages the owner's own CLI posted, so a forged teammate marker names nobody and the list is a convenience scope, not a new boundary.