Formations
What a formation is
A formation is a group of capsules on one machine that work on tasks together. Its members are
declared in a roster.yaml in the project directory: each is a capsule at an exact version, one
of them is the entry member that receives the formation's tasks, and the roster says which
members may call which. mur doctor checks a roster against
the installed capsules. See the Roster Schema.
mur run --roster launches a roster:
- Every member runs as its own
mur runprocess, and every member carries the sameformation_id, minted for this launch. Each member's trace names it. - The other members, the peers, start first, each counted ready only once its door answers as the session it reported. The entry member starts last.
- Launched with
--task, the entry member runs that one task, and when it ends the formation ends. Launched without one, the entry member takes tasks at its door, from the operator or a client such asnexus, one after another, until the formation is stopped withmur stop, a signal or its deadline.
Either way, the entry member's task is a task on its door, which ListTasks lists while it runs.
One launch is one formation. Launching the same roster again makes a new formation with a new id.
See Launching a formation and the
mur run --roster flags and output.
How to launch a formation of capsules scaffolds a two-member
formation, launches it, and follows one member handing the other its task.
Each member works in a directory of its own. The entry member works in the roster's project
directory, with its sessions under <project>/.murmur/. Each peer works in
~/.murmur/formations/<frm_id>/<member>/, a directory no other member can reach. A formation's
directory outlives its launch; the entry member's trace.retain bounds how many are kept. See
Member directories and
Removing formation directories.
How a member gives another work
A member gives another member work with the runtime-provided
call-member tool, which a member has only
when the roster lets it call another:
- The model calls
call-memberwith the member's name and the whole task. - The call returns as soon as the other member holds the task. A member with no room for it turns it away busy; the call returns at once, and the runtime keeps offering it the task.
- The other member runs the task in its own directory.
- Its answer comes back into the caller's same task, fenced as coming from that member, and the caller's model continues with it. A call that ends with no answer — the member stayed busy, timed out or failed — is named by the runtime as giving no answer, and the caller's model is told not to present an answer of its own as that member's.
- A member that has no answer to give, because a member it called gave it none, ends its task
with
end-without-answerinstead of replying. Its caller is then told, in the runtime's own words, that no answer came from it, and which member further down gave none. A member that replies anyway after a missing answer has its reply delivered with a line saying which members it answered without.
A second call to a member is refused, with nothing sent, until the first call's answer has been delivered. A call carries text only, so whatever the other member needs goes in the task text. See Giving a member work.
Reachability: three layers, each closed by default
A call from one member to another succeeds only when all three layers allow it:
| Layer | Declared by | Closed until |
|---|---|---|
| The roster edge | roster.yaml |
A reachability rule lets the caller call the member; only then does the caller have call-member, and only then does the launcher issue a credential for that edge |
| The callee's consent | The callee's murmur.yaml |
The callee declares exports.peer_tasks.accept: true, and its door lets in only a credential issued for it |
| The caller's egress | The caller's murmur.yaml |
The caller's own capabilities.network.allow lists localhost |
The entry member is never called by a peer: its tasks come from the launch or from a client at its
door, and no member holds a credential for it. A rule that lists it in to is refused at
admission with E-ROS-008. To have a member report to the entry member,
let the entry member call it; the answer comes back into the entry member's own task. See
The entry member.
The launcher issues every credential, and the runtime attaches a member's credential to its calls,
so no model ever sees it. A formation credential reaches exactly five methods on the door it was
issued for: SendMessage, which starts a task, and GetTask, ListTasks, CancelTask and
SubscribeToTask, which read, list, cancel and follow the tasks the calling member started. It
reaches no other member's task, and nothing that watches or stops the session. See
What a door answers.
How a formation ends
A formation's members end with it, however it ends. Each member holds one end of a lifeline whose
other end only the launcher holds; when the launcher ends the formation, or dies — even by
SIGKILL — every member's lifeline closes. A member whose lifeline closes records
formation_ended in its trace, cancels the work it had in flight, since nothing remains to receive
the result, and ends its session in order. See How a formation ends.
A sub-capsule a member delegates to is not a member: it shares the formation's id and nothing else. It ends with the member that started it, through a lifeline of its own whose other end only that member's process holds — see A child ends with its parent. When the formation ends, its members end, and their delegated children end with them.
The A2A door
Every capsule with an agent session serves a door: a small HTTP endpoint exposing an A2A v1.0
Agent Card (/.well-known/agent-card.json) and a JSON-RPC 2.0 task
interface, so other capsules and standard A2A clients can discover the capsule and hand it a task.
Whether an incoming message is accepted depends on the capsule's lifecycle.task_acceptance
setting (see Capsule lifecycle). What the door serves of
A2A v1.0 is in A2A v1.0 conformance.
A formation member is reached at its door like any capsule: call-member sends the task there. To
wire two capsules together by hand, with a pinned port and curl, see
Connect two capsules with A2A messaging.
Not yet supported
- Members on more than one machine. Every member runs on the launcher's machine, and every door is served on loopback. See Launching a formation.
- Formation calls from native processes. Shell commands, native tools and a
transport: processharness are handed noMURMUR_FORMATION_PEERSand cannot reach a member's virtual address — see What a member's components are handed. Atransport: processharness still calls members withcall-member, which the runtime serves to it through its loopback tool server; the answer reaches it when the runtime resumes the harness's session — see How the answer arrives. - Credential rotation. A formation credential has no expiry, rotation or revocation; the credentials end with the formation — see Why a formation credential never expires.
- Cancelling a member's task. A formation credential reaches
CancelTaskonly for the tasks its member started, and the runtime sends it only for a task a member took just after the calling task ended. A call that times out or is abandoned while the member works leaves the member's task running until it finishes or the formation ends — see What a door answers. - Passing files between members. A peer's directory is in no other member's reach, so content goes in the task text — see Member directories.
- Resuming a formation.
mur run --rosterrefuses--resume, and a member's session resumed by hand belongs to no formation — see Resuming a member's session.
Next
- How to launch a formation of capsules walks through the scaffold, the launch and the trace of a real run.
- The Roster Schema lists every
roster.yamlfield, the admission checks, and how a formation is launched and enforced.