ACP CLI Reference — v2.4¶
acp_relay.py is both the reference implementation and the command-line interface.
This document covers every flag, common usage patterns, and environment variables.
Synopsis¶
Flags¶
Meta (v0.9)¶
| Flag | Default | Description |
|---|---|---|
--version |
— | Print acp_relay.py <version> and exit |
--verbose / -v |
false |
Enable DEBUG-level logging. Default is INFO. |
--config <FILE> |
(none) | Load defaults from a JSON or YAML config file. CLI flags always override file values. See Config Files below. |
Core¶
| Flag | Default | Description |
|---|---|---|
--name <str> |
ACP-Agent |
Human-readable agent name, shown in AgentCard and peer lists |
--port <int> |
7801 |
WebSocket listen port. HTTP API auto-binds to port + 100 unless --http-port is set |
--http-port <int> |
--port + 100 |
Explicit HTTP API listen port. Useful in CI, containers, or hosts where port + 100 is unavailable |
--join <link> |
(none) | Connect to an existing peer immediately on startup. Accepts acp:// or acp+wss:// links |
--skills <csv> |
(none) | Comma-separated list of skill ids to advertise in AgentCard (e.g. summarize,translate) |
--inbox <path> |
/tmp/acp_inbox_<name>.jsonl |
Path to JSONL message persistence file |
--max-msg-size <bytes> |
1048576 |
Maximum inbound message size in bytes (1 MiB). Larger messages get ERR_MSG_TOO_LARGE. |
Network¶
| Flag | Default | Description |
|---|---|---|
--relay |
false |
Use public HTTP relay instead of direct P2P. Useful behind firewalls / K8s NAT. Produces acp+wss:// link instead of acp:// |
--relay-url <url> |
https://black-silence-11c4.yuranliu888.workers.dev |
Override the relay endpoint. Use this to self-host with relay/acp_worker.js |
Security — HMAC Signing (v0.7 / v1.1)¶
| Flag | Default | Description |
|---|---|---|
--secret <key> |
(none) | Enable HMAC-SHA256 message signing. Both peers must use the same key. Messages with wrong or missing signatures log a warning but are not dropped (warn-only). No extra packages required. |
--hmac-window <seconds> |
300 |
(v1.1) Replay-window for HMAC-signed messages. Inbound messages whose ts field is outside ±SECONDS of server clock are dropped (hard reject). Only active when --secret is set. Set 0 to disable. Recommended: 60–300 s. |
Security — Ed25519 Identity (v0.8)¶
| Flag | Default | Description |
|---|---|---|
--identity [path] |
(flag absent = disabled) | Enable Ed25519 self-sovereign identity. If no path given, uses ~/.acp/identity.json (auto-generated on first run, chmod 0600). Requires pip install cryptography. |
Discovery — mDNS (v0.7)¶
| Flag | Default | Description |
|---|---|---|
--advertise-mdns |
false |
Broadcast presence on LAN via UDP multicast (224.0.0.251:5354). Enables GET /discover endpoint. No external library required. |
Routing Topology — transport_modes (v2.4)¶
| Flag | Default | Description |
|---|---|---|
--transport-modes <modes> |
p2p,relay |
Comma-separated list of routing topologies this node supports. Valid values: p2p (direct peer-to-peer WebSocket) and/or relay (HTTP relay-mediated). Sets the top-level transport_modes field in the AgentCard (/.well-known/acp.json). Distinct from --relay (which forces relay transport) — this field declares capability, not preference. Invalid values are warned and silently ignored. |
Examples:
# Relay-only (sandbox / NAT-only environment)
python3 acp_relay.py --name "SandboxAgent" --transport-modes relay
# P2P-only (public IP, no relay needed)
python3 acp_relay.py --name "EdgeAgent" --transport-modes p2p
# Default (both, peer's choice)
python3 acp_relay.py --name "MyAgent" --transport-modes p2p,relay
Availability Metadata — Heartbeat/Cron Support (v1.2)¶
Opt-in availability block in the AgentCard for heartbeat- or cron-based agents
that wake on a schedule rather than running continuously.
Inspired by the gap identified in A2A protocol (issue #1667).
| Flag | Default | Description |
|---|---|---|
--availability-mode MODE |
(absent = no block) | Agent availability mode: persistent | heartbeat | cron | manual. When set, adds an availability object to the AgentCard. Use persistent to explicitly declare always-on. |
--heartbeat-interval SECONDS |
(none) | Wake interval in seconds. Sets interval_seconds and task_latency_max_seconds in the availability block. Only meaningful with --availability-mode heartbeat\|cron. |
--next-active-at ISO8601 |
(none) | ISO-8601 UTC timestamp of next scheduled wake (e.g. 2026-03-22T09:00:00Z). Callers can use this to set retry timers. |
last_active_at is auto-stamped to the relay's startup time — no manual flag needed.
Port Layout¶
| Purpose | Port | Notes |
|---|---|---|
| WebSocket (P2P) | --port (default 7801) |
Used for agent-to-agent WebSocket connections |
| HTTP API | --http-port or --port + 100 (default 7901) |
All REST endpoints (/message:send, /stream, etc.) |
Usage Patterns¶
Minimal — single peer, P2P¶
# Agent A — host
pip install websockets
python3 acp_relay.py --name "AgentA"
# → Your link: acp://1.2.3.4:7801/tok_xxxxx
# Send this link to Agent B
# Agent B — guest
python3 acp_relay.py --name "AgentB" --join "acp://1.2.3.4:7801/tok_xxxxx"
Custom port¶
python3 acp_relay.py --name "AgentA" --port 9000
# WebSocket: 9000 HTTP API: 9100
python3 acp_relay.py --name "AgentA" --port 9000 --http-port 19001
# WebSocket: 9000 HTTP API: 19001
Behind a firewall — relay fallback¶
python3 acp_relay.py --name "AgentA" --relay
# → Your link: acp+wss://black-silence-11c4.yuranliu888.workers.dev/acp/tok_xxxxx
python3 acp_relay.py --name "AgentB" --join "acp+wss://..."
Self-hosted relay¶
# Deploy relay/acp_worker.js to Cloudflare Workers, then:
python3 acp_relay.py --name "AgentA" --relay --relay-url "https://my-relay.example.com"
HMAC signing — closed deployment¶
# Both agents must use the same --secret
python3 acp_relay.py --name "AgentA" --secret "shared-team-key-2026"
python3 acp_relay.py --name "AgentB" --join "acp://..." --secret "shared-team-key-2026"
Messages with a wrong or absent sig field will log:
HMAC signing with replay-window (v1.1) — high-security deployment¶
# Tight 60-second replay window: replay attacks blocked immediately
python3 acp_relay.py --name "AgentA" --secret "shared-team-key-2026" --hmac-window 60
python3 acp_relay.py --name "AgentB" --join "acp://..." --secret "shared-team-key-2026" --hmac-window 60
Messages with a ts field outside ±HMAC_WINDOW seconds are hard-rejected and dropped:
Ed25519 identity — open federation¶
# Install optional dep
pip install cryptography
# First run — auto-generates ~/.acp/identity.json
python3 acp_relay.py --name "AgentA" --identity
# Subsequent runs — loads existing keypair
python3 acp_relay.py --name "AgentA" --identity
# Custom keypair location
python3 acp_relay.py --name "AgentA" --identity /secrets/acp-identity.json
The AgentCard will include an identity block:
{
"capabilities": { "identity": "ed25519" },
"identity": {
"scheme": "ed25519",
"public_key": "<base64url>"
}
}
Heartbeat agent — cron/scheduled deployment (v1.2)¶
Agents that wake on a schedule (e.g. every hour via cron) can advertise their scheduling metadata so callers know when to expect a response.
# Cron agent: wakes every hour, next wake at 08:00 UTC
python3 acp_relay.py \
--name "HourlyAgent" \
--availability-mode cron \
--heartbeat-interval 3600 \
--next-active-at "2026-03-22T08:00:00Z"
The AgentCard will include:
{
"capabilities": { "availability": true },
"availability": {
"mode": "cron",
"interval_seconds": 3600,
"task_latency_max_seconds": 3600,
"next_active_at": "2026-03-22T08:00:00Z",
"last_active_at": "2026-03-22T07:00:00Z"
}
}
Callers can read next_active_at to set retry timers, and use
task_latency_max_seconds to bound their timeout expectations.
This is the first Agent communication protocol to support scheduling metadata natively.
Live availability update (PATCH) — v1.2¶
A running heartbeat agent can stamp its wake times without restarting the relay
by calling PATCH /.well-known/acp.json on the local HTTP port (--http-port, or --port + 100 by default):
# On each cron wake: update last_active_at + compute next_active_at
NEXT=$(date -u -d '+1 hour' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || \
date -u -v+1H +%Y-%m-%dT%H:%M:%SZ) # macOS fallback
NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
curl -s -X PATCH http://localhost:8100/.well-known/acp.json \
-H 'Content-Type: application/json' \
-d "{\"availability\":{\"last_active_at\":\"$NOW\",\"next_active_at\":\"$NEXT\"}}"
Response:
{
"ok": true,
"availability": {
"mode": "cron",
"interval_seconds": 3600,
"task_latency_max_seconds": 3600,
"last_active_at": "2026-03-22T09:00:00Z",
"next_active_at": "2026-03-22T10:00:00Z"
}
}
PATCH rules:
- Merge semantics — only the fields you send are updated; others are preserved.
- Allowed fields: mode, interval_seconds, next_active_at, last_active_at, task_latency_max_seconds.
- Unknown fields → 400 Bad Request.
- Invalid mode value → 400 Bad Request.
Full feature set¶
pip install websockets cryptography
python3 acp_relay.py \
--name "FullAgent" \
--port 8000 \
--skills "summarize,translate,search" \
--secret "shared-key" \
--identity \
--advertise-mdns \
--inbox /var/log/acp/messages.jsonl
LAN multi-agent mesh¶
# All agents on the same LAN
python3 acp_relay.py --name "Agent1" --advertise-mdns
python3 acp_relay.py --name "Agent2" --advertise-mdns
python3 acp_relay.py --name "Agent3" --advertise-mdns
# Discover peers from any agent
curl http://localhost:7901/discover
# → {"peers": [{"name": "Agent1", "link": "acp://..."}, ...]}
Advertise specific skills¶
python3 acp_relay.py --name "Summarizer" --skills "summarize,bullet-points,tldr"
# Peer can query available skills
curl -X POST http://localhost:7901/skills/query \
-H "Content-Type: application/json" \
-d '{"query": "summarize", "limit": 3}'
Output¶
On startup, acp_relay.py prints:
INFO 🔗 Your link: acp://1.2.3.4:7801/tok_ba366fcab78d4d61
INFO 🌐 HTTP API: http://0.0.0.0:7901
INFO 📬 Inbox: /tmp/acp_inbox_AgentA.jsonl
INFO ⏳ Waiting for peer...
When a peer connects:
When --advertise-mdns is set:
When --secret is set:
When --secret + --hmac-window 60 is set:
When --identity is set:
Quick API Reference¶
Once running, the HTTP API is available at http://localhost:<http-port>:
# Send a message
curl -X POST http://localhost:7901/message:send \
-H "Content-Type: application/json" \
-d '{"text": "Hello"}'
# Stream events (SSE)
curl http://localhost:7901/stream
# AgentCard
curl http://localhost:7901/.well-known/acp.json
# List peers
curl http://localhost:7901/peers
# List tasks
curl http://localhost:7901/tasks
# LAN discovery (requires --advertise-mdns)
curl http://localhost:7901/discover
Full API reference: spec/core-v0.8.md §4.
Config Files¶
All CLI flags (except --version) can be set in a JSON or YAML config file
and loaded with --config <path>. CLI flags always take precedence over
file values.
Supported keys¶
Keys use the same names as CLI long flags (hyphens, not underscores):
| Key | Type | Example |
|---|---|---|
name |
string | "MyAgent" |
port |
int | 8000 |
join |
string | "acp://1.2.3.4:7801/tok_xxx" |
relay |
bool | true |
relay-url |
string | "https://my-relay.example.com" |
skills |
string (CSV) | "summarize,translate" |
inbox |
string | "/var/log/acp/messages.jsonl" |
max-msg-size |
int | 2097152 |
secret |
string | "shared-key" |
hmac-window |
int | 120 |
advertise-mdns |
bool | true |
identity |
string | "~/.acp/identity.json" |
availability-mode |
string | "heartbeat" |
heartbeat-interval |
int | 3600 |
next-active-at |
string | "2026-03-22T09:00:00Z" |
verbose |
bool | true |
JSON example (relay/examples/config.json)¶
YAML example (relay/examples/config-secure.yaml)¶
# HMAC + Ed25519 + LAN discovery
name: SecureAgent
skills: summarize
secret: replace-with-your-shared-secret
advertise-mdns: true
identity: ~/.acp/identity.json
verbose: false
YAML note: The built-in YAML parser supports only JSON-compatible flat key-value pairs (no nested objects, no multi-line strings). For complex YAML, use JSON format instead.
Precedence order¶
Docker (v1.2)¶
An official Dockerfile and docker-compose.yml are included for containerised deployments.
Quick start¶
# Build base image (no optional deps — pure stdlib + websockets)
docker build -t acp-relay .
# Build full image (+ Ed25519 identity support)
docker build --build-arg EXTRAS=full -t acp-relay:full .
# Run a named agent
docker run --rm -p 8000:8000 -p 8100:8100 acp-relay --name MyAgent
# Run with HMAC signing + replay-window
docker run --rm -p 8000:8000 -p 8100:8100 acp-relay \
--name MyAgent --secret mysecret --hmac-window 120
# Run as heartbeat/cron agent
docker run --rm -p 8000:8000 -p 8100:8100 acp-relay \
--name HourlyAgent --availability-mode cron --heartbeat-interval 3600
# Persist Ed25519 keypair across restarts
docker run --rm -p 8000:8000 -p 8100:8100 \
-v acp-identity:/root/.acp \
acp-relay:full --name MyAgent --identity
Health check¶
The image includes a built-in HEALTHCHECK that polls /.well-known/acp.json every 30 s:
Multi-agent local testing¶
# Start Alice + Bob (Bob auto-connects to Alice)
docker-compose up
# Send a message from Bob → Alice via HTTP API
curl -X POST http://localhost:8101/message:send \
-H 'Content-Type: application/json' \
-d '{"role":"user","text":"Hello Alice!"}'
Port mapping¶
| Port | Purpose |
|---|---|
8000 |
WebSocket P2P (default --port) |
8100 |
HTTP API + AgentCard (--http-port, default --port + 100) |
Pass any acp_relay.py flag after the image name to override defaults.
Compatibility¶
| ACP Version | Python | Required packages | Optional packages |
|---|---|---|---|
| v1.2 (current) | 3.9+ | websockets |
cryptography (Ed25519) |
| v1.1 | 3.9+ | websockets |
cryptography (Ed25519) |
| v1.0 | 3.9+ | websockets |
cryptography (Ed25519) |
| v0.8 | 3.9+ | websockets |
cryptography (Ed25519) |
| v0.7 | 3.9+ | websockets |
— |
| v0.5–v0.6 | 3.9+ | websockets |
— |
Test any version against the compat suite: