Transaction lifecycle
One deal, start to finish, as it moves through the four VCP namespaces. Each step is a typed envelope on the bus; nothing here is a side call. The sequence below is the design-of-record path — the executing runtime is in private beta, not a shipped endpoint.
The router enforces a partition table at the wire — wire-level role authentication, not LLM self-discipline. Private-utility memory (max_budget, floor_price) never appears in any payload.
Mandate: delegating buyer authority
The journey opens with delegate.create_purchase_mandate, sent from consumer:persona to buyer:intent. It carries a PurchaseMandate: the natural-language goal, hard constraints (budget, delivery window, must-haves), soft preferences, and an authority block that bounds what the agent may do without asking. This envelope opens the session_id that the entire buyer journey nests inside. Money in the mandate, like everywhere in VCP, is integer minor units — a $400 budget is 40000.
Search and rank: finding candidates
buyer:discovery emits commerce.search to platform:aggregator. The buyer never reads merchant inventory directly; it discovers candidates through the platform and VCP evidence. The aggregator responds with platform.rank_offers, a ranked candidate list. Ranking is a platform-only execution action — a buyer can receive a ranking but can never mint one, which keeps the ordering attributable to a known policy version.
Negotiation: one session, nested rounds
Negotiation is a commerce.* exchange between buyer:negotiation and merchant:pricing. It opens with commerce.propose_offer carrying a GroundedOffer, then commerce.counter_offer rounds bounce back and forth, each linked to the previous via in_reply_to so the whole back-and-forth threads inside a single session. It terminates in commerce.accept_offer(or a reject). Throughout, neither side's private-utility values — the buyer's walk-away price, the merchant's floor — may appear in any payload.
A representative envelope mid-flow, the moment a buyer accepts a counter:
{
"protocol": "vcp",
"version": "1.0",
"msg_id": "msg_0d41",
"ts": "2026-05-15T12:04:18Z",
"from": "buyer:negotiation",
"to": "merchant:pricing",
"session_id": "sess_abc",
"in_reply_to": "msg_0c90",
"idempotency_key": "idem_accept_0d41",
"action": {
"kind": "commerce.accept_offer",
"payload": { "offer_id": "offer_7f2" }
}
}Verification and the match certificate
Acceptance does not move money. First the platform verifies. platform:aggregator runs platform.verify_claims — checking the GroundedOffer's claims against the merchant's permitted-claims set — then issues platform.create_match_certificate. A MatchCertificateattests that this mandate matched this offer truthfully under a named verification policy at a given time: constraint fit, claim grounding, inventory availability, reputation threshold. This certificate, not the merchant's cart, is what settlement will later consume.
The human authority gate
Before payment, the deal passes through the human-in-the-loop gate. buyer:authorization presents the certified match to the principal, who replies with delegate.approve_purchase (or delegate.reject_purchase) referencing the cert_id. This is the answer to “will my agent spend without asking?”
The gate is governed by two fields in the mandate's authority block. can_buy_without_confirmation decides whether the gate can be skipped at all, and max_spend_without_confirmation sets the ceiling under which it may be. A deal whose total sits under the ceiling — and only such a deal — can settle autonomously; anything above it stops and waits for an explicit human approval envelope.
Settlement: the atomic world write
Only platform:psp settles. It runs platform.authorize_payment against the certificate and payment instrument, then platform.settle_payment, which is the one place the world actually changes: world.create_order, world.reserve_inventory, and world.update_ledger happen inside a single governed transaction. The runtime materializes the whole thing as one TransactionStateDiff with atomicity and idempotency invariants recorded. No buyer and no merchant role appears in this write — money moves through the PSP or not at all.
Dispatch and the optional dispute path
With the order on the books, merchant:fulfillment emits commerce.dispatch, applying a signed −qty delta to inventory and transitioning the order to shipped. For most deals the lifecycle ends here. When it does not, the buyer has scoped rights:
- commerce.request_return — buyer:authorization asks merchant:support for a return; it stays inside the same buyer session.
- platform.open_dispute — escalates to platform:adjudicator in a fresh session; the adjudicator may request_evidence from either side.
- platform.rule_dispute — the adjudicator rules, optionally triggering a refund and a reputation update. No merchant ever rules its own dispute.
How a single step actually runs
Each envelope in the flow above is produced by one agent turn. A turn is one inbound envelope driving a bounded internal loop — exactly one inference call per step — that terminates in at most one outbound envelope. The model emits structured decisions the agent acts on:
- load_skill — pull a full SKILL.md into context for this turn only.
- memory_update— write to the agent's private typed memory.
- emit_envelope — construct and send the single outbound envelope, after outbound invariant checks.
- no_reply — end the turn without speaking.
The loop is internal only. To get information from another participant, an agent must emit an envelope and wait for the runtime to deliver the response as a fresh turn — which is exactly why the lifecycle reads as a chain of envelopes rather than a call stack. For the namespaces and roles those envelopes ride on, see Core concepts.