Catalog PyPI
Back to packageRELEASE DETAILS

0.9.0

PyPI

aae-protocol

Accountable Agentic Execution — protocol SDK for Python

aaeagentaiauditgovernancepolicy
Published21 Jul 2026
Direct dependencies16
Transitive dependenciesNot calculated

README

Version 0.9.0 documentation

aae — Python SDK

Reference implementation of the AAE protocol 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.

Install

pip install -e .

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

60-second example

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

AdapterProduction-readyUse case
aae.policy.AllowAllPolicy❌ tests/dev onlySmoke tests
aae.policy.DenyAllPolicy❌ tests/dev onlyFailsafe baseline
aae.policy.YamlAllowlistPolicy⚠️ personal/devSingle-user, file-based rules
aae.audit.InMemoryAuditSink❌ tests/dev onlyUnit tests
aae.audit.JsonlAuditSink✅ single-hostPersistent local log
aae.audit.CompositeAuditSink✅Defense in depth (replicate to multiple sinks)
aae.approvals.AutoGrantApproval❌ tests/dev onlyPipeline tests
aae.approvals.AutoDenyApproval❌ tests/dev onlyFailsafe tests
aae.approvals.CliApproval⚠️ interactive onlyPersonal 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

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.

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Declared dependencies 21

Native requirements
16 direct targetsNot calculated transitive count
Package / targetRequirementRelationship
cryptography
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
cryptography<46,>=42runtime
jsonschema
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
jsonschema<5,>=4.21runtime
pydantic
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
pydantic<3,>=2.6runtime
pyjwt
Environment
{
  "extras": "crypto",
  "marker": null,
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
pyjwt[crypto]<3,>=2.8runtime
python-ulid
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
python-ulid<3,>=2.2runtime
pyyaml
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
pyyaml<7,>=6.0runtime
rfc8785
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
rfc8785<1,>=0.1runtime
httpx
Environment
{
  "extras": "",
  "marker": "extra == \"cerbos\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
httpx<1,>=0.27; extra == "cerbos"runtime
mypy
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
mypy<2,>=1.10; extra == "dev"runtime
ruff
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
ruff<1,>=0.4; extra == "dev"runtime
types-jsonschema
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
types-jsonschema<5,>=4.21; extra == "dev"runtime
types-pyyaml
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
types-pyyaml<7,>=6.0; extra == "dev"runtime
httpx
Environment
{
  "extras": "",
  "marker": "extra == \"opa\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
httpx<1,>=0.27; extra == "opa"runtime
asyncpg
Environment
{
  "extras": "",
  "marker": "extra == \"postgres\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
asyncpg<1,>=0.29; extra == "postgres"runtime
httpx
Environment
{
  "extras": "",
  "marker": "extra == \"slack\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
httpx<1,>=0.27; extra == "slack"runtime
asyncpg
Environment
{
  "extras": "",
  "marker": "extra == \"test\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
asyncpg<1,>=0.29; extra == "test"runtime
httpx
Environment
{
  "extras": "",
  "marker": "extra == \"test\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
httpx<1,>=0.27; extra == "test"runtime
hypothesis
Environment
{
  "extras": "",
  "marker": "extra == \"test\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
hypothesis<7,>=6; extra == "test"runtime
jsonschema
Environment
{
  "extras": "",
  "marker": "extra == \"test\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
jsonschema<5,>=4.21; extra == "test"runtime
pytest-asyncio
Environment
{
  "extras": "",
  "marker": "extra == \"test\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
pytest-asyncio<1,>=0.23; extra == "test"runtime
pytest
Environment
{
  "extras": "",
  "marker": "extra == \"test\"",
  "requires_python": ">=3.11",
  "evaluation": "NOT_EVALUATED"
}
pytest<9,>=8; extra == "test"runtime

Distribution files

2 collected
aae_protocol-0.9.0-py3-none-any.whl

bdist_wheel · 101.39 KB · Python py3

Source file
aae_protocol-0.9.0.tar.gz

sdist · 113.23 KB · Python source

Source file

Repository

PENDING
r3motebee/aae

Shared repository data · GitHub

License detected in repository
Not collected

Metadata history

Meaningful changes
Current collected state Current

Observed since 20 Oct, 16:54 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package_version:294a39d54ad7710bc21eea795504f6540b1ea3e2707bc8abc95ddbe98faa2cd6

Other collected versions

View all collected versions →