Observed since 20 Oct, 16:54 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Accountable Agentic Execution — protocol SDK for Python
Reference implementation of the AAE protocol in Python.
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.
pip install -e .
Dependencies: pydantic, pyjwt[crypto], python-ulid, rfc8785, jsonschema, PyYAML, cryptography.
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:...
| 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:
aae.policy.PolicyAdapter over your engine of choice (OPA, Cerbos, custom HTTP service).aae.audit.AuditSink over your durable store (Postgres, S3 with object lock, append-only DynamoDB tables).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.
LifecycleClient enforces*_failed or *_aborted event is appended before the exception propagates.context.derived_from).sha256:...). v1.x can introduce BLAKE3 or post-quantum hashes without breaking historical chains. See aae.algorithms.ext..AuditSink.append will return an optional witness URL in v0.3 for external chain-head publication.agent_id on proposals will become an optional agent_chain[] for proposals submitted on behalf of other agents.ext.cost_estimate shape in step previews is reserved for v0.3.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 ..
This SDK targets AAE protocol v0.2. The protocol version is exposed as aae.__protocol_version__. The SDK's own version is aae.__version__.
Dual MIT / Apache-2.0. See repo root.
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.9.0 | 21 Jul 2026 | 16 |
This describes our collected data; it is not a security assessment.
Observed since 20 Oct, 16:54 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:5f6d34934551f3f74facf15351dbbc44fe154968abae3b25bd24303ff91f896f