Federation & Swarms
Let another operator’s agents work under your authority while each kernel enforces its own rules and both sign the result.
On this page
Two organizations can share agent work without either one trusting the other’s machines. Everything that crosses the boundary is signed: a grant that only narrows, a treaty each kernel computes for itself, and a receipt both kernels sign. Each operator keeps its keys, its policy, and the last check on its own side.
Chio splits the problem in two. Federation establishes the relationship: which peers an operator recognizes and which shared rules govern their cooperation. Swarm authority binds the work: who may delegate to whom, how far authority may descend, and which budget, route, and revocation state constrain each child task. Recognizing a peer and trusting a capability issuer are separately configured decisions.
Authority crosses the boundary; enforcement stays local
An operator can coordinate a parent task with a partner’s worker without granting that worker the parent’s entire authority. The parent holds a signed grant. The child task receives authority derived from it and narrower than it. The receiving kernel admits the child’s calls under its own trust rules before its local tool runs.
Each operator runs its own parts. Operator A holds the parent grant, runs a revocation oracle that commits its revocations to signed epoch roots, and keeps the receipts it may later share. Operator B runs the worker the child names and the kernel that admits the worker’s calls. Three things cross between them: the delegated capability, the transport that carries peering and revocation roots, and an evidence package.
Every crossing ends at a check in operator B
Operator A delegates, publishes revocation roots, and exports evidence; operator B checks each crossing before its kernel admits the call. Each operator runs the same parts, and the roles reverse when B delegates to A. Federation and swarms.
- delegated capability
- B’s kernel verifies the parent against its own trusted issuers and the child against the parent subject, inside the treaty’s intersection.
- transport
- B admits a connection only from an authenticated peer whose key it pinned; every lane keeps its own verifier above the transport.
- evidence package
- B verifies the package offline against signer keys it chooses, and records the share on import.
Every crossing ends at a check on the receiving side, and none of them installs trust by itself. Pinning A as a federation peer does not add A’s key to B’s trusted capability issuers; that trust is configured separately. Revocation roots merge all or nothing: a batch with one root that does not verify against the oracle key pinned in B’s directory merges nothing into B’s revocation sink.
Three signed artifacts carry the bounds. The task graph bounds recursion and fan-out. Delegation witnesses establish that each step narrowed scope. A continuation binds the child to its route, its allocation, and the revocation epoch it was issued under. The receiving kernel checks them in a fixed order:
| Check | What it establishes |
|---|---|
| Witness issuers are configured | The host supplies the trusted witness issuer keys. Keys inside the submitted bundle are never adopted as trust. |
| The task graph is valid and signed | The graph is validated at the bundle’s time and its signature verifies under a trusted issuer. |
| Delegation evidence is signed | The bundle carries signed swarm delegation evidence for the work it claims. |
| Routes and joins match the graph | Route plan receipts and join receipts refer to tasks the graph contains. |
| The budget pool fits | Allocations reconcile and the pool’s reservations stay within its total. |
| The revocation epoch is current | The bundle is checked against the revocation epoch it claims. |
| Terminal receipts agree | Terminal graph receipts agree with the tasks, routes, joins, and allocations already validated. |
| Continuations bind the child | Each continuation token is bound to the graph hash, its edge, route, join, and allocation. |
| Witness chains narrow scope | Along each edge, the delegation witness chain shows the child’s scope narrowing from its parent’s. |
The order runs from trust and graph integrity, through the records bound to the graph, to the continuation that binds a child to all of them. Any failed step refuses the bundle. The swarm admission path installs these checks on a host and lists their failure modes.
A treaty takes the stricter rule, one action class at a time
A finding, a guarded operation, and a jointly authorized purchase need different treatment. Each operator’s governance manifest classifies its action classes by mode and declares their co-signing, evidence, consistency, and quorum requirements. The modes are ordered:
| Mode | Rank | Coordination role |
|---|---|---|
observation | 0 | Publish or assess a signal without treating it as a settled action. |
guarded | 1 | Apply the configured checks to an action. |
receipt_backed | 2 | Require the action’s declared receipt evidence. |
partition_contingency | 3 | Use the declared contingency treatment for a network partition. |
maintenance | 4 | Classify maintenance operations under their declared requirements. |
A treaty combines the participants’ manifests into a ladder intersection. For each class it takes the strongest mode and the strongest co-signing requirement, unions the required evidence, and requires every participant to declare the same consistency model. A difference between two manifests does not by itself reject the class. An incompatibility does, and it rejects the whole intersection.
One LadderIntersection over 3 action classes.
One LadderIntersection over 3 action classes.
In Rust · treaty.rs:27634 lines
for participant in &treaty_scope.participant_kernel_ids {
// … find this participant's action class
let rank = ladder_mode_rank(&action.mode)?;
if rank > mode_rank {
mode_rank = rank;
mode.clone_from(&action.mode);
}
destructive |= action.destructive;
if let Some(existing) = consistency_model.as_ref() {
if existing != &action.consistency_model {
return rejected(
"chio_federation_ladder_consistency_mismatch",
"governance ladder consistency models do not intersect",
);
}
} else {
consistency_model = Some(action.consistency_model.clone());
}
if co_sign_requirement_rank(&action.co_sign)? > co_sign_requirement_rank(&co_sign)? {
co_sign.clone_from(&action.co_sign);
}
// … merge the co-sign quorum
for item in &action.evidence_required {
evidence_required.insert(item.clone());
}
// … record participant_modes
}
if destructive && mode_rank < ladder_mode_rank("receipt_backed")? {
return rejected(
"chio_federation_ladder_destructive_below_floor",
"intersected destructive action resolves below receipt backed mode",
);
}
// … then the destructive crdt-commutative check
Agree a treaty from your code, and each kernel computes the intersection on its own from the treaty scope and both manifests. The CLI runs the same computation, so either operator can check the other’s result:
import { Computer, USD } from "@chio-protocol/sdk";
const project = await Computer.open("quickstart");
const partner = await Computer.connect("partner");
const treaty = await project.treaty({
partner,
actions: ["workspace.read", "workspace.write", "model.invoke", "merge"],
expiresIn: "7d",
});
console.log(treaty.describe());treaty trt-01aa…4c19, signed by both kernels participants you · partner (did:chio:z6MkpA7…c2T) action you them admitted as workspace.read allow allow either kernel admits under its own policy workspace.write allow allow either kernel admits under its own policy model.invoke allow allow charged at your door merge co-sign co-sign both kernels sign expires in 7 days
from chio import Computer, Delegation, USD
project = Computer.open("quickstart")
partner = Computer.connect("partner")
treaty = project.treaty(
partner=partner,
actions=["workspace.read", "workspace.write", "model.invoke", "merge"],
expires_in="7d",
)
print(treaty.describe())treaty trt-01aa…4c19, signed by both kernels participants you · partner (did:chio:z6MkpA7…c2T) action you them admitted as workspace.read allow allow either kernel admits under its own policy workspace.write allow allow either kernel admits under its own policy model.invoke allow allow charged at your door merge co-sign co-sign both kernels sign expires in 7 days
use chio::{Computer, Delegation, Grant, Treaty, USD};
let project = Computer::open("quickstart").await?;
let partner = Computer::connect("partner").await?;
let treaty = project
.treaty(
Treaty::with(&partner)
.actions(["workspace.read", "workspace.write", "model.invoke", "merge"])
.expires_in("7d"),
)
.await?;
println!("{}", treaty.describe());treaty trt-01aa…4c19, signed by both kernels participants you · partner (did:chio:z6MkpA7…c2T) action you them admitted as workspace.read allow allow either kernel admits under its own policy workspace.write allow allow either kernel admits under its own policy model.invoke allow allow charged at your door merge co-sign co-sign both kernels sign expires in 7 days
chio federation treaty intersect --treaty-scope ./treaty-scope.json \
--manifest ./your-ladder.json --manifest ./partner-ladder.json \
--now-unix-ms 1776970000000 --report ./ladder-intersection.json
jq '.actionClasses[] | select(.actionClassId == "merge")' ./ladder-intersection.json{
"actionClassId": "merge",
"mode": "receipt_backed",
"destructive": true,
"consistencyModel": "totally-ordered",
"coSign": "bilateral_required",
"evidenceRequired": ["receipt_lineage", "workflow_receipt"],
"participantModes": {
"did:chio:z6Mkf3…Qp9": "guarded",
"did:chio:z6MkpA7…c2T": "receipt_backed"
}
}Your manifest declares merge as guarded and your partner’s declares it receipt_backed, so the treaty takes the stronger mode and requires both kernels’ signatures on every merge. Destructive actions have two further floors: a destructive class that resolves below receipt_backed is rejected, and so is one that would use crdt-commutative consistency. The manifests’ hashes must match those the treaty scope names, and the trust handshake exchanges only the manifest references.
Classes that require n_of_m co-signing use FROST threshold authorization under a pinned roster and epoch, and the strict bilateral verifier requires the corresponding chio.frost.authorization.v1 artifact. The governance ladder lists the action-class declarations, and FROST quorum covers threshold verification.
A shared pool caps the whole fan-out
A swarm can divide one budget among parallel specialists, with each child task holding an allocation from the swarm’s pool. The pool enforces two invariants: every allocation accounts for all of its units, and the units reserved across the pool cannot exceed its total.
Grant your partner’s agents a fork under the treaty with one five-dollar pool, two levels of delegation, and four children per parent, then send them issue #417:
const fork = await project.fork();
const authority = fork.grant({
to: partner,
under: treaty,
resources: { "/workspace": ["read", "write"], "/models/default": ["invoke"] },
delegation: { depth: 2, fanout: 4 },
budget: USD(5),
expiresIn: "30m",
});
const run = await partner.exec("fix issue #417", { on: fork, authority });
for await (const event of run.events()) console.log(event.summary);00:00 them admitted "fix issue #417" under trt-01aa…4c19, pool USD 5.00 00:01 them reserve triage-417 1.00 · fixer-417 2.50 · halden-tester 1.00 00:02 them refused reserve 1.00 for fixer-2: swarm budget fanout exceeds pool total 00:02 them reserve fixer-2 0.50, pool reserved 5.00 of 5.00 00:44 them sealed fork c4e81b7 at 9e1f03a spent USD 3.62 of 5.00 across 4 of their agents
fork = project.fork()
authority = fork.grant(
to=partner,
under=treaty,
resources={"/workspace": ["read", "write"], "/models/default": ["invoke"]},
delegation=Delegation(depth=2, fanout=4),
budget=USD(5),
expires_in="30m",
)
run = partner.exec("fix issue #417", on=fork, authority=authority)
for event in run.events():
print(event.summary)00:00 them admitted "fix issue #417" under trt-01aa…4c19, pool USD 5.00 00:01 them reserve triage-417 1.00 · fixer-417 2.50 · halden-tester 1.00 00:02 them refused reserve 1.00 for fixer-2: swarm budget fanout exceeds pool total 00:02 them reserve fixer-2 0.50, pool reserved 5.00 of 5.00 00:44 them sealed fork c4e81b7 at 9e1f03a spent USD 3.62 of 5.00 across 4 of their agents
let fork = project.fork().await?;
let authority = fork.grant(
Grant::to(&partner)
.under(&treaty)
.resource("/workspace", ["read", "write"])
.resource("/models/default", ["invoke"])
.delegation(Delegation::depth(2).fanout(4))
.budget(USD(5))
.expires_in("30m"),
)?;
let mut run = partner.exec("fix issue #417").on(&fork).authority(authority).start().await?;
let mut events = run.events();
while let Some(event) = events.next().await {
println!("{}", event?.summary());
}00:00 them admitted "fix issue #417" under trt-01aa…4c19, pool USD 5.00 00:01 them reserve triage-417 1.00 · fixer-417 2.50 · halden-tester 1.00 00:02 them refused reserve 1.00 for fixer-2: swarm budget fanout exceeds pool total 00:02 them reserve fixer-2 0.50, pool reserved 5.00 of 5.00 00:44 them sealed fork c4e81b7 at 9e1f03a spent USD 3.62 of 5.00 across 4 of their agents
Their coordinator stayed inside the fan-out of four, and the pool still refused its fourth reservation: USD 5.50 reserved against a USD 5.00 total. A violation is refused by name. An allocation whose units do not add up fails with a rollup mismatch, and a pool reserved past its total fails with swarm budget fanout exceeds pool total.
The pool is the swarm’s shared accounting. Each child’s grant is still bounded by its own parent, individually; Budgets and metering covers those per-grant bounds, and Sub-agent budgets covers the pool’s full lifecycle.
Joint actions carry both signatures
When two organizations must commit to the same action, both kernels sign it. Apply your partner’s fix under the treaty, and the merge receipt comes back with your kernel’s signature and your partner’s:
const merged = await project.apply(fork.changes, { treaty });
console.log(merged.receipt.describe());merge main 2f60a91 to 9e1f03a, under trt-01aa…4c19 receipt rcpt-01ab…f2d0, signed by your kernel 4f1c…a2e9 co-signed partner kernel 9b07…d31c, over the same canonical body verified both signatures, against the keys you pinned
merged = project.apply(fork.changes, treaty=treaty)
print(merged.receipt.describe())merge main 2f60a91 to 9e1f03a, under trt-01aa…4c19 receipt rcpt-01ab…f2d0, signed by your kernel 4f1c…a2e9 co-signed partner kernel 9b07…d31c, over the same canonical body verified both signatures, against the keys you pinned
let merged = project.apply(fork.changes()).treaty(&treaty).await?;
println!("{}", merged.receipt().describe());merge main 2f60a91 to 9e1f03a, under trt-01aa…4c19 receipt rcpt-01ab…f2d0, signed by your kernel 4f1c…a2e9 co-signed partner kernel 9b07…d31c, over the same canonical body verified both signatures, against the keys you pinned
Both kernels sign the same canonical body: the receipt as canonical JSON, together with both kernel identifiers. The receipt’s own signature is unchanged, so a classic verifier can still check it alone. A federation-aware verifier also checks both detached signatures, and one operator’s signature cannot stand in for the other’s.
Both kernels signed the same bytes
The federation-aware verifier rebuilds b from the receipt and the two kernel ids, checks your partner’s signature and yours against the keys you pinned, then checks the receipt’s own signature and that the tool host made it. A classic verifier reads only the last two rows and accepts the same file as a receipt from your kernel. Independent verification.
b is the canonical CoSigningBody: the cosigning schema, the receipt as canonical JSON, and both kernel ids; κ is the receipt’s kernel_key. Kernel ids and keys are the Quickstart’s. The DSSE envelope and its predicate checks are left out.Bilateral ReceiptsFor an invocation, Chio verifies a bilateral DSSE envelope with both parties’ signatures over one canonical payload. The strict verifier checks the expected peer keys and the bound authorization context, and the predicate identifier chio.bilateral-cosign-invocation.v1 names the format. Bilateral co-sign covers the envelope, the signature checks, and the evidence requirements.
An auditor checks both kernels’ signatures offline
Export the shared history, and anyone holding the two kernel keys can verify it without access to either operator’s systems:
const evidence = await project.history.export("audit/");
console.log(await evidence.verify());treaty trt-01aa…4c19, ladders intersect as signed receipts 48 signed decisions from 2 kernels co-signed 1 merge, rcpt-01ab…f2d0: both kernel signatures verify refusals 1: reserve 1.00 for fixer-2, pool total spend USD 3.62 of the 5.00 pool verified offline, against both pinned kernel keys
evidence = project.history.export("audit/")
print(evidence.verify())treaty trt-01aa…4c19, ladders intersect as signed receipts 48 signed decisions from 2 kernels co-signed 1 merge, rcpt-01ab…f2d0: both kernel signatures verify refusals 1: reserve 1.00 for fixer-2, pool total spend USD 3.62 of the 5.00 pool verified offline, against both pinned kernel keys
let evidence = project.history().export("audit/")?;
println!("{}", evidence.verify()?);treaty trt-01aa…4c19, ladders intersect as signed receipts 48 signed decisions from 2 kernels co-signed 1 merge, rcpt-01ab…f2d0: both kernel signatures verify refusals 1: reserve 1.00 for fixer-2, pool total spend USD 3.62 of the 5.00 pool verified offline, against both pinned kernel keys
chio evidence export --output audit
chio evidence verify --input audit --partner partnertreaty trt-01aa…4c19, ladders intersect as signed receipts 48 signed decisions from 2 kernels co-signed 1 merge, rcpt-01ab…f2d0: both kernel signatures verify refusals 1: reserve 1.00 for fixer-2, pool total spend USD 3.62 of the 5.00 pool verified offline, against both pinned kernel keys
Each co-signed receipt in the package is one record. The receipt your kernel signed sits whole inside body, and the two kernel signatures sit beside it:
{
"schema": "chio.federation-dual-signed-receipt.v1",
"body": {
"id": "rcpt-01ab…f2d0",
"tool_server": "quickstart",
"tool_name": "merge",
"decision": { "verdict": "allow" },
"kernel_key": "4f1c…a2e9",
"signature": "6b1d…94e0"
},
"orgAKernelId": "kernel.halden.example",
"orgBKernelId": "kernel.larkfield.example",
"orgASignature": "51f0…a3c8",
"orgBSignature": "e7b2…16d9"
}A verifier that reads only body, such as chio receipt verify --trusted-kernel-pubkey 4f1c…a2e9, accepts it as a receipt from your kernel. Only a verifier that checks orgASignature against your partner’s pinned key learns that your partner signed the same bytes.
A swarm also needs a fast signal path
Agents can contribute observations without making every observation a consensus decision. A PheromoneDeposit is a signed coordination signal whose concentration decays with time. A query aggregates live deposits for a subject class and namespace, applying peer weights and the configured newcomer discount.
At hour 5.5, the weighted concentration is 0.106. 3 of 3 deposits count.
In Rust · substrate.rs:21312 lines
let strength = strength_at(deposit, now_unix_ms);
if let Some(floor) = deposit.body.evaporation_floor {
if strength < floor {
continue;
}
}
let weight = peer_weight(&deposit.body.kernel_id, reputation_epoch);
// … WeightOutOfRange unless 0.0 <= weight <= 1.0
let discount = newcomer_discount_for_deposit(
// … deposit, context, epoch, subject class
)?;
total_strength += strength * weight * discount;
Strength halves with every half-life. A deposit below its evaporation floor drops out of the sum. Each remaining deposit is scaled by its peer’s weight and by a newcomer discount that grows toward one as the depositing passport ages in reputation epochs.
The depositing agent signs with its PASSPORT signing key. The deposit is neither a kernel receipt nor an Agent Passport credential. Expiry and admission limits keep stale or excessive deposits from accumulating indefinitely; they do not establish that an observation is true.
A signed pheromone policy can use a query report and peer weights in a runtime admission decision. That is an explicit policy path: a strong signal does not become a receipt, a grant, or reputation evidence by itself. The pheromone store covers decay, quotas, and queries.
Transport carries the relationship; each side configures enforcement
The optional iroh transport uses QUIC and domain-separated endorsements. It authenticates transport keys; treaty scope, authorization, quorum, and revocation checks belong above that connection. Add your partner’s card with --transport iroh, and your code reaches it by name:
const partner = await Computer.connect("partner");
console.log(partner.describe());partner partner, did:chio:z6MkpA7…c2T kernel 9b07…d31c, pinned from its card transport iroh, authenticated at handshake
partner = Computer.connect("partner")
print(partner.describe())partner partner, did:chio:z6MkpA7…c2T kernel 9b07…d31c, pinned from its card transport iroh, authenticated at handshake
let partner = Computer::connect("partner").await?;
println!("{}", partner.describe());partner partner, did:chio:z6MkpA7…c2T kernel 9b07…d31c, pinned from its card transport iroh, authenticated at handshake
chio partner add ./partner-card.json --name partner --transport irohpartner did:chio:z6MkpA7…c2T, named partner kernel 9b07…d31c, pinned from its card transport iroh, endpoint 3fa8…91be, authenticated at handshake lanes signals · revocation · co-sign
Selecting the transport opens the connection. The treaty, the scope it grants, and the receiving operator’s trust anchors are configured separately, by each side.
Independent operators can combine these mechanisms for shared observations, bounded subcontracting, jointly authorized actions, and exchangeable evidence. Markets for agent work adds a delivery-and-payment model for agent-produced work. Each deployment still connects its own admission, transport, policy, and storage paths; a signed format does not by itself establish that integration end to end.