Claude Commerce Agents
Wire tamper-proof receipts into anthropics/commerce-agents on the Messages API, the Agent SDK, and Managed Agents, verify a session offline, and know exactly what a receipt does and does not prove.
anthropics/commerce-agents is Anthropic's reference blueprint for two agents on Claude: a shopping agent a business embeds for customers and a merchant agent its staff use for the back office. Each runs on three paths, the Messages API, the Claude Agent SDK, and Managed Agents, over one set of tool contracts, gates, and skills.
The reference draws a clear line in its docs/safety.md. Enforced in code: fencing of third-party content, provenance gates on every write, caps, memory validation, and a host-approval gate on merchant changes. Left to the deployment: the approval surface, payment, and log hygiene. treeship-commerce is what a deployment adds for the record of what happened. It records; the reference's gates still decide what runs.
Anthropic publishes the reference as a reference: it is not maintained and does not accept contributions. treeship-commerce is a separate package that wraps it through the executor_class seam every runtime already exposes. No line of the reference changes.
Why one wrapper covers three runtimes
Every tool call, on every path, passes through one method: commerce_common.execution.BaseToolExecutor.execute. The reference relies on that for its own guarantees; its safety table says "a rule enforced inside a tool call holds on all three paths." TreeshipExecutorMixin overrides that one method:
- Intent receipt, signed, before dispatch: the tool name, a SHA-256 of the canonical arguments, the role, and the session tag.
- The tool, exactly as the reference runs it: validation, gates, handler, fencing.
- Result receipt, signed, after: status (
ok,blockedwith the gate's name, orerror), a SHA-256 of the result text, the event types the tool emitted, elapsed time, and whether the intent was recorded.
Each receipt names its parent. A session reads intent → result → intent → result … from the Treeship session's root artifact, and treeship verify walks it as one chain. A call the provenance gate holds is a signed refusal: the receipt exists, says blocked, and names provenance.
Anatomy of a receipt
Both receipts below are real, from python -m treeship_commerce.demo on a fresh ship. This is the add-to-cart the reference's provenance gate held, because the product id never came from a catalog read in that session.
The intent, decoded from its DSSE envelope:
{
"type": "treeship/action/v1",
"timestamp": "2026-09-06T19:33:00Z",
"actor": "agent://shopping",
"action": "commerce.tool.add_to_cart.intent",
"parentId": "art_21f62db3cbb2f1d839262a0d672b503b",
"meta": {
"args_digest": "sha256:bbb323b035288b725c4c593fb5c1ee9422d4ff43d18a928dc3e24ae9562c6ad1",
"role": "shopping",
"session_tag": "ea993c8c62ea",
"tool": "add_to_cart"
}
}The result, whose parent is that intent:
{
"type": "treeship/action/v1",
"timestamp": "2026-09-06T19:33:00Z",
"actor": "agent://shopping",
"action": "commerce.tool.add_to_cart.result",
"parentId": "art_57b39c3177d8bf4d95002b9c25344149",
"meta": {
"status": "blocked",
"gate": "provenance",
"result_digest": "sha256:3ab4b78a4205d1c7dd613f0cd8860e5bb5ae767026ab5191f281f41530404308",
"events": [],
"elapsed_ms": 0,
"intent_recorded": true,
"role": "shopping",
"session_tag": "ea993c8c62ea",
"tool": "add_to_cart"
}
}Around each statement sits the standard Treeship record: the DSSE envelope with the Ed25519 signature, payload_type: application/vnd.treeship.action.v1+json, the signing key_id, and the content-addressed artifact_id (art_ + the first 16 bytes of SHA-256 over the signed bytes). Change one byte of the statement and the id no longer matches.
What is deliberately not in a receipt:
| Not written | Why |
|---|---|
| The arguments | A reader with the arguments can recompute args_digest; a reader with the receipt learns nothing about them. |
| The result text | On the reference it is fenced third-party content (catalog rows, reviews, policies). |
| The commerce session id | The reference treats it as the request credential and logs only a twelve-hex tag. The receipt carries that same tag, from the reference's own session_tag helper, so an operator holding the id can correlate and nobody else can. |
Install
# in a clone of anthropics/commerce-agents, with its venv active
pip install -r requirements.txt # the reference's packages; unregistered on PyPI by design
pip install treeship-sdk
pip install "treeship-commerce @ git+https://github.com/zerkerlabs/treeship.git#subdirectory=integrations/commerce-agents"
curl -fsSL https://treeship.dev/install | sh # the CLI does the signing
treeship initWire it
Open a Treeship session around the commerce session
from treeship_sdk import Treeship
from treeship_commerce.lifecycle import start_session
ts = Treeship()
root = start_session(ts, name="storefront:acme", actor="agent://shopping")root is the session's root artifact id, the parent every tool receipt chains from. start_session and close_session take a cwd; the CLI finds the workspace by walking up from the working directory, so a host that runs elsewhere passes the directory it initialized.
Build the receipted executor class
from treeship_commerce import TreeshipReceipts, receipted
from shopping_agent.executor import ShoppingToolExecutor
ReceiptedShopping = receipted(
ShoppingToolExecutor,
recorder=lambda ex: TreeshipReceipts(
ts,
actor="agent://shopping",
session_id=ex._session.session_id, # tagged, never written
parent_id=root,
),
)receipted puts the mixin first in the MRO. The recorder factory runs once per executor on its first tool call, so each executor, which the reference builds per session, gets its own chain and its own tag. MerchantToolExecutor wraps the same way with actor="agent://merchant".
Hand it to the runtime you use
Messages API (shopping_agent_runtime.ShoppingAgent):
agent = ShoppingAgent(
backend=your_backend,
skills_dir=Path("shopping-agent/skills"),
config=ShoppingAgentConfig(brand_name="Your Store"),
executor_class=ReceiptedShopping,
)Agent SDK (shopping_agent_sdk.ShoppingToolset):
toolset = ShoppingToolset(backend=your_backend, executor_class=ReceiptedShopping)Managed Agents (the storefront MCP server, build_server):
server = build_server(backend=your_backend, config=config, executor_class=ReceiptedShopping)On Managed Agents the executor lives inside your MCP server, one per client connection, so the receipts are written where your backend runs, next to the credentials the model never sees.
Seal the session
from treeship_commerce.lifecycle import close_session
sealed = close_session(ts, summary="14 tool calls, 1 held by the provenance gate, checkout handed off")
sealed["package"] # .../.treeship/sessions/ssn_….treeshiptreeship session report publishes the package to a hub and returns a permanent receipt URL; that step is a deliberate, separate action.
If you construct executors yourself, attach(executor, receipts) works on an instance of a receipted class. It refuses a plain reference executor, because one that silently recorded nothing would be the exact failure this exists to remove.
Verify a session
Offline, from the CLI, against your own trust roots:
treeship verify art_44180341eaeafa79d472115c7c38b03a # the chain head
# ✓ verified (11 artifacts . chain intact)
treeship package verify .treeship/sessions/ssn_a52cd0de94f479d3.treeship
# 18 passed, 0 failed, 1 warnings
# ✓ package verified--format json gives a CI-consumable document: every check by artifact id, chain_linkage_ok, and the outcome. A published session renders at treeship.dev/receipt/<id> with the timeline and the same verifier running in the browser (@treeship/verify, WebAssembly); the hub stores bytes and serves proofs, it never issues a verdict.
The demo's output, from a clean ship with the released 0.27.0 CLI and the SDK from PyPI (the 1 warnings above is the package's always-on note that the narrative fields are not signature-bound; the artifacts and Merkle root are):
tool calls intent-id result-id
search_products ok art_51a9a0284cfe4d7b… art_2c9f6de1d8e7ffe8…
get_product_details ok art_8a25d0564a34df52… art_dcf51c090f1b69f0…
add_to_cart ok art_21639e62f897bcfc… art_21f62db3cbb2f1d8…
add_to_cart blocked:provenance art_57b39c3177d8bf4d… art_e415901189dc1613…
checkout ok art_83f3fd84bebb9ae2… art_45b696204da9e0b9…
session receipts=10 events=6 root_verified=TrueWhat a receipt proves, and what it does not
Proves. That this ship's key signed, at that time, the statement that add_to_cart was about to run with arguments of that digest, and then that the provenance gate held it. That the receipts form an unbroken chain from the session root. That nobody edited any of them after the fact, on any machine, checkable without contacting anyone.
Does not prove. That the tool's answer was correct, that the catalog was truthful, or that the customer got what they wanted. A wrong answer with a perfect receipt is still wrong. Treeship authenticates statements; it does not adjudicate commerce.
Trust boundary. The signing key lives on the machine that runs the executor. That is the right place for a deployment's own record of its agent, and it is a boundary: root on that machine can sign anything. A counterparty who wants to trust these receipts pins your ship key once (treeship keys export prints the line) and verifies against it.
Operating it
- Recording never breaks the agent path. A receipt that cannot be written (CLI missing, ship not initialized, disk full) warns once, the tool runs anyway, and
TreeshipReceipts.droppedcounts it. A result whose intent is missing saysintent_recorded: falseand chains from the previous head; no id is ever invented. TREESHIP_DISABLE=1turns recording off.TREESHIP_DEBUG=1logs every drop instead of the first.- Cost. Two CLI invocations per tool call, run off the event loop in a thread; tens of milliseconds each on a laptop. The timeline event is a third, cheaper append.
- Log hygiene. The receipt's
session_tagis the reference's ownsession_tag(session_id)whencommerce_commonis importable, so your log lines and your receipts correlate on the same twelve hex characters.
What comes next
- Approvals. The merchant
apply_changegate passes only for a change id the host marked approved, a mark set just before the click and cleared just after. The next piece turns that mark into a signed, scoped, single-use Treeship approval:attest approval --allowed-actions apply_change --allowed-subjects <change_id> --max-uses 1, the apply receipt echoing the nonce, and the Approval Use Journal making it unspendable twice. The reference'sStagedChangealready carries the operator principal, so the approver is never invented. - Checkout hand-off.
checkout_handoffreturns a hosted URL the model never sees. Signing the cart digest and the URL digest at that moment, chained to the host's order placement, gives the customer a receipt for exactly the cart that went to checkout. See Payment Proofs for the pattern. - A plugin command in the reference's own marketplace format, alongside its
/add-commerce-flow.
Reference
- Package:
integrations/commerce-agents/; installs from the repository today, PyPI publication follows with the next release - Tests: eight cases on a real ship over the reference's retail mock, run in CI against the reviewed commit of the reference
- Related: Integration overview, Agentic commerce, Payment proofs, Trust model