Agent-to-agent messaging across panes and locations
Send framed messages between local and remote agents, discover them with agent list, suppress replies, and scope which panes each agent can reach.
Agents running in atrium panes can send messages to each other through a single CLI command: atrium agent message. This is the only sanctioned inter-agent channel. Humans can also send a message to any agent from the UI — the Activity sidebar has an inline Message field on every agent card (Cmd+Enter / Ctrl+Enter sends), and any card expands into an Omni Chat dock for a fuller back-and-forth. Both use the same framing protocol under the hood.
The command
"$ATRIUM_CLI_PATH" agent message <target> "message body"
<target>— the recipient's pane ID or short unambiguous prefix. For an agent on another box, use<id>@<location>as shown byatrium agent list --all-locations.- The message body is a single string; newlines are fine.
--no-frameskips the sender envelope and delivers a plain user-typed prompt. Use it when the message is a human talking, not an agent relaying — the recipient sees a normal user turn with no[Message from … via atrium]header.--no-replyremoves the reply instruction from the frame. Use it for a one-way update that should not start another conversational turn.
Framing
atrium does not just forward the raw text. It wraps the message in a frame so the recipient knows:
- Who sent it.
- Which adapter they are.
- How to reply.
[Message from "Quinn (QA Engineer)" (claude-code) via atrium]
Please implement the auth middleware.
[Reply with the command: "$ATRIUM_CLI_PATH" agent message <sender-short-id> "your reply"]
The recipient agent sees this as a fresh user turn and responds normally. When it replies using the suggested command, the sender receives the reply as its own fresh user turn. Messages are fire-and-forget from the sender's perspective — do not poll pane read for a response.
Discovering agents
# Just the panes running an adapter (excludes plain terminals)
"$ATRIUM_CLI_PATH" agent list
# Output as JSON for scripting
"$ATRIUM_CLI_PATH" agent list --json
# Include registered SSH locations; remote IDs are qualified with @location
"$ATRIUM_CLI_PATH" agent list --all-locations
"$ATRIUM_CLI_PATH" agent message <short-id>@build-box \
"Report your current status" --no-reply
Each entry has:
pane_id(UUID) and a short prefix.adapter(claude-code,codex,grok, …).workspaceandroom.locationwhen the pane is not local.status(idle, working, waiting-for-input, …).
Access scope
Each pane has an MCP access scope that controls which other panes it can see when it runs atrium pane list / atrium agent list:
same-tab(default) — only panes in the same room.same-workspace— all panes in the project.all— every permitted pane across projects on the current location (the broadest local scope).
mcpAccessScope on the pane. The scope is persisted in the pane snapshot.
Additionally, each pane has mcpVisible — if false, the pane is hidden from other panes' discovery calls entirely.
Use scopes to keep agents from messaging rooms they should not see (for example, a parent project's agents should not reach into a worktree's sandbox by default). --all-locations is a separate, explicit fleet discovery option; cross-location delivery also requires the target location to be reachable. atrium never silently redirects a failed remote message to a local pane.
When not to use messaging
- Do not use
pane writeto poke a message into another agent's stdin directly. That bypasses framing, so the recipient cannot tell who is talking. - Do not use
pane readas a reply mechanism. Polling the recipient's scrollback is brittle and loses framing.