Quick Start
This guide walks you through emitting and verifying your first Agent Receipt with the Python, TypeScript, or Go SDK.
Agent Receipts are signed by a separate obsigna-daemon process, not by your
application. Your code sends tool-call events over a local Unix socket; the daemon
holds the Ed25519 signing key, builds the receipt, signs it, and appends it to the
hash-chained store. The signing key never enters your process — so the audit trail
holds up even if the agent is compromised. This is the canonical deployment shape
(ADR-0022),
and it is the first thing you should reach for.
Each SDK section below follows the same shape: Install → Start the daemon → Emit a receipt → Verify. Pick your language and work top to bottom.
Python
Section titled “Python”1. Install
Section titled “1. Install”Install obsigna-daemon (see Daemon Setup
for your platform), then the Python SDK:
pip install obsigna2. Start the daemon
Section titled “2. Start the daemon”Generate the signing key once, then run the daemon. It listens on the per-OS default
socket — see Daemon Setup for socket paths (on Linux
without XDG_RUNTIME_DIR, that’s under /run) and service installation.
obsigna-daemon --init # one-time: creates the signing keyobsigna-daemon # start the daemon (leave it running)3. Emit a receipt
Section titled “3. Emit a receipt”DaemonEmitter forwards the tool-call event to the daemon, which constructs, signs,
and chains the receipt. It is fire-and-forget — start the daemon before your app.
from obsigna import DaemonEmitter
with DaemonEmitter() as e: # uses AGENTRECEIPTS_SOCKET or the per-OS default e.emit( channel="my-app", tool_name="filesystem.file.read", decision="allowed", input='{"path": "/etc/hosts"}', # raw JSON; the daemon hashes it (and HPKE-encrypts it when parameter disclosure is on) )4. Verify
Section titled “4. Verify”obsigna receipt verify reads the database directly and confirms hash linkage and
signatures — the daemon does not need to be running.
obsigna receipt verify # verifies today's UTC-dated chain in the store at $XDG_DATA_HOME (falls back to ~/.local/share)A successful run prints the chain length and confirms the signatures are intact. If you
started the daemon with a non-default chain id (AGENTRECEIPTS_CHAIN_ID / --chain-id)
or overrode AGENTRECEIPTS_DB or AGENTRECEIPTS_PUBLIC_KEY, pass those same values
here — otherwise verify reads today’s UTC-dated chain at the default paths.
TypeScript
Section titled “TypeScript”1. Install
Section titled “1. Install”Install obsigna-daemon (see Daemon Setup
for your platform), then the TypeScript SDK:
npm install @obsigna/sdk-ts2. Start the daemon
Section titled “2. Start the daemon”Generate the signing key once, then run the daemon. It listens on the per-OS default
socket — see Daemon Setup for socket paths (on Linux
without XDG_RUNTIME_DIR, that’s under /run) and service installation.
obsigna-daemon --init # one-time: creates the signing keyobsigna-daemon # start the daemon (leave it running)3. Emit a receipt
Section titled “3. Emit a receipt”DaemonEmitter forwards the tool-call event to the daemon, which constructs, signs,
and chains the receipt. It is fire-and-forget — start the daemon before your app.
import { DaemonEmitter } from "@obsigna/sdk-ts";
async function main() { const e = new DaemonEmitter(); // uses AGENTRECEIPTS_SOCKET or the per-OS default try { // emit() returns an Error for caller-bug events (bad shape), null otherwise const err = await e.emit({ channel: "my-app", tool: { name: "filesystem.file.read" }, decision: "allowed", input: JSON.stringify({ path: "/etc/hosts" }), // raw JSON; hashed by the daemon (and HPKE-encrypted when parameter disclosure is on) }); if (err) throw err; } finally { e.close(); }}
main().catch((err) => { console.error(err); process.exitCode = 1;});4. Verify
Section titled “4. Verify”obsigna receipt verify reads the database directly and confirms hash linkage and
signatures — the daemon does not need to be running.
obsigna receipt verify # verifies today's UTC-dated chain in the store at $XDG_DATA_HOME (falls back to ~/.local/share)A successful run prints the chain length and confirms the signatures are intact. If you
started the daemon with a non-default chain id (AGENTRECEIPTS_CHAIN_ID / --chain-id)
or overrode AGENTRECEIPTS_DB or AGENTRECEIPTS_PUBLIC_KEY, pass those same values
here — otherwise verify reads today’s UTC-dated chain at the default paths.
1. Install
Section titled “1. Install”Install obsigna-daemon (see Daemon Setup
for your platform), then add the Go SDK to a module:
go mod init example.com/myapp # in a fresh directory, if you don't already have a modulego get github.com/agent-receipts/ar/sdk/go/emitter2. Start the daemon
Section titled “2. Start the daemon”Generate the signing key once, then run the daemon. It listens on the per-OS default
socket — see Daemon Setup for socket paths (on Linux
without XDG_RUNTIME_DIR, that’s under /run) and service installation.
obsigna-daemon --init # one-time: creates the signing keyobsigna-daemon # start the daemon (leave it running)3. Emit a receipt
Section titled “3. Emit a receipt”NewDaemon returns an emitter that forwards the tool-call event to the daemon, which
constructs, signs, and chains the receipt. It is fire-and-forget — start the daemon
before your app.
package main
import ( "context" "encoding/json" "log"
"github.com/agent-receipts/ar/sdk/go/emitter")
func main() { e, err := emitter.NewDaemon() // uses AGENTRECEIPTS_SOCKET or the per-OS default if err != nil { log.Fatal(err) } defer func() { _ = e.Close() }()
if err := e.Emit(context.Background(), emitter.Event{ Channel: "my-app", Tool: emitter.Tool{Name: "filesystem.file.read"}, Decision: "allowed", Input: json.RawMessage(`{"path":"/etc/hosts"}`), // raw JSON; hashed by the daemon (and HPKE-encrypted when parameter disclosure is on) }); err != nil { log.Fatal(err) }}Run it:
go run .4. Verify
Section titled “4. Verify”obsigna receipt verify reads the database directly and confirms hash linkage and
signatures — the daemon does not need to be running.
obsigna receipt verify # verifies today's UTC-dated chain in the store at $XDG_DATA_HOME (falls back to ~/.local/share)A successful run prints the chain length and confirms the signatures are intact. If you
started the daemon with a non-default chain id (AGENTRECEIPTS_CHAIN_ID / --chain-id)
or overrode AGENTRECEIPTS_DB or AGENTRECEIPTS_PUBLIC_KEY, pass those same values
here — otherwise verify reads today’s UTC-dated chain at the default paths.
Appendix: in-process signing (tutorial and testing only)
Section titled “Appendix: in-process signing (tutorial and testing only)”The SDKs can also create and sign a receipt entirely in your process, with no daemon. This is useful for learning the receipt API and for unit tests that should not depend on a running daemon.
from obsigna import ( CreateReceiptInput, create_receipt, generate_key_pair, sign_receipt, verify_receipt,)
keys = generate_key_pair()
unsigned = create_receipt( CreateReceiptInput( issuer={"id": "did:agent:my-agent"}, principal={"id": "did:user:alice"}, action={ "type": "filesystem.file.read", "risk_level": "low", "target": {"system": "local", "resource": "/docs/report.md"}, }, outcome={"status": "success"}, chain={ "sequence": 1, "previous_receipt_hash": None, "chain_id": "chain_session-1", }, ))
receipt = sign_receipt(unsigned, keys.private_key, "did:agent:my-agent#key-1")
valid = verify_receipt(receipt, keys.public_key)print(f"Signature valid: {valid}") # TrueThe equivalent TypeScript and Go signing APIs are documented in each SDK README (TypeScript, Go). The same “not for production” caveat applies to all of them.
Next steps
Section titled “Next steps”- Read the Introduction for background on the protocol
- Follow Daemon Setup for install, socket paths, and running the daemon as a service
- See the Agent Receipt Schema for the full receipt structure
- Explore the Action Taxonomy to understand action types and risk levels
- Set up
obsigna-hookto capture native tool calls — see Hook: Claude Code