12 KiB
Master Prompt: ACT Reference Implementation
Context
You are implementing the reference implementation for the Agent Compact
Token (ACT), as defined in draft-nennemann-act-00. The full draft is
attached or provided in context. Your implementation must be a clean,
well-documented Python package that serves as the normative reference
for the specification — meaning it is the ground truth for
interoperability testing.
The ACS (Agent Compliance Seal) reference implementation (~1,900 lines, Python) exists as a prior art reference for code style and structure. ACT follows the same philosophy: minimal dependencies, sub-millisecond performance for the hot path, Ed25519 as the primary algorithm.
Deliverables
Produce the following files in a single act/ package directory:
act/
├── __init__.py # Public API exports
├── token.py # ACTMandate and ACTRecord dataclasses + serialization
├── crypto.py # Key management: Tier 1 (pre-shared), Tier 2 (PKI),
│ # Tier 3 (DID:key, DID:web); sign/verify primitives
├── lifecycle.py # Phase 1 → Phase 2 transition logic (re-signing)
├── delegation.py # Delegation chain construction and verification
├── dag.py # DAG validation (uniqueness, parent existence,
│ # temporal ordering, acyclicity, capability
│ # consistency)
├── ledger.py # In-memory append-only audit ledger (for testing;
│ # interface suitable for external backends)
├── verify.py # Unified verification entry point (Phase 1 + Phase 2)
├── errors.py # All ACT-specific exception types
└── vectors.py # Generates and validates all Appendix B test vectors
tests/
├── test_token.py
├── test_crypto.py
├── test_lifecycle.py
├── test_delegation.py
├── test_dag.py
├── test_ledger.py
├── test_verify.py
└── test_vectors.py # Must pass all vectors defined in vectors.py
Specification Summary
Token Structure
An ACT is a JWT (JWS Compact Serialization). It has two phases:
Phase 1 — Authorization Mandate (signed by issuing agent):
JOSE Header:
{
"alg": "EdDSA", // Ed25519; also support ES256
"typ": "act+jwt",
"kid": "<key-id>"
// optional: "x5c" (Tier 2), "did" (Tier 3)
}
Required JWT claims:
iss— issuer agent identifier (opaque string / X.509 DN / DID)sub— target agent identifier (same format as iss)aud— intended recipient(s); string or arrayiat— issuance time (NumericDate)exp— expiration time (NumericDate); SHOULD be iat + ≤900s for automated flowsjti— UUID v4; doubles as task identifier for DAG par references
Optional:
wid— workflow UUID grouping related ACTs
Required ACT-specific claims:
task— object: { purpose (str, REQUIRED), data_sensitivity (str, OPTIONAL), created_by (str, OPTIONAL), expires_at (NumericDate, OPTIONAL) }cap— array of { action (str, REQUIRED), constraints (object, OPTIONAL) } action names conform to ABNF: component *("." component) component = ALPHA *(ALPHA / DIGIT / "-" / "_")del— object: { depth (int), max_depth (int), chain (array) } chain entries: { delegator (str), jti (str), sig (base64url str) } chain is ordered root → immediate parent (chain[0] = root authority) Ifdelis absent: treat as root mandate, depth=0, delegation forbidden
Optional:
oversight— { requires_approval_for (array of action strings), approval_ref (str, OPTIONAL) }
Phase 2 — Execution Record (re-signed by executing agent, i.e. sub):
All Phase 1 claims are preserved unchanged. Additional required claims:
exec_act— string; MUST match one of the cap[].action valuespar— array of jti strings (parent task IDs in DAG); [] for root tasksexec_ts— NumericDate; actual execution time; MUST be >= iat; SHOULD be <= exp (if exec_ts > exp: log warning, do NOT reject)status— one of: "completed", "failed", "partial"
Additional optional claims:
inp_hash— base64url(SHA-256(raw input bytes)), no paddingout_hash— base64url(SHA-256(raw output bytes)), no paddingerr— { code (str), detail (str) }; present when status != "completed"
Critical: In Phase 2, the JOSE header kid MUST reference the sub
agent's key (not the iss agent's key). The re-signature is produced by
the executing agent over the complete Phase 2 payload (all Phase 1 claims
- execution claims combined).
Trust Tiers
Tier 1 — Pre-Shared Keys (mandatory-to-implement)
- Keys: Ed25519 (primary) or P-256
kid: opaque string agreed out-of-band- Key registry: a dict mapping kid → public key bytes, configured at init time
- No external resolution needed
Tier 2 — PKI / X.509
kid: SHA-256 thumbprint of DER-encoded certificatex5cJOSE header MAY carry the certificate chain- Verification: standard X.509 chain validation against trusted CA store
Tier 3 — DID
- Support
did:key(self-contained, no resolution needed) - Support
did:web(requires HTTP resolution; cache with configurable TTL) kid: DID key fragment (e.g.did:key:z6Mk...#key-1)didJOSE header MAY carry the full DID for resolution
Delegation Chain
When issuing a delegated ACT (Agent A → Agent B):
del.depth= parent ACT'sdel.depth+ 1del.max_depth≤ parent ACT'sdel.max_depthcapmust be a subset of parent ACT'scapwith constraints at least as restrictive- Each chain entry
sig= Sign(A.private_key, SHA-256(parent_act_compact_bytes)) whereparent_act_compact_bytesis the raw bytes of the parent ACT's JWS Compact Serialization (UTF-8 encoded)
Verification of chain entry:
- Retrieve public key for entry.delegator
- Recompute SHA-256(parent_act_compact_bytes)
- Verify entry.sig against that hash using entry.delegator's public key
Rejection conditions:
del.depth>del.max_depthdel.chainlength !=del.depth- Any chain entry sig fails verification
capcontains actions not in parent ACT'scap- Any constraint in
capis less restrictive than in parent ACT
DAG Validation (Phase 2)
The ACT ledger (or set of received parent ACTs) is the ECT store.
Required checks on receiving a Phase 2 ACT:
jtiuniqueness withinwidscope (or globally ifwidabsent)- Every
jtiinparexists in the ledger/store as a verified Phase 2 ACT - For each parent:
parent.exec_ts < child.exec_ts + 30s(clock skew tolerance) - No cycle: following
parreferences must not return to currentjti— enforce max traversal limit of 10,000 nodes exec_actmatches one of thecap[].actionvalues in the Phase 1 claims
Verification Procedure
Phase 1 verification (ACTVerifier.verify_mandate):
- Parse JWS Compact Serialization
- Check
typ== "act+jwt" - Check
algin allowlist (must include EdDSA/Ed25519, ES256; MUST NOT include "none" or any HS* algorithm) - Resolve public key for
kidper trust tier - Verify JWS signature
- Check
expnot passed (clock skew tolerance: ≤300s) - Check
iatnot unreasonably future (≤30s ahead) - Check
audcontains verifier's own identifier - Check
issis trusted per local policy - Check
submatches verifier's own identifier (when verifier is the target) - Check all required claims present and well-formed
- If
del.chainnon-empty: verify delegation chain
Phase 2 verification (ACTVerifier.verify_record):
All Phase 1 steps, plus:
13. Check exec_act present and matches a cap[].action
14. Check par present; perform DAG validation
15. Check exec_ts present and >= iat; if > exp log warning but do NOT reject
16. Check status present and valid
17. Check re-signature was produced by sub agent's key (kid in Phase 2
header must correspond to sub's public key, not iss's key)
18. Optionally verify inp_hash/out_hash against provided data
Audit Ledger Interface
ACTLedger (in-memory reference implementation):
append(act_record: ACTRecord) -> int— returns sequence numberget(jti: str) -> ACTRecord | Nonelist(wid: str | None) -> list[ACTRecord]verify_integrity() -> bool— verifies no records have been tampered with (hash-chain over sequence-ordered records)
The ledger must enforce append-only semantics: once appended, a record
cannot be modified or deleted. Raise ACTLedgerImmutabilityError on
any attempt.
Error Types
Define in errors.py:
ACTError # base
ACTValidationError # malformed token structure
ACTSignatureError # signature verification failed
ACTExpiredError # token expired
ACTAudienceMismatchError # aud does not contain verifier identity
ACTCapabilityError # no matching capability / capability escalation
ACTDelegationError # delegation chain invalid
ACTDAGError # DAG validation failed (cycle, missing parent, etc.)
ACTPhaseError # wrong phase for operation (e.g. mandate used as record)
ACTKeyResolutionError # cannot resolve kid to public key
ACTLedgerImmutabilityError # attempt to modify ledger
ACTPrivilegeEscalationError # delegated cap exceeds parent cap
Test Vectors (Appendix B)
vectors.py must generate and validate all of the following. Each vector
must include: description, input parameters, expected output (encoded token
or expected exception class).
Valid vectors:
- B.1: Phase 1 ACT — root mandate, Tier 1 (Ed25519 pre-shared key), no delegation
- B.2: Phase 2 ACT — completed execution, transition from B.1 mandate
- B.3: Phase 2 ACT — fan-in, two parent jti values from parallel branches
- B.4: Phase 1 ACT — delegated mandate (depth=1), chain entry with sig
- B.5: Phase 2 ACT — delegated execution record
Invalid vectors (must raise specified exception):
- B.6:
del.depth>del.max_depth→ ACTDelegationError - B.7:
capescalation in delegated ACT → ACTPrivilegeEscalationError - B.8:
exec_actnot incap→ ACTCapabilityError - B.9: DAG cycle (par references own jti) → ACTDAGError
- B.10: Missing parent jti in DAG → ACTDAGError
- B.11: Tampered payload (bit flip in claims) → ACTSignatureError
- B.12: Expired token → ACTExpiredError
- B.13: Wrong audience → ACTAudienceMismatchError
- B.14: Phase 2 re-signed by iss key instead of sub → ACTSignatureError
- B.15: Algorithm "none" → ACTValidationError
Implementation Constraints
Dependencies: use only the Python standard library plus:
cryptography(for Ed25519, P-256, X.509)pyjwtOR manual JWS implementation (prefer manual for spec fidelity)pytest(test runner only)
Performance target: Phase 1 creation ≤ 500µs mean on modern hardware.
Benchmark in a bench/ directory.
Code style:
- Type-annotated throughout (Python 3.11+)
- Dataclasses for token structures
- No global mutable state
- All public API functions documented with docstrings referencing the
relevant draft section (e.g.
# ACT §8.1)
Security constraints:
- MUST NOT use symmetric algorithms (HS256 etc.) anywhere
- MUST NOT implement "alg: none" path
- Ed25519 signing MUST use bound key-pair APIs (private key object that carries the public key) — never pass raw private key bytes
- All secret key material must be zeroed on deletion where the cryptography library supports it
What NOT to implement:
- DID:web resolution with live HTTP calls in the reference implementation (stub it with a configurable resolver callback instead)
- Token revocation infrastructure
- Persistence (ledger is in-memory only)
Output Format
Produce each file completely, in order. After all files, produce a
README.md for the act/ package that includes:
- Installation instructions
- Quick-start example (Phase 1 mandate → Phase 2 record → verify)
- Running the test suite
- Running the test vectors
- Performance benchmark instructions
At the end, confirm: "All Appendix B test vectors pass."