Cross-Operator Delegation
Let a partner's agents work under a grant they can narrow among themselves, and see what your kernel checks on every call.
On this page
When you grant a partner part of a fork, its agents delegate that grant among themselves within the depth and fan-out you set, each holding no more than the one before. Each call they make against your fork is admitted at your kernel, under your policy.
The running example is the Quickstart partner: grant cap-01a9…7e40 under treaty trt-01aa…4c19, delegating up to 2 deep and 4 wide from one USD 5.00 pool.
Their agents' calls are admitted at your door
A delegated grant arrives with the call it authorizes. Your kernel checks it in order, dispatches only when every check passes, and consumes the proof's nonce at dispatch.
κgrantσpossessiongguardsτtreatyBbudget
The proof binds the grant to this call
Their agent presents the federated grant with a DPoP proof signed by the key the grant names. Your kernel verifies the grant, compares each of the proof's bindings with the request it received, then runs your guards, the treaty check, and your budget. The proof's nonce is consumed at dispatch, and your kernel signs the receipt. Proof of possession.
Federated grants require a DPoP proof at your door, signed by the key the grant names. A process that copied the grant signs with its own key and is refused because agent_key does not match the capability subject. After your guards, the treaty check reads the treaty scope and ladder intersection from your own store. A stale treaty is refused before budget or dispatch, with chio_treaty_stale in the receipt's chio_runtime.failure_code.
Read more in Cross-org admission.
One grant under a treaty bounds a partner's whole delegation tree
Fork the project and grant their agents a slice of the fork under the treaty.
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), // one pool for all of their agents
expiresIn: "30m",
});
console.log(authority.describe());grant cap-01a9…7e40, under trt-01aa…4c19
to partner, delegating up to 2 deep and 4 wide
on quickstart, fork c4e81b7 of main 2f60a91
allows /workspace read, write
/models/default invoke
budget USD 5.00 pool shared by all of their agents, charged at your door
expires in 30 minutesfork = 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), # one pool for all of their agents
expires_in="30m",
)
print(authority.describe())grant cap-01a9…7e40, under trt-01aa…4c19
to partner, delegating up to 2 deep and 4 wide
on quickstart, fork c4e81b7 of main 2f60a91
allows /workspace read, write
/models/default invoke
budget USD 5.00 pool shared by all of their agents, charged at your door
expires in 30 minuteslet 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)) // one pool for all of their agents
.expires_in("30m"),
)?;
println!("{}", authority.describe());grant cap-01a9…7e40, under trt-01aa…4c19
to partner, delegating up to 2 deep and 4 wide
on quickstart, fork c4e81b7 of main 2f60a91
allows /workspace read, write
/models/default invoke
budget USD 5.00 pool shared by all of their agents, charged at your door
expires in 30 minutesYou should see the grant bound to the treaty and the delegation limits. Model calls reach your route through your kernel, which holds the pool. Revoking the treaty ends every grant issued under it, delegated ones included.
Read more in Quickstart: partners.
A delegated chain verifies against the issuers you trust
The receiving kernel checks who issued the parent capability separately from which peers it has pinned. The chain is admitted in four steps:
- Parent. An agent holds a capability from its operator's authority. The receiving kernel lists that authority among its trusted capability issuers; pinning the operator as a federation peer adds nothing to issuer trust.
- Child. The agent signs a delegated child for a worker whose subject key lives with the other operator. The child holds the same or narrower scope, lifetime, and resource limits.
- Verify. The receiving kernel verifies the parent against its trusted issuers, the child's signature against the parent's subject, and the stored treaty context and evidence.
- Admit. The action class lies inside the shared ladder intersection and its evidence verifies. Local admission and dispatch-time checks then run as for any call.
An unknown issuer fails capability validation. A federated request without its runtime treaty context fails before dispatch, even on a kernel with no runtime admission hook installed. What a receipt records along this path is on Receipts.
Where the runtime admission hook lives
The hook resolves the treaty context in admission_hook.rs and admission_hook/treaty_evidence.rs of chio-runtime-core.
A signed delegation policy caps what a partner may request
The issuing operator signs a chio.federated-delegation-policy.v1 from one default capability definition. It names the partner and its verifier and sets the parent scope and TTL ceiling every request from that partner is checked against.
$ chio trust federated-delegation-policy-create \
--output ./signed-delegation-policy.json \
--signing-seed-file ./issuer.seed \
--issuer org-a \
--partner acme-corp \
--verifier https://verify.acme.example \
--capability-policy ./capability-policy.yaml \
--expires-at 4102444800 \
--purpose "span review for the acme incident bridge"federated delegation policy created output: ./signed-delegation-policy.json issuer: org-a partner: acme-corp verifier: https://verify.acme.example signer_public_key: 4cb5abf6ad79fbf5abbccafcc269d85cd2651ed4b885b5869f241aedf0a5ba29 ttl_seconds: 300 expires_at: 4102444800
$ jq '.body' ./signed-delegation-policy.json{
"schema": "chio.federated-delegation-policy.v1",
"issuer": "org-a",
"partner": "acme-corp",
"verifier": "https://verify.acme.example",
"signerPublicKey": "4cb5abf6ad79fbf5abbccafcc269d85cd2651ed4b885b5869f241aedf0a5ba29",
"createdAt": 1788589028,
"expiresAt": 4102444800,
"ttlSeconds": 300,
"scope": {
"grants": [
{
"server_id": "mcp-observability",
"tool_name": "query_spans",
"operations": [
"invoke"
]
}
]
},
"purpose": "span review for the acme incident bridge"
}You should see a 300-second TTL and a scope of one grant, invoke on query_spans of mcp-observability. The protocol specification lists this policy among the federation artifacts in section 10.2.
A policy that expires before its creation is refused before signing
An expiry before the creation time stops the command before anything is signed, with exit 1.
$ chio trust federated-delegation-policy-create \
--output ./stale.json \
--signing-seed-file ./issuer.seed \
--issuer org-a \
--partner acme-corp \
--verifier https://verify.acme.example \
--capability-policy ./capability-policy.yaml \
--expires-at 1776302557error[CHIO-DELEGATION-POLICY-WINDOW-INVALID]: the federated delegation policy would expire before it is created help: pass an --expires-at later than the current time, in Unix seconds code: urn:chio:error:delegation:policy-window-invalid docs: https://chio.computer/docs/reference/errors#error-delegation-policy-window-invalid
An imported capability can parent a local delegation
chio trust federated-issue --upstream-capability-id issues a local child whose parent is a capability imported from the other operator. It first verifies a challenge-bound passport presentation, as shown on Passports.
$ jq 'del(.signed_capability)' \
evidence-export-package/capability-lineage.ndjson{
"capability_id": "cap-ioa-web3-provider",
"subject_key": "bff663535a5cf58658cc38595d20dc4501f6dfc076ddf07e20571d413a3ceb5a",
"issuer_key": "a090e1a43efcd3a3bd5ea5b015e6014d20e33e0e2d480194802720e4d38f108b",
"issued_at": 1776995579,
"expires_at": 1776996779,
"grants_json": "{\"grants\":[{\"constraints\":[],\"operations\":[\"invoke\"],\"server_id\":\"provider-review\",\"tool_name\":\"inspect_service_order\"},{\"constraints\":[],\"operations\":[\"invoke\"],\"server_id\":\"provider-review\",\"tool_name\":\"evaluate_provider_reputation\"},{\"constraints\":[],\"operations\":[\"invoke\"],\"server_id\":\"provider-review\",\"tool_name\":\"issue_review_attestation\"}],\"prompt_grants\":[],\"resource_grants\":[]}",
"delegation_depth": 1,
"parent_capability_id": "cap-019dbd30-b376-7b92-ba54-d00aec973369"
}You should see delegation_depth 1 and the imported parent in parent_capability_id. Revoking that parent stops further use of the child and leaves the child's recorded history as it was:
$ chio --revocation-db ./revocations.sqlite3 \
trust revoke --capability-id cap-019dbd30-b376-7b92-ba54-d00aec973369capability_id: cap-019dbd30-b376-7b92-ba54-d00aec973369 revoked: true newly_revoked: true backend: ./revocations.sqlite3
Read more in the CLI reference.
Swarm defines how authority narrows at each hop
Federation sets what the relationship permits. How a grant narrows at each hop of a task graph, and how a continuation binds a child to its route, allocation, and revocation epoch, is the Swarm model.
Read more in Swarm Authority and Continuation Tokens.