ACP Core Specification — v0.8¶
Status: Stable
Authors: ACP Community
Date: 2026-03-21
License: Apache 2.0
Supersedes: core-v0.5.md, core-v0.1.md
See also: transports.md · error-codes.md · identity-v0.8.md
0. Design Principles¶
ACP is a lightweight, open protocol for direct Agent-to-Agent communication.
| Principle | Meaning |
|---|---|
| Zero-server | No central relay, registry, or broker required |
| Zero-config | The acp:// link is the connection — no setup beyond starting the process |
| Curl-compatible | Every endpoint is reachable with plain curl. No SDK required. |
| Single required dep | websockets only. All other features are truly optional. |
| Forward-compatible | Unknown fields MUST be ignored by receivers |
| Warn-not-drop | Signature mismatches produce warnings, never message loss |
Design motto: MCP standardizes Agent↔Tool. ACP standardizes Agent↔Agent.
1. Message Envelope¶
Every ACP message shares a common JSON envelope regardless of transport:
{
"type": "acp.message",
"message_id": "msg_7a3f9c2b",
"server_seq": 42,
"ts": "2026-03-21T07:00:00Z",
"from": "AgentA",
"role": "user",
"parts": [ ... ],
"task_id": "task_abc123", // optional — associate with a task
"context_id": "ctx_xyz456", // optional — multi-turn context group (v0.7)
"sig": "a3f9...", // optional — HMAC-SHA256 hex (v0.7)
"identity": { ... } // optional — Ed25519 identity block (v0.8)
}
1.1 Required Fields¶
| Field | Type | Description |
|---|---|---|
type |
string | Always "acp.message" for user/agent messages |
message_id |
string | Client-generated unique ID (format: msg_<random>). Auto-generated by server if omitted. |
ts |
string | ISO 8601 UTC timestamp |
from |
string | Sender agent name |
role |
string | "user" or "agent" |
parts |
array | One or more Part objects (see §2) |
1.2 Optional Fields¶
| Field | Type | Since | Description |
|---|---|---|---|
server_seq |
integer | v0.5 | Server-assigned monotonic sequence number (ordering guarantee) |
task_id |
string | v0.5 | Associates message with a task |
context_id |
string | v0.7 | Groups messages into a named multi-turn context |
sig |
string | v0.7 | HMAC-SHA256 signature (see §6.1) |
identity |
object | v0.8 | Ed25519 identity block (see §6.2) |
1.3 message_id Generation Strategy¶
message_id is intentionally optional on input:
- If the client provides message_id: used as-is, enables client-side idempotency
- If omitted: server auto-generates msg_<16 hex chars>
This differs from A2A where messageId is REQUIRED (and not enforced in v1.0 SDK). ACP's approach reduces friction for quick integrations while supporting full idempotency when needed.
Idempotency: A server MAY deduplicate messages with the same message_id within a session window.
2. Part Model¶
A Part is the atomic unit of message content. Every message carries one or more Parts.
2.1 Text Part¶
contentMUST be a string.- Use for natural language, instructions, and plain-text data.
2.2 File Part¶
{
"type": "file",
"url": "https://example.com/report.pdf",
"media_type": "application/pdf",
"filename": "report.pdf"
}
urlREQUIRED. Must be an accessible HTTP/HTTPS URL.media_typeRECOMMENDED. Standard MIME type.filenameOPTIONAL. Display name hint.- ACP does not inline raw bytes. Use URL references only (keeps messages relay-friendly).
2.3 Data Part¶
contentMUST be a JSON-serializable value (object, array, string, number, boolean, or null).- Use for structured results, function call outputs, and machine-readable payloads.
2.4 Shorthand¶
For convenience, POST /message:send accepts "text": "..." as shorthand for
"parts": [{"type": "text", "content": "..."}]. The server normalizes to full Part form.
3. Task Lifecycle¶
Tasks are units of delegated work. A task is created by the requesting agent and progresses through a 5-state machine.
3.1 State Machine¶
┌─────────────────────────────────────────────────────┐
│ │
──► submitted ──► working ──► completed (terminal) │
│ │
├──► failed (terminal) │
│ │
└──► input_required ──► working ──► ... │
│ │
└──► canceled (terminal) ◄─────┘
3.2 States¶
| State | Terminal? | Description |
|---|---|---|
submitted |
No | Task created, not yet picked up by the peer |
working |
No | Peer is actively processing |
completed |
✅ Yes | Peer finished successfully; artifact may be attached |
failed |
✅ Yes | Peer encountered an unrecoverable error |
input_required |
No | Peer needs additional input to continue |
canceled |
✅ Yes | Task was explicitly canceled |
3.3 Transition Rules¶
- Terminal states (
completed,failed,canceled) cannot transition to any other state. input_required→workingresumes when the client sends a/tasks/{id}/continuemessage.- A task in any non-terminal state may be moved to
canceledviaPOST /tasks/{id}:cancel.
3.4 Task Object¶
{
"id": "task_abc123",
"status": "working",
"created_at": "2026-03-21T07:00:00Z",
"updated_at": "2026-03-21T07:00:05Z",
"input": { "parts": [...] },
"artifact": { "parts": [...] }, // present when status=completed
"error": "string", // present when status=failed
"message_id": "msg_xyz" // message_id that created this task
}
4. AgentCard¶
Every ACP agent exposes its capabilities via a well-known endpoint:
4.1 AgentCard Schema¶
{
"name": "MyAgent",
"acp_version": "0.8",
"timestamp": "2026-03-21T07:00:00Z",
"skills": [{"id": "summarize", "name": "summarize"}],
"capabilities": {
"streaming": true,
"push_notifications": true,
"input_required": true,
"part_types": ["text", "file", "data"],
"max_msg_bytes": 1048576,
"query_skill": true,
"server_seq": true,
"multi_session": true,
"error_codes": true,
"hmac_signing": false,
"lan_discovery": false,
"context_id": true,
"identity": "none"
},
"identity": null,
"trust": {
"scheme": "none",
"enabled": false
},
"auth": {
"schemes": ["none"]
},
"endpoints": {
"send": "/message:send",
"stream": "/stream",
"tasks": "/tasks",
"agent_card": "/.well-known/acp.json",
"skills_query": "/skills/query",
"peers": "/peers",
"peer_send": "/peer/{id}/send",
"peers_connect": "/peers/connect"
}
}
4.2 Capability Flags¶
| Flag | Type | Description |
|---|---|---|
streaming |
bool | SSE /stream endpoint available |
push_notifications |
bool | Agent can push unsolicited events |
input_required |
bool | Agent supports input_required task state |
part_types |
string[] | Supported Part types |
max_msg_bytes |
int | Maximum message size in bytes (default: 1,048,576) |
query_skill |
bool | /skills/query endpoint available |
server_seq |
bool | Outbound messages include server_seq |
multi_session |
bool | Multiple simultaneous peer connections supported |
error_codes |
bool | Error responses include error_code field (v0.6) |
hmac_signing |
bool | HMAC-SHA256 signing active (v0.7) |
lan_discovery |
bool | mDNS LAN discovery active (v0.7) |
context_id |
bool | context_id field supported (v0.7) |
identity |
string | "ed25519" or "none" (v0.8) |
4.3 Forward Compatibility¶
Receivers MUST ignore unknown capability fields. A flag value of false or absent is equivalent.
5. Error Codes¶
All error responses follow a consistent envelope:
{
"ok": false,
"error_code": "ERR_NOT_CONNECTED",
"error": "No P2P connection",
"failed_message_id": "msg_abc123"
}
| Code | HTTP | Trigger |
|---|---|---|
ERR_NOT_CONNECTED |
503 | No peer WebSocket connection |
ERR_MSG_TOO_LARGE |
413 | Message exceeds max_msg_bytes |
ERR_NOT_FOUND |
404 | Task, peer, or resource does not exist |
ERR_INVALID_REQUEST |
400 | Missing fields, malformed body, invalid state transition |
ERR_TIMEOUT |
408 | Sync wait timed out |
ERR_INTERNAL |
500 | Unexpected server-side exception |
failed_message_id is present for ERR_TIMEOUT and ERR_MSG_TOO_LARGE only.
See error-codes.md for retry guidance and full examples.
6. Optional Extensions¶
Extensions are opt-in fields that can be combined freely. Absence of any extension has no effect on core protocol operation.
6.1 HMAC-SHA256 Signing (v0.7)¶
For closed deployments where both peers share a secret out-of-band:
Outbound: server appends sig to every message:
Inbound: if sig present and secret configured, verify with hmac.compare_digest.
Mismatch → log warning + set _sig_invalid=true on message object. Message is not dropped.
AgentCard: capabilities.hmac_signing = true, trust.scheme = "hmac-sha256".
CLI: --secret <shared_key> (both peers must use the same key).
6.2 Ed25519 Identity (v0.8)¶
For open scenarios where peer identity must be publicly verifiable:
Wire format:
"identity": {
"scheme": "ed25519",
"public_key": "<base64url 32-byte public key>",
"sig": "<base64url 64-byte Ed25519 signature>"
}
Signing input: canonical JSON of full message envelope, excluding identity.sig:
canonical = {k: v for k, v in msg.items() if k != "identity"}
payload = json.dumps(canonical, sort_keys=True, separators=(",",":")).encode()
Keypair storage: ~/.acp/identity.json (auto-generated on first --identity run, chmod 0600).
Verification: warn-only on mismatch; accept if cryptography not installed.
AgentCard: capabilities.identity = "ed25519", identity.{scheme, public_key} block.
CLI: --identity [path]. Requires: pip install cryptography.
HMAC and Ed25519 may be active simultaneously — they serve different use cases.
See identity-v0.8.md for full spec including coexistence table and security properties.
6.3 context_id (v0.7)¶
Groups messages across multiple turns into a named conversation context:
Receivers SHOULD use context_id to correlate related messages in multi-turn workflows.
No server-side enforcement is required — it is a hint field.
6.4 mDNS LAN Discovery (v0.7)¶
When --advertise-mdns is set, the agent broadcasts its presence on the LAN via UDP multicast
(224.0.0.251:5354). Nearby ACP agents appear at GET /discover.
No external library required (raw UDP socket). AgentCard: capabilities.lan_discovery = true.
7. Transport Bindings¶
ACP separates the message envelope (this document) from the transport layer. The same envelope is used across all bindings.
| Binding | Link Scheme | Description |
|---|---|---|
| A — WebSocket P2P | acp:// |
Direct WebSocket (preferred) |
| B — stdio | n/a | Subprocess pipe |
| C — HTTP Relay | acp+wss:// |
Cloudflare Worker relay fallback |
| D — HTTP/SSE | http(s):// |
Standard HTTP with SSE streaming |
| E — TCP | tcp:// |
Raw TCP (LAN) |
Binding A is the default and preferred transport. Use Binding C only when direct IP connectivity is blocked (firewalls, K8s NAT, etc.).
See transports.md for full binding specifications including HMAC header handling.
8. Multi-Session Peer Registry (v0.6)¶
An ACP agent can maintain simultaneous connections to multiple peers.
8.1 Endpoints¶
| Endpoint | Method | Description |
|---|---|---|
/peers |
GET | List all connected peers with metadata |
/peer/{id} |
GET | Get single peer info (agent_card, link, stats) |
/peer/{id}/send |
POST | Send message to a specific peer (same body as /message:send) |
/peers/connect |
POST | Establish a new peer connection {"link": "acp://..."} |
8.2 Peer Object¶
{
"id": "peer_001",
"name": "AgentB",
"link": "acp://1.2.3.4:7801/tok_xxx",
"connected": true,
"connected_at": "2026-03-21T07:00:00Z",
"messages_sent": 12,
"messages_received": 8,
"agent_card": { ... }
}
9. Skill Query API (v0.5)¶
Enables runtime capability discovery:
Response:
AgentCard declares available skills in skills[]. query_skill: true capability flag indicates
this endpoint is available.
10. Versioning¶
ACP uses semantic versioning. The acp_version field in AgentCard declares the implemented version.
Compatibility guarantee: An ACP v0.8 implementation MUST:
1. Accept connections from v0.5+ peers (unknown extension fields are silently ignored)
2. Include acp_version: "0.8" in its AgentCard
3. Return error_code in all error responses if capabilities.error_codes = true
Breaking changes (requiring major version bump): changes to required envelope fields, removal of existing endpoints, changes to HTTP status code semantics.
Non-breaking changes (minor/patch): new optional fields, new endpoints, new capability flags.
11. Reference Implementation¶
The reference implementation is relay/acp_relay.py — a single Python file (~1900 lines)
with one required dependency (websockets).
# Minimal start
pip install websockets
python3 acp_relay.py --name "MyAgent"
# Full feature set
pip install websockets cryptography
python3 acp_relay.py --name "MyAgent" \
--secret "shared-key" \ # HMAC signing
--identity \ # Ed25519 identity
--advertise-mdns # LAN discovery
Compliance can be verified against any implementation using the compat test suite:
Appendix A: Version History¶
| Version | Date | Key Features |
|---|---|---|
| v0.1 | 2026-03-05 | P2P WebSocket, AgentCard, basic send/recv, auto-reconnect |
| v0.2 | 2026-03-10 | JSONL persistence, SSE streaming, relay fallback |
| v0.3 | 2026-03-12 | Task lifecycle (3 states), multi-session, Cloudflare Worker |
| v0.4 | 2026-03-14 | Multimodal parts (text/file/data), relay v2 |
| v0.5 | 2026-03-19 | QuerySkill API, server_seq, message idempotency, 5-state task machine |
| v0.6 | 2026-03-20 | Peer registry, standardized error codes (ERR_*), minimal agent spec |
| v0.7 | 2026-03-20 | HMAC-SHA256 signing, mDNS LAN discovery, context_id |
| v0.8 | 2026-03-21 | Ed25519 identity, Node.js SDK, compat test suite |
Appendix B: Comparison with A2A v1.0¶
ACP and A2A target different deployment contexts:
| A2A v1.0 | ACP v0.8 | |
|---|---|---|
| Target | Enterprise orchestration | Personal / small teams |
| Server required | Yes (agent infrastructure) | No (pure P2P) |
| Auth | OAuth 2.0 (mandatory) | HMAC or Ed25519 (optional) |
| Required SDK deps | Many (incl. implicit SQLAlchemy) | websockets only |
| Identity | Agent Passport System (full PKI) | Self-sovereign Ed25519 (no PKI) |
| message_id | REQUIRED by spec, not enforced in SDK | Optional, auto-generated if absent |
| Test suite | ✅ ITK (Issue #868) | ✅ tests/compat/ |
| Streaming | SSE | SSE |
| Task states | 6 | 5 (no separate unknown state) |
Spec version: v0.8 | Supersedes: core-v0.5.md | Reference impl: relay/acp_relay.py