Metadata-Version: 2.4
Name: zanii
Version: 0.2.0
Summary: Zanii Python SDK — verifiable identity and proof-of-action for AI agents
License-Expression: Apache-2.0
Project-URL: Homepage, https://ledger.zanii.agency
Project-URL: Documentation, https://ledger.zanii.agency/docs
Keywords: ai-agents,identity,transparency-log,merkle,ed25519,audit
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security :: Cryptography
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: cryptography>=42
Provides-Extra: mcp
Requires-Dist: mcp>=1.10; extra == "mcp"
Provides-Extra: runtime
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: httpx>=0.27; extra == "dev"
Requires-Dist: mcp>=1.10; extra == "dev"
Dynamic: license-file

# zanii

**Verifiable identity and proof-of-action for AI agents.** Give every agent a
cryptographic identity (`did:key`), scope what it's allowed to do with signed
delegation certificates, and emit a tamper-proof, hash-chained receipt for every
action it takes — anchored in an RFC 6962 Merkle transparency log. Anyone can
verify what an agent did, offline, without trusting the server that stored it.

Full protocol parity with the TypeScript SDK (`@zanii/core` / `@zanii/sdk`) —
cross-language test vectors guarantee byte-identical hashes and signatures. The
only runtime dependency is [`cryptography`](https://cryptography.io).

```sh
pip install zanii
```

## Quickstart

```python
from zanii import ZaniiAgent, fetch_and_verify_proof
from zanii.core import generate_keypair, create_cert

# 1. Identities. The owner delegates a scoped, expiring capability to the agent.
owner = generate_keypair()
agent = generate_keypair()
cert = create_cert(
    issuer=owner.did,
    subject=agent.did,
    scopes=["crm.*"],                 # this agent may only act within crm.*
    exp="2027-01-01T00:00:00Z",
    issuer_private_key=owner.private_key,
)

# 2. Instrument the agent. Every action is signed and hash-chained locally,
#    then batched to the log.
zanii = ZaniiAgent(
    server_url="https://ledger.zanii.agency",
    agent_did=agent.did,
    agent_private_key=agent.private_key,
    delegation=[cert],
    # api_key="zk_live_...",          # required when the log enforces write auth
)
receipt, receipt_hash = zanii.record(target="crm.lookup", payload={"email": "a@b.co"})
zanii.flush()                         # ship queued receipts to the log

# 3. Anyone can verify that proof — offline, zero trust in the server.
proof = fetch_and_verify_proof("https://ledger.zanii.agency", receipt_hash)
assert proof.ok
```

`wrap_tool` instruments an existing function so every call (sync or async) is
recorded automatically — result on success, error on failure:

```python
lookup = zanii.wrap_tool("crm.lookup", crm.find)   # crm.find is your own function
lookup("a@b.co")                                    # transparently receipted
```

## `zanii.core` — pure verification, no network

Import from `zanii.core` when you only build or verify proofs and never touch the
network (an auditor, a third-party verifier, an offline signer). Everything there
is deterministic and does no I/O; the network client lives on the top-level
`zanii` package.

```python
from zanii.core import verify_audit_bundle

# A self-contained audit bundle (GET /v1/export/{agent_did}) is verified with no
# trusted party: signatures, delegation scope, Merkle inclusion, the per-agent
# hash chain, and on-chain anchor consistency.
report = verify_audit_bundle(bundle)
assert report.ok, [c for c in report.checks if not c["ok"]]
```

Fully typed — ships `py.typed` (PEP 561), so your type-checker sees every
signature.

## MCP proxy

Front any [MCP](https://modelcontextprotocol.io) server so every tool call is
receipted — **no changes to the agent or the upstream server.** Install the
extra:

```sh
pip install "zanii[mcp]"
```

Wrap a connected upstream `ClientSession` with a `ZaniiAgent`; serve the result
in place of the real server. Tool lists pass through unchanged; each call is
recorded as `mcp.<tool>` — result on success, error on failure.

```python
from zanii import ZaniiAgent
from zanii.mcp_proxy import create_zanii_proxy

proxy = create_zanii_proxy(upstream_session, ZaniiAgent(...))
```

Or run it standalone over stdio, wrapping an upstream stdio MCP server:

```sh
ZANII_SERVER=https://ledger.zanii.agency ZANII_IDENTITY=./identity.json \
    python -m zanii.mcp_proxy -- npx some-mcp-server --its-args
```

## Agent runtime — deterministic rails

The SDK above records proofs. `zanii.runtime` (optional) is the layer that *governs
the action itself*: the model **proposes**, tested code **disposes**. It enforces
the accountability rules that sit above the ledger — scoped authority, "no external
receipt → no claim of success", a fixed status vocabulary, manifest validation, and
a human confirmation gate for irreversible actions.

```sh
pip install "zanii[runtime]"   # pure logic, no extra deps — ships with base zanii too
```

```python
from zanii import ZaniiAgent
from zanii.runtime import Runtime, Tool, ToolResult

def send_email(to, subject):
    provider_id = mail.send(to, subject)          # your real integration
    return ToolResult(ok=True, receipt_id=provider_id)   # the provider's receipt

rt = Runtime(ZaniiAgent(...), [
    Tool("email.send", scope="email.*", run=send_email, irreversible=True),
])

d = rt.propose("email.send", {"to": "a@b.co", "subject": "Hi"}, intent="follow up")
# → irreversible ⇒ d.status == "awaiting_confirmation"; nothing sent yet
d = rt.confirm(d.confirmation_id)                 # owner says yes (bound to this exact action)
# → d.status == "sent" (a provider receipt was returned) and it's recorded on the ledger
```

Status is **earned**: no `receipt_id` ⇒ `attempted` (never `sent`); a matched
read-back ⇒ `confirmed`; a thrown tool ⇒ `failed`. Out-of-scope or unknown tools are
rejected before anything runs; low `confidence` returns `clarify` instead of guessing.

## Links

- **Docs & concepts** — https://ledger.zanii.agency/docs
- **Live transparency log** — https://ledger.zanii.agency

## License

Apache-2.0.
