How to launch a formation of capsules
A formation is a group of capsules on one machine that work on one
task together: one member receives the task and hands parts of it to the others. This guide
scaffolds a two-member formation, lead and worker, launches it on a task that lead hands to
worker, and follows the hand-off through the trace until the formation ends.
The relevant manifest options are:
| Option | Controls |
|---|---|
| exports.peer_tasks.accept | Whether the member serves tasks other members send it |
| network.authentication | Whether the member's door refuses callers without a token |
| capabilities.network.allow | Where the member may connect, other members' doors included |
| lifecycle.task_acceptance | How many tasks the member takes |
| lifecycle.after_task | Whether the member exits or waits after a task |
| inference.system_prompt | What each member is told it is for |
The output below is from one real run with OpenAI as the provider and HOME set to a scratch
directory, /tmp/crew-run-d078/home. Your ids, ports, pids, timings and paths will differ.
Step 1 — scaffold the formation
mur new --roster names the provider's driver, endpoint and model from ~/.murmur/config.yaml.
With no inference: block it uses Anthropic. To use OpenAI, set the provider before you scaffold:
Nothing to set.
mur config set -g inference.provider openai
Set inference.provider in ~/.murmur/config.yaml
In an empty directory, scaffold a formation named crew:
mur new --roster crew
Scaffolded formation 'crew' in ./crew
crew/roster.yaml lead is the entry member; lead may call worker
crew/lead/murmur.yaml capsule local/crew-lead@0.1.0
crew/worker/murmur.yaml capsule local/crew-worker@0.1.0, serves peers
Driver: murmur/driver-openai@0.19.0, the newest that fits this mur in github:murmur-nexus/default-artifacts <release>
Next:
mur config set -g credentials.OPENAI_API_KEY <your key>
mur install -g murmur/driver-openai@0.19.0
mur build crew/lead && mur install -g crew/lead/crew-lead-0.1.0.mur.zip
mur build crew/worker && mur install -g crew/worker/crew-worker-0.1.0.mur.zip
mur run --roster crew --task "<your task>"
The scaffold writes three files and nothing else. Every key in them carries a comment and a link
to its reference entry. The Next: lines are the rest of this guide, in order. See
mur new --roster.
Step 2 — read what the generated files declare
crew/roster.yaml
The roster names the members, marks one as the entry member, and says who may call whom:
members:
- name: lead # its name in this roster, used by reachability
capsule: local/crew-lead # built from lead/murmur.yaml
version: 0.1.0 # the exact version installed
…
entry: true
- name: worker # its name in this roster, used by reachability
capsule: local/crew-worker # built from worker/murmur.yaml
version: 0.1.0 # the exact version installed
…
reachability:
- from: lead # the calling member
to: [worker] # the members lead may call
- The entry member.
leadhasentry: true, so it receives the formation's task, and its outcome is the formation's outcome. No rule may list it into: the entry member is never called. See The entry member. - The edge. The one rule lets
leadcallworker. That edge is what givesleadthecall-membertool, and what makes the launcher issueleada credential forworker's door.workermay call nobody. Seereachability.
crew/worker/murmur.yaml
worker consents to tasks from other members, and waits at its door between them:
lifecycle:
task_acceptance: queue # take tasks one after another
after_task: sleep # wait at the door for the next one
…
exports:
peer_tasks: # tasks another member sends to this door
accept: true # serve them; absent or false refuses them
…
network:
authentication: # refuse every caller without a token this session minted
scheme: bearer # Authorization: Bearer <token>, the only scheme
- Consent. A member a rule calls must declare
exports.peer_tasks.accept: true, or admission refuses the roster withE-ROS-005. - Authentication. A roster with any edge requires every member's door to require a token, or
admission refuses it with
E-ROS-006. The door then lets in only the credential the launcher issued for it. See Authentication. - Lifecycle.
queueandsleepkeepworkerserving for as long as the formation runs.
crew/lead/murmur.yaml
lead is told to hand its task on, declares no lifecycle, and may reach other members' doors:
system_prompt: "You are 'lead', the entry member of the formation 'crew'. You do not do the task yourself: worker does it. Your first reply is a call-member tool call that hands worker the task you are given, stated in full. Then end your turn while worker works. When worker's answer arrives in this conversation, answer with it. If the runtime says worker gave no answer, say so in your reply and do not answer the task yourself."
# How the capsule takes tasks is not declared here: mur run --roster sets the entry member's
# lifecycle on every launch. With --task it runs that one task and exits, which ends the
# formation; without, it takes tasks at its door one after another until the formation is
# stopped.
# https://docs.murmur.nexus/reference/cli/#mur-run-roster
…
capabilities:
network: # IP destinations the capsule may reach
allow: # anything not listed is denied
- localhost # every member's door, at any port and scheme
…
network:
authentication: # refuse every caller without a token this session minted
scheme: bearer # Authorization: Bearer <token>, the only scheme
- System prompt.
leaddoes not do the task itself: it hands the task toworkerwithcall-member, ends its turn, and answers with what comes back. - Lifecycle.
leaddeclares none:mur run --rostersets the entry member's lifecycle on every launch. With--task,leadtakes that one task and exits when it ends, and its exit ends the formation. Without--task, it takes tasks at its door until the formation is stopped. - Egress. The roster grants
leada name and a credential, not network access. A member's door is served on loopback at a port chosen at launch, soleadallows the bare hostlocalhost. Without it every call fails, and the launch printsW-RUN-008. See Giving a member work. - Network authentication.
leaddeclares it too: with any edge in the roster, every member must, whether or not it serves peers.
Step 3 — store the key and install the members
Store the provider key in the global config. The key is the one value only you can supply, and
mur run --roster fails without it:
mur config set -g credentials.ANTHROPIC_API_KEY <your key>
mur config set -g credentials.OPENAI_API_KEY <your key>
Set credentials.OPENAI_API_KEY in ~/.murmur/config.yaml
Each member reads the key from credentials: at launch, and
neither prints W-SEC-027.
Install the driver both members declare:
mur install -g murmur/driver-anthropic@0.19.0
mur install -g murmur/driver-openai@0.19.0
Installed murmur/driver-openai@0.19.0 from github:murmur-nexus/default-artifacts
Different ways to install artifacts
mur install fetches an artifact from the source of its scope. murmur/ artifacts come from
the built-in source, murmur-nexus/default-artifacts, with no configuration.
Option A — name the source of a scope in ~/.murmur/config.yaml:
registry:
sources:
- repo: <owner>/<repo>
token: "${GITHUB_TOKEN}"
Then install by full name, with or without a version:
mur install <owner>/<artifact-name>@<version>
Option B — pass a full GitHub reference and skip configuration entirely:
mur install github:<owner>/<repo>@<tag>
See Installing artifacts to learn more.
Build each member and install it into the global store, where mur run --roster looks for it:
mur build crew/lead && mur install -g crew/lead/crew-lead-0.1.0.mur.zip
mur build crew/worker && mur install -g crew/worker/crew-worker-0.1.0.mur.zip
Built artifact: crew/lead/crew-lead-0.1.0.mur.zip
Installed local/crew-lead@0.1.0 from crew/lead/crew-lead-0.1.0.mur.zip
Built artifact: crew/worker/crew-worker-0.1.0.mur.zip
Installed local/crew-worker@0.1.0 from crew/worker/crew-worker-0.1.0.mur.zip
Optionally, check the formation from the crew directory. With no murmur.yaml there,
mur doctor checks the roster against the installed members:
cd crew
mur doctor
cd ..
No murmur.yaml in /tmp/crew-run-d078/work/crew: checking its roster.yaml. Run mur doctor in a member's source directory to check that member's artifacts.
Roster
file: /tmp/crew-run-d078/work/crew/roster.yaml
lead local/crew-lead@0.1.0 entry refuses peers authenticated door
worker local/crew-worker@0.1.0 serves peers authenticated door
reachability: lead → worker
All checks passed.
Step 4 — launch the formation
Launch the formation with the last Next: line, your task in place of <your task>:
mur run --roster crew --task "Write a four-line poem about a lighthouse keeper."
…
formation: frm_01a1100295f47ea3b227ddf4612759f3
peer worker local/crew-worker@0.1.0 pid 2768649 ses_01a11002968476428a785ab0b3ae4fbb http://localhost:39151
workdir /tmp/crew-run-d078/home/.murmur/formations/frm_01a1100295f47ea3b227ddf4612759f3/worker
entry lead starting
…
murmur: url localhost:42759
murmur: auth bearer (mur token ses_01a11002984c7bd0ad3a7b1927d94d5d)
session: ses_01a11002984c7bd0ad3a7b1927d94d5d
status: ok
[worker] [capsule-runtime] formation lifeline closed — the formation has ended; cancelling live tasks and ending the session
The task is the plain task. lead's system prompt already tells it to hand the task to worker.
Step 5 — read the readiness lines
The launcher starts worker first, waits for its door, and only then starts lead:
| Line | Means |
|---|---|
formation: frm_… |
The formation id minted for this launch. Every member carries it, and its trace is addressed by it |
peer worker … |
worker is ready: its process id, its session id, and the door that answered as that session |
workdir … |
worker's work folder, under ~/.murmur/formations/<frm_id>/worker. Its sessions' records are under the same path mirrored in ~/.murmur/records |
entry lead starting |
Every peer is ready, and lead is starting with the task |
murmur: url, murmur: auth, session: |
lead's own startup lines: its door, that the door requires a token, and its session id. lead works in crew, and its sessions' records are under ~/.murmur/records/<crew's path>/ |
status: ok |
lead's task ended ok |
[worker] … formation lifeline closed … |
worker saw the formation end and wound down |
No member prints a door token. While lead runs, mur token <lead's session id> prints the
operator token for its door — see mur token.
A peer that does not come up within 180 seconds refuses the whole launch, and lead never starts.
See Readiness and
Member directories.
Step 6 — follow the hand-off from lead to worker
lead's model calls call-member once, with worker and the task text. The call returns as soon
as worker's door holds the task, or at once when worker is busy, in which case the runtime
keeps offering it the task. lead's model then ends its turn. worker runs the task in its
own directory and answers. lead's same task then continues with one new message holding
worker's answer, fenced as coming from member:worker, and lead answers with it.
From here on, work in the crew directory. Name the formation by its id. mur trace show
searches lead's sessions and every member's directory under ~/.murmur/formations/:
cd crew
mur trace show frm_01a1100295f47ea3b227ddf4612759f3
Different ways to identify a session
mur trace show with no argument reads the most recent session:
mur trace show
To name another one, pass an ordinal counting back from the newest (@2), the last 4 or more
characters of its ID (3e4b), the full ID, or a path to its trace.jsonl:
mur trace show @2
mur trace show 3e4b
mur trace show ses_6801f81dd28b4a9daf434e8324c4793e
mur trace show path/to/trace.jsonl
Use --workdir <path> if your session directories are not under ./workdir. Every command
that names a session takes the same addresses — see
Session addresses.
Other trace exploration commands
mur trace has four subcommands for exploring session output:
mur trace show — print the full trace for a session to the terminal.
mur trace steps — show a turn-by-turn summary of what the agent did in a session. Pass --verbose to include a truncated summary of each tool's input.
mur trace diff — compare the traces of two sessions side by side, or with no arguments the two most recent. Useful for spotting behavioural regressions between runs.
mur trace report — generate a structured summary report from a session's trace. Covers token usage, tool calls, latency, and other session-level metrics.
── Formation ────────────────────────────────────
formation: frm_01a1100295f47ea3b227ddf4612759f3
searched: /tmp/crew-run-d078/home/.murmur/records/tmp/crew-run-d078/work/crew/workdir
searched: /tmp/crew-run-d078/home/.murmur/records/tmp/crew-run-d078/work/crew
searched: /tmp/crew-run-d078/home/.murmur/records/tmp/crew-run-d078/home/.murmur/formations/frm_01a1100295f47ea3b227ddf4612759f3/worker
ses_01a11002968476428a785ab0b3ae4fbb worker crew-worker@0.1.0 ok
ses_01a11002984c7bd0ad3a7b1927d94d5d lead crew-lead@0.1.0 ok may call worker
calls: 1
mcl_01a11002a5d57021b9017e9e2e31a465 lead → worker completed 2.0s delivered
| Line | Means |
|---|---|
searched: |
Each session root searched: the roster directory's, then each peer's own |
worker … |
worker's session: its roster name, its capsule, and how it ended |
lead … may call worker |
lead's session, and the member the roster lets it call |
calls: 1 |
The formation made one call-member call |
mcl_… lead → worker … |
The call: its id, who called whom, completed, how long worker took to answer, and delivered because the answer reached lead's task |
In a larger formation each call made inside a handed-over task is nested under the call that handed it over, and a call one side of which was not found carries a note saying which. See Listing a formation.
The call row comes from three records. In lead's trace,
trace.jsonl in its records directory, the call and its outcome:
{"event_type":"member_call_start","event_id":"evt_01a11002a5e27232b8c9a161b0aa15e5","parent_id":"evt_01a1100298c178d1a950c189b9b5a2c0","session_id":"ses_01a11002984c7bd0ad3a7b1927d94d5d","timestamp":1791269971426,"task_id":"tsk_01a1100298c57060b6d732feee045060","call_id":"mcl_01a11002a5d57021b9017e9e2e31a465","member":"worker","member_task_id":"tsk_01a11002a5db7fe0a98b4e4e743aa444"}
{"event_type":"member_call","event_id":"evt_01a11002adcb7e2394fcf3c1d0e04cd9","parent_id":"evt_01a1100298c178d1a950c189b9b5a2c0","session_id":"ses_01a11002984c7bd0ad3a7b1927d94d5d","timestamp":1791269973451,"task_id":"tsk_01a1100298c57060b6d732feee045060","call_id":"mcl_01a11002a5d57021b9017e9e2e31a465","member":"worker","member_task_id":"tsk_01a11002a5db7fe0a98b4e4e743aa444","status":"completed","duration_ms":2037,"output":"He tends the lamp while black waves climb, \nA steady star against the night, \nThrough salt and storm, he keeps his time, \nGuiding lost sails toward the light.","truncated":false,"delivered":true}
| Field | Here |
|---|---|
call_id |
mcl_…, the same on both records |
member_task_id |
worker's task id for this call |
status |
completed: worker finished the task. A call that brought no answer reads failed, canceled, rejected, timed_out, unreachable, abandoned or no_answer — see member_call |
duration_ms |
From the call to worker's answer |
output |
worker's answer, as lead's model received it |
delivered |
true: the answer reached lead's task |
In worker's trace, the task arrives named for the member that sent it:
{"event_type":"a2a_task_received","event_id":"evt_01a11002a5dc79e2bd4a5bf91768acba","parent_id":"evt_01a11002969c75d28c8244f84ad1425f","session_id":"ses_01a11002968476428a785ab0b3ae4fbb","timestamp":1791269971420,"task_id":"tsk_01a11002a5db7fe0a98b4e4e743aa444","context_id":"ctx_01a11002a5db7fe0a98b4e57a39f2e8a","message_id":"msg_mcl_01a11002a5d57021b9017e9e2e31a465","traceparent_from_caller":null,"caller":"member","caller_member":"lead","message_sha256":"5c1f0e9b7a3d42c8e6f1a0b9d8c7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9"}
mur trace show matches this record to the call by its task_id, the call's member_task_id.
caller is member, caller_member is lead, and the message id carries the call id.
message_sha256 is the hash of the task's message text; under
trace.capture: content the text is stored behind it. See
call-member and the
member_call record.
lead's answer, and so the formation's, is in out/result.txt in its session's
records directory. lead works in the project
directory, so that is ~/.murmur/records/<project directory>/<session_id>/:
cat ~/.murmur/records$PWD/ses_01a11002984c7bd0ad3a7b1927d94d5d/out/result.txt
He tends the lamp while black waves climb,
A steady star against the night,
Through salt and storm, he keeps his time,
Guiding lost sails toward the light.
Step 7 — read each member's own trace
Show lead's session from its root, crew itself:
mur trace show ses_01a11002984c7bd0ad3a7b1927d94d5d --workdir .
── Session ──────────────────────────────────────
session: ses_01a11002984c7bd0ad3a7b1927d94d5d
formation: frm_01a1100295f47ea3b227ddf4612759f3
capsule: crew-lead v0.1.0
model: gpt-5.6-luna
status: ok
duration: 7.1s
capabilities: network
tools: call-member
…
── Tool calls ───────────────────────────────────
count: 1 (1 ok, 0 error) success 100.0%
latency: avg 13ms
turn 0 call-member 13ms ✓ {"member":"worker","task":"Write a four-line poem about a lighthouse keeper. Return only the four-line poem, with exactl…
turn 1 end_turn
…
── Member calls ─────────────────────────────────
mcl_01a11002a5d57021b9017e9e2e31a465 worker tsk_01a11002a5db7fe0a98b4e4e743aa444 completed in 2.0s
── Formation ────────────────────────────────────
formation: frm_01a1100295f47ea3b227ddf4612759f3
searched: /tmp/crew-run-d078/home/.murmur/records/tmp/crew-run-d078/work/crew
searched: /tmp/crew-run-d078/home/.murmur/records/tmp/crew-run-d078/home/.murmur/formations/frm_01a1100295f47ea3b227ddf4612759f3/worker
ses_01a11002968476428a785ab0b3ae4fbb worker crew-worker@0.1.0 ok
ses_01a11002984c7bd0ad3a7b1927d94d5d lead crew-lead@0.1.0 ok may call worker
calls: 1
mcl_01a11002a5d57021b9017e9e2e31a465 lead → worker completed 2.0s delivered
tools: call-member is there because the roster lets lead call worker. The Member calls
section lists each call this session made with the member, its task id, and how it ended. The
Formation section is the listing from Step 6.
Show worker's session from its own directory:
mur trace show ses_01a11002968476428a785ab0b3ae4fbb --workdir ~/.murmur/formations/frm_01a1100295f47ea3b227ddf4612759f3/worker
── Session ──────────────────────────────────────
session: ses_01a11002968476428a785ab0b3ae4fbb
formation: frm_01a1100295f47ea3b227ddf4612759f3
capsule: crew-worker v0.1.0
model: gpt-5.6-luna
status: ok
duration: 7.7s
…
── Formation ended ──────────────────────────────
formation_ended frm_01a1100295f47ea3b227ddf4612759f3 the formation ended; this session wound down
── A2A ──────────────────────────────────────────
received: 1 task — member 1
sent: 0 messages
── Formation ────────────────────────────────────
formation: frm_01a1100295f47ea3b227ddf4612759f3
searched: /tmp/crew-run-d078/home/.murmur/records/tmp/crew-run-d078/home/.murmur/formations/frm_01a1100295f47ea3b227ddf4612759f3/worker
ses_01a11002968476428a785ab0b3ae4fbb worker crew-worker@0.1.0 ok
calls: 1
mcl_01a11002a5d57021b9017e9e2e31a465 lead → worker unknown - lead's trace not found; worker's task ended ok
worker has no tools: line: the roster lets it call nobody, so it has no call-member. Its
Formation section searches its own root and the peers' roots, not the crew directory where
lead records, so the call reads unknown with lead's trace not found. The formation id, run
from crew as in Step 6, finds both sides.
Step 8 — see how the formation ends
Launched with --task, lead takes that one task and exits when it ends. When lead's process
ends, the launcher closes every other member's lifeline, and the formation is over:
-
workerappendsformation_endedto its trace, before anything else its wind-down writes:{"event_type":"formation_ended","event_id":"evt_01a11002b48f78b1ad023e951090a20d","parent_id":"evt_01a11002969c75d28c8244f84ad1425f","session_id":"ses_01a11002968476428a785ab0b3ae4fbb","timestamp":1791269975183,"formation_id":"frm_01a1100295f47ea3b227ddf4612759f3"} -
workercancels anything still in flight, ends its session and exits0. Itssession_endsaysokwhen it was idle, as here, andformation_endedwhen the formation's end canceled a task it was running; neither prints an error. - The launcher exits with
lead's exit status:0here, becauselead's task endedok.
Nothing of the formation is left running:
mur ps
no running capsules
Launched with --task, the formation lives for that one task. To run it again, launch it again
with a new task: the new launch is a new formation with a new id.
Launched without --task, the formation stays up and lead takes tasks at its door one after
another, from the operator or from a client such as nexus. mur ps --json names lead as the
formation's entry_member, mur token <lead's session> prints the token its door takes, and
mur stop frm_<id> ends the formation; the launcher then exits 143. See
Launching a formation.
mur run --roster refuses --resume, and a member's session resumed by hand belongs to no
formation. See
How a formation ends and
Resuming a member's session.
Summary
| Feature / setting | How it works |
|---|---|
| Scaffold | mur new --roster crew writes roster.yaml and one murmur.yaml per member, with the provider from ~/.murmur/config.yaml, and prints the Next: steps |
| Entry member | entry: true in roster.yaml. It receives the formation's tasks and is never called by a peer. With --task the formation ends when that task ends; without, it takes tasks at its door until mur stop |
| Edge | A reachability rule. It gives the caller call-member and a credential for the callee's door |
| Consent | exports.peer_tasks.accept: true on every member a rule calls |
| Authentication | network.authentication on every member once the roster has any edge |
| Egress | The caller's capabilities.network.allow lists localhost |
| Key | mur config set -g credentials.<KEY_VAR> <your key>, first of the Next: steps |
| Launch | mur run --roster crew --task "…" starts the peers, waits for each door, then starts the entry member |
| Hand-off | call-member returns at once: started when the callee holds the task, or busy when it has no room, and the runtime keeps offering a busy callee the task. The answer, or word that none came, comes back into the caller's same task. A member with nothing to give ends its task with end-without-answer, and its caller's call ends no_answer |
| Trace | mur trace show frm_<id> from the roster's directory lists every member by roster name, every call — who called whom, how it ended, whether the answer was delivered, and how many offers a busy callee turned away — and every task a person sent a member over its door, such as one nexus sent with the operator token. Behind each call row, member_call in the caller's trace and a2a_task_received with caller: "member" in the callee's; behind each task row, the member's a2a_task_received with its caller |
| End | With --task, the entry member exits after its task; every other member records formation_ended and exits; the launcher exits with the entry member's status. Without, mur stop frm_<id> ends every member and the launcher exits 143 |