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
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.claims-triagewith a name for this agent. Keep it stable: it is how the agent appears on your fleet.SANNING_SIGNING_SEEDwith 64 hex characters. Generate them once withopenssl rand -hex 32and 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.
Or trace a LangChain agent
The sameanchorer, with no anchor() calls of your own. One callback handler
anchors every chain, model, tool and retriever step, plus every file a tool
produces.
- Use the
withblock. Leaving it out anchors what it can and walks past what it cannot, and you find out when an auditor asks.raise_on_gaps=Truestops 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.
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:Hand over a pack
A pack is one zip:bundle.json, plus a logs/ folder holding the bytes you chose to
disclose.
- 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:00Zto select a narrower window. ./logstorewith the root of your log store, the directory holdingcontent/andrecords/.
--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
Givepack.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:
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.