BuildIntegrated Examples
Agent Commerce Network
Run a budgeted security-review purchase between buyer and provider organizations with signed receipts at both boundaries.
Where the code lives
examples/agent-commerce-network/. Set OPENAI_API_KEY or ANTHROPIC_API_KEY to use an external-model agent loop. With neither variable set, the smoke uses its deterministic CI fallback.The provider edge does not come up
./smoke.sh starts the provider edge with chio mcp serve-http --control-url (provider/run-edge.sh), and that needs trust-control's joint admission authority, which is enabled only by chio trust serve --session-db (crates/products/chio-cli/src/cli/runtime.rs:1405). smoke.sh:36-42 does not pass it, so logs/provider-edge.log holds failed to connect to admission authority: joint admission authority is not configured and the process exits. The rest of the topology does come up: the orchestrator issues a capability, falls back to its deterministic path, and then the buyer answers 403 Forbidden on POST /procurement/quote-requests, which raises out of commerce_network/agents.py:84. Nothing below the architecture on this page comes from a completed run.What it shows
- A buyer agent in Org A (
lattice-platform-security) procures a service from a provider agent in Org B (vanguard-security). - A capability token carries a free quote-read grant and a budgeted job-write grant (
maxInvocations,maxCostPerInvocation,maxTotalCost). - The buyer sidecar (
chio api protect) signs a receipt for each API call. - The provider edge (
chio mcp serve-http) verifies the capability, enforces the policy, and signs its own receipt. - Trust-control tracks budget consumption, revocations, and financial reports (budget usage, exposure ledger, settlement).
- Both boundaries emit signed receipts: the buyer sidecar for each API call, the provider edge for each tool call. The run bundle captures the resulting receipts.
Architecture
Service inventory
smoke.sh picks free ports for its processes. The shape is fixed; the numbers vary per run. Port discovery uses the shared pick_free_port helper that opens a socket on port 0, reads back the OS-assigned port, and prints it. Each call returns a fresh free port:
pick_free_port() {
python3 - <<'PY'
import socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.bind(("127.0.0.1", 0))
print(sock.getsockname()[1])
PY
}The smoke then assigns four of them up front. Every port is OS-assigned; none of the four falls back to a fixed number.
TRUST_PORT="$(pick_free_port)"
PROVIDER_PORT="$(pick_free_port)"
BUYER_API_PORT="$(pick_free_port)"
BUYER_SIDECAR_PORT="$(pick_free_port)"| Process | Command | Listens on |
|---|---|---|
| trust-control | chio trust serve | 127.0.0.1:$TRUST_PORT |
| provider edge | chio mcp serve-http wrapping provider/review_server.py | 127.0.0.1:$PROVIDER_PORT |
| buyer FastAPI | uvicorn buyer.app:app | 127.0.0.1:$BUYER_API_PORT |
| buyer sidecar | chio api protect over the buyer FastAPI | 127.0.0.1:$BUYER_SIDECAR_PORT |
| orchestrator | python orchestrate.py | no listen; CLI client |
Each long-running process writes logs under artifacts/live/<timestamp>/logs/ and its sqlite state under the matching state/ directory. The cleanup trap kills the background processes on exit.
Run It
# From the chio workspace root
cargo build --bin chio
cd examples/agent-commerce-network
./smoke.sh
# Optional: live agent loop instead of the deterministic fallback
export OPENAI_API_KEY=... # OpenAI Agents SDK path
# or
export ANTHROPIC_API_KEY=... # Anthropic SDK path
./smoke.shOn a successful run the script prints two lines:
agent-commerce-network smoke passed
artifacts: /path/to/examples/agent-commerce-network/artifacts/live/<timestamp>Phase 1: Quote
The orchestrator first asks trust-control for a capability with two grants. Read calls (procurement_quote_read) are free; write calls (procurement_job_write) carry the budget envelope.
The buyer agent posts a quote-request to the provider edge. Both sides are JSON. Quote-request body:
{
"quote_id": "quote_req_lattice_001",
"buyer_id": "lattice-platform-security",
"provider_id": "vanguard-security",
"service_family": "security-review",
"requested_scope": "release-review",
"target": "git://lattice.example/payments-api",
"release_window": "2026-05-01T16:00:00Z"
}The committed template the provider starts from:
{
"quote_id": "quote_vanguard_001",
"request_id": "quote_req_lattice_001",
"service_family": "security-review",
"offer_id": "release-review",
"price_minor": 125000,
"currency": "USD",
"approval_required": true,
"estimated_delivery_hours": 48,
"pricing_basis": "bounded release review for one release window"
}cap = trust.issue_capability(
subject_pk="00" * 32,
scope={
"grants": [
{"server_id": "http-sidecar-client",
"tool_name": "procurement_quote_read",
"operations": ["invoke"], "constraints": []},
{"server_id": "http-sidecar-client",
"tool_name": "procurement_job_write",
"operations": ["invoke"], "constraints": [],
"maxInvocations": 3,
"maxCostPerInvocation": _usd(args.budget_minor),
"maxTotalCost": _usd(args.budget_minor)},
],
"resource_grants": [], "prompt_grants": [],
},
ttl=3600,
)The agent then calls POST /procurement/quote-requests on the buyer sidecar, which forwards to the provider edge. The provider responds with a per-call quote and the approval_required flag flips when the quote exceeds 100,000 cents.
{
"quote_id": "quote_<random>",
"request_id": "quote_req_<random>",
"service_family": "security-review",
"offer_id": "release-review",
"price_minor": 45000,
"currency": "USD",
"approval_required": false,
"estimated_delivery_hours": 48,
"pricing_basis": "bounded hotfix-review for git://lattice.example/payments-api"
}Three things about that body are worth naming. price_minor is 45000 rather than the template's 125000 because smoke.sh:89 asks for scope hotfix-review, and provider/review_server.py:42-48 prices that at 45000. approval_required is therefore false, since APPROVAL_THRESHOLD_MINOR is 100000 (review_server.py:15,55). And offer_id still says release-review: quote_payload never updates that key, so it is the template's value on every scope. Read the offer id as stale, not as the offer that was quoted.
The edges do not log per call
chio mcp serve-http writes one line when it binds and nothing per tools/call. Receipt ids are 64 lowercase hex characters with no prefix (crates/platform/chio-http-core/src/receipt.rs:343-353), so read a call's outcome out of the receipt store rather than out of a log line.Phase 2: Job Creation and Budget Check
The buyer service turns the quote into a job and checks it against the budget envelope client-side, before anything dispatches. If the job budget is below the quoted price it stamps denied_budget; if the quote tripped the approval threshold it stamps pending_approval; otherwise it executes. That client-side check is the whole budget enforcement in this example.
The grant caps do not reach the issued token
orchestrate.py sends the job-write grant with camelCase keys, maxInvocations, maxCostPerInvocation and maxTotalCost. The wire keys ToolGrant deserializes are snake_case, and the type declares neither rename_all nor deny_unknown_fields (crates/core/chio-core-types/src/capability/scope.rs:94-118), so /v1/capabilities/issue drops all three without an error and hands back a grant carrying only server_id, tool_name and operations. Trust-control has no envelope to enforce. Read the caps below as the shape the example means to express, and treat the budget check in buyer/app.py as the only one that runs.if budget_minor < quote["price_minor"]:
job["status"] = "denied_budget"
job["denial_reason"] = "requested work exceeds the buyer budget envelope"
elif quote["approval_required"]:
job["status"] = "pending_approval"
else:
self._execute_job(job)Phase 3: Dispatch
The buyer issues an MCP tools/call for execute_review. The capability rides on the request as the X-Chio-Capability header, and the auth bearer rides on Authorization: Bearer ...:
headers: dict[str, str] = {"Authorization": f"Bearer {auth_token}"}
if cap_header:
headers["X-Chio-Capability"] = cap_header
r = http.post(f"{buyer_url}{path}", headers=headers, json=body)The provider edge runs its policy pipeline and forwards into the review tool. The tool returns a fulfillment package:
{
"fulfillment_id": "fulfillment_vanguard_001",
"job_id": "job_lattice_001",
"service_family": "security-review",
"deliverables": [
"executive-summary.md",
"findings.json",
"remediation-checklist.md"
],
"status": "completed_with_findings",
"severity_summary": {"critical": 0, "high": 2, "medium": 5, "low": 7}
}Phase 4: Receipts
Each boundary signs its own receipt. The buyer sidecar ( chio api protect) writes an HTTP receipt for each call it mediates into state/buyer-receipts.sqlite3 (one row per receipt in the http_receipts table). The provider MCP edge signs a receipt for each tools/call it admits. Trust-control ingests receipts into state/trust-receipts.sqlite3 (the chio_tool_receipts table). The run bundle captures all of them: the evidence is the set of independently signed receipts across both orgs, each under its own kernel signature.
Peek at the two stores directly. Route, method, and verdict live inside the receipt blob rather than in dedicated columns:
# Trust-control ingested receipts: one row per admitted call
sqlite3 state/trust-receipts.sqlite3 \
'select receipt_id, tool_server, tool_name, decision_kind from chio_tool_receipts order by seq;'
# Buyer sidecar HTTP receipts: id plus the signed receipt blob
sqlite3 state/buyer-receipts.sqlite3 \
'select id, receipt_json from http_receipts order by rowid;'Bilateral intent binding is a protocol feature, not this example
GovernedTransactionIntent binding, where a buyer and provider receipt each commit to one shared intent hash, is documented under Bilateral Receipts. This runnable example keeps the flow simpler: a capability grant with a budget envelope, a client-side budget check, and independently signed receipts on each side.Phase 5: Reconcile
This example runs no reconciliation
/v1/budgets/reconcile-spend, and the twelve budget routes are listed at crates/platform/chio-control-plane/src/trust_control/service_types/paths.rs:127-144. No file under examples/agent-commerce-network/ calls any of them. commerce_network/chio.py:212 defines a charge_budget helper that targets /v1/budgets/charge, which is not one of the twelve, and it has no call sites in this example. What this run reconciles is the settlement contract the two agents write, not a kernel budget ledger.Phase 6: Settle
In the example the settlement rail is stubbed and returns a synthetic settlement_id:
{
"settlement_id": "settlement_lattice_vanguard_001",
"job_id": "job_lattice_001",
"quoted_amount_minor": 125000,
"approved_amount_minor": 125000,
"settled_amount_minor": 125000,
"currency": "USD",
"status": "reconciled",
"buyer_position": "accepted",
"provider_position": "accepted"
}See On-chain Settlement and Pricing for how the payment_reference field on the receipt connects to an EVM, Solana, CCIP, or x402 settlement rail.
Inspect Run Files
Each run creates a directory under artifacts/live/<timestamp>/:
cd artifacts/live/<timestamp>
# Capability that drove the run
cat capability.json
# What the agent decided and which tools it called
cat agent-output.json
cat summary.json
# Contracts captured from the agent's tool calls
ls contracts/
cat contracts/quote-response.json
cat contracts/fulfillment-package.json
cat contracts/settlement-reconciliation.json
# Trust-control financial reports
cat financial/budget-usage.json
cat financial/exposure-ledger.json
cat financial/settlement-report.json
# Persistent state per service
ls state/
# trust-receipts.sqlite3 receipts ingested by trust-control
# trust-revocations.sqlite3 revocation table
# trust-authority.sqlite3 authority signing seed + history
# trust-budgets.sqlite3 grant usage records
# buyer-receipts.sqlite3 receipts written by chio api protect
# provider-sessions.sqlite3 provider edge session state
# Per-process logs
ls logs/
# trust.log chio trust serve
# provider-edge.log chio mcp serve-http
# buyer-api.log uvicorn buyer.app
# buyer-sidecar.log chio api protectThe smoke also runs the in-tree verifier: commerce_network.verify.verify_bundle reads the run directory, confirms the required files is present, that each contract parses, and that the agent and summary final_status agree. Its output lands in review-result.json; the smoke aborts if ok is false.
Smoke Assertions
The smoke runs the in-tree verifier as its last step. The post-orchestrator block in smoke.sh aborts the run if a required file is missing or a check fails:
uv run --project "${EXAMPLE_ROOT}" python -c "
import sys; sys.path.insert(0, '${EXAMPLE_ROOT}')
from commerce_network.verify import verify_bundle
import json
r = verify_bundle('${ARTIFACT_ROOT}')
json.dump(r, open('${ARTIFACT_ROOT}/review-result.json', 'w'), indent=2)
assert r['ok'], r['errors']
"
printf 'agent-commerce-network smoke passed\n'
printf 'artifacts: %s\n' "${ARTIFACT_ROOT}"verify_bundle checks that agent-output.json, the three contracts, and summary.json all exist; that the agent's final_status matches summary.json; and that three named contract files parse as JSON. The name list is hard-coded at verify.py:54, so a fourth file dropped into contracts/ is not checked. There is no price-consistency check and no receipt-chain check.
One check in the verifier can never fire. verify.py:71 looks for a negative totalCostCharged in financial/budget-usage.json, and no budget response carries that key: the wire keys are totalExposureCharged and totalRealizedSpend (crates/platform/chio-control-plane/src/trust_control/service_types/responses.rs:298-310), and arc asserts its absence in its own test (trust_control/service_runtime/tests/budget.rs:1070). The assertion passes because it never finds anything to fail on. Do not read it as a guarantee about spend.
Inspect After
Set the env vars to the run's sqlite paths and walk the state with the standard CLI tooling:
cd artifacts/live/<timestamp>
export TRUST_RECEIPT_DB="$(pwd)/state/trust-receipts.sqlite3"
export BUYER_DB="$(pwd)/state/buyer-receipts.sqlite3"
# Five most recent receipts trust-control ingested
sqlite3 "$TRUST_RECEIPT_DB" \
'select receipt_id, tool_server, tool_name, decision_kind from chio_tool_receipts order by seq desc limit 5;'
# Buyer-side HTTP receipts: id plus the signed receipt blob
sqlite3 "$BUYER_DB" \
'select id, receipt_json from http_receipts order by rowid;'
# Budget consumption, from the trust-control financial report
jq '.' financial/budget-usage.json
# Pricing slice of the quote contract
jq '{price_minor, currency, approval_required}' contracts/quote-response.json
# Settlement reconciliation, bilateral position
jq '{status, buyer_position, provider_position, settled_amount_minor}' \
contracts/settlement-reconciliation.jsonThe two stores answer in different shapes. chio_tool_receipts has dedicated tool_server and tool_name columns; http_receipts, which is what chio api protect writes, has only id and receipt_json, with the route, the method and the verdict nested inside the blob (the verdict at .verdict.verdict, because Verdict is #[serde(tag = "verdict")] at crates/platform/chio-http-core/src/verdict.rs:82). Receipt ids in either store are 64 lowercase hex characters.
The pricing and settlement slices depend on the scope you run. Under the smoke's hotfix-review, price_minor is 45000 and approval_required is false; the 125000 in the settlement template above is the committed contract fixture, and reflects release-review pricing.
Failure Paths
The example carries three intentional failure branches. With the arguments the smoke actually passes, --scope hotfix-review --budget-minor 90000 (smoke.sh:89-91), none of them fires: 45000 is under the 90000 envelope and under the 100000 approval threshold. Drive them by changing the scope or the budget.
- Budget denial: requested work exceeds the envelope. The buyer service stamps the job as
denied_budget; it does not dispatch work to the provider. - Pending approval: the quote tripped
approval_required. The agent must callapprove_jobwith a reason; the dispatch proceeds only after the approval is recorded on the job. - Dispute: after fulfillment, the agent calls
dispute_job. The settlement record flips toreversal_pending.
Use this example when...
Where to read more
MeteredBillingQuote. Bilateral Receipts covers receipt-pair verification.