Metadata-Version: 2.4
Name: aae-protocol
Version: 0.9.0
Summary: Accountable Agentic Execution — protocol SDK for Python
Project-URL: Homepage, https://github.com/r3moteBee/aae
Project-URL: Documentation, https://github.com/r3moteBee/aae/tree/main/docs
Project-URL: Repository, https://github.com/r3moteBee/aae
Project-URL: Issues, https://github.com/r3moteBee/aae/issues
Author-email: Brent Ellis <brent@r3motely.net>
License: MIT OR Apache-2.0
Keywords: aae,agent,ai,audit,governance,policy
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Security
Classifier: Topic :: System :: Systems Administration
Requires-Python: >=3.11
Requires-Dist: cryptography<46,>=42
Requires-Dist: jsonschema<5,>=4.21
Requires-Dist: pydantic<3,>=2.6
Requires-Dist: pyjwt[crypto]<3,>=2.8
Requires-Dist: python-ulid<3,>=2.2
Requires-Dist: pyyaml<7,>=6.0
Requires-Dist: rfc8785<1,>=0.1
Provides-Extra: cerbos
Requires-Dist: httpx<1,>=0.27; extra == 'cerbos'
Provides-Extra: dev
Requires-Dist: mypy<2,>=1.10; extra == 'dev'
Requires-Dist: ruff<1,>=0.4; extra == 'dev'
Requires-Dist: types-jsonschema<5,>=4.21; extra == 'dev'
Requires-Dist: types-pyyaml<7,>=6.0; extra == 'dev'
Provides-Extra: opa
Requires-Dist: httpx<1,>=0.27; extra == 'opa'
Provides-Extra: postgres
Requires-Dist: asyncpg<1,>=0.29; extra == 'postgres'
Provides-Extra: slack
Requires-Dist: httpx<1,>=0.27; extra == 'slack'
Provides-Extra: test
Requires-Dist: asyncpg<1,>=0.29; extra == 'test'
Requires-Dist: httpx<1,>=0.27; extra == 'test'
Requires-Dist: hypothesis<7,>=6; extra == 'test'
Requires-Dist: jsonschema<5,>=4.21; extra == 'test'
Requires-Dist: pytest-asyncio<1,>=0.23; extra == 'test'
Requires-Dist: pytest<9,>=8; extra == 'test'
Description-Content-Type: text/markdown

# aae — Python SDK

Reference implementation of the [AAE protocol](https://github.com/r3moteBee/aae) in Python.

## Status

End-to-end working. The lifecycle orchestrator walks `PROPOSE → PREVIEW → APPROVE → COMMIT → AUDIT` with pluggable adapters for policy, audit, approvals, and capability tokens. All 27 conformance tests pass.

This is the **canonical reference SDK**. When the spec is ambiguous, this implementation's behavior is the answer; clarifications then propagate back into [`docs/v0.2-clarifications.md`](../../docs/v0.2-clarifications.md).

## Install

```bash
pip install -e .
```

Dependencies: pydantic, pyjwt[crypto], python-ulid, rfc8785, jsonschema, PyYAML, cryptography.

## 60-second example

```python
import asyncio
from datetime import datetime, timezone
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from ulid import ULID

from aae import (
    BlastRadius, Context, Decision, Effect, LifecycleClient,
    Proposal, Step, StepPreview,
)
from aae.approvals import AutoGrantApproval
from aae.audit import InMemoryAuditSink
from aae.policy import AllowAllPolicy
from aae.tokens import JwtSigner
from aae.tools import InMemoryToolRegistry, StepResult, Tool


# 1. Define a tool: preview predicts effects, commit executes.
async def echo_preview(step):
    return StepPreview(
        step_index=0,
        predicted_effects=[Effect(type="read_only", target="echo")],
        warnings=[],
    )


async def echo_commit(step, token):
    return StepResult(
        success=True,
        outputs={"echoed": step.args.get("message", "")},
    )


registry = InMemoryToolRegistry()
registry.register(Tool(
    name="echo",
    plan_schema={
        "type": "object",
        "properties": {"message": {"type": "string"}},
        "additionalProperties": False,
    },
    preview_fn=echo_preview,
    commit_fn=echo_commit,
    default_blast_radius=BlastRadius.READ_ONLY,
))

# 2. Build the JWT signer.
priv = Ed25519PrivateKey.generate()
priv_pem = priv.private_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PrivateFormat.PKCS8,
    encryption_algorithm=serialization.NoEncryption(),
)
pub_pem = priv.public_key().public_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PublicFormat.SubjectPublicKeyInfo,
)
signer = JwtSigner(
    active_kid="key-1",
    active_private_key=priv_pem,
    trusted_public_keys={"key-1": pub_pem},
    issuer="my-host",
)

# 3. Wire the lifecycle.
client = LifecycleClient(
    registry=registry,
    policy=AllowAllPolicy(),               # for production: OPA/Cerbos/YamlAllowlistPolicy
    audit=InMemoryAuditSink(),             # for production: JsonlAuditSink or your own
    approvals=AutoGrantApproval(),         # for production: CliApproval or your own delivery
    tokens=signer,
    issuer="my-host",
)

# 4. Submit a proposal.
proposal = Proposal(
    proposal_id=str(ULID()),
    agent_id="my-agent",
    tenant_id="my-tenant",
    intent="say_hello",
    context=Context(rationale="Demo"),
    steps=[
        Step(tool="echo", args={"message": "hi"},
             blast_radius=BlastRadius.READ_ONLY),
    ],
    submitted_at=datetime.now(timezone.utc),
)

result = asyncio.run(client.execute(proposal))
print(result.decision)               # Decision.ALLOW
print(result.step_results[0].outputs)  # {'echoed': 'hi'}
print(result.audit_chain_tip)        # sha256:...
```

## Adapters shipped

| Adapter | Production-ready | Use case |
|---|---|---|
| `aae.policy.AllowAllPolicy` | ❌ tests/dev only | Smoke tests |
| `aae.policy.DenyAllPolicy` | ❌ tests/dev only | Failsafe baseline |
| `aae.policy.YamlAllowlistPolicy` | ⚠️ personal/dev | Single-user, file-based rules |
| `aae.audit.InMemoryAuditSink` | ❌ tests/dev only | Unit tests |
| `aae.audit.JsonlAuditSink` | ✅ single-host | Persistent local log |
| `aae.audit.CompositeAuditSink` | ✅ | Defense in depth (replicate to multiple sinks) |
| `aae.approvals.AutoGrantApproval` | ❌ tests/dev only | Pipeline tests |
| `aae.approvals.AutoDenyApproval` | ❌ tests/dev only | Failsafe tests |
| `aae.approvals.CliApproval` | ⚠️ interactive only | Personal tools, demos |
| `aae.tokens.JwtSigner` | ✅ | EdDSA / ES256 / RS256 capability tokens |

For production:
- **Policy:** implement `aae.policy.PolicyAdapter` over your engine of choice (OPA, Cerbos, custom HTTP service).
- **Audit:** implement `aae.audit.AuditSink` over your durable store (Postgres, S3 with object lock, append-only DynamoDB tables).
- **Approvals:** implement `aae.approvals.ApprovalDelivery` over your notification + decision-collection surface (Slack app, web UI, mobile push).

The `Protocol` types are designed so a custom adapter is typically 50-150 lines.

## Design notes

### What `LifecycleClient` enforces

1. Phases happen in order. The lifecycle never advances without the prior phase emitting its audit event.
2. Audit events for a phase transition are persisted **before** the next phase begins. An interrupted lifecycle leaves a partial-but-verifiable chain.
3. Capability tokens bind to the SHA-256 of canonical approved-steps bytes. The commit phase re-verifies this hash before executing.
4. Approval grants trigger a re-evaluation of policy. A grant is not automatically an allow; policy may have changed during the wait.
5. On any failure, the corresponding `*_failed` or `*_aborted` event is appended before the exception propagates.

### What it does NOT do

- Multitenant routing, agent authentication, approval persistence across host restarts — those are the host application's responsibility.
- Rate limiting, replay protection, network calls — those are the policy engine's or gateway's responsibility.
- Long-running plan deviation — use re-proposal (submit a new proposal with `context.derived_from`).

### Future-ready seams

- Algorithm-agility: hashes carry their algorithm in the value (`sha256:...`). v1.x can introduce BLAKE3 or post-quantum hashes without breaking historical chains. See `aae.algorithms`.
- Extension fields: payloads accept any keys not collision-prone with current/future standardized names. Convention: prefix host-specific fields with `ext.`.
- Federated audit: `AuditSink.append` will return an optional witness URL in v0.3 for external chain-head publication.
- Multi-agent: `agent_id` on proposals will become an optional `agent_chain[]` for proposals submitted on behalf of other agents.
- Cost-aware policy: `ext.cost_estimate` shape in step previews is reserved for v0.3.

## Testing

```bash
cd sdks/python
PYTHONPATH=src python -m pytest tests/
PYTHONPATH=src python -m aae.conformance   # cross-implementation conformance
```

The `aae-conformance` console script runs after `pip install -e .`.

## Versioning

This SDK targets AAE protocol v0.2. The protocol version is exposed as `aae.__protocol_version__`. The SDK's own version is `aae.__version__`.

## License

Dual MIT / Apache-2.0. See repo root.
