atrium:// protocol
URI-based dispatch for panes, state, hooks, and commands.
The atrium:// protocol is atrium's internal dispatch system. It turns every workspace concept — panes, rooms, workspaces, commands, hooks, state reads — into addressable URIs. The CLI, keybindings, and adapters all resolve to atrium:// URIs internally.
You rarely invoke these URIs by hand, but you can bind custom keybindings to them (see Keybindings) and reference them from adapter hooks.
URI shape
atrium://<category>/<path>[?<query>]
Categories:
commands— execute an app action.panes— read pane state.agents— message an agent pane, manage its dismiss / wake state, and read named agent definitions.skills— read the skills registry and bind named agents to panes.state— read or write namespaced state.hooks— dispatch an adapter hook event, plus a read sub-route for harness state-capture assertions.
mcp:// namespace was retired in the protocol cleanup (Epic 51). Anything that used to route through mcp://panes/... or mcp://agents/... now goes through the equivalent atrium:// route.
Commands
Dispatches an app command. Actions mirror the command list shown in Settings → Keyboard (also surfaced by atrium commands).
atrium://commands/pane.split.horizontal?pane=$FOCUS
atrium://commands/pane.split.vertical?pane=$FOCUS
atrium://commands/pane.close?pane=$FOCUS
atrium://commands/room.new
atrium://commands/room.close?room=$ACTIVE
atrium://commands/workspace.new
atrium://commands/launcher.open?pane=$FOCUS
Query parameters:
$FOCUS— substituted with the currently focused pane's UUID.$ACTIVE— substituted with the active room or workspace.- Explicit UUIDs are also accepted.
Panes
Read-only URIs for pane introspection.
atrium://panes/ list all panes (respecting access scope)
atrium://panes/{pane-id}/ pane state snapshot
atrium://panes/{pane-id}/scrollback rendered scrollback
atrium://panes/{pane-id}/write write to PTY stdin (POST-like, via CLI)
These are the transport layer for atrium pane list, atrium pane read, and atrium pane write — the CLI just translates its arguments into the URI and calls the resolver.
Agents
A single agents namespace covers two route groups: runtime routes targeted at adapter-managed panes (message, dismiss, wake) and definition routes that read the named-agent registry. Both are resolved by atrium's AgentsHandler. (Epic 74 folded the former atrium://profiles/* routes into agents; the profiles category no longer exists.)
Runtime routes — {id} accepts a full pane UUID or an unambiguous prefix:
atrium://agents/{id}/message send text to the agent's PTY
atrium://agents/{id}/dismiss dismiss the agent's activity card
atrium://agents/{id}/wake clear dismiss and bring the card back
Pane-to-agent dispatch from the source-control pane's Send to agent, from notepad canvas/HTML notes, and from atrium agent message all resolve through these routes. Access checks for the calling pane's MCP scope (same-tab / same-workspace / all) apply the same way they do for panes/....
Definition routes — read and validate named agents (agent.md files under ~/.atrium/agents/):
atrium://agents/ discovery: list available agent routes
atrium://agents/list list every named agent definition
atrium://agents/{slug} fetch a single definition as JSON
atrium://agents/load/{slug} load a definition + its expanded body
atrium://agents/validate validate a definition against the schema
These are the transport behind atrium agent definition list / show / load / validate.
Skills
Routes for the cross-adapter skills registry and pane-to-profile binding.
atrium://skills/index[?source=<segment>] list registered skills (defaults to current workspace + canonical dedup)
atrium://skills/pane-binding/launcher POST: bind a named profile to a launcher-mounted pane
atrium://skills/index is the transport behind atrium skills list. The optional source query accepts the same scope tokens as a +name@scope sigil (atrium-user, atrium-project, harness-<adapter>, harness-project-<adapter>, vercel-labs-skills).
atrium://skills/pane-binding/launcher is the route the in-pane launcher's agent picker writes into when a tile commits its selection. The CLI's atrium agent launch <slug> hits the same route. POSTing a { paneId, profileSlug, skillSelection? } payload registers the resolved agent snapshot for the pane; the existing SessionStart manifest emit delivers the agent's prompt and skill bodies on the first turn. (The payload key stays profileSlug — it's the frontend↔backend binding contract, deliberately left unrenamed when the rest of the "profile" vocabulary became "agent".)
State
Namespaced state reads and writes. Used internally for things like editor scroll position, file-tree expansion, and launcher history.
The path form is atrium://state/{scope}/{namespace} where:
{scope}is one ofapp,workspace,tab(room),pane.{namespace}is an arbitrary, dotted key namespaced by feature area.
atrium://state/workspace/file-tree
atrium://state/pane/{pane-id}/editor
atrium://state/tab/{tab-id}/subtab-groups
Writes are atomic and flow into the relevant snapshot (state.json or the workspace snapshot).
This surface is for internal and adapter use; user-facing configuration lives in config.json, not state.
Hooks
Hook dispatch target. Adapter manifests' hooks object maps event names to these URIs. When the adapter fires an event, atrium runs the URI, which:
- Parses the adapter name and event.
- Forwards the event (with any payload) to the hook server and the activity atom.
- Updates the relevant pane's activity status.
atrium://hooks/claude-code/session-start
atrium://hooks/claude-code/tool-use-pre
atrium://hooks/codex/permission-request
Hooks are the mechanism that drives the activity sidebar's status transitions, the task dispatch machinery, and the "agent needs input" notifications.
_state read sub-route
Adapter test harnesses (test-adapter.sh in atrium-adapters) sometimes need to assert that an emitted hook event was actually stored with the expected shape. The reserved _state sub-route returns the recent hook-event log for a pane:
POST atrium://hooks/_state?paneId=<id>&limit=<n>
→ { "paneId": "...", "count": N,
"events": [ { adapterName, eventName, timestamp, payload, turnId }, ... ] }
_state is reserved — the adapter schema enforces [a-z0-9-]+ on adapter names (no underscore allowed), so the sub-route can't collide with a real adapter.
Resolving from the CLI
Keybindings and adapter hooks call URIs automatically. For ad-hoc resolution from a shell:
atrium exec protocol.resolve --params '{"uri":"atrium://commands/pane.split.horizontal?pane=$FOCUS"}'
Error handling
- Unknown category — error
invalid_params. - Unknown command or pane ID — error
not_found. - Access denied by MCP scope (pane tried to read a pane in another room when scope is
same-tab) — erroraccess_denied.
Why a URI scheme
Every workspace action has to be reachable from three places: the UI, the CLI, and the inter-agent coordination layer. A URI scheme keeps the three in sync — if you can describe an action with a URI, all three surfaces can invoke it without a separate binding per entry point. Custom keybindings and adapter hooks are the most common places to write a URI directly.