atrium — docs
homearchitectureagent statusidentityfleetscontrol planetrust & securityreaping

The control plane (ctl)

A fleet is a saved org chart; ctl builds one live, and lets an agent (or you) drive it. With --allow-ctl, a pane can spawn, send to, observe, and kill other agent panes, turning atrium from a viewer of agents into a runtime for them.

What ctl is

The point of doing this inside atrium is legibility. Every agent ctl creates is a visible, killable pane with a status border, listed in the org chart. Because the whole hierarchy is on screen and controllable, atrium coordination stays legible in a way opaque subagents are not.

The shape it is built for is one human, a couple of coordinators, and a wide bench of workers:

  you (a shell/agent pane)
   └─ hand an initiative to →
      lead_a, lead_b          (coordinators)
        └─ each splits it across →
           dev_1, dev_2, …    (workers, each its own pane)

Trust posture (how hands-off spawned agents run, and the ceiling they are capped at) is set with --trust and is covered separately; see trust and security.

Turning it on

The control plane is opt-in and off by default. Without --allow-ctl the channel is not bound and atrium ctl refuses; a session that did not ask for it is unchanged.

atrium --allow-ctl claude                 # bind the control channel
atrium --allow-ctl --trust claude         # …and launch every agent trusted + hands-off
atrium --allow-ctl --max-depth 3 claude   # cap spawn recursion (default 6; 0 = unlimited)

--max-depth <N> bounds spawn recursion so a self-spawning agent cannot fork-bomb the machine. The default is 6; 0 removes the cap. Width is never capped, only recursion depth.

When ctl is on, atrium opens a per-instance control channel (a named pipe on Windows, a unix socket elsewhere; zero-dep, non-blocking-drained in the run loop) and injects two env vars into every pane it spawns:

Any process in a pane (a shell command, or an agent via a tool or hook) issues control by running atrium ctl <cmd>, which speaks one JSON request per line and prints the one-line JSON reply.

The command surface

Targets are a pane id (its numeric agent id) or a role label (dev_1); an ambiguous role is a clear error.

CommandDoesReply
atrium ctl spawn [--role R] [--identity X] [--here|--window] -- <cmd…>Open a visible worker running <cmd> (a new window, or --here tiled beside the caller), tagged R, optionally under credential identity X.{"ok":true,"pane":3,"role":"dev_1","session":"…"}
atrium ctl listThe live org chart: every pane with parent, role, depth, and agsess status.{"ok":true,"tree":[{"id":0,"parent":null,"role":null,"title":"claude","depth":0,"status":"idle"},…]}
atrium ctl send <target> <text…>Deliver <text> to a pane's agent as a submitted prompt. Queued until the target is idle (agsess-gated), so it never lands mid-turn.{"ok":true,"target":3,"queued":true}
atrium ctl status [<target>]One target's status, or (no target) a roll-up of the caller's subtree. Backed by agsess: working / waiting-approval / waiting-prompt / idle (or null if unbound).{"ok":true,"pane":3,"status":"working"}
atrium ctl kill <target>Terminate a worker and its whole subtree (a lead's kill reaps its ICs). The dead panes' windows re-tile or close on the next tick.{"ok":true,"killed":[3,4,5]}
atrium ctl audit [N]The control-request log (most recent N, or all), for reconstructing a run.{"ok":true,"audit":[{"seq":1,"caller":0,"action":"spawn","detail":"role=dev_1 argv=claude identity=-","ok":true,"note":"pane=1"},…]}
atrium ctl board set <key> <field=value…>Merge fields into the shared board (create if absent); an empty value clears a field.{"ok":true,"key":"auth","entry":{"by":"dev_1","ms":…,"fields":{"status":"DONE","owner":"Max"}}}
atrium ctl board get <key> / list / del <key>Read one entry, roll up the whole board, or remove an entry.{"ok":true,"board":[{"key":"auth","by":"dev_1","fields":{"status":"DONE"}},…]}
atrium ctl bus pub <topic> [--decision] <field=value…>Publish a structured event to topic. Default urgency fyi; --decision marks an escalation that needs a human answer.{"ok":true,"event":{"seq":7,"topic":"deploy","kind":"fyi","from":"dev_1","fields":{"msg":"merged"}}}
atrium ctl bus sub <topic…> / unsub [<topic…>]Subscribe (merged; * = firehose) or unsubscribe (empty ⇒ all) the caller.{"ok":true,"subscribed":["deploy","*"]}
atrium ctl bus feed [--since <seq>]Pull the caller's subscribed events with seq > since (no echo of your own); returns a cursor to resume from.{"ok":true,"feed":[{"seq":7,"topic":"deploy",…}],"cursor":7}
atrium ctl bus resolve <seq>Mark a decision_needed event answered (clears the bar/panel escalation).{"ok":true,"seq":7,"resolved":true}

The board: shared source of truth

send is the ephemeral message stream ("I just shipped auth"); the board is the durable current truth (auth: DONE, owner: Max, url: …). It answers "what is currently true?".

It is a schemaless key → fields tracker living in the atrium daemon. Because atrium is the single broker process every pane talks to, it is a plain map behind the pipe: single writer, no locking, no consensus. A coordinating lead sets tasks and reads board list for status/owner/blocker instead of re-scraping each teammate's transcript; teammates update their own entry as they work.

The board is part of the ctl surface, so it is gated by --allow-ctl. Set ATRIUM_BOARD=<file> to persist it across restarts (in-memory otherwise). Every write records who made it (in the audit log and the entry's by), but the board is shared by the whole session; there are no per-teammate walls.

The bus: the team's event stream

If the board answers "what is currently true?", the bus answers "what just happened, and does anyone need to act?". A teammate publishes a structured event to a topic (bus pub deploy msg=shipping url=…); teammates pull the topics they subscribe to (bus sub deploy then bus feed). State-only coordination means polling the board; the bus lets a finished worker notify without everyone re-checking.

Two urgency classes carry the visibility-vs-approval split:

Open decisions surface actively: the Ctrl+A b panel lists them first and the status bar shows N decisions need you, so you do not have to be looking. bus resolve <seq> clears one once answered.

Backpressure is designed in, not bolted on, because unread messages cost attention:

Like the board, the bus lives in the single broker process (a plain in-memory log, no locking), is gated by --allow-ctl, records who sent each event, and persists across restarts with ATRIUM_BUS=<file>.

A worked run: initiative to leads to ICs

# You launch the control plane, trusted so the fleet runs hands-off. With
# --trust, every ctl-spawned agent also comes up trusted (no per-action prompts),
# so a lead can drive its ICs without you clicking through dialogs.
atrium --allow-ctl --trust claude

# From your pane, hand the initiative to two coordinators:
atrium ctl spawn --role lead_a -- claude
atrium ctl spawn --role lead_b -- claude
atrium ctl send lead_a "Own the parser rewrite. Split it across two ICs, TDD."
atrium ctl send lead_b "Own the docs refresh. One IC is enough."

# lead_a, from its own pane, builds its team beside it (--here) and tasks them:
atrium ctl spawn --here --role dev_1 -- claude
atrium ctl spawn --here --role dev_2 -- claude
atrium ctl send dev_1 "Rewrite the tokenizer, tests first."
atrium ctl send dev_2 "Rewrite the AST builder, tests first."

# lead_a coordinates by status, not by scraping terminals:
atrium ctl status            # roll-up of lead_a's own subtree
atrium ctl status dev_1      # one IC (lead_b's team is out of scope, refused)

# When an IC is done, its lead reaps it (and anything under it):
atrium ctl kill dev_1

# You see all of it, any time: the org chart and the full trail:
atrium ctl list
atrium ctl audit

The multiplier: many workers, coordinated by a few of them, driven by one human, and every level is a pane you can watch, redirect, zoom into (Ctrl+A z), or kill. ctl is the small verb layer; the panes, tiling, identity injection, and agsess status it stands on already existed.

Delegation skills: atrium-delegate and atrium-coordinate

ctl is the mechanism; two Claude Code skills (in skills/) are the instruction layer that makes an agent use it. Copy the directories into ~/.claude/skills/ and any atrium-hosted claude picks them up. They exist because the trigger is who decides to delegate:

Drop a spawned lead straight into coordinate mode by telling it to coordinate:

atrium ctl spawn --role lead -- claude
atrium ctl send lead "Coordinate this across a team, one teammate per part: <the initiative>"

or, from your own pane, just say "coordinate this across a team: …" and let the skill surface.

See also