Serve Other Agents
Publish your project as a service that agents you do not run can call.
On this page
Your code becomes something other agents can use. Each caller presents a grant from your kernel, and the kernel checks it and signs the decision before your code runs.
The pricing functions from part 1 become a service. One outside agent gets quotes but not refunds, deep discounts wait for your signature, and an agent with no grant gets nothing. At the end you read what each caller used.
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. Publish your code as a service
Name the functions to expose. The service runs your project's code inside the computer and speaks MCP and HTTP, so any agent with an MCP client or an HTTP library can reach it. Use the project and identity from part 1.
import { Computer, USD } from "@chio-protocol/sdk";
const project = await Computer.open("quickstart");
const pricing = await project.service("pricing", {
tools: { quote: "src/pricing.py:quote", refund: "src/pricing.py:refund" },
});
const endpoint = await pricing.publish({ protocols: ["mcp", "http"] });
console.log(endpoint.describe());service pricing, from quickstart @ main 2f60a91 tools quote · refund mcp https://127.0.0.1:9440/pricing/mcp http https://127.0.0.1:9440/pricing/v1, OpenAPI at /openapi.json admits callers that hold a grant from your kernel
from chio import Computer, USD
project = Computer.open("quickstart")
pricing = project.service(
"pricing",
tools={"quote": "src/pricing.py:quote", "refund": "src/pricing.py:refund"},
)
endpoint = pricing.publish(protocols=["mcp", "http"])
print(endpoint.describe())service pricing, from quickstart @ main 2f60a91 tools quote · refund mcp https://127.0.0.1:9440/pricing/mcp http https://127.0.0.1:9440/pricing/v1, OpenAPI at /openapi.json admits callers that hold a grant from your kernel
use chio::{Approval, Computer, ServiceGrant, USD};
let project = Computer::open("quickstart").await?;
let pricing = project
.service("pricing")
.tool("quote", "src/pricing.py:quote")
.tool("refund", "src/pricing.py:refund")
.create()
.await?;
let endpoint = pricing.publish(["mcp", "http"]).await?;
println!("{}", endpoint.describe());service pricing, from quickstart @ main 2f60a91 tools quote · refund mcp https://127.0.0.1:9440/pricing/mcp http https://127.0.0.1:9440/pricing/v1, OpenAPI at /openapi.json admits callers that hold a grant from your kernel
Read more in OpenAPI and MCP.
2. Meet an agent you do not run
The demo caller runs on this machine with its own key. Your kernel knows it only by its identity.
chio agent demo --out caller.jsonagent did:chio:z6MkbY4…e1R key held by the agent, not by your kernel connects https://127.0.0.1:9440/pricing/mcp
3. Grant it quotes, not refunds
The grant is the caller's whole authority: one tool, a call rate, a budget, and an expiry.
const access = pricing.grant({
to: "did:chio:z6MkbY4…e1R",
tools: ["quote"],
limits: { calls: 100, per: "1h" },
budget: USD(2),
expiresIn: "24h",
});
console.log(access.describe());grant cap-01ab…77d1, signed by your kernel to did:chio:z6MkbY4…e1R allows pricing.quote limits 100 calls per hour budget USD 2.00, USD 0.01 per call expires in 24 hours
access = pricing.grant(
to="did:chio:z6MkbY4…e1R",
tools=["quote"],
limits={"calls": 100, "per": "1h"},
budget=USD(2),
expires_in="24h",
)
print(access.describe())grant cap-01ab…77d1, signed by your kernel to did:chio:z6MkbY4…e1R allows pricing.quote limits 100 calls per hour budget USD 2.00, USD 0.01 per call expires in 24 hours
let access = pricing.grant(
ServiceGrant::to("did:chio:z6MkbY4…e1R")
.tool("quote")
.limit(100, "1h")
.budget(USD(2))
.expires_in("24h"),
)?;
println!("{}", access.describe());grant cap-01ab…77d1, signed by your kernel to did:chio:z6MkbY4…e1R allows pricing.quote limits 100 calls per hour budget USD 2.00, USD 0.01 per call expires in 24 hours
Anything outside the grant is refused before your code runs.
4. Require a person for deep discounts
An approval rule pauses a matching call until you sign for that exact request.
await pricing.require({ approval: "you", when: "quote.percent > 50" });pricing.require(approval="you", when="quote.percent > 50")pricing.require(Approval::by_you().when("quote.percent > 50")).await?;Read more in Human-in-the-loop.
5. Watch the calls arrive
Start the demo traffic. The caller asks for quotes, tries a refund, and keeps going; a second agent with no grant tries too.
chio agent demo --traffic caller.json --with-strangertraffic z6MkbY4…e1R with its grant, z6MkQx8…7Ha with none
for await (const call of pricing.events()) console.log(call.summary);00:00 z6MkbY4 quote sku-118 at 10% allow USD 0.01 00:02 z6MkbY4 quote sku-204 at 25% allow USD 0.01 00:04 z6MkbY4 refund order-77 refused not in the grant 00:05 z6MkQx8 quote sku-118 at 10% refused no grant from your kernel 00:09 z6MkbY4 quote sku-301 at 80% pending approval required 00:41 you approve sku-301 at 80% signed this request only 00:41 z6MkbY4 quote sku-301 at 80% allow USD 0.01 58:12 z6MkbY4 quote sku-118 at 10% refused 100 calls per hour reached
for call in pricing.events():
print(call.summary)00:00 z6MkbY4 quote sku-118 at 10% allow USD 0.01 00:02 z6MkbY4 quote sku-204 at 25% allow USD 0.01 00:04 z6MkbY4 refund order-77 refused not in the grant 00:05 z6MkQx8 quote sku-118 at 10% refused no grant from your kernel 00:09 z6MkbY4 quote sku-301 at 80% pending approval required 00:41 you approve sku-301 at 80% signed this request only 00:41 z6MkbY4 quote sku-301 at 80% allow USD 0.01 58:12 z6MkbY4 quote sku-118 at 10% refused 100 calls per hour reached
let mut calls = pricing.events();
while let Some(call) = calls.next().await {
println!("{}", call?.summary());
}00:00 z6MkbY4 quote sku-118 at 10% allow USD 0.01 00:02 z6MkbY4 quote sku-204 at 25% allow USD 0.01 00:04 z6MkbY4 refund order-77 refused not in the grant 00:05 z6MkQx8 quote sku-118 at 10% refused no grant from your kernel 00:09 z6MkbY4 quote sku-301 at 80% pending approval required 00:41 you approve sku-301 at 80% signed this request only 00:41 z6MkbY4 quote sku-301 at 80% allow USD 0.01 58:12 z6MkbY4 quote sku-118 at 10% refused 100 calls per hour reached
The 80 percent quote pauses. Approve it from your terminal; the signature covers that request and no other.
chio approvals pending
chio approve apr-01ac…5e2pending apr-01ac…5e2 z6MkbY4…e1R quote sku-301 at 80% waiting 12s approved apr-01ac…5e2, signed by did:chio:z6Mkf3…Qp9, binds request 9e1f…c2a0
You should see two refusals before any of your code ran: a refund outside the grant, and a stranger with no grant at all. After the hundredth call, the hourly limit refused the next one.
6. Account for every caller
Read what each caller used.
console.log(await pricing.usage({ since: "1h" }));caller calls refused approvals spent z6MkbY4…e1R 100 2 1 USD 1.00 z6MkQx8…7Ha 0 1 0 USD 0.00
print(pricing.usage(since="1h"))caller calls refused approvals spent z6MkbY4…e1R 100 2 1 USD 1.00 z6MkQx8…7Ha 0 1 0 USD 0.00
println!("{}", pricing.usage().since("1h").await?);caller calls refused approvals spent z6MkbY4…e1R 100 2 1 USD 1.00 z6MkQx8…7Ha 0 1 0 USD 0.00
Each row is computed from that caller's signed receipts, so the totals and the evidence are the same records.
Read more in Budgets and metering.
Next: work with their agents
Outside agents can now use your service on your terms. Part 3 lets your agents and another operator's agents work in the same project.