Metadata-Version: 2.5
Name: attestify-sdk
Version: 0.2.0
Summary: Official Python SDK for Attestify OS — governed agent execution, signed authority envelopes, x402-native payments, and immutable receipts. Also ships Attestify Trust: portable identity, signed evidence, and free public verification for agents that never touch a wallet.
Project-URL: Homepage, https://attestifyos.com
Project-URL: Documentation, https://attestifyos.com/docs
Project-URL: Repository, https://github.com/attestifyagent/attestify-sdk
License: MIT
Keywords: agent-evidence,agent-governance,ai-agents,attestify,audit-trail,authority-envelope,compliance,ed25519,fintech,governance,no-wallet,receipts,trust,usdc,x402
Requires-Python: >=3.9
Provides-Extra: dev
Requires-Dist: cryptography>=42.0.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Provides-Extra: trust
Requires-Dist: cryptography>=42.0.0; extra == 'trust'
Description-Content-Type: text/markdown

# attestify-sdk — Python

Add governed lane execution to any Python codebase in a few lines.

## Install

```bash
pip install attestify-sdk
```

Or directly from the repo while the package is in early access:

```bash
pip install "git+https://github.com/attestifyagent/attestify-os.git#subdirectory=sdk/python"
```

## Quick start

```python
import os
from attestify import create_client

client = create_client(api_key=os.environ["ATTESTIFY_API_KEY"])

result = client.run_lane(
    input="Summarise the latest advances in quantum error correction.",
    lane_id="researcher-v2",
    session_id="my-session-001",
    options={"verify": True, "write_memory": True},
)

print(result.output)             # lane response text
print(result.receipt.run_id)     # unique run identifier
print(result.receipt.evidence)   # full EvidenceBundle dataclass
print(result.receipt.pricing)    # cost breakdown
```

## Features

- **`create_client(api_key)`** — configure once, use anywhere
- **`client.run_lane(input, ...)`** — single call handles sessions, retries, idempotency
- **Typed receipts** — `RunReceipt` and `EvidenceBundle` dataclasses with full field coverage
- **Auto-routing** — omit `lane_id` and Attestify picks the best lane for the task
- **Idempotency** — pass `idempotency_key` to guarantee exactly-once execution
- **Budget controls** — pass `constraints={"max_cost_usdc": 0.05}` to cap spend per run
- **Webhook support** — pass `options={"webhook_url": "..."}` for async delivery
- **Zero dependencies** — stdlib only (`urllib`, `json`, `uuid`) for everything above; Attestify Trust's local signing needs the optional `trust` extra (see below)

## API Reference

### `create_client(api_key, base_url?, max_retries?, timeout_s?)`

| Parameter | Type | Default | Description |
|---|---|---|---|
| `api_key` | `str` | **required** | Your Attestify API key (`ATTESTIFY_API_KEY`) |
| `base_url` | `str` | `https://attestifyos.com` | Override the endpoint |
| `max_retries` | `int` | `2` | Retries on transient 5xx errors |
| `timeout_s` | `float` | `60.0` | Per-attempt timeout in seconds |

---

### `client.run_lane(input, ...)`

| Parameter | Type | Description |
|---|---|---|
| `input` | `str` | **Required.** The task or intent |
| `lane_id` | `str` | Optional — omit to auto-route |
| `session_id` | `str` | Memory / conversation continuity ID |
| `idempotency_key` | `str` | Prevents duplicate charges for the same key |
| `context` | `dict` | Freeform context forwarded to the lane |
| `constraints` | `dict` | Budget / SLA constraints (see below) |
| `options` | `dict` | Runtime flags (see below) |

Returns `RunLaneResult(output, receipt, raw)`.

**`constraints` fields:**
```python
constraints={
    "max_cost_usdc": 0.05,    # abort if estimated cost exceeds this
    "max_latency_ms": 10000,  # abort if estimated latency exceeds this
    "budget_id": "proj-abc",  # link to a named budget envelope
}
```

**`options` fields:**
```python
options={
    "verify": True,                    # run output verification (default False)
    "write_memory": True,              # persist session memory (default False)
    "include_memory": True,            # inject prior memory into context
    "webhook_url": "https://...",      # async result delivery
}
```

---

### `client.authorize(...)` — authority envelopes

Request a signed, server-enforced authority envelope before running one or more
governed actions. `run_lane(envelope=...)` attaches it; `/api/run` verifies the
signature, tenant/agent binding, expiry, live-policy epoch, and remaining
action count / spend cap before every run, and atomically consumes one action.

```python
envelope = client.authorize(
    agent_id="analyst-v1",
    intent="batch-report-generation",
    risk_class="low",
    max_spend_usd=0.50,
    action_count=20,     # this envelope authorises up to 20 actions
    expiry_window_s=300, # capped at 3600s server-side
)

result = client.run_lane(
    lane_id="analyst-v1",
    input="Summarise Q2 revenue trends.",
    envelope=envelope,
)
```

The client caches non-Enterprise envelopes locally and reuses them until the
action count or expiry window is exhausted. If the agent's policy has
`per_action_mode` enabled, the server forces `action_count=1` and
`expiry_window_s=0` and the SDK never caches the result — every action gets a
fresh envelope. A stale envelope (policy changed since issue) is rejected with
HTTP 409; call `authorize()` again to obtain a fresh one.

### `client.get_receipt(loop_id)`

Fetch a stored receipt by its `loop_id`.

```python
receipt = client.get_receipt("loop_abc123")
print(receipt.verification)   # grade, score, output_hash
print(receipt.settlement)     # on-chain tx hash if x402 used
```

Returns `RunReceipt`.

---

## Attestify Trust — no wallet, ever

Prove what a wallet-free agent did. `client.trust` is a completely separate
surface from everything above — no lanes, no x402, no gas, at any point.
The private signing key never leaves your process; only a public key and
signatures are ever sent to Attestify.

Needs the `trust` extra, since real Ed25519 signing isn't in Python's
standard library the way it is in Node's built-in `node:crypto`:

```bash
pip install "attestify-sdk[trust]"
```

```python
import os
from attestify import create_client

attestify = create_client(api_key=os.environ["ATTESTIFY_API_KEY"])

# 1. Generate a keypair once, store the private key yourself (env var,
#    secrets manager — the same way you'd hold any other API secret).
keypair = attestify.trust.generate_key_pair()

# 2. Register the agent and its key.
agent = attestify.trust.create_agent(display_name="Invoice Bot")
attestify.trust.register_key(agent.id, keypair.public_key)

# 3. Sign and submit evidence for real work the agent did.
receipt = attestify.trust.submit_evidence(
    agent_id=agent.id,
    schema="work-completion/v1",
    payload={"summary": "Extracted 3 line items from a sample invoice"},
    private_key=keypair.private_key,
    action_basis="discretionary",  # did this on its own initiative, not because it was told to
)

# 4. Anyone can verify it — no API key required.
result = attestify.trust.verify(receipt.id)
print(result.integrity_verified)  # True
```

### `client.trust.generate_key_pair()`

Generates a local Ed25519 keypair. No network call. Returns a
`TrustKeyPair(public_key, private_key)`, both base64url-encoded.

### `client.trust.create_agent(display_name=None, framework=None, industry=None, country=None)`

| Parameter | Type | Description |
|---|---|---|
| `display_name` | `str` | Optional, private — never shown to a verifier |
| `framework` | `str` | e.g. `"langchain"`, `"crewai"` |
| `industry` | `str` | Self-reported |
| `country` | `str` | Self-reported |

Returns `TrustAgent`.

### `client.trust.register_key(agent_id, public_key)`

Registers (or rotates) the Ed25519 signing key for an agent. Returns `TrustKeyVersion`.

### `client.trust.submit_evidence(agent_id, schema, payload, private_key, action_basis="explicit", nonce=None)`

| Parameter | Type | Description |
|---|---|---|
| `agent_id` | `str` | **Required** |
| `schema` | `str` | **Required** — e.g. `"work-completion/v1"` |
| `payload` | `dict` | **Required** — bounded to 16KB |
| `private_key` | `str` | **Required** — from `generate_key_pair()` |
| `action_basis` | `"explicit" \| "discretionary"` | Default `"explicit"` |
| `nonce` | `str` | Default: a random value — override only for a specific replay-defence need |

Canonicalizes and signs the event locally with `private_key`, then submits
it. Returns `TrustReceipt` — an immutable, signed record with a content
hash, not the raw evidence.

### `client.trust.verify(receipt_id)`

Public, no API key sent. Independently recomputes the receipt's hash and
re-verifies the signature server-side on every call — not just an echo of
what's stored. Returns `TrustVerifyResult(receipt, integrity_verified)`.

### `client.trust.get_receipt(receipt_id)`

Your own tenant's full receipt detail (requires the API key that created
it). Use `verify()` instead for the public, redacted view anyone can check.

---

### `RunReceipt` fields

| Field | Type | Description |
|---|---|---|
| `run_id` | `str` | Unique run identifier |
| `loop_id` | `str` | Persistent loop/session receipt ID |
| `lane_id` | `str` | Lane that handled the run |
| `lane_name` | `str` | Human-readable lane name |
| `output` | `str` | Lane response text |
| `paid` | `bool` | Whether x402 payment was used |
| `subscription_used` | `bool` | Whether an API key subscription covered this run |
| `evidence` | `EvidenceBundle` | Full governance evidence bundle |
| `verification` | `dict \| None` | Verification grade, score, output hash |
| `pricing` | `dict` | Cost breakdown in USDC |
| `settlement` | `dict \| None` | On-chain settlement details |
| `receipt_url` | `str` | Shareable receipt permalink |

---

## Available lanes

| `lane_id` | Description |
|---|---|
| `researcher-v2` | Deep research and synthesis |
| `analyst-v1` | Data analysis and structured output |
| `coder-v1` | Code generation and review |
| `writer-v1` | Long-form and structured writing |
| `strategist-v1` | Strategic planning and frameworks |
| `support-v1` | Customer support and triage |
| `comedian-v1` | Creative and entertainment tasks |

Omit `lane_id` entirely to let Attestify auto-route based on your `input`.

## Environment variables

```bash
ATTESTIFY_API_KEY=atst_live_...
```

## Async usage

The client is synchronous by design (zero dependencies). For async workflows:

```python
import asyncio
from attestify import create_client

client = create_client(api_key=os.environ["ATTESTIFY_API_KEY"])

async def main():
    result = await asyncio.to_thread(
        client.run_lane,
        input="Analyse Q2 revenue trends",
        lane_id="analyst-v1",
    )
    print(result.output)

asyncio.run(main())
```
