How a Purchase Works
Run one service purchase, then follow how the buyer reserves, waits for approval, verifies delivery, and books what it owes.
On this page
A purchase between agents is four decisions with four owners: the terms the provider quotes, the reservation the buyer holds, the delivery the buyer checks, and the entries that record what is owed. The procurement application puts a Chio boundary on each side of the trade, so every operation that crosses either one is admitted under a grant and signed.
Run the purchaseOne run buys a review and books USD 450.00
Download the procurement application. It needs Python 3.11 or newer, uv, and the Chio CLI; its Python SDK and dependency lock are included.
cd agent-commerce-network
uv run --locked run.pyVerified 4 signed receipts, 3 delivered artifacts, and balanced book entries. Status: fulfilled Evidence: .state/runs/<run-id> Application data and budget are retained in .state/.
You should see a fulfilled job. The launcher started four local services, bought a hotfix-review of the payments-api workspace for USD 450.00 inside a USD 900.00 job envelope, verified what came back, and stopped the services. Identities, jobs, reservations, reviewed files, and receipts stay in .state/; the buyer starts with a USD 1,500.00 allowance. Settlement here is an internal book entry: no card is charged and no rail moves funds.
Two boundaries split the trade between buyer and provider
The buyer exposes a FastAPI service behind a Chio HTTP boundary; the provider exposes MCP tools behind a Chio MCP boundary. Each boundary admits only the operations its own grant covers and signs a receipt for each decision.
The buyer reserves first, then the provider reviews
The agent requests a quote and creates a job through the buyer's HTTP boundary. The buyer consumes the quote and reserves USD 450 in one SQLite transaction, persists executing, and only then calls execute_review through the provider's MCP boundary. Reserve before dispatch.
The buyer application owns the commercial decisions: it checks the quote's terms, keeps the allowance in SQLite, coordinates approval, verifies delivery, and maintains the ledger. The provider runs the scope's source checks, retains each fulfillment, and returns named deliverables with content hashes. An operator funds the allowance and approves large purchases with a credential the buying agent never holds.
Both sides run on one machine under one trust authority, which issues capabilities and keeps the shared revocation state; the launcher selects each boundary's signing key before it accepts that boundary's responses. Independently operated organizations add a peer trust arrangement instead. The allowance is the application's own ledger, separate from a kernel-priced grant, so it is no evidence that this price was charged against a capability; Budgets & Metering covers that path.
The quote fixes scope and price before any work exists
The buying agent holds a grant for the buyer's operations, and the buyer holds one for the provider's tools. Each grant admits its operations; neither settles the commercial terms. The buyer asks for a quote on a provider, service family, target, and scope, and keeps both the request and the provider's trace of the reply.
{
"approval_required": false,
"checks": [
"python-call-patterns"
],
"currency": "USD",
"estimated_delivery_hours": 0,
"max_bytes": 200000,
"max_files": 10,
"offer_id": "hotfix-review",
"price_minor": 45000,
"pricing_basis": "bounded hotfix-review for payments-api",
"provider_id": "vanguard-security",
"quote_id": "quote_e19826ebf7",
"request_id": "quote_req_c9e8cea266",
"service_family": "security-review",
"target": "payments-api"
}The hotfix scope checks Python call patterns, limited to 10 files and 200,000 bytes, at a fixed price for that scope. The quote is application data returned by a governed tool; it advertises no metered price through an MCP manifest.
Read more in Signed Tool Manifests and Pricing Models.
The reservation commits before the provider hears of the job
The buyer resolves the quote by id, checks that the provider and service match it, and refuses a quote another job already consumed. A caller cannot raise the operator's budget. The price must fit both the job envelope and the remaining allowance:
if quote["price_minor"] > min(budget, self.store.available(db)):
job.update(
status="denied_budget",
denial_reason="Quoted work exceeds the requested or remaining operator budget",
)
else:
db.execute(
"INSERT INTO positions VALUES(?,?,?)",
(job["job_id"], quote["price_minor"], "reserved"),
)
if quote["approval_required"]:
job["status"] = "pending_approval"
db.execute(
"INSERT INTO jobs VALUES(?,?,?)", (job["job_id"], payload.quote_id, json.dumps(job))
)Writing the job, consuming the quote, and reserving the price happen in one SQLite transaction, so concurrent purchases compete for the same allowance. In the run, the envelope was USD 900.00 and the reservation USD 450.00. The envelope caps this one job; the remaining allowance is checked separately and is never a fresh allocation per purchase. A price over either limit records denied_budget.
Read more in the application walkthrough.
Approval stays with the operator, outside the agent
A purchase above USD 1,000.00 enters pending_approval with its reservation held. The buying agent can read the job but holds no approval credential. The operator approves through a separate action that records the approver and the reason, and the transition is accepted once.
{
"ok": true,
"agent_status": "fulfilled",
"agent_mode": "operator-approval",
"verified_receipts": 3,
"artifacts": 3,
"settled_amount_minor": 125000,
"settlement_kind": "internal_book_entry",
"errors": []
}The captured approval run bought a USD 1,250.00 release review across a restart: the pending job and its reservation survived, the operator approved it, and the buyer verified 3 receipts and 3 files. Funding adds an allocation to the existing ledger. This approval belongs to the application; Human-in-the-Loop covers the kernel's approval bound to one request.
Delivery is accepted only when the provider's receipt matches the request
Before calling the provider, the buyer persists executing. It sends the job id, quote id, service family, scope, target, and release window. The provider reads the files, checks for dynamic execution, shell invocation, and weak digests, and returns findings, a summary, and a remediation checklist with the reviewed files' hashes. It never executes the submitted code.
checks = verify_receipt_with_trusted_signers(receipt, [self.trusted_kernel_key])
if not checks["ok"] or receipt["metadata"]["receipt_context"]["request_id"] != request_id:
raise RuntimeError(
"Provider receipt is invalid, untrusted or belongs to another request"
)
if (
receipt["action"]["parameters"] != arguments
or receipt["tool_name"] != name
or receipt["capability_id"] not in capability_ids
):
raise RuntimeError("Provider receipt does not match the requested operation")
if receipt["decision"]["verdict"] == "allow":
if sha256_hex_utf8(canonicalize_json(result)) != receipt["content_hash"]:
raise RuntimeError("Provider result does not match its signed output hash")
elif not result.get("isError"):
raise RuntimeError("Provider reported success for a refused operation")The buyer then checks that the fulfillment belongs to this job, quote, service, scope, and target, and hashes every delivered file. The report establishes which checks ran on which files, and no more than that. The model-driven run verified 5 receipts and 3 files; receipt counts vary with the operations performed. Separate HTTP and MCP receipts are not a bilateral receipt pair, which needs a shared binding and both signatures.
Retained fulfillment gives a repeated request for the same unchanged job its original result. Changed terms, changed source files, or changed retained deliverables are refused.
Verified delivery becomes two equal book entries
Only after verification does the buyer settle the reservation and write a USD 450.00 buyer debit and a matching provider credit. The job becomes fulfilled, and the settlement record names internal_book_entry.
amount = job["quote"]["price_minor"]
entries = [
{"account": self.buyer_id, "amount_minor": -amount},
{"account": job["provider_id"], "amount_minor": amount},
]
for entry in entries:
db.execute(
"INSERT INTO ledger VALUES(?,?,?)",
(job_id, entry["account"], entry["amount_minor"]),
)
db.execute(
"UPDATE positions SET status='settled' WHERE job_id=? AND status='reserved'",
(job_id,),
)The purchase consumes the accepted fixed price, with no adjustment for observed usage; Reconciliation covers the metered path, where quoted and observed costs differ. To resolve the obligation on a payment system, connect a settlement rail and keep its confirmation: an internal settled position does not show that funds moved.
Keep the quote, accepted job, original receipts, trusted signer configuration, delivered files, verification result, and ledger entries together, unresolved outcomes included. The application's verifier compares operation associations, hashes, monetary terms, and the retained accounting; Verify Commerce Evidence practices the same check on an offline bundle.
An uncertain outcome keeps its reservation until someone resolves it
The qualification run starts the services with separate state and attacks the purchase on purpose:
uv run --locked run.py checkverified 31 HTTP operations, 20 signed provider operations
race 8 concurrent purchases: 7 fulfilled, 1 refused
refused zero and negative budgets, budget increase, provider substitution,
quote replay, unauthorized approval, approval replay, revoked capability
unknown provider outage: outcome_unknown, reservation heldYou should see seven of eight simultaneous purchases complete and the eighth refused, because they all compete for one allowance. Each state the run can leave behind has its own accounting effect:
denied_budget. Nothing is reserved. Inspect the envelope and the remaining allowance; the operator can fund the ledger, and the original quote stays consumed by its job.pending_approval. The reservation is held until the operator approves; the agent cannot.outcome_unknown. A failed reply or delivery check keeps the reservation. Inspect the job, the provider trace, the receipt, and the retained fulfillment; the buyer never resends the purchase on its own.executingafter a crash. The dispatch state and reservation stay unresolved until someone establishes whether the provider finished.- Quote replay or changed terms. The operation is refused. Request a new quote only for a deliberately new purchase.
disputedanddispute_submitting. The settlement becomesreversal_pendingand the debit remains until the provider's dispute record resolves it; recording a dispute refunds nothing.
Sell an existing result instead of commissioning work
This application commissions work after accepting a quote. An information-good purchase buys a result that already exists, such as a repair. The Cognition Market binds one reveal envelope to its purchase, and the kernel's delivery check runs before the charge is captured. Both purchases connect authority, terms, delivery, and accounting; their state machines and enforcement owners differ.