ASPL (Agent Supply Protocol Layer) is a trust and supply-chain layer for agent capabilities. It sits above MCP (agent↔tool) and A2A (agent↔agent): it lets an agent find, verify, trust, and receive capabilities with cryptographic provenance.
This document defines the wire protocol so that any implementation — in any
language — can interoperate. An implementation is conformant if it passes the
conformance suite in conformance/ (python -m conformance.aspl_conformance <url>).
- Version string:
aspl/0.1 - Transport: HTTP/1.1, JSON bodies (
Content-Type: application/json). - Signatures: Ed25519. Keys are lowercase hex. A namespaced key is written
ed25519:<hex>. - Hashes:
sha256:<first-32-hex-chars-of-sha256>over the JSON-canonical form of the value (json.dumps(value, sort_keys=True, separators=(',',':'))). - IDs:
ag_<hex>(agents),cap_<hex>(capabilities),txn_<hex>(transactions). Opaque to clients.
- Node — a server implementing this spec. Holds an Ed25519 node identity and signs capabilities and deliveries.
- Agent — a client. Receives an Ed25519 keypair + API key at registration.
- Capability — a unit of value:
template | block | tool | config | knowledge.
Registration is gated by proof-of-work to raise the cost of mass sybil creation.
GET /v1/pow/challenge→{challenge_id, prefix, difficulty, algorithm:"sha256", ttl_seconds}- Client finds
noncesuch thatsha256(prefix + nonce)has ≥difficultyleading zero bits. POST /v1/register {name, environment?, pow_challenge_id, pow_nonce}→{agent_id, api_key, public_key, passport}.
The passport is a JSON document the node signs over "{agent_id}:{public_key}:{created}"
with the node key; it carries shop_signature and shop_public_key.
A challenge is single-use and expires after ttl_seconds. Difficulty is
node-configurable; PoW is a speed-bump, not a strong barrier (see README).
All mutating endpoints require X-API-Key: <api_key>. The node stores only a
SHA-256 hash of the key. Missing/invalid key → 401.
POST /v1/publish (auth) with {type, intent, intent_tags?, description, requires?, provides?, content, safety_level?, version?, source_protocol?, source_ref?}.
The node MUST:
- compute
content_hashovercontent; - run a security scan over
content(§7). If the scan returnssafe=false(a CRITICAL finding), reject with422and the findings; - set
safety_levelto the stricter of the declared level and the scan result; - co-sign:
shop_signature = sign("{content_hash}:{publisher_id}").
POST /v1/need {intent, type_filter?, min_trust?, max_results?, environment?, include_imported?} → {matches, query_intent, total_found}.
Ranking MUST combine an intent-match score and trust: combined = 0.7*intent + 0.3*trust.
min_trust MUST filter on the capability's trust score (not on combined).
The trust score used for ranking/gating is the stored score after read-time
freshness decay (§5): a capability not exercised for a long time ranks lower.
If environment is supplied, each match SHOULD carry env_compatible (§6).
Revoked capabilities MUST NOT appear.
POST /v1/accept {capability_id}→{transaction_id, status:"accepted"}. Accepting a revoked capability →410.GET /v1/deliver/{transaction_id}(auth, caller must own the txn) →{transaction_id, capability:{..., content_hash, shop_signature, shop_public_key}, content, integration_hint}. The node signsdelivery = sign("deliver:{transaction_id}:{content_hash}"). Clients MUST verify before use: recomputecontent_hashfromcontentand check it equals the advertised hash, then verifyshop_signatureover"deliver:{transaction_id}:{content_hash}"againstshop_public_key. Delivery of a revoked capability MUST be refused with410, even for a transaction accepted before the revocation (revocation is not bypassable by pre-accepting).POST /v1/confirm {transaction_id, success, feedback?}→ updates agent and capability trust (§5) and returns the new scores. A self-dealing confirmation (the confirmer is the capability's publisher) MUST NOT raise the publisher's agent trust nor the capability's trust.
- Agent trust = Bayesian success rate (prior 0.5, weight 5) × activity (log, saturates ~100 txns) × inactivity decay (30-day half-life).
- Capability trust: unused →
0.3 × publisher_trust; used →0.7 × (success_rate × usage_factor) + 0.3 × publisher_trust + maturity_bonus. Usage and success rate MUST be computed over distinct non-publisher confirmers (each confirmer counts once; the publisher's own confirmations are excluded), so trust cannot be pumped by volume from a single identity. - Read-time decay: when trust is READ for ranking/gating, a 30-day half-life decay is applied based on time since the capability was last exercised.
- Imported capabilities start at MCP
0.5, LangChain0.4, A2A0.3. Federated (mirrored) capabilities carry0.5 ×the origin's reported trust (§9.4).
Trust is advisory metadata; clients decide their own thresholds via min_trust.
POST /v1/probe/{capability_id} {environment?, run_smoke?} → {compatible, score, reasons, smoke_ran, smoke_ok, smoke_detail}. Compatibility compares the
capability's requires (runtime/os/permissions/dependencies/memory) to the
agent's declared environment. The optional smoke probe executes the
capability's probe/code under resource limits and is NOT a security sandbox.
At publish time the node scans content for prompt injection, exfiltration,
resource abuse, embedded secrets, and (for code capabilities) dangerous calls.
Scanning MUST be robust to trivial evasion: unicode/homoglyph normalization,
base64/hex decode-and-rescan, and AST analysis of code. Severity → risk:
CRITICAL ⇒ reject; HIGH ⇒ RED; MEDIUM ⇒ YELLOW; else GREEN.
POST /v1/revoke {capability_id, reason, severity?}(auth, publisher only) — removes the capability from discovery/delivery.GET /v1/revocations/stream(auth) — Server-Sent-Events; each revocation is pushed asevent: revocationwith a signeddatapayload.GET /v1/revocations— node-signed status list of all revoked capabilities for catch-up (CRL-style).
POST /v1/ingest/mcp {server_url, tools}— import MCP tool definitions.POST /v1/ingest/mcp/url {server_url}— fetch a live MCP server'stools/list(JSON-RPC 2.0) and import.POST /v1/ingest/a2a <AgentCard>— import an A2A Agent Card as a passport + capabilities.POST /v1/ingest/langchain {tools:[{name, description, args_schema?}]}— import LangChain tool descriptors as capabilities (initial trust0.4).
POST /v1/federation/mirror {peer_url} (auth) — mirror a peer node's catalogue.
The node MUST, before importing anything:
- fetch the peer's signed tree head (§10) and verify its Ed25519 signature;
- fetch the peer's signed revocation list (§8) and verify it was signed by the
same key as the tree head.
Each non-revoked peer capability is mirrored as a local discovery record tagged
source_protocol = "aspl-federated", at0.5 ×the peer's reported trust, recording origin-node provenance (origin_node,origin_url,origin_capability_id). Peer revocations MUST be honoured.
Acquisition proxy. POST /v1/federation/acquire/{capability_id} (auth) lets a
local agent acquire a mirrored capability without contacting the peer itself: the
node accepts + delivers the capability AT THE ORIGIN (using credentials it
registered there during mirroring), verifies the origin's delivery signature and
content hash, and returns {content, origin_url, origin_capability_id, content_hash, origin_node, origin_signature_verified}. It MUST return 410 if
the origin (or local mirror) has revoked the capability, and 502 if the origin's
signature does not verify. SDK get() routes a federated match through this proxy
transparently.
GET /v1/audit/verify→{chain_valid, entries}. Every security-relevant action is appended to a hash-chained log; verification recomputes the chain.GET /v1/audit/recent?n=&event_type=→ recent entries (IPs partially masked).
A Merkle tree over the audit entries (RFC 6962 hashing: leaf SHA-256(0x00‖entry),
node SHA-256(0x01‖left‖right)) gives external, O(log n) verifiability:
GET /v1/log/sth→ Signed Tree Head{tree_size, root_hash, timestamp, signature, node_public_key}, signed over the canonical{tree_size, root_hash, timestamp}.GET /v1/log/proof/inclusion?leaf_index=→{leaf_index, tree_size, leaf_hash, audit_path, root_hash}— proves an entry is in the tree.GET /v1/log/proof/consistency?first=&second=→{proof, first_root, second_root}— proves the size-firsttree is an append-only prefix of sizesecond(detects history rewrites).GET /v1/log/leaves?start=&end=→ the entries with their leaf hashes.
Standard HTTP status codes. Bodies are {"detail": ...} (FastAPI default) or, for
scan rejections, {"error":"capability_rejected","reason":...,"findings":[...]}.
401 auth, 403 not owner, 404 missing, 410 revoked, 422 scan-rejected,
429 rate-limited, 502 upstream (MCP) fetch failure.
An implementation is conformant at level 0.1 if a fresh node passes every
check in conformance/aspl_conformance.py against a live instance. Run:
python -m conformance.aspl_conformance http://localhost:5010