Skip to main content
This takes you from an organization on the roster to an evidence pack a third party can check offline. Budget about ten minutes.

Prerequisites

Before you begin, you must have:
  • Python 3.11 or later. A TypeScript path exists and uses the same commands.
  • A terminal.
  • An organization set up on console.sanning.io. Access starts with a conversation with the Sanning team.

Create an API key

In the console, open Settings, API keys and select Create key. Name the key and leave Verify unchecked. Every key can already anchor, which covers everything an agent runtime does: enrolling its signing key on the first anchor (agent:enroll), anchoring (anchor:write), and reading your records back so you can build a pack (anchor:read). Those three travel together, and a key you create carries either them or them plus the two verify scopes. There is nothing to select. See API keys and scopes. Store the key as SANNING_API_KEY. It authenticates your organization. It is not the key that signs your evidence.

Install the SDK

Tracing a LangChain agent? Install the extra instead, and follow the LangChain branch at the next step:

Anchor a step

Your agent holds an Ed25519 signing key. Sanning never receives it, and never receives the content you anchor. Both stay on your machine.
Replace the following:
  • claims-triage with a name for this agent. Keep it stable: it is how the agent appears on your fleet.
  • SANNING_SIGNING_SEED with 64 hex characters. Generate them once with openssl rand -hex 32 and keep them in your secret store. A fresh seed on the next boot reads as an unauthorized identity rotation and fails.
content is hashed locally. The hash is anchored; the bytes are not sent. The SDK enrolls your signing key with your organization before the first anchor, so the agent appears on your fleet without a separate registration step.
environment="production" writes to a permanent public record. Records cannot be withdrawn.

Or trace a LangChain agent

The same anchorer, with no anchor() calls of your own. One callback handler anchors every chain, model, tool and retriever step, plus every file a tool produces.
Everything after this point is identical: the same log store, the same wait for the witness, the same pack. Two things decide whether this is correct, so they sit here rather than in a guide:
  • Use the with block. Leaving it out anchors what it can and walks past what it cannot, and you find out when an auditor asks. raise_on_gaps=True stops the agent on a hole in the trail instead.
  • The whole step is committed, prompts, outputs, tool input and output. It is hashed locally and stays with you.
For the event vocabulary, the promoted fields and worked examples, see Trace a LangChain agent.

Wait for the witness

Sanning commits your records to a public record on an interval, not one transaction per event. That is what keeps it inexpensive: cost tracks intervals, not volume. So a pack does not exist the moment you anchor. Until the interval containing your records is sealed and reaches the chain, the pack command tells you so:
That message means not yet, rather than you have no evidence. Wait a few minutes and run it again.

Hand over a pack

A pack is one zip: bundle.json, plus a logs/ folder holding the bytes you chose to disclose.
Replace the following:
  • The dates with the period you want to hand over. A date-only bound names a whole day, so pass a full timestamp such as 2026-09-01T09:00:00Z to select a narrower window.
  • ./logstore with the root of your log store, the directory holding content/ and records/.
--logs is required in practice. The command reads your record objects to work out which events belong to this agent, because a minimum-disclosure envelope does not carry that name. Without the store it cannot tell your events from anyone else’s. The command reads your records back from the index, fetches each inclusion proof and the stamp that witnessed it, pulls the disclosed bytes from your own store, and verifies the result before writing anything. If the pack does not verify, nothing is written. Once the pack writes, bundle reports it to Sanning with the API key it already holds: your agent, the environment, the window, the record count, the SHA-256 of bundle.json, and the SDK name and version. It never sends the pack itself. Find it later under Evidence in the console. The TypeScript SDK takes the same flags and produces the same bundle.json:

Verify it without Sanning

Give pack.zip to whoever asked. They have two ways to check it, and neither needs an account. In a browser, they open verify.sanning.io and drop the file on the page. The page runs the open kernel in their browser. At a command line:
Same verbs, same flags, same exit codes. Your auditor picks whichever runtime they already have, and needs neither an account nor a network. A verified pack exits 0. A pack with one changed byte exits non-zero and names the record that failed.

What you proved

Your counterparty checked your agent’s evidence using an open-source kernel, against a public record, with no call to Sanning. That is the property the whole design exists to produce.

What Sanning receives

The boundary between what leaves your machine and what does not.