Chio/Docs
LOGIN · JOIN

EconomyWalkthroughs

Procurement Tour

Trace one procurement transaction from capability issuance to settlement, including the records Chio signs.

Where the code lives

The full example sits in examples/agent-commerce-network/. Snippets on this page are quoted from that source. Run it with cargo build --bin chio && ./smoke.sh. Set OPENAI_API_KEY or ANTHROPIC_API_KEY for a live agent loop; with neither set, the run uses the deterministic fallback used in CI.

The cast

  • Buyer: lattice-platform-security. Runs a FastAPI procurement service behind chio api protect.
  • Provider: vanguard-security. Runs an MCP tool server behind chio mcp serve-http.
  • Trust control: an authority that issues capabilities, tracks budgets, and projects financial reports. Both edges run with --control-url pointed at it, so their budget calls are HTTP rather than local.
  • Procurement agent: an OpenAI Agents SDK loop, an Anthropic SDK loop, or a deterministic fallback, chosen by which API key is set.
rendering
One procurement transaction across the two kernels and trust control, in the order the example issues the calls. The example builds its settlement record in process rather than dispatching to a rail, so no settlement hop is drawn.
sourceexamples/agent-commerce-network/orchestrate.pyexamples/agent-commerce-network/buyer/app.pyat fe56570

Step 1: the buyer issues a capability with a budget

The orchestrator asks trust control for a capability. The token carries two grants: a free read for quote requests, and a budgeted write grant for job creation. The budget cap is denominated in USD minor units (cents).

examples/agent-commerce-network/orchestrate.pypython
def _usd(cents: int) -> dict:
    return {"units": cents, "currency": "USD"}

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 grant uses the three-tier shape from Economics: maxInvocations, maxCostPerInvocation, and maxTotalCost. Both money caps take the same figure, args.budget_minor.

Two budget defaults, and they differ

orchestrate.py gives --budget-minor a default of 90,000 cents, and smoke.sh passes that figure explicitly alongside --scope hotfix-review, which prices at 45,000. The buyer service's own fallback, used when a job request names no budget, is DEFAULT_BUDGET_MINOR = 150_000. The walkthrough below follows the release-review scope at 125,000 cents against a 150,000-cent envelope, which is what the committed contract templates under contracts/ carry. At 90,000 the guard in step 5 refuses the job and the walkthrough stops there.

Step 2: the buyer reads the provider manifest

Vanguard's tool server publishes a manifest declaring the review tools it exposes. The agent learns the input schema for request_quote from the manifest. The wire form is SignedManifest (declared in chio-manifest), with an Ed25519 signature over the canonical JSON encoding of ToolManifest. The kernel verifies the signature against the server's registered public key before admitting the tool. Pricing rides on the same record as a ToolPricing entry on each ToolDefinition.

Pricing is advisory on the manifest

The manifest declares advertised pricing. The contract a buyer binds to is the per-call quote returned by request_quote, captured as a MeteredBillingQuote on the next governed intent. See Manifests and Pricing.

Step 3: the agent asks for a quote

The agent calls the buyer's procurement service, which proxies into the provider via MCP. The provider starts from the committed contract template and overrides six fields from the request. The quote response carries price, currency, and an approval_required flag.

examples/agent-commerce-network/provider/review_server.pypython
def quote_payload(arguments: dict[str, Any]) -> dict[str, Any]:
    template = deepcopy(contract_template("quote-response.json"))
    scope = arguments["requested_scope"]
    price_minor = {
        "hotfix-review": 45_000,
        "release-review": 125_000,
        "release-plus-cloud-review": 175_000,
        "full-estate-review": 325_000,
    }.get(scope, template["price_minor"])
    template.update(
        {
            "quote_id": random_id("quote"),
            "request_id": arguments["request_id"],
            "service_family": arguments["service_family"],
            "price_minor": price_minor,
            "approval_required": price_minor > APPROVAL_THRESHOLD_MINOR,
            "pricing_basis": f"bounded {scope} for {arguments['target']}",
        }
    )

The committed template, which is also what a release-review request produces:

examples/agent-commerce-network/contracts/quote-response.jsonjson
{
  "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"
}

APPROVAL_THRESHOLD_MINOR is 100,000, so the 125,000-cent release-review comes back with approval_required set and the 45,000-cent hotfix-review does not. The override list does not include offer_id, so every scope answers with the template's release-review in that field while pricing_basis names the scope actually requested. The buyer caches the response keyed by quote_id for the duration of the procurement.


Step 4: binding the quote to a governed action

The example posts a quote id and a budget to its own procurement service, which is enough for the buyer-side guard in step 5. The kernel-side binding for a governed action is a GovernedTransactionIntent carrying a MeteredBillingContext. The sidecar accepts one as an optional governed_intent field on its mediated route and forwards it verbatim into the kernel, so a grant that requires a governed intent can be authorized rather than denied.

crates/core/chio-core-types/src/capability/governance.rsrust
pub struct GovernedTransactionIntent {
    pub id: String,
    pub server_id: String,
    pub tool_name: String,
    pub purpose: String,
    pub max_amount: Option<MonetaryAmount>,
    pub metered_billing: Option<MeteredBillingContext>,
    // ... commerce, runtime_attestation, call_chain, autonomy, context, body
}

The metered-billing types nested inside it are declared earlier in the same module: the settlement mode, the quote it is bound to, and the context that carries both.

crates/core/chio-core-types/src/capability/governance.rsrust
#[serde(rename_all = "snake_case")]
pub enum MeteredSettlementMode {
    MustPrepay,
    HoldCapture,
    AllowThenSettle,
}

#[serde(rename_all = "camelCase")]
pub struct MeteredBillingQuote {
    pub quote_id: String,
    pub provider: String,
    pub billing_unit: String,
    pub quoted_units: u64,
    pub quoted_cost: MonetaryAmount,
    pub issued_at: u64,
    pub expires_at: Option<u64>,
}

#[serde(rename_all = "camelCase")]
pub struct MeteredBillingContext {
    pub settlement_mode: MeteredSettlementMode,
    pub quote: MeteredBillingQuote,
    pub max_billed_units: Option<u64>,
    pub verified_outcome: Option<VerifiedOutcomeRequestV1>,
}

GovernedTransactionIntent carries no rename_all, so its own fields stay snake_case on the wire while the two metered-billing types nested inside it are camelCase. The kernel computes the intent hash with binding_hash(), a SHA-256 over the canonical JSON of the whole intent. That hash anchors approval tokens, receipts, and the matching provider-side receipt to the same governed action.

Three settlement modes

MustPrepay requires funds in escrow before the tool runs. HoldCapture places a hold and settles by capture or release. AllowThenSettle lets the action run first and reconciles after, with truthful Pending status on the receipt. The example runs the third shape through the authorize-exposure and reconcile-spend pair below.

Step 5: the buyer kernel authorizes the budget

Before the provider tool runs, the buyer kernel reserves the worst-case cost against the running grant total. Because the sidecar runs with --control-url, its budget store is a client of trust control and these are HTTP calls:

RouteMethodHandlerConstant
/v1/budgets/authorize-exposurePOSThandle_try_charge_costBUDGET_AUTHORIZE_EXPOSURE_PATH
/v1/budgets/release-exposurePOSThandle_reverse_charge_costBUDGET_RELEASE_EXPOSURE_PATH
/v1/budgets/reconcile-spendPOSThandle_reduce_charge_costBUDGET_RECONCILE_SPEND_PATH

If the reservation would breach maxTotalCost or maxInvocations, the kernel denies the call. The buyer service applies its own envelope check first and surfaces that as denied_budget:

examples/agent-commerce-network/buyer/app.pypython
budget_minor = payload.budget_minor or self.default_budget_minor

# ...

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)

The 125,000-cent quote passes the 150,000-cent envelope and lands in pending_approval, because the provider flagged it above the approval threshold. The agent calls approve_job with an approver and a reason, and approve_job refuses any job whose status is not pending_approval.


Step 6: the provider kernel verifies and dispatches

Every buyer call carries the serialized capability in the X-Chio-Capability header. The provider edge (chio mcp serve-http) verifies the capability and runs its guard pipeline before forwarding into execute_review:

examples/agent-commerce-network/commerce_network/agents.pypython
def _call_buyer(method: str, path: str, body: dict | None = None) -> dict:
    headers: dict[str, str] = {"Authorization": f"Bearer {auth_token}"}
    if cap_header:
        headers["X-Chio-Capability"] = cap_header
    if method == "GET":
        r = http.get(f"{buyer_url}{path}", headers=headers)
    else:
        r = http.post(f"{buyer_url}{path}", headers=headers, json=body)
    r.raise_for_status()

The tool returns a fulfillment package. The provider kernel signs a receipt that records the action, the cost, and the guard evidence. Both kernels use the same governed intent hash as the binding anchor, which is what makes the pair of receipts a bilateral record.


Step 7: bilateral receipts and cost charged

Each kernel produces a receipt whose financial metadata block is a FinancialReceiptMetadata: grant index, cost charged, currency, remaining and total budget, delegation depth, root budget holder, payment reference, settlement status, and the optional cost breakdown, oracle evidence, and attempted cost on denials. The type documents invariants the kernel must uphold when it writes one: cost_charged <= budget_total, and budget_remaining computed as the post-charge balance, which the source marks a best-effort snapshot at read time under a split brain. Neither is enforced by the type. Buyer-side metadata after dispatch, with illustrative values in real field names:

json
{
  "financial": {
    "grant_index": 1,
    "cost_charged": 125000,
    "currency": "USD",
    "budget_remaining": 25000,
    "budget_total": 150000,
    "delegation_depth": 0,
    "root_budget_holder": "lattice-platform-security",
    "payment_reference": "settlement_lattice_vanguard_001",
    "settlement_status": "pending"
  },
  "governed_transaction": {
    "intent_id": "intent-job-001",
    "intent_hash": "[64 hex, GovernedTransactionIntent::binding_hash]",
    "purpose": "security review for one release window",
    "server_id": "vanguard-security",
    "tool_name": "execute_review",
    "metered_billing": {
      "settlementMode": "allow_then_settle",
      "quote": {
        "quoteId": "quote_vanguard_001",
        "provider": "vanguard-security",
        "billingUnit": "review",
        "quotedUnits": 1,
        "quotedCost": { "units": 125000, "currency": "USD" },
        "issuedAt": 1747776000
      }
    }
  }
}

One review is one billing unit here, so quotedUnits is 1 and quotedCost is the whole price. That is the same convention the metered stations of this tour use with a smaller unit: billingUnit names one billable unit and quotedUnits counts them.

Two receipts, one binding

The provider kernel writes a corresponding receipt under its own kernel key. Both share intent_hash. A regulator or counterparty can verify either side independently and confirm the pair lines up. Bilateral Receipts covers the verification flow.

Step 8: reconciliation

The authorization reserved the worst-case cost. After dispatch, the kernel reconciles through /v1/budgets/reconcile-spend with the actual cost from ToolInvocationCost, which is minor units, a currency code, and an optional breakdown. Actual below charged returns the difference; actual equal to charged changes nothing; actual above charged is a cost_overrun, which closes the hold at the authorized amount and sets settlement_status to Failed. See Reconciliation.


Step 9: settlement

The example drives no rail. Its buyer service fills the committed contracts/settlement-reconciliation.json template in process from the job's own quote, and a later dispute rewrites the same record with settled_amount_minor of 0 and status of reversal_pending. The committed template:

examples/agent-commerce-network/contracts/settlement-reconciliation.jsonjson
{
  "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"
}

A deployment that moves money drives one of the rails in chio-settle instead: EVM escrow via PreparedEvmCall, Solana via PreparedSolanaSettlement, cross-chain CCIP via CcipSettlementMessage, or HTTP-native rails via X402PaymentRequirements and PreparedCircleNanopayment. There the receipt's payment_reference carries the rail-level identifier and its settlement_status moves from Pending to Settled as confirmation lands.

Watchdogs cover the gap

SettlementWatchdogJob wraps long-running dispatches so a stuck transaction does not leave a receipt forever in Pending. See Settlement Rails and On-chain Settlement.

Step 10: audit

The orchestrator pulls financial reports from trust control and writes them to the output directory:

examples/agent-commerce-network/orchestrate.pypython
# -- Financial reports from trust-control --
budget_state = trust.query_budgets(capability_id=cap["id"])
_write(out / "financial" / "budget-usage.json", budget_state)

try:
    exposure = trust.exposure_ledger()
    _write(out / "financial" / "exposure-ledger.json", exposure)
except Exception:
    _write(out / "financial" / "exposure-ledger.json", {"status": "not_available"})

try:
    settlements = trust.settlement_report()
    _write(out / "financial" / "settlement-report.json", settlements)
except Exception:
    _write(out / "financial" / "settlement-report.json", {"status": "not_available"})

Three files come out of one transaction: budget usage (grant invocation count and total cost charged; see Economics), exposure ledger (outstanding committed cost; type ExposureLedgerReport, schema chio.credit.exposure-ledger.v1), and a settlement report. Only the first is unconditional. The other two are written inside a try, and a failure writes {"status": "not_available"} into the file rather than aborting the run, so an empty-looking report is a caught error and not an empty ledger.


Flow summary

The ten steps produce: (1) a capability token, (2) a signed manifest, (3) a quote response, (4) an intent hash both kernels bind to, (5) authorized budget state, (6) a fulfillment package, (7) two ChioReceipt records sharing an intent_hash, (8) reconciled financial receipt metadata, (9) a settlement record, (10) the three financial report files.


Failure paths

A procurement transaction can fail in three ways. Budget denial: the requested work exceeds the envelope; job status is denied_budget with the reason "requested work exceeds the buyer budget envelope", and nothing dispatches. Pending approval: the quote exceeds the approval threshold; the agent calls approve_job with a reason before dispatch. Dispute: after fulfillment, the buyer calls dispute_job; the settlement record flips to reversal_pending with positions contested and review_requested; adjudication is the entry point into the claims path.


Run it yourself

From the Chio workspace root:

bash
cd examples/agent-commerce-network
cargo build --bin chio
./smoke.sh

# Optional: live agent reasoning
export OPENAI_API_KEY=...
./smoke.sh

# Inspect the artifacts
ls artifacts/live/*/
cat artifacts/live/*/financial/budget-usage.json
cat artifacts/live/*/contracts/settlement-reconciliation.json

On success smoke.sh prints two lines, the second naming a UTC-timestamped output directory. Each service binds a free port picked at runtime and every service's stdout goes to a log file under that directory, so the terminal stays quiet:

examples/agent-commerce-network/smoke.shbash
printf 'agent-commerce-network smoke passed\n'
printf 'artifacts: %s\n' "${ARTIFACT_ROOT}"

The output directory holds the run records: summary.json, financial/ (budget-usage, exposure-ledger, settlement-report), contracts/ (quote-response, fulfillment-package, settlement-reconciliation), and per-service stdout under logs/. The run ends by calling verify_bundle, which asserts that five named files exist and that the agent output and the summary agree on a final status. The smoke test uses the deterministic fallback when no API key is set, which is what CI runs.

Where to go next

Pricing for how quotes and pricing models compose, Bilateral Receipts for receipt-pair verification, Underwriting for risk and premium, Settlement Rails for the dispatch step, Reconciliation for post-execution accounting.
Procurement Tour · Chio Docs