Work With Their Agents
Let your agents work with theirs, under terms both kernels enforce.
On this page
Each side keeps its own kernel, keys, and policy. A treaty sets what crosses between them, and both kernels admit and sign every crossing.
Their agents fix the next bug in your project while your agents review the work. You pair the two kernels, agree on terms, merge the fix with both signatures, verify the history offline, and end the arrangement with one call.
Start- Govern your agentsYour agents, your resources, your kernel.
- Serve other agentsAgents you do not run call your service.
- Work with their agentsAnother operator's kernel, under a treaty.
1. Pair your kernel with theirs
A partner sends you its partner card: the operator identity and kernel key you verify its work against. chio partner demo runs a partner on this machine, with its own kernel, keys, policy, and agents, and writes its card. Add it over iroh, and the kernels connect directly over authenticated QUIC. Use the project and identity from part 1.
chio partner demo --out partner-card.json
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
import { Computer, USD } from "@chio-protocol/sdk";
const project = await Computer.open("quickstart");
const partner = await Computer.connect("partner");from chio import Computer, Delegation, USD
project = Computer.open("quickstart")
partner = Computer.connect("partner")use chio::{Computer, Delegation, Grant, Treaty, USD};
let project = Computer::open("quickstart").await?;
let partner = Computer::connect("partner").await?;Read more in Iroh transport.
2. Agree on what crosses
The treaty names the actions the two operators cooperate on and what each requires. Each kernel computes the intersection of both governance ladders on its own, so the stricter requirement wins.
const treaty = await project.treaty({
partner: 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
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
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
Every action is admitted by the kernel that serves it, under that kernel's own policy. merge needs both signatures. Only the two kernels are joined across the boundary; everything else crosses through them.
Both kernels sign the same terms
The treaty names the actions the two operators cooperate on and what each action requires. Each kernel pins its own governance ladder and computes the intersection itself, so the stricter requirement wins. Your grant then gives their agents a slice of one fork, a budget pool, and limits on how far they delegate. Treaties.
3. Give their agents a slice
Fork the project and grant their agents part of the fork under the treaty: the workspace, your model route, and five dollars they all share.
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 minutesEach agent they add holds less than the agent that delegated to it. Model calls reach your route through your kernel, which holds the pool and refuses the call that would overdraw it.
4. Work together
Start two reviewers on your side and send the task. Their agents post candidates to the shared reviews mailbox; your reviewers claim them and answer.
// Your side: two reviewers claim candidates from a shared mailbox.
const reviews = await project.mailbox("reviews", { partner: partner, treaty });
const reviewers = await project.agents("review", { count: 2, reads: reviews });
// Their side: their agents work the issue in your fork.
const run = await partner.exec("fix issue #414", {
on: fork,
authority,
reportTo: reviews,
});
for await (const event of run.events({ include: reviewers })) console.log(event.summary);
await run.joinTree();00:00 them admitted "fix issue #414" under trt-01aa…4c19 00:01 them delegate coordinator → fixer-1, fixer-2, tester, each narrower 00:06 them signal tester deposits flaky: tests/test_pricing.py::test_issue_414 00:06 you signal admitted over iroh, weight 0.62 after decay 00:31 them refused fixer-2 read /secrets/.env: outside the grant, at your door 00:58 them mailbox fixer-1 sends candidate a41c9e2 to reviews 01:00 you mailbox reviewer-1 claims a41c9e2, checks test_issue_414 first 01:19 you mailbox reviewer-1 requests a change: round half up, not half even 01:44 them mailbox fixer-1 sends candidate 5b93d10 01:46 you approve reviewer-2 approves 5b93d10, pytest 14 passed 01:47 them sealed fork c4e81b7 → 5b93d10 spent USD 3.12 of 5.00 across 4 of their agents
# Your side: two reviewers claim candidates from a shared mailbox.
reviews = project.mailbox("reviews", partner=partner, treaty=treaty)
reviewers = project.agents("review", count=2, reads=reviews)
# Their side: their agents work the issue in your fork.
run = partner.exec("fix issue #414", on=fork, authority=authority, report_to=reviews)
for event in run.events(include=reviewers):
print(event.summary)
run.join_tree()00:00 them admitted "fix issue #414" under trt-01aa…4c19 00:01 them delegate coordinator → fixer-1, fixer-2, tester, each narrower 00:06 them signal tester deposits flaky: tests/test_pricing.py::test_issue_414 00:06 you signal admitted over iroh, weight 0.62 after decay 00:31 them refused fixer-2 read /secrets/.env: outside the grant, at your door 00:58 them mailbox fixer-1 sends candidate a41c9e2 to reviews 01:00 you mailbox reviewer-1 claims a41c9e2, checks test_issue_414 first 01:19 you mailbox reviewer-1 requests a change: round half up, not half even 01:44 them mailbox fixer-1 sends candidate 5b93d10 01:46 you approve reviewer-2 approves 5b93d10, pytest 14 passed 01:47 them sealed fork c4e81b7 → 5b93d10 spent USD 3.12 of 5.00 across 4 of their agents
// Your side: two reviewers claim candidates from a shared mailbox.
let reviews = project.mailbox("reviews").partner(&partner).treaty(&treaty).open().await?;
let reviewers = project.agents("review").count(2).reads(&reviews).start().await?;
// Their side: their agents work the issue in your fork.
let exec = partner.exec("fix issue #414").on(&fork).authority(authority.clone());
let mut run = exec.report_to(&reviews).start().await?;
let mut events = run.events().include(&reviewers);
while let Some(event) = events.next().await {
println!("{}", event?.summary());
}
run.join_tree().await?;00:00 them admitted "fix issue #414" under trt-01aa…4c19 00:01 them delegate coordinator → fixer-1, fixer-2, tester, each narrower 00:06 them signal tester deposits flaky: tests/test_pricing.py::test_issue_414 00:06 you signal admitted over iroh, weight 0.62 after decay 00:31 them refused fixer-2 read /secrets/.env: outside the grant, at your door 00:58 them mailbox fixer-1 sends candidate a41c9e2 to reviews 01:00 you mailbox reviewer-1 claims a41c9e2, checks test_issue_414 first 01:19 you mailbox reviewer-1 requests a change: round half up, not half even 01:44 them mailbox fixer-1 sends candidate 5b93d10 01:46 you approve reviewer-2 approves 5b93d10, pytest 14 passed 01:47 them sealed fork c4e81b7 → 5b93d10 spent USD 3.12 of 5.00 across 4 of their agents
A signal from their tester tells your reviewer which test to read first; it ranks work and grants nothing. Fixer-2's read of /secrets/.env stops at your kernel, before a byte leaves your machine.
Read more in Signals between agents.
5. Merge with both signatures
The sealed fork carries exactly what their agents changed. Apply it under the treaty.
console.log(await fork.diff());
const merged = await project.apply(fork.changes, { treaty });
console.log(merged.receipt.describe());src/pricing.py
@@ def quote(sku, percent):
- return round(discount(prices[sku], percent), 2)
+ amount = Decimal(str(discount(prices[sku], percent)))
+ return float(amount.quantize(Decimal("0.01"), ROUND_HALF_UP))
merge main 2f60a91 → 5b93d10
receipt rcpt-01ab…f2d0, co-signed by your kernel and theirsprint(fork.diff())
merged = project.apply(fork.changes, treaty=treaty)
print(merged.receipt.describe())src/pricing.py
@@ def quote(sku, percent):
- return round(discount(prices[sku], percent), 2)
+ amount = Decimal(str(discount(prices[sku], percent)))
+ return float(amount.quantize(Decimal("0.01"), ROUND_HALF_UP))
merge main 2f60a91 → 5b93d10
receipt rcpt-01ab…f2d0, co-signed by your kernel and theirsprintln!("{}", fork.diff().await?);
let merged = project.apply(fork.changes()).treaty(&treaty).await?;
println!("{}", merged.receipt().describe());src/pricing.py
@@ def quote(sku, percent):
- return round(discount(prices[sku], percent), 2)
+ amount = Decimal(str(discount(prices[sku], percent)))
+ return float(amount.quantize(Decimal("0.01"), ROUND_HALF_UP))
merge main 2f60a91 → 5b93d10
receipt rcpt-01ab…f2d0, co-signed by your kernel and theirsApply publishes only if main still matches the fork's base. The receipt is evidence for both operators: you accepted this change, and they delivered it.
6. Check every decision
Export the run and verify it with the two pinned kernel keys and nothing else.
const evidence = await project.history.export("audit/");
console.log(await evidence.verify());treaty trt-01aa…4c19, ladders intersect as signed receipts 61 signed decisions from 2 kernels lineage your grant → their coordinator → 3 agents, narrower at every step signals 1 deposit admitted, advisory only mailbox 4 messages, each sent and claimed under its own grant refusals 1: fixer-2 read /secrets/.env spend USD 3.12 of the 5.00 pool merge co-signed; main 5b93d10 matches the approved candidate verified offline, against both pinned kernel keys
evidence = project.history.export("audit/")
print(evidence.verify())treaty trt-01aa…4c19, ladders intersect as signed receipts 61 signed decisions from 2 kernels lineage your grant → their coordinator → 3 agents, narrower at every step signals 1 deposit admitted, advisory only mailbox 4 messages, each sent and claimed under its own grant refusals 1: fixer-2 read /secrets/.env spend USD 3.12 of the 5.00 pool merge co-signed; main 5b93d10 matches the approved candidate 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 61 signed decisions from 2 kernels lineage your grant → their coordinator → 3 agents, narrower at every step signals 1 deposit admitted, advisory only mailbox 4 messages, each sent and claimed under its own grant refusals 1: fixer-2 read /secrets/.env spend USD 3.12 of the 5.00 pool merge co-signed; main 5b93d10 matches the approved candidate verified offline, against both pinned kernel keys
chio evidence verify --input audit --partner partnerverified 61 receipts from 2 kernels, offline
--partner requires the partner's co-signature on every joint receipt and checks it against the receipt signer chio partner add pinned, so a package missing either side fails. Verification needs no access to either operator's systems, so an auditor outside both can run it.
7. Revoke, and the work stops
Start their agents on a new task, then revoke the treaty while they work.
const next = await project.fork();
const run2 = await partner.exec("add banker's rounding as an option", {
on: next,
authority: authority.reissue({ on: next }), // same terms, new fork
});
await treaty.revoke();
for await (const event of run2.events()) console.log(event.summary);00:00 them admitted "add banker's rounding as an option" under trt-01aa…4c19 00:04 you revoked trt-01aa…4c19, revocation root sent over iroh 00:05 them refused fixer-1 write /workspace/src/pricing.py: treaty revoked 00:05 them stopped their work closed, no effects after revocation
next_fork = project.fork()
run2 = partner.exec(
"add banker's rounding as an option",
on=next_fork,
authority=authority.reissue(on=next_fork), # same terms, new fork
)
treaty.revoke()
for event in run2.events():
print(event.summary)00:00 them admitted "add banker's rounding as an option" under trt-01aa…4c19 00:04 you revoked trt-01aa…4c19, revocation root sent over iroh 00:05 them refused fixer-1 write /workspace/src/pricing.py: treaty revoked 00:05 them stopped their work closed, no effects after revocation
let next = project.fork().await?;
let grant = authority.reissue(&next)?; // same terms, new fork
let task = partner.exec("add banker's rounding as an option").on(&next).authority(grant);
let mut run2 = task.start().await?;
treaty.revoke().await?;
let mut events = run2.events();
while let Some(event) = events.next().await {
println!("{}", event?.summary());
}00:00 them admitted "add banker's rounding as an option" under trt-01aa…4c19 00:04 you revoked trt-01aa…4c19, revocation root sent over iroh 00:05 them refused fixer-1 write /workspace/src/pricing.py: treaty revoked 00:05 them stopped their work closed, no effects after revocation
The revocation crosses to their kernel over iroh, and their next call is refused. Every grant issued under the treaty ends with it, including the ones their agents delegated to each other.
Read more in Revocation checkpoints.