Skip to content

Latest commit

 

History

History
1147 lines (888 loc) · 45.1 KB

File metadata and controls

1147 lines (888 loc) · 45.1 KB

ACP Core Specification — v1.0

Status: Stable
Authors: ACP Community
Date: 2026-04-04 (v2.47: RFC 8615 well-known headers; capabilities.groups; tasks pagination; auth evaluation)
License: Apache 2.0
Supersedes: core-v0.8.md
See also: transports.md · error-codes.md · identity-v2.0.md · auth-evaluation.md

Stability Promise (v1.0+): Endpoints and fields marked stable will not change in a backwards-incompatible way within the v1.x series. Endpoints marked experimental may change with a minor version bump and advance notice. New optional fields may be added at any time.


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 Stability Description
type string stable Always "acp.message" for user/agent messages
message_id string stable Client-generated unique ID (format: msg_<random>). Auto-generated by server if omitted.
ts string stable ISO 8601 UTC timestamp
from string stable Sender agent name
role string stable "user" or "agent" — MUST be present; MUST be one of these two values
parts array stable One or more Part objects (see §2). MUST contain at least one Part.

v0.9 change: role is now server-enforced. A missing or invalid role value MUST return 400 ERR_INVALID_REQUEST. Prior versions silently defaulted to "user".

1.2 Optional Fields

Field Type Since Stability Description
server_seq integer v0.5 stable Server-assigned monotonic sequence number (ordering guarantee)
task_id string v0.5 stable Associates message with a task
context_id string v0.7 stable Groups messages into a named multi-turn context
sig string v0.7 stable HMAC-SHA256 signature (see §7.1)
identity object v0.8 stable Ed25519 identity block (see §7.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 — stable

{
  "type":    "text",
  "content": "string content here"
}
  • content MUST be a string.
  • Use for natural language, instructions, and plain-text data.

2.2 File Part — stable

{
  "type":       "file",
  "url":        "https://example.com/report.pdf",
  "media_type": "application/pdf",
  "filename":   "report.pdf"
}
  • url REQUIRED. Must be an accessible HTTP/HTTPS URL.
  • media_type RECOMMENDED. Standard MIME type.
  • filename OPTIONAL. Display name hint.
  • ACP does not inline raw bytes. Use URL references only (keeps messages relay-friendly).

2.3 Data Part — stable

{
  "type":    "data",
  "content": { "any": "json", "value": true }
}
  • content MUST 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 ──► ...    │
                      │         │                                 │
                      │         └──► cancelling ──► canceled ◄───┘
                      │                                           │
                      └──► cancelling ──► canceled (terminal) ◄──┘

v2.6 — cancelling intermediate state (ACP-unique): When a client calls POST /tasks/{id}:cancel, the task transitions to cancelling first (an observable SSE event is pushed), then asynchronously completes to canceled (terminal). This fills the semantic gap in A2A issues #1684 and #1680, where A2A lacks both a "being cancelled" intermediate state and a formal CancelTaskRequest definition.

3.2 States

State Terminal? Stability Description
submitted No stable Task created, not yet picked up by the peer
working No stable Peer is actively processing
completed ✅ Yes stable Peer finished successfully; artifact may be attached
failed ✅ Yes stable Peer encountered an unrecoverable error
input_required No stable Peer needs additional input to continue
cancelling No stable (v2.6+) Cancel requested; completion is in progress. Observers can detect cancellation before it finalises.
canceled ✅ Yes stable Task has been fully canceled

3.3 Transition Rules

  • Terminal states (completed, failed, canceled) cannot transition to any other state.
  • input_requiredworking resumes when the client sends a /tasks/{id}/continue message.
  • Any non-terminal, non-cancelling state may transition to cancelling via POST /tasks/{id}:cancel.
  • cancellingcanceled is completed asynchronously by the server after the in-flight work is stopped.
  • Calling :cancel on a task already in cancelling or canceled is idempotent — returns 200 with the current status.

3.3.1 Two-Phase Cancel Protocol (v2.6+)

Client                          Server
  |                               |
  |-- POST /tasks/{id}:cancel --> |
  |                               | phase-1: state → cancelling
  |                               |   SSE: {"type":"status","state":"cancelling"}
  |<-- 200 {status:cancelling} -- |
  |                               | phase-2 (async): stop work, state → canceled
  |                               |   SSE: {"type":"status","state":"canceled"}

This two-phase approach lets SSE stream consumers observe the cancellation in progress, which is especially useful when the underlying work cannot be stopped instantaneously (e.g., long-running LLM calls, external API requests).

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. HTTP API Endpoints

4.1 Endpoint Stability Matrix

Endpoint Method Stability Description
/message:send POST stable Send a message to the connected peer
/message:recv GET stable Poll pending received messages
/stream GET stable SSE event stream
/.well-known/acp.json GET stable AgentCard capability declaration
/tasks GET stable List tasks; supports pagination via page_size/after/status (v2.40+, requires capabilities.tasks_pagination=true)
/tasks POST stable Create a new task
/tasks/{id} GET stable Get task by ID
/tasks/{id} PUT stable Update task state/artifact
/tasks/{id}:cancel POST stable Cancel a task
/tasks/{id}:continue POST stable Resume input_required task
/skills/query POST stable Query peer capability skills
/peers GET stable List connected peers
/peer/{id} GET stable Get single peer info
/peer/{id}/send POST stable Send to a specific peer
/peers/connect POST stable Connect to a new peer via acp:// link
/discover GET experimental List mDNS-discovered LAN peers
/status GET stable Relay status and uptime

4.2 /message:send — Required Field Validation

POST /message:send body (JSON):

Field Required? Validation
role MUST Must be "user" or "agent". Missing or other value → 400 ERR_INVALID_REQUEST
parts / text / content MUST At least one of these must be present. Missing → 400 ERR_INVALID_REQUEST
message_id Optional Auto-generated if absent
task_id Optional Must reference an existing task if provided
context_id Optional Free-form string grouping identifier

Implementation note: role validation was added in v0.9. Implementations upgrading from v0.8 MUST add explicit role validation and MUST NOT silently default to "user".


5. AgentCard

Every ACP agent exposes its capabilities via a well-known endpoint:

GET /.well-known/acp.json   [stable]

5.1 AgentCard Schema

{
  "name":            "MyAgent",
  "acp_version":     "2.8.0",
  "timestamp":       "2026-03-28T07:00:00Z",
  "skills":          [{"id": "summarize", "name": "summarize"}],
  "transport_modes": ["p2p", "relay"],
  "extensions": [
    {"uri": "acp:ext:hmac-v1",  "required": false, "params": {"scheme": "hmac-sha256"}},
    {"uri": "acp:ext:mdns-v1",  "required": false, "params": {}},
    {"uri": "acp:ext:h2c-v1",   "required": false, "params": {}}
  ],

  "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",
    "supported_transports":  ["http", "ws"]
  },

  "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"
  }
}

5.2 Top-Level AgentCard Fields

Field Type Stability Description
name string stable Human-readable agent name
acp_version string stable ACP protocol version implemented
timestamp string stable ISO-8601 UTC timestamp of card generation
skills object[] stable List of {id, name} skill descriptors
transport_modes string[] stable v2.4+ Routing modes supported by this node. Values: "p2p" (direct peer-to-peer), "relay" (HTTP relay fallback). Default: ["p2p", "relay"]. Absent means ["p2p", "relay"]. See §5.4.
capabilities object stable Protocol capability flags (see §5.3)
identity object|null stable Ed25519 public key block, or null
trust object stable HMAC signing configuration
auth object stable Supported auth schemes
endpoints object stable Endpoint path map
availability object experimental v1.2+ heartbeat/cron availability metadata
extensions object[] stable v2.8+ URI-identified extension declarations. Always present (empty [] when none). See §5.5.
limitations string[] experimental v2.7+ declared limitations (what the agent CANNOT do)
supported_interfaces string[] experimental v2.5+ interface groups implemented

5.3 Capability Flags

Flag Type Stability Description
streaming bool stable SSE /stream endpoint available
push_notifications bool stable Agent can push unsolicited events
input_required bool stable Agent supports input_required task state
part_types string[] stable Supported Part types
max_msg_bytes int stable Maximum message size in bytes (default: 1,048,576)
query_skill bool stable /skills/query endpoint available
server_seq bool stable Outbound messages include server_seq
multi_session bool stable Multiple simultaneous peer connections supported
error_codes bool stable Error responses include error_code field
hmac_signing bool stable HMAC-SHA256 signing active
lan_discovery bool experimental mDNS LAN discovery active
context_id bool stable context_id field supported
identity string stable "ed25519" or "none"
supported_transports string[] stable Protocol bindings active on this node (v2.2+). Values: "http" (HTTP/1.1), "ws" (WebSocket), "h2c" (HTTP/2 cleartext). Absent means ["http"]. Note: This declares protocol bindings; for routing topology, see top-level transport_modes (v2.4+).
well_known_rfc8615 bool stable /.well-known/* endpoints include RFC 8615 headers (Cache-Control/Vary/X-Content-Type-Options) — v2.47+
tasks_pagination bool stable GET /tasks supports page_size/after/status pagination — v2.40+
message_priority bool stable message.priority field (critical/high/normal/low) supported — v2.43+
delivery_ack bool stable message.delivery_ack=true triggers explicit delivery acknowledgement — v2.43+

5.3.1 capabilities.groups — Structured Grouping (v2.46+)

Since v2.46, capability flags are also available as a structured groups object for semantic discovery. Flat flags remain for backward compatibility; consumers SHOULD prefer groups for negotiation, producers MUST keep flat flags populated.

{
  "capabilities": {
    "streaming": true,
    "multi_session": true,
    "groups": {
      "messaging": {
        "streaming":       true,
        "push":            false,
        "input_required":  true,
        "message_priority": true,
        "delivery_ack":    false
      },
      "tasks": {
        "cancelling":      true,
        "pagination":      true,
        "context_id":      true
      },
      "identity": {
        "ed25519":         true,
        "hmac":            true,
        "jwks":            true,
        "did":             false
      },
      "transport": {
        "sse":             true,
        "http2":           false,
        "p2p_direct":      true,
        "dcutr":           true,
        "relay_fallback":  true
      },
      "discovery": {
        "lan_mdns":        false,
        "skills_list":     true,
        "query_skill":     true
      }
    }
  }
}

Unknown group keys MUST be ignored by consumers (forward compatibility).

5.4 transport_modes — Routing Topology Declaration (v2.4+)

transport_modes is a top-level AgentCard field that declares which routing topologies this node supports.

Distinction: capabilities.supported_transports declares protocol bindings (e.g. HTTP/1.1, WebSocket). transport_modes declares routing topology (e.g. direct P2P, relay-mediated). They are orthogonal dimensions.

Valid values:

Value Meaning
"p2p" Agent supports direct peer-to-peer connections (WebSocket direct connect)
"relay" Agent supports relay-mediated message delivery (HTTP relay fallback)

Semantics:

  • Default (absent or ["p2p", "relay"]): agent supports both topologies; peer may choose
  • ["p2p"]: agent prefers/requires direct connection; relay not available (e.g. exposes public IP)
  • ["relay"]: agent is behind NAT/firewall and relay-only; P2P not possible (e.g. sandbox environment)

CLI flag: --transport-modes p2p,relay (comma-separated subset)

Example — relay-only sandbox agent:

{
  "name": "SandboxAgent",
  "transport_modes": ["relay"],
  ...
}

Example — P2P-only edge agent:

{
  "name": "EdgeAgent",
  "transport_modes": ["p2p"],
  ...
}

Receivers MUST treat transport_modes as advisory. Unknown values in the list MUST be ignored.

5.5 Extension Mechanism (v2.8+) — stable

ACP supports a lightweight, declarative extension system inspired by A2A's extension model but designed to remain minimal and registry-free.

5.5.1 Extension Object Schema

Each entry in the top-level extensions array is an Extension Object:

{
  "uri":      "acp:ext:hmac-v1",
  "required": false,
  "params":   {"scheme": "hmac-sha256"}
}
Field Type Required Description
uri string yes Unique URI identifying the extension
required bool no If true, clients that don't support this extension SHOULD abort. Default: false.
params object no Arbitrary key-value parameters for the extension. Default: {}.

5.5.2 URI Naming Convention

Namespace Format Example
ACP built-in acp:ext:<name>-v<version> acp:ext:hmac-v1
External / vendor Full HTTPS URL https://corp.example.com/ext/billing

Custom URIs SHOULD use a full HTTPS URL to ensure global uniqueness and documentation discoverability. Short acp:ext: URIs are reserved for officially defined ACP extensions.

5.5.3 Well-Known Built-in Extensions

URI Description Auto-registered when
acp:ext:hmac-v1 HMAC-SHA256 message signing --secret flag is set
acp:ext:mdns-v1 mDNS LAN peer discovery --advertise-mdns flag is set
acp:ext:h2c-v1 HTTP/2 cleartext transport --http2 flag is set

Built-in extensions are automatically registered in the relay based on runtime configuration. No explicit declaration is needed; they appear in the extensions array when the corresponding feature is active.

5.5.4 Semantics and Compatibility Rules

  1. Always present: The extensions array MUST always be present in the AgentCard response, even when empty ([]). This makes extension discovery unambiguous.

  2. Non-required default: required: false is the default. Receivers that do not recognise an extension MUST ignore it. This preserves full backward compatibility.

  3. Required extensions: When required: true, a receiver that does not understand the extension SHOULD treat the connection as incompatible (e.g. abort task submission). This is opt-in and not enforced by the relay itself.

  4. No registry: ACP does not maintain a central extension registry. URI uniqueness is the responsibility of the extension definer.

  5. Deduplication: If the same URI appears multiple times (e.g. from --extension and auto-registration), implementations MUST deduplicate by URI, keeping the first occurrence.

5.5.5 Discovery

Extensions are visible via:

  • GET /.well-known/acp.json — AgentCard's extensions array
  • GET /extensions — dedicated extensions list endpoint (same data)
curl http://localhost:7901/extensions
# {"extensions": [{"uri": "acp:ext:hmac-v1", "required": false, "params": {"scheme": "hmac-sha256"}}]}

5.5.6 CLI Flags

Flag Description
--extension URI[,required=true][,key=val...] Declare a single extension (repeatable). Supports per-extension params.
--extensions URI[,URI,...] Shorthand: declare multiple extensions by URI (comma-separated, no per-extension params).

Example:

# Declare a single custom extension with params
python3 acp_relay.py --name Agent --extension "https://corp.example.com/ext/billing,required=true,tier=pro"

# Declare multiple extensions shorthand
python3 acp_relay.py --name Agent --extensions "acp:ext:custom-v1,https://corp.example.com/ext/audit"

5.6 Forward Compatibility

Receivers MUST ignore unknown capability fields. A flag value of false or absent is equivalent.


6. 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 Stability Trigger
ERR_NOT_CONNECTED 503 stable No peer WebSocket connection
ERR_MSG_TOO_LARGE 413 stable Message exceeds max_msg_bytes
ERR_NOT_FOUND 404 stable Task, peer, or resource does not exist
ERR_INVALID_REQUEST 400 stable Missing required fields (role, parts), malformed body, invalid state transition
ERR_TIMEOUT 408 stable Sync wait timed out
ERR_INTERNAL 500 stable 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.


7. Optional Extensions

Extensions are opt-in fields that can be combined freely. Absence of any extension has no effect on core protocol operation.

7.1 HMAC-SHA256 Signing (v0.7) — stable

For closed deployments where both peers share a secret out-of-band:

Outbound: server appends sig to every message:

sig = HMAC-SHA256(secret, "{message_id}:{ts}").hexdigest()

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).

7.2 Ed25519 Identity (v0.8) — stable

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.

7.3 context_id (v0.7) — stable

Groups messages across multiple turns into a named conversation context:

{ "context_id": "ctx_xyz456", ... }

Receivers SHOULD use context_id to correlate related messages in multi-turn workflows. No server-side enforcement is required — it is a hint field.

7.4 mDNS LAN Discovery (v0.7) — experimental

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.

Experimental: LAN discovery behavior may change in v1.1. The /discover endpoint response format is considered stable; the underlying mDNS broadcast mechanism is not.


8. Task Event Sequence (v2.5) — stable

This section defines the normative ordering of SSE events over the Task lifecycle, the mandatory fields each event MUST carry, and complete wire-format examples.

Why this matters: Clients that consume GET /stream or GET /tasks/{id}:subscribe need a deterministic event sequence to drive state machines without polling. Implementations that omit required fields force clients to fall back to HTTP polling — defeating the purpose of SSE. This section formalises what was previously only implied by the implementation.

8.1 SSE Event Envelope

Every SSE event delivered via GET /stream is a JSON object with the following mandatory fields:

Field Type Constraint Description
type string MUST Event category: "status", "artifact", "message", "peer", "mdns"
ts string MUST ISO 8601 UTC timestamp of event emission
seq integer MUST Monotonically-increasing SSE sequence counter (global, per-server). Enables gap detection.
task_id string MUST (when event is Task-related) Associates the event with a specific Task. MUST be present for type=status and type=artifact.

seq vs server_seq: The message-level server_seq field (§1.2) is assigned to messages and increments per outbound message. The SSE-level seq field is a separate counter that increments for every SSE event (across all types). Both use monotonically increasing integers; they are independent counters.

8.1.1 SSE Wire Format

ACP uses named events for task-related SSE types:

event: acp.task.status
data: {"type":"status","ts":"2026-03-27T07:00:00Z","seq":1,"task_id":"task_abc123","state":"submitted"}

event: acp.task.artifact
data: {"type":"artifact","ts":"2026-03-27T07:00:05Z","seq":3,"task_id":"task_abc123","artifact":{...}}

All other types (message, peer, mdns) use plain data-only events:

data: {"type":"message","ts":"2026-03-27T07:00:01Z","seq":2,"message_id":"msg_xyz","role":"user","parts":[...]}

8.2 Task Lifecycle Event Sequence

For a Task progressing from submitted to completed, the server MUST emit SSE events in the following order:

1. type=status  state=submitted   ← Task accepted by server
2. type=status  state=working     ← Server/peer starts processing
3. type=artifact                  ← (optional) intermediate artifact(s)
4. type=status  state=completed   ← Task finished; final artifact attached if any

For other terminal outcomes, the sequence diverges after step 2:

submitted → working → failed                          (unrecoverable error)
submitted → working → input_required                  (peer needs more info)
submitted → working → cancelling → canceled           (explicit cancel via :cancel, v2.6+)

v2.6+ cancel path: The server MUST emit a cancelling SSE event before emitting the final canceled event. Clients that observe cancelling know the cancel is in-progress and MUST NOT re-send the cancel request. See §3.3.1 for the two-phase cancel protocol.

For input_required, once the client calls /tasks/{id}:continue, the sequence resumes:

input_required → working → completed | failed | canceled

8.3 Status Event Schema

type=status events MUST include:

Field Type Constraint Description
type string MUST "status"
ts string MUST ISO 8601 UTC timestamp
seq integer MUST Global SSE sequence counter
task_id string MUST Associated Task ID
state string MUST One of: submitted, working, completed, failed, input_required, cancelling, canceled
error string SHOULD (if state=failed) Human-readable error description
context_id string MAY Multi-turn context group (if task has context)

Complete status event examples:

// submitted
{
  "type":    "status",
  "ts":      "2026-03-27T07:00:00Z",
  "seq":     1,
  "task_id": "task_abc123",
  "state":   "submitted"
}

// working
{
  "type":    "status",
  "ts":      "2026-03-27T07:00:01Z",
  "seq":     2,
  "task_id": "task_abc123",
  "state":   "working"
}

// completed
{
  "type":    "status",
  "ts":      "2026-03-27T07:00:05Z",
  "seq":     4,
  "task_id": "task_abc123",
  "state":   "completed"
}

// failed
{
  "type":    "status",
  "ts":      "2026-03-27T07:00:03Z",
  "seq":     3,
  "task_id": "task_abc123",
  "state":   "failed",
  "error":   "Upstream service unavailable"
}

// input_required
{
  "type":    "status",
  "ts":      "2026-03-27T07:00:04Z",
  "seq":     3,
  "task_id": "task_abc123",
  "state":   "input_required"
}

// cancelling (v2.6+) — cancel in progress, will transition to canceled
{
  "type":    "status",
  "ts":      "2026-03-27T07:00:05Z",
  "seq":     4,
  "task_id": "task_abc123",
  "state":   "cancelling"
}

// canceled — cancel complete
{
  "type":    "status",
  "ts":      "2026-03-27T07:00:05Z",
  "seq":     5,
  "task_id": "task_abc123",
  "state":   "canceled"
}

8.4 Artifact Event Schema

type=artifact events MUST include:

Field Type Constraint Description
type string MUST "artifact"
ts string MUST ISO 8601 UTC timestamp
seq integer MUST Global SSE sequence counter
task_id string MUST Associated Task ID
artifact object MUST Artifact payload; MUST contain "parts" array
context_id string MAY Multi-turn context group (if task has context)

Complete artifact event example:

{
  "type":     "artifact",
  "ts":       "2026-03-27T07:00:04Z",
  "seq":      3,
  "task_id":  "task_abc123",
  "artifact": {
    "parts": [
      { "type": "text", "content": "Here is your summary: ..." }
    ]
  }
}

8.5 Message Event Schema

type=message events carry a message delivered to or from this agent:

Field Type Constraint Description
type string MUST "message"
ts string MUST ISO 8601 UTC timestamp
seq integer MUST Global SSE sequence counter
message_id string MUST Unique message identifier
role string MUST "user" or "agent"
parts array MUST One or more Part objects (see §2)
task_id string MAY Present if message is associated with a Task
context_id string MAY Multi-turn context group

Complete message event example:

{
  "type":       "message",
  "ts":         "2026-03-27T07:00:01Z",
  "seq":        2,
  "message_id": "msg_7a3f9c2b",
  "role":       "agent",
  "parts":      [{ "type": "text", "content": "Processing your request..." }],
  "task_id":    "task_abc123"
}

8.6 Full Lifecycle Example (SSE Wire Format)

A complete Task lifecycle for a summarization request — as it appears on the GET /stream wire:

event: acp.task.status
data: {"type":"status","ts":"2026-03-27T07:00:00Z","seq":1,"task_id":"task_abc123","state":"submitted"}

data: {"type":"message","ts":"2026-03-27T07:00:01Z","seq":2,"message_id":"msg_in01","role":"user","parts":[{"type":"text","content":"Summarize this document."}],"task_id":"task_abc123"}

event: acp.task.status
data: {"type":"status","ts":"2026-03-27T07:00:02Z","seq":3,"task_id":"task_abc123","state":"working"}

data: {"type":"message","ts":"2026-03-27T07:00:04Z","seq":4,"message_id":"msg_out01","role":"agent","parts":[{"type":"text","content":"Working on summary..."}],"task_id":"task_abc123"}

event: acp.task.artifact
data: {"type":"artifact","ts":"2026-03-27T07:00:06Z","seq":5,"task_id":"task_abc123","artifact":{"parts":[{"type":"text","content":"Summary: The document discusses..."}]}}

event: acp.task.status
data: {"type":"status","ts":"2026-03-27T07:00:06Z","seq":6,"task_id":"task_abc123","state":"completed"}

8.7 Conformance Requirements

Implementations MUST:

  1. Include type, ts, seq in every SSE event.
  2. Include task_id in every status and artifact event.
  3. Emit state=submitted before state=working.
  4. Not emit state=working (or any later state) before state=submitted.
  5. Not transition a terminal task (completed, failed, canceled) to any other state.
  6. Emit exactly one state=submitted event per Task lifetime.
  7. Use a strictly monotonically increasing seq counter (no resets, no gaps).
  8. Validate role field in /message:send requests — reject missing or invalid values with 400 ERR_INVALID_REQUEST. (v0.9+)
  9. Use timezone-aware datetime objects for all ts fields — never naive UTC. (v2.44+)
  10. Return RFC 8615 headers (Cache-Control: no-cache, no-store / Vary: Accept / X-Content-Type-Options: nosniff) on all /.well-known/* endpoints when capabilities.well_known_rfc8615=true. (v2.47+)

Implementations SHOULD:

  • Emit a state=completed or state=failed event as the final event in a Task's SSE stream.
  • Include error in state=failed events.
  • Populate capabilities.groups in AgentCard for structured capability discovery (v2.46+).
  • Support GET /tasks pagination parameters (page_size, after, status) when capabilities.tasks_pagination=true (v2.40+).

Implementations MAY:

  • Emit intermediate type=message events between working and terminal states.
  • Emit multiple type=artifact events for streaming/chunked results.
  • Support message.priority field for delivery ordering hints (v2.43+).
  • Declare delivery_ack=true to request explicit message acknowledgement (v2.43+).

9. Transport Bindings

ACP separates the message envelope (this document) from the transport layer. The same envelope is used across all bindings.

Binding Link Scheme Stability Description
A — WebSocket P2P acp:// stable Direct WebSocket (preferred)
B — stdio n/a stable Subprocess pipe
C — HTTP Relay acp+wss:// stable Cloudflare Worker relay fallback
D — HTTP/SSE http(s):// stable Standard HTTP with SSE streaming
E — TCP tcp:// experimental 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.


10. Multi-Session Peer Registry (v0.6) — stable

An ACP agent can maintain simultaneous connections to multiple peers.

10.1 Endpoints

Endpoint Method Stability Description
/peers GET stable List all connected peers with metadata
/peer/{id} GET stable Get single peer info (agent_card, link, stats)
/peer/{id}/send POST stable Send message to a specific peer (same body as /message:send)
/peers/connect POST stable Establish a new peer connection {"link": "acp://..."}

10.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":       { ... }
}

11. Skill Query API (v0.5) — stable

Enables runtime capability discovery:

POST /skills/query
{ "query": "summarize", "limit": 5 }

Response:

{
  "ok": true,
  "skills": [
    { "id": "summarize", "name": "summarize", "match_score": 0.95 }
  ]
}

AgentCard declares available skills in skills[]. query_skill: true capability flag indicates this endpoint is available.


12. CLI Reference (v0.9)

The reference implementation CLI flags (all stable unless noted):

Flag Default Description
--name ACP-Agent Agent display name (appears in AgentCard)
--port 7801 WebSocket listener port
--http-port 7901 HTTP API port
--skills "" Comma-separated skill IDs to advertise
--secret "" HMAC-SHA256 shared secret
--identity disabled Ed25519 identity; optional path to key file
--advertise-mdns off Broadcast via mDNS (experimental)
--relay "" acp+wss:// relay URL for Binding C
--max-msg-bytes 1048576 Maximum inbound message size
--version Print version and exit
--verbose / -v off Set log level to DEBUG
--config "" Load flag defaults from JSON/YAML file (CLI > config > defaults)

Port layout:

Port Protocol Purpose
7801 (default) WebSocket P2P peer connections (Binding A)
7901 (default) HTTP REST API + SSE stream

13. Package Distribution (v0.9)

Python

pip install acp-relay                   # installs acp-relay CLI + relay module
pip install "acp-relay[identity]"       # + Ed25519 support
pip install "acp-relay[dev]"            # + pytest/httpx for testing

Node.js

npm install acp-relay-client            # zero-dep client SDK

Source

git clone https://github.com/Kickflip73/agent-communication-protocol
cd agent-communication-protocol
pip install -e ".[dev]"

14. Versioning

ACP uses semantic versioning. The acp_version field in AgentCard declares the implemented version.

v1.0 stability promise: endpoints and fields marked stable will not change incompatibly in v1.x.

Compatibility guarantee: An ACP v1.0 implementation MUST:

  1. Accept connections from v0.5+ peers (unknown extension fields are silently ignored)
  2. Include acp_version: "1.0" in its AgentCard
  3. Return error_code in all error responses
  4. Reject /message:send requests with missing or invalid role with 400 ERR_INVALID_REQUEST

Breaking changes (requiring major version bump): changes to required envelope fields, removal of stable endpoints, changes to HTTP status code semantics.

Non-breaking changes (minor/patch): new optional fields, new endpoints, new capability flags.


15. Reference Implementation

The reference implementation is relay/acp_relay.py — a single Python file with one required dependency (websockets).

# Minimal start
pip install acp-relay
acp-relay --name "MyAgent"

# Full feature set
pip install "acp-relay[identity]"
acp-relay --name "MyAgent" \
  --secret "shared-key" \    # HMAC signing
  --identity \               # Ed25519 identity
  --advertise-mdns           # LAN discovery (experimental)

# Config file
acp-relay --config relay/examples/config.json

Compliance can be verified against any implementation using the compat test suite:

ACP_BASE_URL=http://localhost:7901 python3 tests/compat/run.py

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
v0.9 2026-03-21 CLI flags (--version/--verbose/--config), async SDK stdlib-only, 63 unit tests, role server validation, pip install acp-relay, acp-relay-client npm
v1.0 2026-03-21 Stability annotations, API surface freeze, v1.0 compatibility guarantee
v2.5 2026-03-27 §8 Task Event Sequence spec, SSE mandatory field completeness (type/ts/seq/task_id), full lifecycle examples, conformance requirements
v2.6 2026-03-27 cancelling intermediate state (§3.2), two-phase cancel protocol (§3.3.1), A2A #1684/#1680 gap analysis
v2.7 2026-03-28 supported_transports field, §5.4 transport_modes section, Appendix B A2A comparison table
v2.8 2026-03-28 Extension mechanism (§5.5): URI-identified extensions, acp:ext:* built-ins, required/optional semantics
v2.18 2026-03-30 Ed25519 identity + JWKS endpoint (/.well-known/jwks.json, RFC 7517), trust.signals[]
v2.20 2026-03-31 limitations[] structured format (LimitationObject), permanent/kind fields
v2.21 2026-03-31 PATCH /.well-known/acp.json for runtime limitations update; ?filter_limitations= query param
v2.24 2026-04-01 Peer card cache (GET /peers/{id}/card); AgentCard availability field
v2.29 2026-04-01 PATCH /skills/{id}/limitations — per-skill runtime limitations update
v2.40 2026-04-03 GET /tasks pagination (page_size/after/status filter); has_more/next_cursor response
v2.43 2026-04-03 message_priority extension (critical/high/normal/low); delivery_ack flag
v2.44 2026-04-04 datetime.utcnow() → timezone-aware (datetime.now(tz=UTC)) migration (Python 3.12)
v2.46 2026-04-04 capabilities.groups structured grouping (messaging/tasks/identity/transport/discovery)
v2.47 2026-04-04 RFC 8615 well-known headers (Cache-Control: no-cache/Vary: Accept/X-Content-Type-Options: nosniff) on all /.well-known/* endpoints; capabilities.well_known_rfc8615=true

Appendix B: Comparison with A2A v1.0

ACP and A2A target different deployment contexts:

A2A v1.0 ACP v1.0
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 — #883) websockets only
Identity Agent Passport System (full PKI) Self-sovereign Ed25519 (no PKI)
role validation Not enforced in SDK v1.0 Server-enforced (400 on missing/invalid)
message_id REQUIRED by spec, not enforced Optional, auto-generated if absent
Test suite ✅ ITK tests/compat/ (MUST-level assertions)
Streaming SSE SSE
Task states 6 (no cancelling intermediate) 6 (cancelling intermediate + no unknown, v2.6+)
Cancel semantics CancelTaskRequest undefined (#1684), no intermediate state (#1680) Two-phase cancel: cancellingcanceled; idempotent; fully specified (§3.3.1)
Install pip install a2a-sdk (heavy) pip install acp-relay (websockets only)
npm @google-a2a/a2a acp-relay-client

Spec version: v1.0 (updated v2.47) | Supersedes: core-v0.8.md | Reference impl: relay/acp_relay.py (v2.47.0)