Trust infrastructure for AI agents — constitution enforcement, cryptographic receipts, MCP governance gateway
Project description
Sanna — Trust Infrastructure for AI Agents
Sanna checks reasoning during execution, halts when constraints are violated, and generates portable cryptographic receipts proving governance was enforced. Constitution-as-code: your governance rules live in version-controlled YAML, not in a vendor dashboard.
Quick Start — Library Mode
pip install sanna
Set up governance (one-time):
sanna init # Choose template, set agent name, enforcement level
sanna keygen # Generate Ed25519 keypair (~/.sanna/keys/)
# Output:
# Generated Ed25519 keypair (a1b2c3d4e5f6...)
# Private key: /Users/you/.sanna/keys/a1b2c3d4e5f6...7890.key
# Public key: /Users/you/.sanna/keys/a1b2c3d4e5f6...7890.pub
sanna sign constitution.yaml --private-key ~/.sanna/keys/<key-id>.key
Now wrap the functions you want to govern. @sanna_observe decorates the functions you choose — internal reasoning, prompt construction, and non-governed function calls produce no receipts.
from sanna import sanna_observe, SannaHaltError
@sanna_observe(
constitution_path="constitution.yaml",
constitution_public_key_path="~/.sanna/keys/<key-id>.pub", # from sanna keygen above
)
def my_agent(query: str, context: str) -> str:
return "Based on the data, revenue grew 12% year-over-year."
# @sanna_observe wraps the return value in a SannaResult with .output and .receipt.
# The original str return is available as result.output.
try:
result = my_agent(
query="What was revenue growth?",
context="Annual report: revenue increased 12% YoY to $4.2B."
)
print(result.output) # The original str return value
print(result.receipt) # Cryptographic governance receipt (dict)
# To persist receipts, use ReceiptStore separately:
# from sanna import ReceiptStore
# store = ReceiptStore(".sanna/receipts.db")
# store.store(result.receipt)
except SannaHaltError as e:
print(f"HALTED: {e}") # Constitution violation detected
Quick Start — Gateway Mode
No code changes to your agent. The gateway sits between your MCP client and downstream servers.
pip install sanna[mcp]
sanna init # Creates constitution.yaml + gateway.yaml
sanna keygen --label gateway
sanna sign constitution.yaml --private-key ~/.sanna/keys/<key-id>.key
sanna gateway --config gateway.yaml
Minimum gateway.yaml:
gateway:
constitution: ./constitution.yaml
signing_key: ~/.sanna/keys/<gateway-key-id>.key # Key generated by sanna keygen
constitution_public_key: ~/.sanna/keys/<author-key-id>.pub # Public key of constitution signer
receipt_store: .sanna/receipts/
downstream:
- name: notion
command: npx
args: ["-y", "@notionhq/notion-mcp-server"]
env:
OPENAPI_MCP_HEADERS: "${OPENAPI_MCP_HEADERS}"
default_policy: can_execute
Point your MCP client (Claude Desktop, Claude Code, Cursor) at the gateway instead of directly at your downstream servers. Every tool call is now governed. The gateway governs tool calls that pass through it — only actions that cross the governance boundary produce receipts. Reasoning is captured via the explicit _justification parameter in tool calls, not from internal model reasoning. The gateway cannot observe LLM chain-of-thought.
MCP Client (Claude Desktop / Claude Code / Cursor)
|
v (MCP stdio)
sanna-gateway
| 1. Receive tool call
| 2. Evaluate against constitution
| 3. Enforce policy (allow / escalate / deny)
| 4. Generate signed receipt
| 5. Forward to downstream (if allowed)
v (MCP stdio)
Downstream MCP Servers (Notion, GitHub, filesystem, etc.)
Demo
Run a self-contained governance demo — no external dependencies:
sanna demo
This generates keys, creates a constitution, simulates a governed tool call, generates a receipt, and verifies it.
Core Concepts
Constitution — YAML document defining what the agent can, cannot, and must escalate. Ed25519-signed. Modification after signing is detected on load. Constitution signing (via sanna sign) is required for enforcement. Constitution approval is an optional additional governance step for multi-party review workflows.
Receipt — JSON artifact binding inputs, reasoning, action, and check results into a cryptographically signed, schema-validated, deterministically fingerprinted record. Receipts are generated per governed action — when an agent calls a tool or executes a decorated function — not per conversational turn. An agent that reasons for twenty turns and executes one action produces one receipt.
Coherence Checks (C1-C5) — Five built-in deterministic heuristics. No API calls or external dependencies.
| Check | Invariant | What it catches |
|---|---|---|
| C1 | INV_NO_FABRICATION |
Output contradicts provided context |
| C2 | INV_MARK_INFERENCE |
Definitive claims without hedging |
| C3 | INV_NO_FALSE_CERTAINTY |
Confidence exceeding evidence strength |
| C4 | INV_PRESERVE_TENSION |
Conflicting information collapsed |
| C5 | INV_NO_PREMATURE_COMPRESSION |
Complex input reduced to single sentence |
Authority Boundaries — cannot_execute (deny, checked first, tool names only), must_escalate (prompt user, checked second, matches full action context including parameters), can_execute (allow, checked third, tool names only). A tool in can_execute is still subject to must_escalate conditions. Policy cascade: per-tool override > server default > constitution.
Key Management — Public keys are stored in ~/.sanna/keys/ and referenced by their key ID (SHA-256 fingerprint of the public key). For verification, pass the public key path explicitly via --public-key on the CLI or constitution_public_key_path in code. See docs/key-management.md for key roles and rotation.
Receipt Format
Every governed action produces a reasoning receipt — a JSON artifact that cryptographically binds inputs, outputs, check results, and constitution provenance. See spec/sanna-specification-v1.0.md for the full specification.
Identification
| Field | Type | Description |
|---|---|---|
spec_version |
string | Schema version, "1.0" |
tool_version |
string | Package version, e.g. "0.13.7" |
checks_version |
string | Check algorithm version, e.g. "5" |
receipt_id |
string | UUID v4 unique identifier |
correlation_id |
string | Path-prefixed identifier for grouping related receipts |
Integrity
| Field | Type | Description |
|---|---|---|
receipt_fingerprint |
string | 16-hex SHA-256 truncation for compact display |
full_fingerprint |
string | 64-hex SHA-256 of all fingerprinted fields |
context_hash |
string | 64-hex SHA-256 of canonical inputs |
output_hash |
string | 64-hex SHA-256 of canonical outputs |
Content
| Field | Type | Description |
|---|---|---|
timestamp |
string | ISO 8601 timestamp |
inputs |
object | Dictionary of function arguments passed to the decorated function (e.g., query, context) |
outputs |
object | Contains response |
Governance
| Field | Type | Description |
|---|---|---|
checks |
array | List of CheckResult objects with check_id, passed, severity, evidence |
checks_passed |
integer | Count of checks that passed |
checks_failed |
integer | Count of checks that failed |
status |
string | "PASS" / "WARN" / "FAIL" / "PARTIAL" |
constitution_ref |
object | Contains document_id, policy_hash, version, source, signature_verified, constitution_approval |
enforcement |
object or null | Contains action, reason, failed_checks, enforcement_mode, timestamp when enforcement triggered |
evaluation_coverage |
object | Contains total_invariants, evaluated, not_checked, coverage_basis_points |
Receipt Triad (Gateway)
| Field | Type | Description |
|---|---|---|
input_hash |
string | 64-hex SHA-256, present in gateway receipts |
reasoning_hash |
string | 64-hex SHA-256 of reasoning content |
action_hash |
string | 64-hex SHA-256 of action content |
assurance |
string | "full" or "partial" |
Identity and Signature
| Field | Type | Description |
|---|---|---|
receipt_signature |
object | Contains value, key_id, signed_by, signed_at, scheme |
identity_verification |
object or null | Verification results for identity claims, when present |
Extensions
| Field | Type | Description |
|---|---|---|
extensions |
object | Reverse-domain namespaced metadata (com.sanna.gateway, com.sanna.middleware) |
This section provides a high-level overview. For a complete field reference and normative format details, see spec/sanna-specification-v1.0.md.
Minimal example receipt (abbreviated -- production receipts typically contain 3-7 checks):
{
"spec_version": "1.0",
"tool_version": "0.13.7",
"checks_version": "5",
"receipt_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"receipt_fingerprint": "7b4d06e836514eef",
"full_fingerprint": "7b4d06e836514eef26ab96f5c62b193d036c92b45d966ef7025d75539ff93aca",
"correlation_id": "sanna-my-agent-1708128000",
"timestamp": "2026-02-17T00:00:00+00:00",
"inputs": {"query": "refund policy", "context": "All sales are final."},
"outputs": {"response": "Unfortunately, all sales are final per our policy."},
"context_hash": "...(64 hex)...",
"output_hash": "...(64 hex)...",
"checks": [
{"check_id": "C1", "name": "Context Contradiction", "passed": true, "severity": "info"}
],
"checks_passed": 1,
"checks_failed": 0,
"status": "PASS",
"constitution_ref": {"document_id": "support-agent/1.0", "policy_hash": "...", "signature_verified": true},
"enforcement": null
}
Constitution Format
Constitutions are YAML documents that define an agent's governance boundaries. They are version-controlled, cryptographically signed (and optionally approved) before enforcement.
sanna_constitution: "1.1"
identity:
agent_name: support-agent
domain: customer-support
description: Handles refund and billing inquiries
provenance:
authored_by: governance-team
approved_by: vp-risk
approval_date: "2026-01-15"
boundaries:
- id: B1
description: Only answer questions about products in the catalog
category: scope
severity: critical
- id: B2
description: Never promise refunds outside the 30-day window
category: policy
severity: critical
invariants:
- id: INV_NO_FABRICATION
rule: Never state facts not grounded in provided context
enforcement: critical
- id: INV_MARK_INFERENCE
rule: Clearly mark any inference or assumption
enforcement: warning
- id: INV_NO_FALSE_CERTAINTY
rule: Do not express certainty beyond what evidence supports
enforcement: warning
- id: INV_PRESERVE_TENSION
rule: When context contains conflicting rules, surface both
enforcement: warning
- id: INV_NO_PREMATURE_COMPRESSION
rule: Do not over-summarize multi-faceted context
enforcement: warning
authority_boundaries:
cannot_execute: # checked FIRST — tool names only
- Delete customer accounts
- Access payment credentials
must_escalate: # checked SECOND — matches tool name + parameters
- Issue refund over $500
- Override account restrictions
can_execute: # checked THIRD — tool names only
- Look up order status
- Search knowledge base
escalation_targets:
- condition: "refund over limit"
target:
type: webhook
url: https://ops.example.com/escalate
reasoning:
require_justification: true
assurance_level: full
Custom Evaluators
Register domain-specific invariant evaluators alongside the built-in C1-C5 checks:
from sanna.evaluators import register_invariant_evaluator
from sanna.receipt import CheckResult
@register_invariant_evaluator("INV_PII_CHECK")
def pii_check(query, context, output, **kwargs):
"""Flag outputs containing email addresses."""
import re
has_pii = bool(re.search(r'\b[\w.+-]+@[\w-]+\.[\w.]+\b', output))
return CheckResult(
check_id="INV_PII_CHECK",
name="PII Detection",
passed=not has_pii,
severity="high",
evidence="Email address detected in output" if has_pii else "",
)
Add the invariant to your constitution and it runs alongside C1-C5 automatically.
Receipt Querying
from sanna import ReceiptStore
store = ReceiptStore(".sanna/receipts.db")
# Query with filters
receipts = store.query(agent_id="support-agent", status="FAIL", limit=10)
# Drift analysis
from sanna import DriftAnalyzer
analyzer = DriftAnalyzer(store)
report = analyzer.analyze(window_days=30, threshold=0.15)
Or via CLI:
sanna drift-report --db .sanna/receipts.db --window 30 --json
Constitution Templates
sanna init offers three interactive templates plus blank:
| Template | Use Case |
|---|---|
| Enterprise IT | Strict enforcement, ServiceNow-style compliance |
| Customer-Facing | Standard enforcement, Salesforce-style support agents |
| General Purpose | Advisory enforcement, starter template |
| Blank | Empty constitution for custom configuration |
Five additional gateway-oriented templates are available in examples/constitutions/. Each includes inline documentation explaining the authority boundary evaluation order and common mistakes:
| Template | Use Case |
|---|---|
openclaw-personal |
Individual agents on personal machines |
openclaw-developer |
Skill builders for marketplace distribution |
cowork-personal |
Knowledge workers with Claude Desktop |
cowork-team |
Small teams sharing governance via Git (each dev runs own gateway) |
claude-code-standard |
Developers with Claude Code + MCP connectors |
CLI Reference
All commands are available as sanna <command> or sanna-<command>:
| Command | Description |
|---|---|
sanna init |
Interactive constitution generator with template selection |
sanna keygen |
Generate Ed25519 keypair (--label for human-readable name) |
sanna sign |
Sign a constitution with Ed25519 |
sanna verify |
Verify receipt integrity, signature, and provenance chain |
sanna verify-constitution |
Verify constitution signature |
sanna approve |
Approve a signed constitution |
sanna demo |
Run self-contained governance demo |
sanna inspect |
Pretty-print receipt contents |
sanna check-config |
Validate gateway config (dry-run) |
sanna gateway |
Start MCP enforcement proxy |
sanna mcp |
Start MCP server (7 tools, stdio transport) |
sanna diff |
Diff two constitutions (text/JSON/markdown) |
sanna drift-report |
Fleet governance drift report |
sanna bundle-create |
Create evidence bundle zip |
sanna bundle-verify |
Verify evidence bundle (7-step) |
sanna generate |
Generate receipt from trace-data JSON |
API Reference
The top-level sanna package exports 10 names:
from sanna import (
__version__, # Package version string
sanna_observe, # Decorator: governance wrapper for agent functions
SannaResult, # Return type from @sanna_observe-wrapped functions
SannaHaltError, # Raised when a halt-enforcement invariant fails
generate_receipt, # Generate a receipt from trace data
SannaReceipt, # Receipt dataclass
verify_receipt, # Offline receipt verification
VerificationResult, # Verification result dataclass
ReceiptStore, # SQLite-backed receipt persistence
DriftAnalyzer, # Per-agent failure-rate trending
)
Everything else imports from submodules: sanna.constitution, sanna.crypto, sanna.enforcement, sanna.evaluators, sanna.verify, sanna.bundle, sanna.hashing, sanna.drift.
Verification
Verification proves four properties:
- Schema validation: Receipt structure matches the expected format.
- Hash verification: Content hashes match the actual inputs and outputs (tamper detection).
- Signature verification: Receipt was signed by a known key (authenticity).
- Chain verification: Constitution was signed, and any approvals are cryptographically bound.
# Verify receipt integrity
sanna verify receipt.json
# Verify with signature check
sanna verify receipt.json --public-key <key-id>.pub
# Full chain: receipt + constitution + approval
sanna verify receipt.json \
--constitution constitution.yaml \
--constitution-public-key <key-id>.pub
# Evidence bundle (self-contained zip)
sanna bundle-create \
--receipt receipt.json \
--constitution constitution.yaml \
--public-key <key-id>.pub \
--output evidence.zip
sanna bundle-verify evidence.zip
No network. No API keys. No vendor dependency.
Enterprise Features
- DMARC-style adoption: Start with
logenforcement (observe), move towarn(escalate), thenhalt(enforce). - Ed25519 cryptographic signatures: Constitutions, receipts, and approval records are independently signed and verifiable.
- Offline verification: No platform dependency. Verify receipts with a public key and the CLI.
- Evidence bundles: Self-contained zip archives with receipt, constitution, and public keys for auditors.
- Drift analytics: Per-agent failure-rate trending with linear regression and breach projection. See docs/drift-reports.md.
- Receipt Triad: Cryptographic binding of input, reasoning, and action for auditability. See docs/reasoning-receipts.md.
- Receipt queries: SQL recipes, MCP query tool. See docs/receipt-queries.md.
- Key management: SHA-256 key fingerprints, labeled keypairs. See docs/key-management.md.
- Production deployment: Docker, logging, retention, failure modes. See docs/production.md.
- Gateway configuration: Full config reference. See docs/gateway-config.md.
Security
- Ed25519 cryptographic signatures: Constitutions, receipts, and approval records are independently signed and verifiable offline.
- Prompt injection isolation: Evaluator prompts use trust separation -- trusted policy rules are isolated from untrusted agent content to mitigate prompt injection risks through trust separation and input escaping. Untrusted content is wrapped in
<audit>tags with XML entity escaping. - Atomic file writes: All file operations use symlink-protected atomic writes (
O_NOFOLLOW,O_EXCL,fsync,os.replace()). - SQLite hardening: Receipt stores validate file ownership, enforce 0o600 permissions, and reject symlinks.
- Signature structure validation: Enforcement points validate Ed25519 base64 encoding and 64-byte signature length, rejecting whitespace, junk, and placeholder strings.
Cryptographic Design
- Signing: Ed25519 over canonical JSON (RFC 8785-style deterministic serialization)
- Hashing: SHA-256 for all content hashes, fingerprints, and key IDs
- Canonicalization: Sorted keys, NFC Unicode normalization, integer-only numerics (no floats in signed content)
- Fingerprinting: Pipe-delimited fields hashed with SHA-256; 16-hex truncation for display, 64-hex for full fingerprint
See the specification for full cryptographic construction details.
Threat Model
Defends against:
- Tampering with stored receipts (detected via fingerprint and signature verification)
- Unverifiable governance claims (receipts are cryptographically signed attestations)
- Substitution of receipts across contexts (receipts are cryptographically bound to specific inputs, outputs, and correlation IDs; verifiers should enforce timestamp and correlation expectations)
- Unauthorized tool execution (constitution enforcement blocks or escalates disallowed actions)
Does not defend against:
- Compromised runtime environment (if the host is compromised, all bets are off)
- Stolen signing keys (key compromise requires re-keying and re-signing)
- Bypassing Sanna entirely (governance only applies to functions decorated with
@sanna_observeor tool calls routed through the gateway) - Malicious constitutions (Sanna enforces the constitution as written; it does not validate whether the constitution itself is correct or sufficient)
Limitations
Receipts are attestations of process, not guarantees of outcome.
- Receipts do not prove internal reasoning was truthful -- they prove that checks were run against the output
- Receipts do not prove upstream input was complete or accurate
- Receipts do not protect against a compromised host or stolen signing keys
- Receipts do not prove the constitution itself was correct or sufficient for the use case
- Heuristic checks (C1-C5) are deterministic but not exhaustive -- they catch common failure modes, not all possible failures
Observability (OpenTelemetry)
Sanna can emit OpenTelemetry signals to correlate governed actions with receipts on disk. Receipts are the canonical audit artifact — telemetry is optional and intended for dashboards, alerts, and correlation.
pip install "sanna[otel]"
See docs/otel-integration.md for configuration and signal reference.
Install
pip install sanna # Core library (Python 3.10+)
pip install sanna[mcp] # MCP server + gateway
pip install sanna[otel] # OpenTelemetry bridge
Development
git clone https://github.com/sanna-ai/sanna.git
cd sanna
pip install -e ".[dev]"
python -m pytest tests/ -q
License
AGPL-3.0
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sanna-0.13.7.tar.gz.
File metadata
- Download URL: sanna-0.13.7.tar.gz
- Upload date:
- Size: 488.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
79e2c09aa87ad92b328dec14f8f8d367ec0b7fbb90be5441155df055c4adf67a
|
|
| MD5 |
9f76a8ff9751aa33f0acd9ca34708dca
|
|
| BLAKE2b-256 |
cb8796b7eaa361e86303f1a8e554a09be017599758cad607791ac65ad6df7ddd
|
File details
Details for the file sanna-0.13.7-py3-none-any.whl.
File metadata
- Download URL: sanna-0.13.7-py3-none-any.whl
- Upload date:
- Size: 247.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8f69a0e7910a3cbaf182d72492e4d0ac6d51b26885b0180f71137d4df8324051
|
|
| MD5 |
8fb96829bbde4b69647afc045092b21d
|
|
| BLAKE2b-256 |
85572e88d7d83bb0512711f941aa3cc9e91e6123ad8094d71fda21fdd41c9fda
|