| title | Overview |
|---|---|
| nav_order | 1 |
| permalink | / |
A dependency-light Python reference implementation of the Agent Compute Market Protocol (ACMP), the economic layer defined by A2Agora (GitHub · Codeberg).
Rendered docs → a2agora.org/sdk-reference · Project home → a2agora.org
This SDK exists to prove the protocol is implementable, not to be a production-grade agent framework. It follows the spec's Layer 1 (Transport & Invocation), Layer 2 (Task Decomposition Format), Layer 4 (Escrow & Settlement), and Layer 6 (Negotiation Protocol) documents field-for-field.
- Stage 1 — Layer 1 minimal transaction. A buyer invokes a single task on a provider and gets a result back, over an in-memory JSON-RPC transport.
- Stage 2 — Layer 6 negotiation. A buyer requests an offer, accepts it (locking escrow), then invokes using the negotiated price and escrow id.
- Stage 3 — Layer 2 DAG. A buyer-side orchestrator walks a task DAG, resolving each task's input from upstream outputs and invoking independent tasks concurrently.
- Stage 4 — Layer 1 streaming, heartbeats & cancellation. Output
streaming (
acmp/streamChunk), input streaming (acmp/inputChunk), automatic + manual heartbeats (acmp/heartbeat), cooperative cancellation (acmp/cancel), buyer-sidetimeout_msenforcement, and -33007 feature gating. - Stage 5 — Layer 4 escrow agent & a real network transport. A genuine
EscrowAgent— lock/bind/release/reclaim/claim/dispute/status, the four-state lifecycle,op_refidempotency, the bound-escrow reclaim guard, and the challenge-window auto-release and expiry auto-reclaim — served as its own party, never shared in-process. Plus an optional WebSocket transport, so buyer, provider, and Escrow Agent run as three real network endpoints end-to-end.
Further contributions (additional transports, streaming DAG edges, dispute-resolution mechanisms) are welcome — see the notes below on what's intentionally out of scope so far.
cd sdk-reference
pip install -e ".[dev]"The core SDK (src/acmp/) has no runtime dependencies. The WebSocket
transport (acmp.ws_transport, used by demo 05) is an opt-in extra —
pip install -e ".[dev]" already includes it for running the tests, but on
its own it's:
pip install -e ".[ws]"python examples/01_minimal_invoke.py # Layer 1 only
python examples/02_negotiated_invoke.py # Layer 6 -> Layer 1 -> Layer 4
python examples/03_dag_pipeline.py # Layer 2 DAG -> Layer 1 (concurrent)
python examples/04_streaming_cancel.py # streaming + heartbeats + cancel
python examples/05_escrow_e2e.py # Layer 4 escrow agent over real WebSockets (needs [ws])The first two spin up a provider offering a sentiment-analysis
capability — the same scenario as the worked example in
spec/layers/02-task-format.md §6.1
(GitHub · Codeberg).
The second demo additionally runs the offer/accept exchange from
spec/layers/06-negotiation-protocol.md
(GitHub · Codeberg)
before invoking, then locks, binds, releases, and reclaims through a real
EscrowAgent — the RFC-0001 happy-path numbers (lock 0.005, release 0.003,
reclaim 0.002). The third demo runs the split → parallel sentiment →
aggregate pipeline from spec/layers/02-task-format.md §6.2
(GitHub · Codeberg).
The fifth demo is the full three-party topology of
spec/layers/04-escrow-settlement.md
(GitHub · Codeberg)
— buyer, provider, and Escrow Agent as three separate WebSocket connections
— running both the happy path and the silent-buyer safety path (claim +
challenge-window auto-release, via an injected clock rather than a real
wait).
pytestsrc/acmp/
errors.py ACMP error codes (-33xxx, -35xxx) and the AcmpError exception
messages.py Task/Payload/Result dataclasses + JSON-RPC framing helpers
transport.py Transport ABC + InMemoryTransport (paired in-process channel)
ws_transport.py Transport over a real websockets connection (optional, acmp[ws])
provider.py Capability registry, invoke/streaming/cancel dispatch, TaskContext
buyer.py invoke() with chunk/heartbeat callbacks, timeout, cancel, input chunks
negotiation.py Layer 6 offer request/offer/accept dataclasses + Negotiator
escrow.py Layer 4 EscrowAgent, EscrowClient, CreditLedger (the real escrow)
dag.py Layer 2 DAG/Edge/InputRef model + DagOrchestrator
Every module docstring cites the spec section it implements.
- Idempotency (Layer 1 §3.1.1): the
Providercaches responses bytask_id— invoking the same task twice returns the cached result instead of re-executing or re-billing. - Proof of execution:
proof_method="result-hash"produces a sha256 hash of the canonical output JSON — a runnable stand-in for Layer 3. - Pricing: each registered capability has a
price_cu; the provider rejects withbudget_exceeded(-33001) ifprice_cu > task.max_price_cu(checked both at negotiation time and at invoke time). - Negotiation (Layer 6, now
draft):Negotiator.request_offer()/.accept()implement the offer-request → offer → accept → ack flow. The spec formalized this after the SDK proved it, adopting the SDK's method names and -34xxx error codes wire-compatibly. Per the draft, the buyer locks escrow and suppliesescrow_idat accept (the ack echoes it; omitting it means direct settlement), and offers carry the negotiatedchallenge_window_msterm. The offersigenvelope (Layer 7) is transported but not produced — this SDK has no key infrastructure. - Escrow (Layer 4, now
draft):EscrowAgentis a genuine neutral third party — served over its ownTransportconnection(s), never shared in-process with buyer or provider, unlike the retiredEscrowStub. It implements the four-state lifecycle (open → claimed → disputed → closed), all sevenacmp/escrow*messages,op_refidempotency (including a dedicated agent-wide dedup forescrowLock, which has noescrow_idyet to key a per-escrow cache off), party authorization (-35005), the once-only bind (including a lock-timepayee_idalready counting as bound), the claim fast-forward release rule (§4.3), the bound-escrow reclaim guard (§4.4), and the challenge-window auto-release and expiry auto-reclaim — both driven by an injectable clock so tests and demo 05's safety-path scenario never wait on a real timer.CreditLedgerimplements the §7.2 non-blockchain rail (P4): a plain funding/payout balance, with one idempotent payout per(escrow_id, transition). Dispute resolution is out of scope (v0.1 leaves it to agent policy, per the spec); anEscrowAgentonly freezes a disputed escrow.EscrowClientis the buyer- or provider-side wrapper (built the same wayNegotiatorwraps aBuyer) and also satisfies theEscrowVerifierprotocol aProvideruses for itsescrow_invalid(-33005) check. - Transport:
WebSocketTransport(in the optionalacmp.ws_transportmodule,pip install acmp[ws]) implements the sameTransportinterface asInMemoryTransportover a real socket — the SDK's core stays dependency-light, and no core module imports it.party_idfor anEscrowAgentconnection is read from a?party_id=query parameter on the connection URL (self-reported, same as the in-memory demos — verifiable identity binding is Layer 7's job). - DAG execution (Layer 2 §3):
DagOrchestratortreats the DAG purely as a buyer-side plan — it is never sent over the wire. Providers only ever see individual, literalacmp/invoketasks;InputRefs (thesourceform) are always resolved to a concretePayloadbefore invoking. Independent tasks (no unresolved dependency) are invoked concurrently. - Field path resolution (Layer 2 §3 "Path grammar"): supports the
spec's small subset — member access (
data.foo) and array indexing (data.items[0]), rooted at the referenced task's whole{type, data}output. Wildcards/slices are out of scope, per the spec. - Failure policy (Layer 2 §4):
DagOrchestratoris fail-fast — the first task failure cancels sibling tasks still in flight, sendsacmp/cancelso their providers stop working too, and re-raises. Best-effort (continue independent branches) is a valid alternative per the spec but not implemented here. - Streaming & liveness (Layer 1 §3.4–§3.7, §7): output and input
streaming, heartbeats (automatic keep-alives at the provider's
heartbeat_interval_msplus manual progress viactx.heartbeat), and cooperative cancellation are implemented. Capability handlers opt in by declaring a second parameter (handler(task, ctx)receives aTaskContext); single-parameter handlers keep working unchanged.task.timeout_msis enforced buyer-side as the hard deadline (Layer 1 §3.1), withacmp/cancelsent on expiry. Feature gating uses -33007; since the in-memory transport has no MCPinitializehandshake, the advertisement check is enforced provider-side only. Proof over streamed output is not implemented (-33006).stream_eligibleDAG edges (Layer 2 §5) remain out of scope.