Agent Commerce Network
A buyer org procures a security review from a provider org. Every boundary is mediated by chio: a capability with a budget, a buyer API sidecar, a provider MCP edge, signed receipts on both sides bound to the same governed intent, and a settlement reconciliation record. This page is the runnable companion to Worked Example: Agent Commerce Network, which narrates the same flow.
Where the code lives
examples/agent-commerce-network/. Run it with ./smoke.sh. Set OPENAI_API_KEY or ANTHROPIC_API_KEY to use a live agent loop, or leave both unset for the deterministic CI fallback.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 every 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 kernels emit receipts that bind to the same governed transaction intent. The pair is the bilateral evidence record.
Architecture
Service Inventory
smoke.sh picks free ports for every process. 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
}
# smoke.sh then assigns four ports up front:
TRUST_PORT="$(pick_free_port)"
PROVIDER_PORT="$(pick_free_port)" # provider edge, default 8931
BUYER_API_PORT="$(pick_free_port)"
BUYER_SIDECAR_PORT="$(pick_free_port)" # buyer sidecar, default 8940| 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 |
Every long-running process writes its log under artifacts/live/<timestamp>/logs/ and its sqlite state under the matching state/ directory. The cleanup trap kills every background pid 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"
}Quote-response body:
{
"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_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"
}Expected log lines (provider edge):
chio.mcp.serve-http: tools/call request_quote -> allow
chio.mcp.serve-http: receipt receipt_<id> sealed (verdict=allow)Phase 2: Governed Intent
The buyer wraps the quote in a GovernedTransactionIntent with a metered billing context. The intent hash anchors both the buyer-side and the provider-side receipt. Three settlement modes are available; the example uses AllowThenSettle through the trust-control split-budget endpoints.
Before dispatch, the buyer kernel calls /v1/budgets/authorize-exposure with the worst-case price. If that would breach maxTotalCost or maxInvocations, the call denies and the buyer service surfaces denied_budget:
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: Receipt Pair
Each kernel emits its own signed receipt. Both share the same intent_hash, which is what makes the pair a bilateral artifact. The buyer sidecar writes to state/buyer-receipts.sqlite3; trust-control accumulates trust receipts in state/trust-receipts.sqlite3.
The governed intent is the input to both signatures. It carries the capability id, the metered billing context, and the worst-case price the buyer kernel pre-charges before dispatch:
{
"intent_id": "intent_lattice_vanguard_001",
"capability_id": "cap-019d921b-...-3e9-...",
"buyer_org": "lattice-platform-security",
"provider_org": "vanguard-security",
"tool_server": "provider-security-review",
"tool_name": "execute_review",
"settlement_mode": "AllowThenSettle",
"billing_context": {
"scheme": "metered",
"quote_id": "quote_vanguard_001",
"max_price_minor": 125000,
"currency": "USD"
},
"intent_hash": "sha256:7bf4...c0a1"
}Both sides emit a receipt that commits to intent_hash. The buyer kernel signs its receipt with the buyer kernel public key; the provider kernel signs with its own. The pair is the bilateral evidence record:
{
"id": "rcpt-019d921b-buyer-...",
"timestamp": 1776272775,
"capability_id": "cap-019d921b-...-3e9-...",
"intent_hash": "sha256:7bf4...c0a1",
"tool_server": "http-sidecar-client",
"tool_name": "procurement_job_write",
"decision": {"verdict": "allow"},
"kernel_key": "f257309e...d6ed0",
"signature": "3d86c9f9...3e107",
"settlement_status": "Charged"
}{
"id": "rcpt-019d921b-provider-...",
"timestamp": 1776272776,
"capability_id": "cap-019d921b-...-3e9-...",
"intent_hash": "sha256:7bf4...c0a1",
"tool_server": "provider-security-review",
"tool_name": "execute_review",
"decision": {"verdict": "allow"},
"kernel_key": "8c1a2bf3...07e29",
"signature": "9e0a4d61...88e44"
}Both rows commit to the same intent_hash. The verifier loads both rows, recomputes the hash from the buyer intent, and confirms the provider receipt commits to the same hash under a different kernel key. Different signers, same fact.
Two receipts, one binding
Phase 5: Reconcile
After dispatch, the buyer kernel calls /v1/budgets/reconcile-spend with the actual cost. Three outcomes:
- Actual < charged: the difference is credited back to the grant.
- Actual = charged: no adjustment.
- Actual > charged: the receipt's
settlement_statusis set toFailedand the overrun is recorded.
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 a real EVM, Solana, CCIP, or x402 rail.
Inspect the Artifacts
Every run drops 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 artifact directory, recomputes hashes, and confirms every required artifact is present. 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 any required artifact is missing or any hash recompute 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 every contract under contracts/ exists and parses, that the quote-response's price_minor is consistent with the settlement-reconciliation's quoted_amount_minor, and that the receipt id chain is unbroken from the buyer side through to the provider side.
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 SETTLEMENT_DB="$(pwd)/state/trust-budgets.sqlite3"
export BUYER_DB="$(pwd)/state/buyer-receipts.sqlite3"
# Five most recent receipts trust-control ingested
sqlite3 "$TRUST_RECEIPT_DB" \
'select id, tool_server, tool_name, verdict, created_at \
from receipts order by created_at desc limit 5;'
# Settlement rows in the budget store
sqlite3 "$SETTLEMENT_DB" \
'select capability_id, status, amount_charged, amount_settled \
from settlements;'
# Buyer-side receipts
sqlite3 "$BUYER_DB" \
'select id, tool_name, verdict, intent_hash from receipts;'
# Pricing slice of the quote contract
cat contracts/quote-response.json | jq '{price_minor, currency, approval_required}'
# Settlement reconciliation, bilateral position
cat contracts/settlement-reconciliation.json \
| jq '{status, buyer_position, provider_position, settled_amount_minor}'Expected output (with one happy-path run):
# trust-receipts: two rows, one per kernel
rcpt-...buyer|http-sidecar-client|procurement_job_write|allow|1776272775
rcpt-...provider|provider-security-review|execute_review|allow|1776272776
# settlements: one reconciled row at the quoted amount
cap-019d921b-...|reconciled|125000|125000
# buyer-receipts: same intent_hash on the buyer row
rcpt-...buyer|procurement_job_write|allow|sha256:7bf4...c0a1
# pricing slice
{
"price_minor": 125000,
"currency": "USD",
"approval_required": true
}
# settlement bilateral
{
"status": "reconciled",
"buyer_position": "accepted",
"provider_position": "accepted",
"settled_amount_minor": 125000
}Failure Paths
Three branches the agent can fall down. Each is intentional and each leaves an artifact:
- Budget denial: requested work exceeds the envelope. The buyer service stamps the job as
denied_budget; the pre-charge never lands. - Pending approval: the quote tripped
approval_required. The agent must callapprove_jobwith a reason. Trust-control records the approval in the receipt metadata before the dispatch proceeds. - 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.