Skip to main content

sanning-anchor

Produce verifiable evidence from your Python agents. Hash locally, anchor a signed commitment, verify offline — with no Sanning account needed to check it.

pip install sanning-anchor

Anchor an event

from nacl.signing import SigningKey
from sanning_anchor import Anchorer

anchorer = Anchorer(
    api_key="sanning_...",
    subject={"type": "producer", "producer_id": "my-agent"},
    signing_key=SigningKey.generate(),
    environment="production",
)

result = anchorer.anchor(
    content=b'{"step": "tool_call", "tool": "search"}',
    event_type="myapp.tool_call",
)

result.record_bytes   # ← RETAIN THESE

What leaves your process, and what does not

Your content never does. It is hashed locally; only the hash goes into the signed envelope. The control plane is content-blind by construction, not by policy — it never receives the bytes.

record_bytes is your retention obligation. It is what payload_hash commits to. Sanning never holds it, so if you lose it you hold a commitment to something you can no longer produce.

What you do not need

No Arweave wallet. No chain identity. No data item. Placement is Sanning's act, which is why this package has no blockchain code in it at all — and why it is a few hundred lines rather than a chain client.

LangChain

pip install "sanning-anchor[langchain]"
from sanning_anchor import AnchorCallbackHandler

with AnchorCallbackHandler(anchorer) as handler:
    agent.invoke(inputs, config={"callbacks": [handler]})

for outcome in handler.results:
    my_store.put(outcome.event_id, outcome.record_bytes)   # ← RETAIN THESE

Every chain, model, tool and retriever step is anchored, with LangChain's run_id/parent_run_id tree committed alongside a per-run seq and prev_event_id. That makes the trail deletion-evident and reorder-evident: a missing event leaves a gap in seq, a moved one breaks the chain.

Prompts, outputs and tool I/O go into the committed record, which stays with you. Pass map_payload to redact before hashing; returning None skips an event without leaving a gap. What you receive there is a copy, so redacting cannot touch your agent's live state.

When a step cannot be anchored

The trail never claims completeness it does not have. If an event cannot be anchored — a network blip, a bad key, a value that has no faithful JSON form — three things happen, and none of them is a log line you have to be watching for:

  • the event still burns its seq slot, so the survivors cannot close ranks over it: an auditor sees a hole in seq and a dangling prev_event_id, offline, with no cooperation from us;
  • its record_bytes are still retained where the record got as far as being signed — after a failure they are the only copy, and a timeout tells you nothing about whether the plane accepted it;
  • leaving the with block raises IncompleteTrailError, naming every gap.

A gap is reported as undelivered, never as a bad record. An anchoring failure is ours, not yours — but it is never reported as fine either.

handler.is_complete          # False if anything was dropped
handler.gaps                 # the outcomes that are not in the anchored trail
handler.close()              # raises IncompleteTrailError; returns the outcomes
AnchorCallbackHandler(anchorer, raise_on_gap=True)   # stop the agent instead
AnchorCallbackHandler(anchorer, on_event=alert)      # stream every outcome

Transient failures (a 5xx, a 429, a transport error) are retried before any of that. A 4xx is not: the plane has told us the envelope is wrong, and repeating it is a slower failure, not a recovery.

Verifying

Use the open kernel, which needs no account and no network:

pip install sanning-proof
from sanning_proof import verify_envelope
verify_envelope(result.envelope, payload_bytes=result.record_bytes)

Byte-identical to the TypeScript SDK

sanning-anchor and @sanning/anchor produce the same signed bytes for the same event. Both are gated against the same pinned conformance corpus, so a pack produced by one verifies identically under the other. This matters more than it sounds: two SDKs disagreeing by one byte would produce evidence that cross-verifies as tampered.

The LangChain adapters commit JSON.stringify(payload), which json.dumps is not (1.01.0 where JavaScript gives 1; NaN and Infinity, which are not JSON at all; integer-like keys unsorted). That serializer has its own cross-language corpus, generated from the real JSON.stringify and asserted from both sides.

Where it deliberately refuses rather than diverges: an integer outside ±(253−1), a set, a reference cycle. JavaScript would round the first one — and two different 64-bit trace ids can round to the same double, which is a false integrity verdict rather than a formatting difference. So it fails at anchor time, when you can still fix it, rather than at verification, when the record is already sealed. Convert the value in map_payload.

MIT licensed.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sanning_anchor-0.1.1.tar.gz (79.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sanning_anchor-0.1.1-py3-none-any.whl (49.3 kB view details)

Uploaded Python 3

File details

Details for the file sanning_anchor-0.1.1.tar.gz.

File metadata

  • Download URL: sanning_anchor-0.1.1.tar.gz
  • Upload date:
  • Size: 79.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for sanning_anchor-0.1.1.tar.gz
Algorithm Hash digest
SHA256 6916f1be93ddace81c432330311cd946e8e4a6b2aa6ac568f6cfee5c1b75482d
MD5 f076acf69f84ab1a5b20d2911746f2c2
BLAKE2b-256 f8caaf39b883ff44c352b0dd3174d0853eb1ecc71c8c0300183b72e5a1b8c174

See more details on using hashes here.

Provenance

The following attestation bundles were made for sanning_anchor-0.1.1.tar.gz:

Publisher: release-python.yml on sanning-io/anchor

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file sanning_anchor-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: sanning_anchor-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 49.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for sanning_anchor-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 15a3ad80bf3d0d79e68b74e8002d145ffbd6a1c4ba6c684da86a586d509df40c
MD5 d9931780f5c357a441d2ea2d813c5599
BLAKE2b-256 26d4e17920fab7c9639d7ddbbc6edc93f6dc5fac2d47ea6e7643f2820fd0c9d9

See more details on using hashes here.

Provenance

The following attestation bundles were made for sanning_anchor-0.1.1-py3-none-any.whl:

Publisher: release-python.yml on sanning-io/anchor

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page