Swarm coordination: primitives and Skills
Status: Planned (S15–S22 in the
roadmap). Source candidates
today: S16 port leases, the S19 Run ledger, S18a Session-bound worker
credentials, S18b fan-out guards, the maia-orchestration Skill and
run create.
Maia supplies deterministic primitives; Skills carry the coordination (decision). The verb set is small: create an isolated unit, start a Session, send, read status, wait for a state, read the transcript by cursor, cancel. Maia exposes those verbs and the shared resource primitives below through the protocol registry, so the CLI, SDK and MCP agree. Agents coordinate only through the Node, the hub every agent and harness shares (decision).
Shared resources: one lease table
Agents on one machine collide on ports, databases, devices and merges. One
advisory lease table covers them, with two kinds: port (source candidate
since S16 wave 1) and lock (planned, S16 wave 2). Leases are advisory: the
Node never restricts binds in the kernel, kills processes by port or name,
reserves files by glob, adds pre-commit guards or makes a lease a launch
prerequisite (decision).
Port leases (source candidate)
maia port lease acquire --purpose <label> [--session <session-id>] [--count <1..8>] [--preferred <port>]
maia port lease renew <lease-id> --expected-revision <revision>
maia port lease release <lease-id>
maia port lease list [--session <session-id>] [--limit <1..100>] [--after <cursor>]
- The Node allocates 1 to 8 TCP ports from its range (
MAIA_PORT_RANGE, default41000-41999) for an exact Session or, without--session, for the caller.--preferredis a hint honored inside the range; ports browsers block are skipped. - A port is granted only when a TCP connect to
127.0.0.1and::1is refused, an exclusive bind on127.0.0.1succeeds and, where available, the listening-socket table does not list it. A bind probe alone reports busy ports as free on macOS, because Node.js setsSO_REUSEADDR. - A lease lasts 15 minutes and is renewable at its exact revision. It also
ends when its Session stops or fails, and leases are reconciled at Node
start.
releaseis idempotent. - Codex and Claude Code Sessions that hold leases at launch get
MAIA_PORTS=<port>,<port>; nothing else in their environment changes, and Maia never overridesPORTor any harness variable. listreports, per port, whether something listens on it now. The MCP toolsport_lease_acquire,port_lease_renew,port_lease_releaseandport_lease_listmirror the commands. Typed failures:port_pool_exhausted(retryable),port_lease_not_found,port_lease_expired,port_lease_conflict.
Planned (S16 wave 2, S15)
-
lockkind: the same lease record as ports:{ leaseId, kind: port | lock, key, ownerSession, name?, reason?, grantedAt, expiresAt, nodeEpoch }The key is an exact string, no globs. Conventions:
db:<name>:migrate,device:<udid>,task:<id>,git:merge:<repo>,file:<path>. Acquire returns a grant orheld { holderSession, reason, expiresAt };--waitreturns within its bound. Locks last 10 minutes by default and end with their owner Session's cleanup, at expiry without renewal and when the Node epoch changes. Only the owner renews or releases.maia lock acquire <key> [--ttl 10m] [--reason <text>] [--wait 60s] maia lock renew <lease-id> [--ttl <duration>] maia lock release <lease-id> maia lock list [--mine | --session <session-id>] -
Launch variables:
MAIA_SESSION_ID,MAIA_WORKSPACE_SLUG(lower case, digits,-and_, valid as a Docker Compose project name),MAIA_PORT,MAIA_PORTSfor Pi Sessions, and a stable preferred port derived from the Project, worktree and name. -
Resources (S15, D-167):
maia node resourcesand the worker toolshow_node_resourcesread the Node's environment, CPU, memory, disks and capacity, and the usage of each Session the caller may see, so an agent can size its work before it starts. -
Listener attribution (S15):
maia port lease list [--port <n>] [--unleased]joins listening sockets with leases and process trees: port, interface, process, command, Session, and how it was attributed (lease, tree, working directory or none). -
Databases, devices, Redis and Docker Compose use
MAIA_WORKSPACE_SLUGandlock, not dedicated primitives; build tools keep their own locks; stable ports give stable URLs without a.localhostreverse proxy.
Orchestrator memory: the Run ledger
Status: Source candidate (S19). The Run ledger is the orchestrator's
durable memory over executor Sessions, so a coordinator that loses context or
restarts rebuilds its state by reading it
(decision). It lives beside the local-v1 terminal Run
(run create), whose tables cannot hold Session dispatches
(decision).
maia run ledger create --workspace <workspace-id> --graph-stdin [--command-id <id>]
maia run ledger list --workspace <workspace-id> [--limit <1..100>] [--after <cursor>]
maia run ledger task list --run <run-id> [--ready]
maia run ledger task show <task-id> --run <run-id>
maia run ledger task dispatch <task-id> --run <run-id> --session <session-id> --expected-task-revision <revision> --expected-session-revision <revision> [--command-id <id>]
maia run ledger task settle <task-id> --run <run-id> --session <session-id> --outcome <succeeded|failed> --expected-task-revision <revision> --evidence-stdin [--turn <turn-id>] [--command-id <id>]
- The graph on stdin is
{ "tasks": [{ "clientKey", "dependsOn", "objective" }] }with the local-v1 bounds (32 Tasks, 8 direct dependencies and dependents, depth 8) and no root or Dispatch Task. - A dispatch is one attempt at a Task on one running Codex or Claude Code
Session of the Run's execution workspace (
workspaceId, the compatibility name of an Execution Environment until R1b). The Node queues the Task's objective as a turn in the transaction that records the dispatch; wait on that turn withsession turn wait. A Session holds one active dispatch at a time. - Settle records the outcome, the settled turn (the dispatched one or a
later turn of the same Session, after it ended) and evidence (1 to 8192
bytes).
succeededneeds a completed turn. A Task that fails three dispatches isstopped; reopening it is not built yet. - Task state is derived from the dispatches, never stored:
active,succeeded,stopped, otherwisereadyonce every dependency succeeded andblockedbefore.--readylists exactly the ready Tasks. The Run issucceeded,blocked(nothing ready or active, a stopped Task in the way) oropen. - Dispatch and settle compare the Task revision, so two coordinators cannot
both act on one Task, and replay their first receipt for a reused command
id. A restarted coordinator finds its open Run with
run ledger listand its Sessions withsession list, then reads every Task and dispatch, with its Session and turn, fromrun ledger task list. - A Node without the Session's executor runtime refuses a new dispatch with
command_not_implementedbefore recording anything, assession turn senddoes; a retry of a recorded dispatch still replays its receipt. - The MCP tools
run_ledger_create,run_ledger_list,run_ledger_task_list,run_ledger_task_show,run_ledger_task_dispatchandrun_ledger_task_settlemirror the commands. Typed failures:run_task_not_ready,run_task_not_active,run_turn_unsettled(retryable).
session observe reports one attention value per Session when it needs the
orchestrator, most urgent first: failed (the Session failed),
approval_pending (its worker waits for a person to approve a command,
patch or tool call), question (an unanswered recorded or worker question;
approvals), turn_failed (a running Session whose latest turn failed or ended with an unknown outcome, until a later turn is sent or the Session stops; a turn a stop ended raises nothing), stalled (a running turn past the stall window) and
settled_unreviewed (a Run ledger dispatch whose turns ended and that nobody
settled). session list --all --attention (session.list without a
Project, with attention: true) pages through every Session of the Node that
has one, by the same rule and the default stall window, and each
session.list page names the attention value of its listed Sessions. Maia
adds no planner or scheduler. Until S19's follow-up converges the local-v1
Run onto the ledger (D-119), its Tasks and the
ledger's keep separate tables and state names
(decision).
Isolation and review
Source candidate (S5; details in
coding Sessions). Each coding
Session works in its own worktree from a base ref. Creation copies the
git-ignored files the Project lists in .maia/worktree.json (such as .env);
the Project's setup command runs before the first launch where the Node admits
Project commands, and inside the Session's sandbox from S47b
(D-174). session diff [--stat] shows the change against the base;
session archive stops the Session, commits its work to the kept branch, and
an hour later the Node removes the worktree with a cleanup proof. Integration
(rebase, test, merge or pull request) needs a person's confirmation; from
S61a a Workspace may instead grant a GitHub write tool to exact Agents for
exact repositories, enforced by the Node at the write
(D-116).
Safety
Maia must never become an unauthenticated side channel between agents, such as a shared message board reached with one shared credential (decision).
- One authenticated channel. Agents coordinate only through the Node. Every message carries a Node-stamped sender; peer content is data, never authority or consent.
- No shared credentials, and workers cannot read the operator's. Each
worker gets a Session-bound credential with a
workerororchestratorrole; on Linux, Landlock denies a reading Claude Code worker reads of the Node runtime directory and the auth file (S18a, source candidate), Codex's own profile denies them to Codex commands on every platform (D-110), and a coding Claude Code worker's own sandbox, rules and Maia's guard deny them on every platform (D-184). See the worker credential. Without this, roles mean nothing. Maia adds no secret broker (decision). - Bounded fan-out. The Node enforces, per Project, Session depth (1 by
default: an operator's Session has depth 1, its child depth 2; never past
2 levels, whatever the Project sets, D-151), live
children per parent Session (8), messages a Session sends per minute (60)
and leases a Session holds (8), inside the write that creates, sends or
leases (S18b, source candidate). Each limit trips with its own code:
guard_depth_exceeded,guard_live_children_exceeded,guard_messages_per_minute_exceeded,guard_leases_per_session_exceeded. A person reads them withmaia project guard showand changes them withmaia project guard set(a preview, then a confirmation) (project.guard.showis also an MCP tool). A Session messages another Session by exact ID, through the Node's mailboxes; there is no broadcast board, LLM voting or consensus. Today the recipient must share the sender's checkout. Planned (D-166): every Session of the Project by default, and Projects of one Workspace whose guards both setmessageReach: workspace. - Session mailboxes.
session.message.send,session.inbox.checkandsession.inbox.ackare in both Session roles and are thesession_message_send,session_inbox_checkandsession_inbox_ackworker MCP tools. The Node names the sender from the credential, never from a parameter, so a Session cannot send as another one, and a recipient outside its reach, unknown or ended is refused. Delivery stays in arrival order until the recipient acknowledges it, and the receipt namesactor_<sessionId>. A recipient reads its inbox when it calls the tool; the Node does not wake an idle Session yet. Subject and body are peer content: everysession.inbox.checkresult carriescontent: "untrusted_peer", and the tool text and themaia-workerSkill say the same. It grants no authority, consent or permission change, and the Node's authorization and confirmations still apply. The Skill also tells the recipient to reply only when needed. Planned (D-168): the Node keeps a message's subject and body only until the recipient acknowledges it and its harness's reader returns them with a matching digest; the trace (IDs, sender, recipient, sizes, digest, times) stays, and the harnesses' own transcripts hold the text. - Liveness belongs to the Node. No worker heartbeats and no typing into terminals; agents use structured protocols.
- Fast, exact stops. An emergency hold on one exact Project, Connection,
Node or capability rejects new work at once and fences running work at the
next safe edge (S20; design in
operations);
there is no
alltarget (decision). - Audit. Every mutation has a receipt and an event with a timestamp; leases and holds are events too.
Sessions across servers
The Node, not the machine, is the boundary. Every mailbox lives in its Node, so its Sessions message each other whichever worker process runs them, as on one system: today within one checkout, and with D-166 within a Project, or across Projects of one Workspace that both allow it. Sessions in different Nodes cannot message each other yet.
Today Planned (MN1-MN5)
server A server B Control Node: one store, per-Session mailboxes
Node A Node B |-- server A runs S1, S2
S1 <-> S2 no S3 <-> S4 '-- server B runs S3, S4
S1 <-> S3: same Project, same Node
- Today (source candidate). A Node runs all its workers on its own
machine. Several servers are several independent Nodes, each with its own
store, Workspaces and mailboxes. One CLI reaches each of them with
maia --node <name>, andmaia node listreads them all (D-141). That is control, not a channel: a Session in one Node cannot address a Session in another. - Planned: one Node across servers. With MN1 and its first placement slice, an execution Node runs Sessions for one Control Node, which keeps the only store (multi-server, D-150). The mailboxes stay in the Control Node, so the reach of D-166 applies on all of its servers with no new mechanism. Files stay per server: the Control Node prepares each checkout on the server that runs it and replicates nothing.
- Planned later: between Nodes. Independent Nodes, and other organizations' Nodes, exchange messages over A2A (A2A boundary). Each Node stamps and receipts its own side, and no authority is shared.
- Limits in every case. A Session reaches only the Sessions its reach
allows, by exact ID (D-166), and there is no
broadcast. Links between exact Workspaces come in a later tier. A recipient reads its inbox when it calls the tool; waking it on
arrival is planned after S26.
maia project guard setbounds the messages a Session sends per minute.
Skills
Few, cohesive Skills: a short kernel plus references the maia binary serves
for its own version (progressive disclosure, so a Skill never drifts from the
CLI it describes).
Orchestrator Skills (the user's plugin):
| Skill | Encodes |
|---|---|
maia-orchestration (exists) | The loop: discover, spawn, send, join, observe, intervene, receipts. An empty wait is a checkpoint; silence never authorizes stop, retry or release. |
maia-swarm-plan | Effort scaling (one Session by default; 2–4 for comparisons or independent slices; 3–5 to start for wide work); budget check first; task brief (target, change, constraints, owned files, observable acceptance); real dependencies only; record the graph in the Run. |
maia-swarm-supervise | Launch the whole independent wave, then wait; validate each report against its task; retry only a positive failure in a new Session, escalate to a person after two; stall ladder (observe, events, interrupt, resend once); progress at milestones; every settled Session reused, kept or archived. |
maia-swarm-review | Diff stat, then the diff; run the acceptance tests; an independent read-only reviewer Session; at most two fix rounds; integrate one Session at a time with rebase and tests; merge only after a person confirms. |
Worker Skills (injected into worker Sessions only):
| Skill | Encodes |
|---|---|
maia-worker | Exists for coding Sessions (at launch): the commands a Session credential reaches (context, status, port leases, child create), limits and exit codes. Planned: do the assigned task in the owned files; never start Sessions; ask the parent instead of the local question UI; read the inbox before new files, after tests and before the report; use maia port lease and maia lock; report contract: outcome, three-sentence summary, files changed, tests run, risks, long output by file reference. |
maia-worker-review | Check against the task and acceptance; findings with severity and file:line; no edits. |
How a Skill is written as a standard operating procedure, pinned, kept up to date and traced: Skills. Coordination rules that matter for safety are enforced by the Node, not by Skill text: agents ignore rules written only in prompts.
Imported from Maia OS docs at 3dc3e07