Skip to content

CHANGELOG

All notable changes to ACP (Agent Communication Protocol) are documented here.

Format: Semantic VersioningMAJOR.MINOR.PATCH-status Dates: Asia/Shanghai (UTC+8)


v3.17.2 — Public Launch Polish (2026-06-12)

Added

  • CodeQL code scanning for Python, JavaScript/TypeScript, and Go.
  • OpenSSF Scorecard workflow with SARIF upload and published scorecard results.
  • SUPPORT.md, CITATION.cff, and expanded English contributor guidance for public collaboration.

Changed

  • README badges now show live release, CI, CodeQL, docs, Docker, license, and OpenSSF Scorecard status.
  • Development extras now include MkDocs dependencies so make docs works after pip install -e ".[dev]".
  • Root and Node SDK license files now use the full Apache License 2.0 text, enabling GitHub SPDX detection.
  • Security policy links now point directly to private vulnerability reporting and the published security model.

Fixed

  • Runtime UTC timestamp generation now uses timezone-aware datetime.now(timezone.utc).
  • Identity documentation examples now avoid deprecated datetime.utcnow().

v3.17.1 — Public Release Readiness (2026-06-12)

Added

  • GitHub Actions CI for Python, Node, Go, Rust, and MkDocs.
  • Dependabot configuration, issue templates, pull request template, security policy, code of conduct, editor config, and a top-level Makefile.

Changed

  • Python package metadata now matches the current relay version and builds clean sdist/wheel artifacts.
  • CI now runs a stable release smoke suite covering certification, integration, reliable messaging, hybrid identity, and Python SDK tests.

Fixed

  • Relay HTTP startup no longer performs reverse-DNS lookup during bind, avoiding local startup hangs.
  • Docker images now keep the HTTP API bound to 0.0.0.0 even when custom relay flags replace CMD.
  • Push webhook delivery now executes from the SSE broadcast path instead of unreachable code.
  • Reliable messaging tests use the active Python interpreter and explicit HTTP ports for deterministic CI runs.
  • MkDocs strict builds now pass after fixing stale relative links.

v2.95.0 — Skill-Scoped Trust Scores (2026-04-10)

Added

  • _compute_skill_trust_scores() — computes per-skill trust scores from bilateral IR evidence
  • Algorithm: base=0.3 + min(unique_callers,10)*0.04 + min(bilateral_count,50)*0.005; clamped to [0.0, 1.0]
  • Returns {} when no bilateral IR records exist (no evidence → no score)
  • A2A #1717 community convergence: skill-scoped trust for granular authorization
  • aeoess APS v1.37.0 importBilateralEvidence() per-skill accumulation pattern aligned
  • GET /trust/skill-scores — new endpoint returning per-skill trust scores dict
  • trust_scores: {"<skill_id>": float, ...}, method: "skill_scoped_v1", algorithm block
  • Returns {} trust_scores when no IR evidence
  • governance_metadata.trust_scores — dict (skill_id → float) embedded in GM block
  • trust_score_method: "skill_scoped_v1" declared alongside
  • Backward compat: global trust_score retained; updated to per-skill average when IR evidence present
  • QuerySkill skill_trust_score fieldPOST /skills/query response includes per-skill score
  • null when no bilateral IR evidence for that skill_id
  • float [0.0, 1.0] when IR evidence exists
  • capabilities.skill_scoped_trust_scores: true in AgentCard
  • endpoints.skill_trust_scores: /trust/skill-scores in AgentCard
  • Tests SS01–SS16 (tests/test_skill_scoped_trust_v295.py) — 16/16 PASS
  • SS01–SS04: VERSION + capability/endpoint flags + backward compat
  • SS05–SS07: empty no-IR response, schema, algorithm fields
  • SS08–SS11: score computation (single skill, multi-skill, clamping, skill_count)
  • SS12–SS14: QuerySkill skill_trust_score field (null/populated)
  • SS15–SS16: governance_metadata integration + global trust_score backward compat

Changed

  • VERSION: 2.94.0 → 2.95.0
  • test_principal_diversity_v294.py: PD01/PD15 version assertions relaxed to >= 2.94.0 (version-agnostic)
  • _build_governance_metadata(): always populates trust_scores and trust_score_method

Notes

  • Backward compatibility preserved: global trust_score scalar still present in GM block
  • Empty dict semantics: {} trust_scores = "no bilateral IR evidence yet" (not a 404)
  • No breaking changes: all existing /trust/ endpoints unchanged

v2.94.0 — Principal Diversity Defense for Bilateral IR (2026-04-10)

Added

  • _principal_diversity_score(peer_id) — new function implementing the colluding-pair inflation defense from aeoess adversarial-trust-fixture.json (A2A #1718 gist:bdcd1dd0512661138ff7a71bf1e946c7)
  • Parameters aligned with aeoess model: concentration_threshold=0.60, penalty_weight=0.10, min_records_for_analysis=3
  • Returns: concentration_ratio, penalty_applied, diversity_weight, effective_bilateral_count, top_counterparty, unique_counterparties
  • Formula: effective = normal_count + excess_count * 0.10 when top_counterparty_ratio > 0.60
  • GET /trust/bilateral-ir/diversity — new endpoint exposing principal diversity analysis per peer
  • ?peer_id=<did> — required query param
  • 400 on missing peer_id, 404 on unknown peer
  • Response includes full defense_params block with reference to aeoess gist
  • _bilateral_ir_adj() enhanced — now returns 4-tuple (adj, count, merkle_root, diversity_dict); effective_bilateral_count (penalty-adjusted) used in threshold calculation instead of raw count
  • capabilities.principal_diversity_defense: true in AgentCard
  • endpoints.bilateral_ir_diversity: /trust/bilateral-ir/diversity in AgentCard
  • principal_diversity sub-field in /trust/signals/capability-token response factors
  • POST /trust/bilateral-ir/inject — test helper endpoint for seeding bilateral IR records in tests
  • Tests PD01–PD16 (tests/test_principal_diversity_v294.py) — 16/16 PASS
  • PD01–PD04: VERSION + capability/endpoint flags
  • PD05–PD07: 400/404 error handling
  • PD08–PD10: defense params values and response schema
  • PD11–PD14: penalty logic (no-penalty below threshold, penalty above, effective count formula, insufficient records)
  • PD15–PD16: version in response, backward compat

Changed

  • _bilateral_ir_adj() return type: 3-tuple → 4-tuple (backward compat: callers updated)
  • VERSION: 2.93.0 → 2.94.0

Security

  • Colluding-pair inflation attack (AF-002 adversarial scenario) now has a concrete defense
  • Peer concentrating >60% of bilateral interactions with a single counterparty receives diversity_weight < 1.0
  • effective_bilateral_count replaces raw count in tier threshold calculation — prevents artificial T3 inflation from exclusively mutual interactions

v2.93.0 — ACP-RFC-004: Decentralized Agent Identity Without CA (2026-04-09)

Added

  • ACP-RFC-004: docs/rfc/identity-without-ca.md — Full specification for self-sovereign Ed25519 agent identity
  • Problem analysis: 6 failure modes of the central CA approach (A2A #1672)
  • Three-layer identity model: who (Ed25519) → what they can do (capability_token) → what happened (bilateral IR)
  • Self-signed AgentCard: card_signature = Ed25519 over canonical card JSON
  • POST /identity/verify-card cross-instance verification (no CA, no network lookup, no prior relationship)
  • Multi-provider DID support: did:key, did:web, did:acp — independently verifiable
  • Integration with credential_lifecycle (RFC-003) as CRL/OCSP replacement
  • Security analysis: 5 threats + mitigations (spoofing/key compromise/replay/MITM/Sybil)
  • Python reference implementation: ~50 lines for full key generation + sign + verify
  • Comparison table: 9 dimensions vs A2A #1672 CA approach
  • Relation to IETF RFC 8032/8785, W3C DID Core, draft-prakash-aip
  • A2A community engagement: docs/community/a2a-1712-comment.md — Ready-to-post comment for A2A #1712
  • Addresses WTRMRK proposal by 64R3N
  • Responds to aeoess's three-layer framework (who/what/what happened)
  • Concrete ACP implementation references for all three layers
  • CA vs self-signed comparison table

Changed

  • VERSION: 2.92.0 → 2.93.0

ACP vs A2A Competitive Position Update

  • ACP Ed25519 self-signed identity (v2.85, default-on) vs A2A #1672 central CA (still proposed, no implementation) — 3.5 month lead
  • ACP three-layer model (identity + capability_token + bilateral IR) — complete, no A2A equivalent
  • RFC-004 published; A2A discussion in #1672 (414+ comments) and #1712 still open

v2.85.0 — Ed25519 Identity Default-On + GET /protocol-binding/compatibility (2026-04-08)

Changed (Breaking-safe)

  • Ed25519 identity is now auto-generated by default — no --identity flag required
  • On first run, ~/.acp/identity.json is created automatically (persisted across restarts)
  • --no-identity new flag: disables auto-generation for embedded/testing scenarios
  • --identity <path> still works for custom keypair path (backward compatible)
  • capabilities.identity_default=True in AgentCard when identity loaded

Added

  • GET /protocol-binding/compatibility — multi-protocol compatibility matrix
  • 6 entries: websocket (native), http/sse (native), a2a (partial), anp (partial), mcp (none), grpc (none)
  • aligned_sections[] for partial entries (e.g. A2A §2/§3/§5.8)
  • acp_binding URI + version fields
  • POST returns 405
  • endpoints.protocol_binding_compatibility in AgentCard
  • Aligns with A2A #1723 (SLIMRPC protocol declaration discussion)
  • Tests: ID-01..ID-10 (identity default) + PBC-01..PBC-10 (compat matrix) = 20/20 PASS

v2.84.0 — protocol_bindings[] AgentCard Array + client_msg_id Idempotency (2026-04-08)

Added

  • protocol_bindings[] — A2A §5.8 aligned plural array on AgentCard top-level; backward-compatible singular protocol_binding retained; capabilities.protocol_bindings_array=True
  • client_msg_id — accepted as alias for message_id on /message:send and /peer/{id}/send; echoed in all send responses; dedup cache covers both forms (ANP §3.2 borrow)
  • tests/test_protocol_binding_v279.py extended; tests/test_client_msg_id_v284.py added (CM1–CM4)

Fixed (BUG-056)

  • /peer/{id}/send was silently ignoring client_msg_id field — not echoed, not deduped. Fixed to match /message:send behaviour.

v2.82.0 — evidence_stream: SSE Task Lifecycle Subscription (2026-04-08)

Added

  • GET /tasks/{id}/evidence-stream — SSE subscription to task lifecycle events (evidence anchoring, status transitions, artifact appends)
  • Events: evidence.anchored, task.status, task.artifact
  • capabilities.evidence_stream=True; endpoints.evidence_stream in AgentCard

v2.81.0 — task_evidence: Lifecycle Evidence Anchoring (2026-04-08)

Added

  • POST /tasks/{id}/evidence — anchor arbitrary evidence payloads to a task
  • GET /tasks/{id}/evidence — retrieve all anchored evidence records
  • Evidence fields: evidence_type, payload, anchored_at, anchor_id
  • capabilities.task_evidence=True

v2.80.0 — heartbeat_period_ms: AgentCard Heartbeat Interval Declaration (2026-04-08)

Added

  • heartbeat_period_ms field on AgentCard top-level — declares relay's preferred heartbeat polling interval (default 30 000 ms)
  • capabilities.heartbeat_period_ms=True

v2.79.0 — GET /protocol-binding + AgentCard protocol_binding Declaration (2026-04-07)

Added

  • _PROTOCOL_BINDING = "urn:acp:binding:p2p-relay/v1" global constant
  • GET /protocol-binding — returns {binding_uri, transport, addressing, nat_traversal, sse, ws, spec_url}
  • AgentCard top-level protocol_binding field; capabilities.protocol_binding=True
  • PB-01..PB-25 = 25/25 PASS
  • Aligns with A2A PR #1619 (merged 2026-04-07) §5.8 URI-based CPB identification

v2.78.0 — SINT Token Revocation: POST /trust/signals/capability-token/revoke (2026-04-07)

Added

  • POST /trust/signals/capability-token/revoke — revoke a SINT token by JTI with reason + revoked_by
  • GET /revocations — list all revoked tokens
  • /fixtures/validate Check 6: revocation status (highest priority check)
  • 409 ERR_ALREADY_REVOKED on duplicate revoke; forward revocation (unknown JTI) supported
  • Completes SINT capability quad: v2.74 declare + v2.75 fixture + v2.77 validate + v2.78 revoke
  • RV-01..RV-30 = 30/30 PASS; full regression 157/157 PASS

v2.77.0 — SINT Dynamic Token Validation: POST /trust/signals/capability-token/fixtures/validate (2026-04-07)

Added

  • POST /trust/signals/capability-token/fixtures/validate — 5-check validation pipeline: expiry (TOCTOU) → scope → skill_id → subject → required_fields
  • Priority deny ordering with deny_details audit trail
  • 405 on GET (must use POST)
  • TV-01..TV-30 = 30/30 PASS
  • Aligns with A2A #1716 @pshkv SINT PR#111 runtime enforcement

v2.76.0 — effective_tier Factor 5: bilateral_ir_adj from Local IR Log (2026-04-07)

Added

  • Factor 5 in _compute_effective_tier(): bilateral_ir_adj derived from local bilateral IR record log
  • _bilateral_ir_adj(peer_id) → int — counts recent verified bilateral IRs; +1 for ≥2 clean, -1 for ≥1 flagged
  • Five-factor formula: max(tier_rule, depth_floor, base + combined_adj5)
  • factors{} extended with bilateral_ir_count, bilateral_ir_adj

v2.75.0 — SINT Canonical Authorization Fixture: GET /trust/signals/capability-token/fixtures (2026-04-07)

Added

  • GET /trust/signals/capability-token/fixtures — canonical authorization fixture for SINT token generation
  • Fixture fields: issuer_did, subject_did, skill_id, scope, exp_ttl_s, required_fields[]
  • capabilities.capability_token_fixtures=True

v2.74.0 — SINT Capability Token Declaration: GET /trust/signals/capability-token (2026-04-07)

Added

  • GET /trust/signals/capability-token — relay's SINT capability token declaration
  • Returns: token_type, algorithm, issuer_did, supported_scopes[], max_ttl_s
  • capabilities.capability_token=True; endpoints.capability_token in AgentCard
  • Aligns with A2A #1716 SINT per-invocation token design (@pshkv)

v2.73.0 — GET /agent-limitations/schema: Typed JSON Schema for agent_limitations (2026-04-07)

Added

  • GET /agent-limitations/schema — returns canonical JSON Schema for agent_limitations object
  • Schema covers all limitation fields with type, description, and default values
  • capabilities.agent_limitations_schema=True

v2.72.0 — GET /trust/bilateral-ir/log: Queryable Bilateral IR Record Log (2026-04-07)

Added

  • GET /trust/bilateral-ir/log — queryable log of all bilateral interaction records
  • Filters: ?agent_did=, ?since=, ?limit=, ?verified_only=true
  • Returns: {records[], count, total}
  • Aligns with A2A #1718 importBilateralEvidence() interface

v2.71.0 — security_posture as 13th Trust Signal Type (2026-04-07)

Added

  • security_posture as signal type #13 in _build_trust_signals()
  • Fields: tls_enforced, auth_required, rate_limit_active, max_payload_enforced
  • capabilities.security_posture_signal=True

v2.70.0 — trust.signals Severity + Category Metadata + GET /trust/signals/schema (2026-04-07)

Added

  • TRUST_SIGNAL_SCHEMA constant: canonical severity (critical/high/medium/low) and category (identity/integrity/authorization/discovery/attestation) for all 12 signal types
  • Each signal in _build_trust_signals() now includes severity + category fields
  • GET /trust/signals?category=<cat>&severity=<sev> filter params
  • GET /trust/signals/schema — returns static canonical schema for all signal types
  • SC-1..15 = 15/15 PASS; TS-1..14 regression = 14/14 PASS

v2.68.0 — trust.signals[] v2: 4 New Signal Types + GET /trust/signals (2026-04-06)

Added

  • Signal types #9–#12 added to _build_trust_signals() (total: 12):
  • bilateral_ir — bilateral signed IR (v2.59)
  • capability_token — SINT-format per-invocation token (v2.57)
  • wtrmrk — WTRMRK sequence-root trust factor (v2.62)
  • external_token — cross-protocol SINT verification (v2.63)
  • GET /trust/signals — full trust signal inventory, filterable by ?type= and ?enabled=

Fixed (BUG-054)

  • NameError in _build_trust_signals(): _skills global was referenced before definition

v2.67.0 — Direct Message Mode: POST /message/send (2026-04-06)

Added

  • POST /message/send — returns Message directly (no Task created); A2A v1.0.0 SendMessageResponse { oneof { Task task; Message message; } } alignment
  • Response: {ok, type:'message', message_id, role, parts[], timestamp}
  • Optional: context_id, task_id, metadata echoed back
  • role required (user|agent); parts or text required; 415 on non-JSON
  • DM-1..14 = 14/14 PASS

v2.66.0 — Task rejected Terminal State — A2A v1.0.0 Alignment (2026-04-06)

Added

  • TASK_REJECTED = 'rejected' constant; added to TERMINAL_STATES
  • POST /tasks/{id}:agent-reject — agent-initiated rejection for any task
  • GET /tasks?status=rejected filter support
  • T3 :reject endpoint: confirmation_pendingrejected (was failed)
  • capabilities.rejected_state=True; endpoints.agent_reject in AgentCard
  • RJ-1..10 = 9/10 PASS (1 skip: T3 skill not in test-mode)

v2.65.0 — POST /ir/import-evidence: APS-Compatible Reputation Update Payload (2026-04-06)

Added

  • POST /ir/import-evidence — accept external bilateral IR, verify relay_signature + caller_signature (Ed25519), return trust_delta(-1/0/+1) + freshness_hint
  • GET /ir/imported-evidence — list imported evidence, filter by agent_did + limit
  • _verify_ir_signatures() — dual Ed25519 verification with error collection
  • _build_reputation_update() — APS v1 reputation_update builder
  • capabilities.import_evidence=True
  • IE-1..20 = 20/20 PASS
  • Aligns with A2A #1718 importBilateralEvidence() interface (@aeoess)

v2.64.0 — Bilateral IR Test Vectors + Governance Live Endpoint (2026-04-06)

Added

  • GET /ir/test-vectors — 4 canonical deterministic test vectors for cross-implementation IR verification (A2A #1718 @aeoess)
  • tv-ir-001: bilateral IR, both Ed25519 signatures valid
  • tv-ir-002: unilateral IR, relay-only signature
  • tv-ir-003: tampered payload, caller_signature_valid=false (negative test)
  • tv-ir-004: did:key format in canonical payload
  • GET /governance/live-endpoint — APS live governance endpoint declaration
  • ITV-1..4 = 4/4 PASS

v2.63.0 — Cross-Protocol Token Verify: GET /identity/did-key + POST /verify/external-token (2026-04-06)

Added

  • GET /identity/did-key — relay's did:key + public key material (algorithm, multicodec, hex, base64)
  • Multicodec [0xed, 0x01] + base58btc — W3C spec, APS v1.32.0 toDIDKey() and SINT keyToDid() compatible
  • POST /verify/external-token — SINT-format token verify (7-step: fields → expiry → decode → did:key → canonical → sig → optional MoltTrust)
  • capabilities.external_token_verify = bool(_ed25519_private) (requires --identity)
  • ETV-1..16 = 16/16 PASS

v2.62.0 — wtrmrk_sequence_root Factor 4: Attestation History Adjustment in effective_tier (2026-04-06)

Added

  • _query_wtrmrk(sequence_root: str) → int | None — queries WTRMRK registry (api.moltrust.ch/capability-token/validate), 300s TTL cache, fail-closed (returns None on any exception)
  • _wtrmrk_to_adj(grade: int | None) → int — maps WTRMRK grade to attestation_history_adjustment:
  • Grade None (query failure) → 0 (neutral, fail-closed)
  • Grade 0 (unknown on-chain) → +1 (raise floor)
  • Grade 1–2 (basic/established) → 0 (neutral)
  • Grade 3 (hardware-attested, long track record) → -1 (may lower floor)
  • POST /tasks body: metadata.wtrmrk_sequence_root — optional Merkle commitment; if present, triggers Factor 4 computation during tier check
  • GET /skills/{id}/effective-tier: new query param wtrmrk_sequence_root=<base64url> — activates Factor 4 in the response
  • AgentCard capabilities.wtrmrk_attestation: True
  • tests/test_wtrmrk_attestation.py — WA-1..14 (14/14 PASS)

Changed

  • _compute_effective_tier(skill_obj, peer_id, wtrmrk_sequence_root=None) — fourth factor added
  • New factors{} fields: wtrmrk_sequence_root, wtrmrk_queried (bool), wtrmrk_grade (int|null), wtrmrk_adj (int|null), combined_adj (int)
  • combined_adj = clamp(-1, +1, reputation_adj + wtrmrk_adj) with asymmetric safety rule: if either factor is +1, combined cannot be -1
  • combined_adj replaces reputation_adj as the applied adjustment (when base_int >= T2)
  • T3 skills remain immune to any combined_adj downgrade
  • _check_authorization_tier(skill_id, peer_id, wtrmrk_sequence_root=None) — passes wtrmrk_sequence_root through to _compute_effective_tier

Asymmetric Safety Rule

combined_adj = clamp(-1, +1, rep_adj + wtrmrk_adj)
if rep_adj == +1 OR wtrmrk_adj == +1:
    combined_adj = max(0, combined_adj)   # cannot be -1 if either signal is hostile

This means: - Both signals must agree to lower the floor (combined=-1): requires Grade 3 WTRMRK AND established peer - Either signal alone can raise the floor (combined=+1): defense in depth - Grade 3 + Grade 0 signals cancel to neutral (combined=0), not hostile

Factor 4 Cache Behaviour

  • TTL: 300 seconds (configurable via _WTRMRK_CACHE_TTL)
  • Cache key: sequence_root string
  • On cache hit: returns cached grade without network call
  • On failure: caches (None, timestamp) to avoid hammering failed endpoint

Four-Factor Formula (v2.62)

effective_tier = max(
    tier_rule,          # Factor 1: declared authorization_tier
    depth_floor,        # Factor 2: delegation chain depth → conservative floor
    base + combined_adj # Factor 3+4: rep + wtrmrk combined (only when base >= T2)
)
where base = max(tier_rule_int, depth_floor_int)

Tests

  • tests/test_wtrmrk_attestation.py — WA-1..14 (14/14 PASS, 29s)
  • WA-1/2: No wtrmrk_sequence_root → wtrmrk_queried=False, adj fields null
  • WA-3: wtrmrk_sequence_root query param → wtrmrk_queried=True
  • WA-4: Query failure → fail-closed, wtrmrk_grade=None, no server crash
  • WA-5: _wtrmrk_to_adj mapping: None→0, 0→+1, 1→0, 2→0, 3→-1 ✅
  • WA-6: Without high-rep peer, combined_adj >= 0 confirmed
  • WA-7/8: Asymmetric safety rule: single +1 prevents combined=-1 ✅
  • WA-9: T3 tier immune to downgrade even with Grade-3 wtrmrk ✅
  • WA-10/11: POST /tasks metadata.wtrmrk_sequence_root accepted ✅
  • WA-12: AgentCard.capabilities.wtrmrk_attestation = True
  • WA-13/14: T2+combined=-1→T1; T1 unaffected by combined=+1 (pure logic) ✅
  • Full regression: 525+ passed, 4 skipped, 0 failed

v2.61.0 — caller_signature: Complete Bilateral Signing in Interaction Records (2026-04-06)

Added

  • caller_signature (str, optional) — base64url-encoded Ed25519 signature from the caller over the canonical IR payload
  • caller_public_key (str, optional) — base64url-encoded raw Ed25519 public key of the caller; required for verification
  • caller_signature_valid (bool|null) — True when signature verifies; False when signature present but invalid; null when no signature provided
  • bilateral (bool) — True only when both relay_signature and caller_signature are cryptographically valid; False otherwise
  • AgentCard capabilities.bilateral_interaction_records: True — signals full bilateral signing support to peers

Changed

  • _create_interaction_record() — expanded to accept caller_signature and caller_public_key; verifies caller's Ed25519 signature against canonical IR payload (relay_did+caller_did+task_id+sequence_a+timestamp); new return fields: caller_signature, caller_public_key, caller_signature_valid, bilateral
  • POST /tasks handler — extracts caller_signature and caller_public_key from the request body and passes them to _create_interaction_record()
  • POST /tasks role validation — BUG FIX: role field is now correctly looked up from the top-level request body first (body.get("role")), with payload as fallback; previously only checked nested payload, causing silent 400 when role was in the top level (correct position)

Canonical Payload for Caller Signature Verification

The caller must sign the following canonical string (concatenated with |):

<relay_did>|<caller_did>|<task_id>|<sequence_a>|<timestamp>
Where timestamp is the ISO 8601 UTC timestamp of the interaction record. The relay verifies this signature using the provided caller_public_key (Ed25519 raw bytes).

Behaviour

  • caller_signature provided + caller_public_key absent → caller_signature_valid: false, bilateral: false
  • caller_public_key provided + caller_signature absent → caller_signature_valid: false, bilateral: false
  • Neither provided → caller_signature: null, caller_public_key: null, caller_signature_valid: null, bilateral: false
  • Invalid signature (e.g. wrong bytes) → caller_signature_valid: false, bilateral: false; record is stored, not rejected
  • bilateral: true is achieved only with a relay identity loaded (--identity) AND a valid caller signature

Design Rationale (A2A #1718)

A2A Issue #1718 (bilateral signed interaction records) identified the core weakness of relay-only signing: the relay can forge or selectively reveal interaction records — making them repudiable. ACP v2.61 implements the caller-side signature in a fully backward-compatible manner: - Unilateral records (relay-only) continue to work as before — bilateral: false - Callers that supply caller_signature upgrade the record to cryptographically non-repudiable bilateral evidence - No new endpoints required; just add two fields to the existing POST /tasks body

Tests

  • tests/test_caller_signature.py — CS-1..12 (12/12 PASS, 3.98s)
  • Full regression (excluding known flaky/long-running files): 511+ passed, 4 skipped, 0 failed
  • CS-1: no caller_signature → bilateral=false, caller_signature_valid=null
  • CS-2: invalid sig (bad bytes) → caller_signature_valid=false, bilateral=false
  • CS-3: sig without pubkey → caller_signature_valid=false
  • CS-4: pubkey without sig → caller_signature_valid=false
  • CS-5: AgentCard.capabilities.bilateral_interaction_records=true
  • CS-6..12: GET /interaction-records bilateral field, chain linkage, field presence, None vs False semantics

v2.56.0 — principal_chain[] OBO Delegation Chain — Trust-Block Propagation + Runtime Management (2026-04-05)

Added

  • _principal_chain — module-level list of {did, role, added_at} OBO delegation entries
  • GET /principal-chain — list current chain; response includes self_did, count, principal_chain[]
  • POST /principal-chain — add or upsert a principal by DID; body: {"did": "...", "role": "orchestrator"|"delegator"|"owner"|<str>}
  • DELETE /principal-chain/<did> — remove a specific principal; 404 when DID not found
  • GET /peers/{peer_id}/principal-chain — return the principal_chain embedded in a connected peer's AgentCard trust block
  • 404: peer not found in registry
  • 422: peer registered but has not yet shared an AgentCard
  • AgentCard trust.principal_chain[] — emitted when _principal_chain is non-empty; absent when chain is empty
  • capabilities.principal_chain = bool(_principal_chain) — discoverable via /.well-known/acp.json
  • endpoints.principal_chain = "/principal-chain" and endpoints.peer_principal_chain = "/peers/{peer_id}/principal-chain" in AgentCard
  • --principal DID[,role=ROLE] CLI flag — populate chain at startup; repeatable; upsert semantics; role defaults to delegator
  • POST /message:send — new optional field on_behalf_of (str DID or list[str] DIDs)
  • If provided: outgoing message carries principal_chain: [self_did, ...on_behalf_of]
  • If omitted but _principal_chain is non-empty: auto-attach the standing chain to all outbound messages

Behaviour

  • POST /principal-chain with a DID that already exists → upserts (replaces) the entry; no duplicates
  • DELETE /principal-chain/<did> on unknown DID → 404 with {"removed": false, "count": <int>}
  • trust.principal_chain key is absent from AgentCard when chain is empty (clean card for non-OBO agents)
  • on_behalf_of: "did:acp:X" is equivalent to on_behalf_of: ["did:acp:X"] — both produce a list
  • Message principal_chain format: [<self_did>, <principal1>, <principal2>, ...] — sender always first

Design Rationale (A2A #1713)

A2A Issue #1713 (OBO — "On Behalf Of", 15 comments, still open) discusses cross-org accountability when Agent A acts on behalf of Agent B without a shared Authorization Server. ACP v2.56 provides a lightweight, zero-infrastructure alternative: - No shared AS required — principal DIDs are self-sovereign (Ed25519 / did:acp / did:key) - No OAuth token exchange — chains are plain JSON arrays, verifiable via existing DID infrastructure - Runtime-mutable via REST — delegates can be added/removed without restart - Composable with v2.54 trust_integration and v2.55 per-peer verification

Tests

  • tests/test_principal_chain.py — PC-1..10 (10/10 PASS)
  • Full regression: 239/239 PASS (pending test round; prior baseline 238/238)

v2.55.0 — GET /peers/{peer_id}/verify-card — On-Demand Per-Peer AgentCard Re-Verification (2026-04-05)

Added

  • GET /peers/{peer_id}/verify-card — on-demand AgentCard re-verification for a known connected peer
  • Reuses the v2.54 TTL cache infrastructure (_verify_card_cached) for efficient repeated queries
  • Query params:
    • force=1 — bypass TTL cache; always re-verify (also triggered by ttl=0)
    • trust=1 — if verification succeeds, upsert a card_verified signal into peer's trust.signals
    • ttl=<seconds> — custom cache TTL override (default: 300; 0 treated as force=1)
  • Response 200 fields: ok / peer_id / name / connected / card_available / valid / did / did_consistent / public_key / scheme / error / cached / cache_expires_in / trust_signal_written / last_connected / card_received_at
  • Response 404 (ERR_PEER_NOT_FOUND): peer not in registry
  • Response 422 (ERR_CARD_UNAVAILABLE): peer registered but has not yet shared an AgentCard
  • capabilities.peer_verify_card = True in AgentCard
  • endpoints.peer_verify_card = "/peers/{peer_id}/verify-card" in AgentCard
  • card_received_at timestamp now written to _peers[peer_id] when a peer shares its AgentCard
  • /debug/inject enhanced: accepts optional agent_card field to inject a peer with an AgentCard (test fixture support)

Behaviour

  • trust_signal_written = false when valid=false or trust=0 (no spurious signals)
  • cached=true on second call within TTL; cache_expires_in reports remaining seconds
  • force=1 and ttl=0 both bypass cache read and write paths (consistent with v2.54 behaviour)
  • Peer with no AgentCard returns 422, not 404 — distinguishes "unknown peer" from "card not yet received"

Bug Fixes

  • Removed duplicate from urllib.parse import urlparse, parse_qs inside do_GET handler — Python scoping rule caused UnboundLocalError for all GET routes when the local import shadowed the module-level import
  • Replaced 3 call sites of non-existent _iso_now() with the correct _now()

Tests

  • tests/test_peer_verify_card.py — PVC-1..10 (10/10 PASS)
  • Full regression: 238/238 PASS

v2.54.0 — POST /verify-card (v2) — Batch + Fetch + TTL Cache + Trust Integration (2026-04-05)

Added

  • POST /verify-card — enhanced AgentCard verification endpoint (v2), three modes:
  • mode=single (default): verify one card with TTL cache (300 s default, ttl=0 bypasses cache)
  • mode=batch: verify up to 100 AgentCards in one request; returns valid_count / invalid_count / unknown_count / results[]
  • mode=fetch: fetch AgentCard from URL (wrapped or raw) then verify; returns fetched_from + card_name
  • Optional params on all modes: ttl (int), trust_integration (bool), peer_id (str)
  • _verify_card_cache dict + _VERIFY_CARD_CACHE_TTL = 300 — module-level TTL cache
  • _verify_card_cache_key(card) — stable (public_key, card_sig) cache key
  • _verify_card_cached(card, ttl) — cached wrapper around _verify_agent_card()
  • _fetch_agent_card_from_url(url, timeout) — URL fetcher with wrapped/raw support
  • _verify_card_batch(cards, ttl) — batch verifier with per-result index field
  • _apply_trust_integration(vr, peer_id) — upserts card_verified signal into trust.signals
  • capabilities.verify_card_v2 = True in AgentCard
  • endpoints.verify_card_v2 = "/verify-card" in AgentCard

Behaviour

  • ttl=0 now correctly bypasses cache on both read and write paths (bug fix from initial design)
  • mode=batch non-dict items in list → valid=False, error="not a JSON object" (no crash)
  • mode=fetch failures → 422 Unprocessable Entity with {ok: false, error: "..."}
  • trust_integration=true + valid=falsetrust_signal_written=false (no spurious signals)
  • Result cache is keyed by card signature, not card content — ordering-independent

Tests

  • tests/test_verify_card_v2.py — VC2-1..16 (16/16 PASS)
  • Full regression: 237/237 PASS

v2.53.0 — skill.rate_limit — Per-Skill / Per-Peer Invocation Frequency Limiting (2026-04-05)

Added

  • skill.rate_limit field in AgentCard skills[]: {requests_per_minute?, requests_per_day?, burst?}
  • _parse_rate_limit(): normalises and validates rate_limit config (non-int / ≤0 values silently dropped)
  • _rl_buckets: in-memory (skill_id, peer_id) → bucket state {min_count, day_count, burst_used, min_start, day_start}
  • _check_rate_limit(skill_id, peer_id): checks + increments counters; resets expired windows (60s minute, 86 400s day)
  • POST /tasks: _check_rate_limit() inserted after _check_param_constraints, before _needs_human_confirmation
  • ERR_RATE_LIMIT error constant; HTTP 429 response includes limit_type / limit / burst / effective_limit / current_count / reset_in_seconds / skill_id / peer_id
  • capabilities.skill_rate_limit = true in AgentCard

Behaviour

  • burst extends requests_per_minute only (effective_limit = rpm + burst); does not extend requests_per_day
  • Counters are isolated per (skill_id, peer_id) — one peer's throttle never affects another
  • Skills without rate_limit → no change (fully backward-compatible)
  • Enforcement order (complete): authorization_tier → param_constraints → rate_limit → human_confirmation

Tests

  • SRL1–SRL12: 12/12 PASS
  • Full regression: 236/236 PASS

v2.52.0 — Task Audit Log + Skill Deprecation Notice (2026-04-05)

Two compliance-focused features completing the ACP T3 accountability story.

Task Audit Log: - task.audit_log[] — append-only, seq-monotonic audit trail on every task object - _append_audit(task, event_type, detail) helper — never removes or mutates entries - Events: created (seed), skill_invoked (skill_id/peer_id/tier), status_changed (from/to), confirmed (by human), rejected (by human + reason) - GET /tasks/{id}/audit-log endpoint — returns {task_id, status, audit_log, total}; supports ?since_seq=N + ?limit=N for incremental polling - audit_log also embedded in task objects from GET /tasks/{id} and POST /tasks - capabilities.task_audit_log = True

Skill Deprecation Notice: - skill.deprecation_notice field in AgentCard skill objects — fields: deprecated, deprecated_since, sunset_at, replacement_skill, message - POST /tasks on deprecated skill → 201 + deprecation_warning top-level field (non-blocking) - deprecated: false or absent → no warning injected (backward-compatible) - Visible in GET /skills skill objects - ERR_SKILL_DEPRECATED error constant added (informational) - capabilities.skill_deprecation_notice = True - Works alongside T3 human_confirmation: deprecated T3 skills return both deprecation_warning AND enter confirmation_pending

Bug fix (development): _skills NameError in POST /tasks — replaced with (_status["agent_card"] or {}).get("skills", []) lookup pattern, consistent with existing _check_authorization_tier / _check_param_constraints.

AUD1–AUD10 + DEP1–DEP6: 16/16 PASS | full regression: 217/217 PASS | commit: ac55111


v2.51.0 — T3 Human Confirmation Gate (2026-04-05)

Per-skill authorization tier enforcement at POST /tasks. Inspired by A2A #1716 (SINT Protocol RFC); implemented without OAuth by reusing trust.signals (v2.14) + per-peer trust scores (v2.34).

  • skill.authorization_tier field in AgentCard skill objects (T0/T1/T2/T3/null)
  • T2 requires trust_score >= 0.7; T3 requires trust_score >= 0.9 + verified_identity signal
  • ERR_AUTHORIZATION_TIER error code, 403 response with skill_id + peer_id
  • capabilities.skill_authorization_tiers = True in AgentCard
  • SAT1–SAT12: 12/12 PASS | core regression: 172/172 PASS

v2.48.0 — GET /peers/\<id>/messages — Per-Peer Message History (2026-04-05)

  • New endpoint GET /peers/<peer_id>/messages with direction / since_seq / sort / limit / offset filtering
  • New --test-mode flag + POST /debug/inject endpoint for integration tests without real P2P connection
  • capabilities.peer_message_history = True + endpoints.peer_messages in AgentCard
  • PMH1–PMH10: 10/10 PASS

v2.31.0 — PATCH /skills//limitations (2026-04-02)

  • feat: PATCH /skills/<id>/limitations — 运行时 per-skill limitations 动态更新,无需重启
  • feat: limitations_merge: true 支持追加模式(by kind+code de-dup)
  • feat: 空数组 [] 清除 runtime override,恢复声明默认值
  • feat: GET /skills/<id>/statusGET /skills 自动反映 override
  • feat: capabilities.skill_limitations_patch: true
  • test: SU1–SU8 = 8/8 PASS;回归 189/189 PASS

v2.30.0 — error_failed_msg_id 能力声明 (2026-04-01)

  • feat: capabilities.error_failed_msg_id: true 正式声明(功能自 v0.6 起实现,ref ANP)
  • test: FM1–FM8 = 8/8 PASS

v2.29.0 — GET /skills//status Per-Skill 可用性探测 (2026-04-01)

  • feat: GET /skills/<id>/status — 轻量 per-skill 可用性探测
  • feat: runtime (permanent:false) capability/access limitation → available: false
  • feat: 响应含 limitations[](runtime override 合并后的完整列表)
  • feat: capabilities.skill_status_probe: true
  • test: SS1–SS12 = 12/12 PASS

v2.11.0 — Skills 字段增强 (2026-03-28)

  • feat: GET /skills 响应中每个 skill 新增 input_modesoutput_modesexamples 字段
  • feat: GET /.well-known/acp.json AgentCard skills[] 包含完整新字段
  • feat: /skills/query 新增 constraints.input_mode 过滤 — 无 skill_id 时按 input_mode 筛选
  • security: /webhooks/register/webhooks/deregister 现在限制仅 localhost 调用,防止消息泄露(BUG-039)

[Unreleased] — post-v2.0-offline


[2.8.0] — 2026-03-28 (Extension Mechanism — URI-Identified Extensions in AgentCard)

Added

  • Extension mechanism (relay/acp_relay.py):
  • _make_builtin_extensions() — auto-registers built-in extensions based on runtime config:
    • acp:ext:hmac-v1 when --secret is set (HMAC-SHA256 signing)
    • acp:ext:mdns-v1 when --advertise-mdns is set (mDNS LAN discovery)
    • acp:ext:h2c-v1 when --http2 is set (HTTP/2 cleartext transport)
  • _make_agent_card() now always emits extensions: [] (empty list when none declared) — was opt-in before v2.8
  • Deduplication by URI: if same URI appears in built-in and user-declared, kept once (first occurrence)
  • --extensions URI[,URI,...] new CLI flag — shorthand for declaring multiple extensions by URI
  • Built-in + user-declared extensions merged in card; built-ins first, then user-declared
  • Python SDK (sdk/python/acp_client/models.py):
  • Extension dataclass — uri (str, required), required (bool, default False), params (dict, default {})
    • Extension.to_dict() — serialises to dict; omits params when empty
    • Extension.from_dict(d) — parses dict; validates uri required; forward-compat (skips malformed entries)
    • __repr__ — human-readable with required indicator
  • AgentCard.extensions: List[Extension] field (default [])
  • AgentCard.has_extension(uri) — bool check by URI
  • AgentCard.get_extension(uri)Extension | None
  • AgentCard.required_extensions()List[Extension]
  • AgentCard.from_dict() — handles missing/null extensions field (backward compat)
  • AgentCard.to_dict() — always emits extensions key
  • Spec (spec/core-v1.0.md):
  • New §5.5 "Extension Mechanism (v2.8+)" with full schema, URI naming convention, well-known built-in URIs table, semantics/compat rules, discovery, CLI flags
  • AgentCard schema example updated to show extensions array
  • Top-level fields table updated: extensionsstable
  • Tests (tests/test_extensions.py): 39 test cases (all passing):
  • Extension dataclass defaults, serialisation, round-trip
  • AgentCard extensions field: default empty, to_dict/from_dict
  • Backward compat: old responses without extensions field
  • Convenience methods: has_extension, get_extension, required_extensions
  • Relay: _make_builtin_extensions for all 3 built-ins
  • Relay: _make_agent_card always emits extensions key
  • Relay: user-declared merge, deduplication
  • --extensions CLI bulk URI parsing

Changed

  • relay/acp_relay.py VERSION: 2.7.02.8.0
  • tests/unit/test_relay_core.py: updated test_extensions_absent_when_empty to assert extensions key always present (v2.8 semantics)

Design

  • Inspired by A2A extension model; designed to remain minimal and registry-free
  • URI naming: acp:ext:<name>-v<version> for built-ins; full HTTPS URL for external/vendor extensions
  • Non-required default: required: false — clients that don't recognise an extension MUST ignore it
  • No registry, no central authority — URI uniqueness is the extension definer's responsibility

[1.8.0] — 2026-03-28 (acp-client LangChain Tool Adapter)

Added

  • sdk/python/acp_client/integrations/ — new optional integrations sub-package
  • langchain.py — LangChain Tool adapter (ACPTool, ACPCallbackHandler, create_acp_tool)
    • ACPToolBaseTool subclass (lazy import; langchain is optional dep, not required for core SDK)
    • name = "acp_send", LLM-readable description
    • _run(message) -> str — synchronous send + receive via RelayClient
    • _arun(message) -> str — async wrapper (thread-pool executor, non-blocking)
    • Graceful error handling: returns descriptive error strings, never raises, so LLM can recover
    • ACPCallbackHandlerBaseCallbackHandler subclass (lazy import)
    • on_tool_start / on_tool_end / on_tool_error — structured log entries via logging
    • _calls list accumulates all events for post-run inspection
    • create_acp_tool(relay_url, peer_id, timeout=30) — factory helper
  • __init__.py — package docstring (zero required imports)
  • __init__.py — conditional top-level re-export of create_acp_tool (available when langchain installed)
  • pyproject.toml — new optional extra: [langchain] = langchain>=0.1.0
  • tests/test_langchain_integration.py — 38 test cases (all passing, mock-only, no real langchain required)
  • TC-01: init (name, description, relay_url, peer_id, timeout)
  • TC-02: _run success paths (send_and_recv, specific peer_id, instance method)
  • TC-03: _run timeout (None reply → error string, no raise)
  • TC-04: _run ACPError handling
  • TC-05: _arun async wrapper
  • TC-06: missing langchain ImportError with install hint
  • TC-07: create_acp_tool factory
  • TC-08: ACPCallbackHandler events
  • TC-09: repr
  • TC-10: integration smoke tests
  • TC-11: public API (top-level re-export)
  • TC-12: pyproject.toml optional dep declared
  • sdk/python/README-sdk.md — new "LangChain Integration" chapter

Design

  • Lazy import pattern: LangChain never imported at module load time; ImportError with pip hint raised only at first instantiation if langchain absent
  • Dynamic subclassing via __new__: builds a real BaseTool/BaseCallbackHandler subclass at instantiation, compatible with all LangChain versions
  • Zero new mandatory dependencies; core acp_client remains stdlib-only
  • Python 3.9–3.13 compatible

Bump

  • __version__: 1.7.01.8.0

[1.7.0] — 2026-03-28 (acp-client Python pip Package)

Added

  • sdk/python/acp_client/ — new pip-installable acp-client package (v1.7.0)
  • client.pyRelayClient (sync, stdlib urllib, zero external deps)
  • async_client.pyAsyncRelayClient (async via run_in_executor bridge)
  • models.py — typed dataclasses: AgentCard, Message, Task, TaskStatus, Part, PartType
  • exceptions.pyACPError hierarchy: PeerNotFoundError, TaskNotFoundError, TaskNotCancelableError, SendError, AuthError, TimeoutError
  • __init__.py — clean public API surface
  • _cli.pyacp-client CLI entry-point (status / card / link / peers / send / recv / tasks / stream)
  • sdk/python/pyproject.toml — PEP 517 build config (Python ≥ 3.9, zero mandatory deps, optional: [async], [http2], [dev])
  • sdk/python/README-sdk.md — complete SDK documentation (install + 30s quick-start + full API reference + relay integration guide)
  • sdk/python/tests/test_sdk_package.py — 60 test cases (all passing, no live relay required — uses in-process mock HTTP server)

Design

  • Zero mandatory external dependencies (stdlib urllib only for core HTTP)
  • Optional extras: httpx for native async, h2 for HTTP/2
  • Backward-compatible: sdk/python/acp_sdk/ unchanged; existing from acp_sdk import RelayClient continues to work
  • Fully typed public API with rich exception hierarchy
  • acp-client CLI covers all major relay operations

[3.0.0] — 2026-03-28 (Automatic NAT Traversal — Three-Level P2P Integration)

主题:v1.4 NAT 穿透与 /peers/connect 完整集成,实现零感知自动三级降级

Added

  • _connect_with_nat_traversal(link, name, role) — 三级连接策略,替换 /peers/connect 直连逻辑
  • Level 1(直连):ws://IP:PORT/TOKEN,3s 超时
  • Level 2(DCUtR 打洞):交换 signaling → TCP/UDP SYN 打洞,12s 超时
  • Level 3(Relay 降级):HTTP Relay(Cloudflare Worker)兜底
  • /peers/connect 端点现在自动调用 _connect_with_nat_traversal()(不再硬编码 Level 1 直连)
  • _peers[peer_id]["transport_level"] 字段:记录实际使用的连接级别("direct" | "dcutr" | "relay")
  • --relay 语义变更:v1.4 起从「用户主动选择 relay」→「强制 Level 3 跳过 L1+L2」
  • SSE 事件 dcutr_started / dcutr_connected / relay_fallback:连接过程可观测
  • http_relay scheme 链接直接进入 Level 3(跳过 L1+L2)

Changed

  • VERSION: 2.9.03.0.0(里程碑:P2P 无中间人核心设计完整落地)
  • /peers/connect 路由逻辑:原始 await guest_mode(...) 替换为 await _connect_with_nat_traversal(...)

Fixed

  • BUG-037: messages_received 多 peer 场景计数始终为 0(懒绑定修复,commit ced26b3
  • 根因:HTTP relay 通道先于 P2P 注册处理 acp.agent_card,agent_name 未及时绑定
  • 修复:_on_message 中增加懒绑定路径,将 _from 绑定到最新未命名 peer 再计数
  • 测试:12/12 PASS(tests/test_bug037_messages_received.py

Design

  • 里程碑意义:ACP 核心设计「P2P 无中间人」完整落地
  • 连接流程对用户完全透明,不再需要手动指定 --relay
  • 最优路径(Level 1 直连)成功率 ≥70%(同网段或公网 IP 场景)
  • NAT 穿透成功率预期 ≥55%(Full Cone / Restricted Cone NAT)
  • 对称 NAT 自动降级 Level 3,零用户感知

[2.9.0] — 2026-03-28 (Message History List — GET /messages with Filtering + Pagination)

Added

  • GET /messages — 历史消息列表端点(分页 + 过滤)
  • 参数:?limit=20&offset=0&from=<agent_name>&since=<epoch>&message_type=<type>
  • 响应:{"messages":[...], "total": N, "has_more": bool, "next_offset": N}
  • _recv_queue 中读取,支持 sender 过滤和时间窗口过滤
  • [stable] 端点声明(spec/core-v1.3.md 端点列表)
  • 文档更新:docs/whats-new.md v2.9 节

Design

  • 参考 A2A v1.0 tasks/list 分页模式,ACP 风格化简化(无游标,仅 offset)
  • GET /recv(弹出队列)区分:GET /messages 只读不消费

[2.8.0] — 2026-03-28 (Extension Mechanism + LangChain Adapter + Node SDK v2.1.0)

Added — Extension Mechanism(URI-identified extensions in AgentCard)

  • _extensions 全局变量:[{uri, required, params}] 扩展列表
  • --extension <URI> / --extensions <URI1,URI2,...> CLI flags
  • POST /extensions/register / DELETE /extensions/{uri} 运行时扩展管理
  • GET /.well-known/acp.json 始终包含 extensions 字段(空列表 [] 时不省略)
  • 自动内建扩展推导:--identity 启用时自动添加 acp:ext:did_identity--secret 启用时添加 acp:ext:hmac_signing
  • spec/extensions.md:Extension URI 命名规范 + well-known 扩展表

Added — LangChain Adapter

  • sdk/python/acp_client/integrations/langchain.py
  • ACPTool:LangChain BaseTool 子类,将 ACP relay 封装为 LangChain 工具
  • ACPCallbackHandler:LangChain callback handler,拦截 tool call 并转发至 ACP relay
  • 可与 LangChain Agent、LCEL chain、AgentExecutor 直接集成

Added — Node.js SDK v2.1.0(Extension 支持)

  • Extension 类:{uri, required, params}toDict() / fromDict() / toString()
  • RelayClient.agentCard() 升级为 async,自动解析 extensions[]Extension 实例
  • RelayClient.hasExtension(uri) — 快捷检查 AgentCard 是否含指定扩展
  • RelayClient.requiredExtensions() — 过滤必需扩展列表
  • 向后兼容:extensions 字段缺失时返回 []
  • sdk/python/acp_client/__init__.py:顶层导出 Extension 类(sdk v1.9.0)
  • 测试:14 个新增测试用例,32/32 PASS

Added — GitHub Pages 文档站

  • docs/ MkDocs Material 配置(mkdocs.yml
  • docs/index.mddocs/getting-started/docs/guides/docs/spec/
  • GitHub Pages CI workflow:docs 推送自动部署

Fixed

  • BUG-036: /peer/{id}/send 响应缺少 server_seq 字段(commit da09a6f

[2.7.0] — 2026-03-28 (AgentCard limitations Field — Three-Part Capability Boundary)

Added

  • limitations: string[] top-level AgentCard field: declares what this agent CANNOT do
  • Completes three-part capability boundary triad: capabilities (can-do) + availability (scheduling) + limitations (cannot-do)
  • --limitations CLI flag: comma-separated string (e.g. --limitations "no_file_access,no_internet")
  • _status["limitations"] in /status endpoint response
  • _limitations global variable initialized to [] (backward-compatible default)
  • spec/core-v1.3.md §11: limitations field schema, well-known values table, 3-part boundary explanation
  • docs/whats-new.md: v2.7 section with usage examples and A2A #1694 comparison
  • README: new row in vs-A2A comparison table + callout paragraph for #1694
  • tests/test_limitations.py: 20 tests across LM1–LM5 (all pass)

Design

  • ACP-exclusive: A2A #1694 (2026-03-27) proposes the same concept — ACP ships working code same day
  • Fully backward-compatible: old clients ignore the optional limitations field
  • Limitation strings are free-form snake_case; well-known values documented in spec §11.3

[2.6.0] — 2026-03-27

Added

  • Task cancelling 中间状态(两阶段取消协议)
  • AgentCard capabilities.task_cancelling: true 能力声明
  • spec §3.3.1 两阶段取消时序图
  • spec Appendix B A2A 对比(Issue #1684/#1680 差异化说明)
  • tests/test_task_cancel.py(10 个测试用例)

[v2.5.0] - 2026-03-27

Added

  • spec §8: Task 事件序列规范(7 MUST + 2 SHOULD 合规要求)
  • SSE 事件 Envelope 必填字段:type/ts/seq/task_id
  • Task 完整生命周期 SSE Wire Format 示例
  • relay/acp_relay.py: Named event 行(acp.task.status / acp.task.artifact)
  • AgentCard: supported_interfaces 字段
  • tests/test_task_event_sequence.py: 10 个 Task 事件序列测试

Fixed

  • BUG-031: test_dcutr_t6_scenario_a.py T6.7 缺少 role 字段
  • BUG-032: test_scenario_bc.py relay 启动等待不足
  • BUG-033: cert teardown TimeoutExpired

[2.4.0] — 2026-03-27 (AgentCard transport_modes Top-Level Field)

Added — transport_modes Routing Topology Declaration (v2.4 milestone)

  • transport_modes — new top-level AgentCard field (v2.4+)
  • Declared at /.well-known/acp.json as a top-level key (not nested under capabilities)
  • Declares the routing topologies supported by this node (distinct from capabilities.supported_transports which declares protocol bindings)
  • Valid values: "p2p" (direct peer-to-peer WebSocket) and/or "relay" (HTTP relay-mediated)
  • Default: ["p2p", "relay"] — both topologies supported; peer may choose
  • Examples:
    • ["p2p", "relay"] — standard node, both modes available (default)
    • ["relay"] — sandbox/NAT-only node; P2P not possible
    • ["p2p"] — edge agent with public IP; no relay dependency
  • Absent means ["p2p", "relay"] (backwards-compatible)
  • Receivers MUST treat as advisory; unknown values MUST be ignored

  • --transport-modes CLI flag (v2.4+)

  • Comma-separated routing modes: --transport-modes p2p,relay (default), --transport-modes p2p, --transport-modes relay
  • Invalid values are warned and silently ignored; empty result falls back to default

  • Spec updatespec/core-v1.0.md §5.2–§5.5

  • §5.2: New "Top-Level AgentCard Fields" table (formally documents all top-level keys)
  • §5.3: Capability Flags table updated with note distinguishing supported_transports vs transport_modes
  • §5.4: New dedicated section — transport_modes semantics, valid values, CLI, examples
  • §5.5: Forward Compatibility (renumbered from §5.3)

  • Teststests/unit/test_transport_modes_v24.py — 15 new unit tests

  • transport_modes present in AgentCard, is a list, top-level (not under capabilities)
  • Default ["p2p", "relay"], p2p-only, relay-only variants
  • Snapshot semantics (mutation does not affect global)
  • Version check (>= 2.4.0)
  • Global default and valid values

Changed

  • relay/acp_relay.py: VERSION bumped 2.2.02.4.0
  • _make_agent_card(): returns transport_modes as a snapshot list (not reference)

[2.2.0] — 2026-03-27 (GET /tasks List Endpoint with Filtering + Pagination)

Added — GET /tasks List Queries (v2.2 milestone)

  • GET /tasks — full list + filtering + dual pagination
  • ?status=<s> — filter by task status (submitted/working/completed/failed/canceled/input_required)
    • Returns 400 ERR_INVALID_REQUEST for unknown status values
    • Backwards-compatible: legacy ?state= parameter still accepted (status takes precedence)
  • ?peer_id=<id> — filter by peer; checks both task.peer_id (top-level) and task.payload.peer_id (BUG-014 dual-layer lookup)
  • ?created_after=<ISO 8601> — return only tasks created after given timestamp
  • ?updated_after=<ISO 8601> — return only tasks updated after given timestamp
  • ?sort=asc|desc — sort by created_at; default desc (newest first)
    • Legacy created_asc / created_desc values also accepted
  • ?limit=<n> — page size; default 20, max 100 in offset mode; legacy default 50, max 200
  • ?offset=<n> — offset-based pagination (v2.2 new); triggers offset mode
  • Response shape (offset mode):
    {
      "tasks": [...],
      "total": N,
      "has_more": true,
      "next_offset": 20
    }
    
  • total reflects filtered count (not raw len(_tasks))
  • next_offset only present when has_more=true
  • Legacy keyset cursor mode (?cursor=<task_id>) preserved when offset param absent

Tests (TL1–TL10, tests/test_tasks_list.py)

  • TL1: No params → returns all tasks with required fields
  • TL2: ?status=working filters correctly; only matching tasks returned
  • TL3: ?peer_id= matches both top-level and payload.peer_id (BUG-014)
  • TL4: ?limit=2&offset=0 — first page
  • TL5: ?limit=2&offset=2 — second page; no overlap with first
  • TL6: has_more=true when items remain; next_offset present only when has_more=true
  • TL7: ?sort=asc returns oldest task first
  • TL8: ?created_after=<ISO> filters out older tasks
  • TL9: Impossible filter → {"tasks": [], "total": 0, "has_more": false}
  • TL10: ?status=bogus400 ERR_INVALID_REQUEST

Results: 10/10 passed — full regression: 256 passed, 4 skipped, 0 failed


[2.0.0-alpha.1] — 2026-03-26 10:17 (Offline Delivery Queue)

Added — Offline Message Delivery Queue (v2.0 milestone)

  • _offline_enqueue(msg, peer_id) — buffers messages when peer is disconnected (v2.0)
  • Called automatically from _ws_send() on ConnectionError
  • Per-peer keyed queue (peer_id or "default" for legacy single-peer sends)
  • deque(maxlen=100) per bucket — oldest messages dropped when full (never blocks)
  • Stores metadata: _queued_at, _offline_for_peer

  • _offline_flush(ws, peer_id) — delivers buffered messages on reconnect (v2.0)

  • Called automatically in host_mode and guest_mode after peer connects / reconnects
  • Flushes in FIFO order; strips internal bookkeeping fields; adds _was_queued: True marker
  • Tries peer-specific bucket first, then falls back to "default" bucket
  • Logs delivery count: 📤 Flushed N offline message(s) to peer '<id>' on connect

  • _offline_queue_snapshot() — serializable view of all queue buckets

  • GET /offline-queue — inspect offline delivery buffer

  • Returns {total_queued, max_per_peer, queue: {peer_id: {depth, messages: [{type, queued_at}]}}}

  • capabilities.offline_queue: true — advertised in AgentCard

  • endpoints.offline_queue: "/offline-queue" — advertised in AgentCard endpoints block

Behaviour change

  • POST /message:send and POST /send no longer immediately fail with 503 and drop the message. They still return 503 ERR_NOT_CONNECTED (API contract unchanged), but the message is now silently buffered for delivery the moment a peer reconnects.
  • Callers who want guaranteed delivery can poll GET /offline-queue to confirm the message is buffered.

Tests (OQ1–OQ10, tests/test_offline_queue.py)

  • OQ1: capabilities.offline_queue=True advertised
  • OQ2: endpoints.offline_queue="/offline-queue" in AgentCard
  • OQ3: GET /offline-queue → empty queue on fresh relay
  • OQ4: Required structure fields (total_queued, max_per_peer, queue)
  • OQ5: POST /message:send → 503 + message buffered
  • OQ6: Queue depth increments with each failed send
  • OQ7: Queue snapshot metadata has type, queued_at per message
  • OQ8: Legacy POST /send also buffers to offline queue
  • OQ9: Queue bounded by OFFLINE_QUEUE_MAXLEN=100 (oldest dropped)
  • OQ10: Relay /status healthy after offline queue activity

Results: 10/10 passed — full regression: 236 passed, 4 skipped, 0 failed

Motivation

  • A2A has no offline delivery mechanism — if a task message is sent while the receiving agent is offline, the message is simply lost.
  • ACP v2.0 offline queue: "send and forget safely" — messages survive short disconnects, auto-delivered on reconnect without any extra code by the caller.
  • Show HN talking point: "If your peer is offline when you send, ACP queues it and delivers it the moment they reconnect. A2A drops it silently."

[1.9.0] — 2026-03-26 07:45

Added — Peer AgentCard Auto-Verification (v1.9)

  • acp.agent_card handler now auto-verifies peer card on receipt
  • When peer sends AgentCard with identity.card_sig, immediately calls _verify_agent_card()
  • Result stored in _status["peer_card_verification"]
  • Logs ✅ AgentCard verified: <name> | did=<did>... on success
  • Logs ⚠️ AgentCard sig INVALID: <name> | <reason> on failure
  • Gracefully handles unsigned peers (valid=None, descriptive error)

  • _send_agent_card() now sends signed card (v1.9 integration with v1.8)

  • Calls _sign_agent_card(card) before sending during handshake
  • Peer receives a verifiable card from the first message

  • GET /peer/verify — peer card verification result endpoint

  • Returns {peer_name, peer_did, verified, valid, did_consistent, public_key, scheme, error}
  • verified: convenience boolean (True iff valid is True)
  • 404 when no peer is connected
  • Cleared automatically on disconnect

  • _status["peer_card_verification"] initialized to None; cleared on disconnect (both host-mode and guest-mode disconnect paths)

  • capabilities.auto_card_verify: true — always advertised (all relays)

  • endpoints.peer_verify: "/peer/verify" — advertised in AgentCard endpoints block

Tests (PV1–PV8, tests/test_peer_card_verify.py)

  • PV1: capabilities.auto_card_verify=True on both relays
  • PV2: GET /peer/verify → 404 when no peer connected
  • PV3: endpoints.peer_verify = "/peer/verify" in AgentCard
  • PV4: /.well-known/acp.json returns signed card when --identity enabled
  • PV5: auto-verify after peer connect → verified=True (skipped: sandbox no public IP)
  • PV6: unsigned peer card → valid=False + descriptive error
  • PV7: /peer/verify response has all required fields (valid, did, public_key, scheme, error)
  • PV8: peer_card_verification=None when no peer connected

Results: 7 passed, 1 skipped — full regression: 226 passed, 4 skipped, 0 failed

Motivation

  • Completes the identity story: v1.8 lets you sign your card; v1.9 auto-verifies the peer's card
  • Together: when two ACP agents connect, both sides automatically know if the other's identity is cryptographically verified — zero extra API calls needed
  • Show HN talking point: "Connect two agents → identity mutual verification happens at handshake"

[1.8.0] — 2026-03-26 05:15

Added — AgentCard Self-Signature (card_sig)

  • _sign_agent_card(card) (commit TBD, v1.8)
  • Signs AgentCard with Ed25519 private key at serve time
  • Signature covers canonical JSON (sorted keys, separators ','/':') with identity.card_sig excluded to avoid circular reference
  • Result stored at card.identity.card_sig (base64url, no padding)
  • No-op when --identity not enabled (zero-breaking backward compat)

  • _verify_agent_card(card)

  • Verifies any ACP AgentCard's Ed25519 self-signature
  • Returns {valid, did, did_consistent, public_key, scheme, error}
  • did_consistent: cross-checks did:acp: matches identity.public_key
  • Works for any relay's card — not just the local agent's

  • GET /.well-known/acp.json now returns signed card when --identity enabled

  • identity.card_sig field added to response

  • GET /verify/card — self-verification endpoint

  • Returns {self_verification, card_signed} for the local agent's own card

  • POST /verify/card — arbitrary card verification endpoint

  • Body: raw AgentCard JSON or wrapped {self: card} form
  • Returns full verification result
  • Invalid JSON body → 400

  • capabilities.card_sig: true when --identity enabled, false otherwise

  • endpoints.verify_card: "/verify/card" advertised in AgentCard endpoints block

Tests (CS1–CS10, tests/test_card_signature.py)

  • CS1: card_sig present in GET /.well-known/acp.json when --identity enabled
  • CS2: GET /verify/card self-verification → valid=True
  • CS3: POST /verify/card valid signed card → valid=True
  • CS4: POST /verify/card tampered card → valid=False
  • CS5: POST /verify/card unsigned card → valid=False + "card_sig missing"
  • CS6: capabilities.card_sig=True with --identity
  • CS7: POST /verify/card accepts wrapped {self: card} form
  • CS8: POST /verify/card invalid JSON → 400
  • CS9: did_consistent=True when did:acp: matches public_key
  • CS10: card_sig absent without --identity; capabilities.card_sig=False

Results: 11/11 PASS — full regression: 219 passed, 3 skipped, 0 failed

Motivation

  • Directly addresses A2A issue #1672 (Agent Identity Verification — no protocol-level mechanism)
  • ACP ships cryptographic AgentCard verification today; A2A has no timeline
  • Any ACP peer can now verify "this card was signed by the owner of this did:acp:" identity without any external CA or registration service

[1.7.0] — 2026-03-25 20:30

Updated (spec + README — post-release patch)

  • spec/error-codes.md: explicitly documents Content-Type: application/json; charset=utf-8 for all responses including errors; rejects application/problem+json (RFC 9457) by design; references A2A #1685 as motivation (commit 81ffd30)
  • README vs-A2A table (commit 81ffd30):
  • New row: "Error response Content-Type" — ACP uniform vs A2A #1685 ambiguous
  • New row: "Webhook security" — ACP URL-only vs A2A #1681 credentials leaked in plaintext
  • New callout paragraph referencing A2A #1681 + #1685

Added (Python SDK)

  • RelayClient.tasks() v1.4 time-window filters (commit 00e4a09)
  • New params: created_after, updated_after, peer_id, sort, cursor, limit
  • Aligns sync and async clients with full relay /tasks endpoint query surface

  • RelayClient.cancel_task() v1.5.2 §10 idempotent semantics

  • Default: returns error dict on 409 ERR_TASK_NOT_CANCELABLE (no exception)
  • raise_on_terminal=True: raises ValueError for terminal-state tasks
  • Async client (AsyncRelayClient.cancel_task()) upgraded identically

  • RelayClient.capabilities() — new method

  • Extracts capabilities block from AgentCard (http2 / did_identity / hmac_signing / mdns)
  • Returns {} gracefully when relay unreachable

  • RelayClient.identity() — new method

  • Returns identity block with did:acp: DID field (v1.3+)

  • RelayClient.did_document() — new method

  • Fetches /.well-known/did.json W3C DID Document (v1.3+)

  • AsyncRelayClient: all above methods added to async client as well

Added (relay server)

  • SSE context_id propagation (commit b91f642)
  • _create_task(): stores context_id on task object; includes it in initial status SSE event
  • _update_task(): propagates task.context_id to all subsequent status and artifact SSE events
  • /tasks/create endpoint and /send inline task creation both pass context_id through
  • Tasks without context_id: events cleanly omit the field (no null pollution)
  • Closes parity gap with A2A Issue #1683 (contextId missing from SSE events)

Updated (README)

  • vs-A2A comparison table: new row "Cancel task semantics"
  • ACP v1.5.2 §10: synchronous + idempotent (200 / 409 ERR_TASK_NOT_CANCELABLE)
  • A2A: CancelTaskRequest schema missing (#1684), async cancel state disputed (#1680)
  • New callout referencing A2A issues #1680 and #1684

Tests

  • sdk/python/tests/test_relay_client_v17.py: 10 tests, 10/10 PASS
  • T1–T3: tasks() time-window + combined filter query string construction
  • T4–T6: cancel_task() success / 409 no-raise / 409 raise
  • T7: capabilities() http2 + did_identity flags
  • T8: identity() did:acp: field
  • T9: did_document() W3C DID Document structure
  • T10: capabilities() fallback on unreachable server
  • tests/test_context_id_sse.py: 17/17 PASS (C1–C8, context_id SSE propagation)

Full suite: 140 passed, 0 failed ✅


[1.4.1-dev] — 2026-03-25 14:40

Added

  • DCUtR HTTP reflection fallback (relay/acp_relay.py, commit b3da914)
  • DCUtRPuncher.attempt(): when STUN fails (UDP blocked by corporate firewall), falls back to HTTP reflection via _relay_get_public_ip() to discover public IP
  • Appends {http_ip}:{local_port} to candidate address list; Level 2 hole punch continues
  • _status["relay_base_url"] now populated at both relay startup paths (--relay CLI flag and P2P guest_mode fallback)
  • SSE event dcutr_http_reflect emitted for observability
  • Graceful no-op when relay_base_url is unset

Tests

  • tests/test_nat_http_reflect.py: 12 unit tests, 12/12 PASS (mock-based, no network required)
  • R1–R3: _relay_get_public_ip success / timeout / invalid JSON
  • R4: _status["relay_base_url"] round-trip
  • R5: DCUtR triggers HTTP reflection when STUN fails + relay_base set
  • R6: DCUtR skips HTTP reflection when relay_base_url is None

Fixed

  • BUGS.md: BUG-012 status label corrected to ✅ (code fix was already present in prior commits; status record was missed)

[1.6.0] — 2026-03-25 13:50

Added

  • HTTP/2 cleartext (h2c) transport binding (relay/acp_relay.py)
  • Optional dependency: hypercorn + h2 (graceful fallback to HTTP/1.1 if unavailable)
  • Implementation: raw h2 state machine over socketserver.ThreadingTCPServer
  • --http2 CLI flag; capabilities.http2: true in AgentCard
  • _H2Handler._dispatch(): bridges h2c frames to existing LocalHTTP handler via fake socket
  • Supports all endpoints: /status, /.well-known/acp.json, /tasks, SSE streams

Tests

  • tests/test_http2_transport.py: 6 scenarios (H1–H6) all PASS
  • H1 server startup, H2 AgentCard, H3 SSE, H4 POST /tasks, H5 /status, H6 discovery
  • Test infrastructure overhaul (commit 21e3e7d)
  • tests/conftest.py: global http_proxy strip + clean_subprocess_env() for relay subprocesses
  • pytest.mark.p2p: skip P2P-dependent tests in sandbox (--with-p2p to enable)
  • test_scenario_h: rewritten as HTTP-only concurrent isolation test
  • Full suite: 15 passed, 3 skipped (P2P), 0 failed, 0 errors

Key commits: 3f06b24, e8974b2, cf578e3, 394b71c (HTTP/2), 21e3e7d (test infra), 0ac2215 (BUG-019 docs)


[1.5.2-dev] — 2026-03-25 05:55

Added

  • spec §10 — Task Cancel Semantics (spec/core-v1.3.md): explicit synchronous cancel contract
  • Cancel is synchronous and immediate: :cancel returns final canceled state in the same HTTP response, no async/deferred mechanism
  • Cancel is idempotent: calling :cancel on an already-canceled task returns 200 with existing state
  • New error code: ERR_TASK_NOT_CANCELABLE (409) for tasks in terminal states (completed, failed)
  • Design rationale documented: deliberate contrast with A2A issue #1680 (async cancel, unresolved)
  • Agent-side cancel behavior guidance (best-effort signal, not a transaction rollback)
  • Show HN draft updated (docs/show-hn-draft.md): added A2A competitive comparison points
  • A2A #1681 security bug: PushNotificationConfig leaks credentials by default; ACP has no Push Notification mechanism
  • A2A #1680 cancel design gap: async cancel unresolved; ACP cancel is synchronous and unambiguous
  • Updated anti-trolling prep with cancel and security talking points
  • spec Appendix A: version history updated to v1.5.2

Research (2026-03-25 05:25 — Competitive scan #7, post-1.5.1-dev update)

  • A2A 9-day code freeze continues (last merge 2026-03-16, TSC governance mode)
  • A2A #1681 (security bug): GetTaskPushNotificationConfig leaks full credentials in response — ACP has no PushNotification mechanism, zero exposure to this class of vulnerability; strong differentiation point for Show HN
  • A2A #1680 (design gap): async cancel semantics unresolved — community debating two approaches for cancel-in-progress tasks; ACP cancel is simple synchronous (canceled state returned immediately), no async webhook complexity
  • A2A #1679: Python tutorial docs require full rewrite for v1.0-alpha.0 breaking changes; ACP API stable, low doc maintenance burden
  • ANP: confirmed archived (last update 2026-03-05), no new activity

[1.5.0-dev] — 2026-03-24 (pre-1.5.1, NAT signaling layer)

Added (22:47 — v1.4 NAT traversal signaling layer)

  • Cloudflare Worker v2.1: NAT traversal signaling endpoints (commit 8c162d4)
  • GET /acp/myip — reflect caller's public IP via CF-Connecting-IP header; used by agents to discover their public address when STUN UDP is blocked
  • POST /acp/announce — register {token, ip, port, nat_type} with 30s TTL; auto-expires, no message content stored
  • GET /acp/peer?token= — one-time fetch + delete of peer announce record (prevents address harvesting)
  • Privacy design: signaling records are ephemeral (30s) and one-time-read, no persistent storage of agent addresses
  • Python signaling helpers (acp_relay.py) — stdlib-only (urllib), no new deps
  • _relay_get_public_ip(relay_base_url) — HTTP reflection fallback for when STUN UDP is firewalled
  • _relay_announce(relay_base_url, token, ip, port, nat_type) — register address via Worker
  • _relay_get_peer_addr(relay_base_url, token) — fetch peer address (one-time, auto-deletes)
  • These complement STUNClient: STUN → primary; HTTP reflection → corporate firewall fallback
  • tests/test_nat_signaling.py: 22/22 PASS — covers all helpers, error paths, edge cases, full roundtrip; uses local mock server, no network required

Fixed (20:33)

  • BUG-016 (P1): /peer/{id}/send connection race — ERR_PEER_CONNECTING guard (commit 665f767)
  • Root cause: _register_peer() sets connected=True, ws=None immediately on /peers/connect; send handler only checked connected, not ws, causing a spurious "not connected" 503 during WS handshake
  • Fix: added ws is None guard returning 503 ERR_PEER_CONNECTING with retry hint
  • Test fix: wait_peer_ready() now uses probe-send success as readiness signal instead of peer list polling
  • Verified: test_scenario_fg.py 19/19 ✅ (was 16/19 before fix)

Fixed (20:00)

  • BUG-015 (P3): test_scenario_fg.py pytest incompatibility (commit 58dbb66)
  • Root cause: module-level sys.exit() triggered INTERNALERROR: SystemExit when mixed with other pytest suites
  • Fix: refactored to run_fg_tests() + test_scenario_fg() pytest entry; sys.exit() moved to if __name__ == "__main__": guard
  • Verified: 7 tests collected cleanly in mixed-suite run; standalone python3 execution unchanged

Research (scan #6 — 2026-03-24 21:37)

  • A2A PR #1678 (NEW ⭐): Python SDK tutorial updated to v1.0.0-alpha.0
  • AgentCard.url renamed to icon_url (breaking); new supported_interfaces + extended_agent_card fields
  • Signal: A2A AgentCard still churning; ACP's minimal, stable AgentCard format is a differentiation point
  • supported_interfaces adds protocol negotiation complexity — ACP's "one link, zero config" narrative strengthened
  • A2A code layer: 9 consecutive days without spec/code merge (last: 2026-03-16)
  • Window remains open for ACP v1.4 + v2.0 launch before A2A stabilizes
  • A2A #1676: PushNotificationConfig missing (still unresolved) — ACP /recv polling unaffected
  • A2A #1672: getagentid.dev identity CA discussion still open, no resolution
  • ANP: archived, no new activity (dropped from tracking)
  • Full report: acp-research/reports/2026-03-24-scan-2.md

Research (scan #5 — 2026-03-24 18:00)

  • A2A #1676 (NEW): PushNotificationConfig definition missing from A2A spec (bug)
  • ACP is unaffected; /recv polling design avoids push config complexity entirely
  • A2A #1672 (47 comments): getagentid.dev emerging as de-facto A2A identity CA
  • Centralized registration service; external dependency; single point of failure
  • ACP did:acp: advantage: self-sovereign, derived from Ed25519 pubkey, zero external resolver, zero registration, works fully offline — already shipping in v1.5
  • A2A code layer: 8 consecutive days with no merges (last: 2026-03-16, CODEOWNERS update)
  • TSC governance mode confirmed; fast-iteration window remains open for ACP
  • ANP: confirmed archived (last update 2026-03-05), dropped from active tracking
  • Show HN draft updated with getagentid.dev vs did:acp: talking points (commit e39ac4f)
  • Full report: acp-research/reports/2026-03-24-scan.md

[1.5.1-dev] — 2026-03-24

Added

  • GET /tasks time-window filterscreated_after and updated_after (commit a187471)
  • created_after=<ISO-8601> — return only tasks created after this timestamp
  • updated_after=<ISO-8601> — return only tasks updated after this timestamp
  • Combinable with existing state / peer_id / cursor / sort params
  • Future timestamps → empty list (correct behavior, TF4)
  • Invalid timestamp strings → 200/400, no 500 crash (TF5)
  • Tests: 6/6 PASS (tests/test_tasks_filtering.py — TF1–TF6)
  • Inspired by A2A v1.0.0 tasks/list + last_updated_after (research scan #4)

Fixed

  • BUG-014 (P2): GET /tasks?peer_id= filter was always returning empty list
  • Root cause: peer_id is stored in payload.peer_id, not top-level t["peer_id"]
  • Fix: filter now checks both t.get("peer_id") and t.get("payload", {}).get("peer_id")
  • Previously silently broken with zero test coverage; discovered during TF6 regression test

Research

  • A2A v1.0.0 released 2026-03-12 — competitive analysis scan #4 (commit 8f0c9b5)
  • A2A v0.3.0 → v1.0.0 with multiple BREAKING CHANGES (OAuth modernization, gRPC multi-tenancy, extendedAgentCard restructure, canceled spelling standardization)
  • ACP's P2P/zero-server positioning MORE differentiated vs. A2A enterprise trajectory
  • A2A #1667 (heartbeat agent): ACP availability block already ships this natively
  • A2A #1672 (agent identity): reference impl submitted (getagentid.dev, centralized CA); ACP ed25519 self-sovereign model is superior (no third-party CA dependency)
  • Action items: P2 — SDK compat version docs; P3 — highlight self-sovereign identity in README
  • Full report: acp-research/reports/2026-03-24-scan4.md

[1.5.0-dev] — 2026-03-24 (hybrid identity)

Added

  • Hybrid Identity Model (--ca-cert) — v1.5 (commit 7aaa2cb)
  • New CLI flag: --ca-cert <PATH_OR_PEM>
  • When used alongside --identity: AgentCard gains identity.ca_cert (PEM string)
  • identity.scheme upgraded from "ed25519""ed25519+ca" in hybrid mode
  • capabilities.identity: "none" | "ed25519" | "ed25519+ca" (new enum)
  • All did:acp: / public_key fields preserved — fully backward compatible
  • New spec: spec/identity-v1.5.md (hybrid trust model, 4 verification strategies)
  • Tests: 6/6 PASS (tests/test_v15_hybrid_identity.py)
  • Motivation: A2A #1672 (43 comments) converging toward same "hybrid" conclusion; ACP ships this today vs. A2A still in discussion

Research

  • A2A code layer: 8 consecutive days without a merge (last commit 2026-03-16)
  • A2A #1672 hybrid identity: self-sovereign + CA model — ACP v1.5 preemptively ships this
  • A2A #1628 trust.signals[]: enterprise blockchain-level trust, out of ACP scope
  • A2A #1606 data handling declarations: compliance metadata, v2.0 extensions candidate
  • Reports: acp-research/reports/2026-03-24-scan.md, 2026-03-24-scan2.md

[1.4.0-dev] — 2026-03-24

Added

  • Java SDK (sdk/java/) — zero external dependencies, JDK 11+ (commit 28813ed)
  • RelayClient.of(url) — ping, send, recv, connectPeer, sendToPeer, stream (SSE), patchAvailability
  • Full model classes: Part, Message, Task, SendRequest, SendResponse, SseEvent
  • Zero-dependency JSON serializer/parser (Json.java, hand-written recursive descent)
  • Maven pom.xml; zero runtime dependencies (JDK 11 java.net.http only)
  • Spring Boot @Bean integration example in README
  • Tests: 41/41 ✅ (21 JsonTest unit + 10 RelayClientTest unit + 10 integration)
  • Scenario H test — multi-agent concurrent routing validation (commit 06f6fac)
  • H1: Hub simultaneous dual-peer connect (2/2 peers)
  • H2: Hub→WA + Hub→WB parallel 10-msg each; zero cross-routing errors ✅
  • H3: WA↔WB bidirectional concurrent exchange ✅
  • H4: Idempotency ID isolation across peers ✅
  • 6/6 PASS — completes all 8 scenario coverage (A–H)
  • README: new ## Heartbeat / Cron Agents section with Python template (commit 06f6fac)
  • Research: ANP downgraded to archived in ROADMAP (last updated 2026-03-05)

Test Coverage (cumulative)

Scenario Status File
A — P2P dual agent test_three_level_connection.py
B — Orchestrator→Workers test_scenario_bc.py
C — Pipeline A→B→C→A test_scenario_bc.py
D — Stress (100 msgs, concurrent) test_scenario_d_stress.py
E — NAT 3-level fallback (real) ⏳ needs real NAT environment
F — Error handling test_scenario_fg.py
G — Disconnect/reconnect test_scenario_fg.py
H — Multi-agent concurrent routing (ad-hoc, 2026-03-24)

[1.3.0-dev] — 2026-03-22/23

Added (v1.4-dev)

  • Three-level connection strategy fully integrated in guest_mode:
  • Level 1: Direct WebSocket (unchanged)
  • Level 2: DCUtR UDP hole punch via relay signaling (NEW — wired into main connect flow)
    • Signaling-only relay WS for address exchange
    • STUNClient public address discovery
    • Simultaneous UDP probes via DCUtRPuncher
    • SSE events: dcutr_started, dcutr_connected, relay_fallback
    • status.connection_type: p2p_direct | dcutr_direct | relay
  • Level 3: Relay permanent fallback (unchanged)
  • tests/test_three_level_connection.py: 20/20 PASS

Added (v1.1)

  • GET /tasks pagination — keyset cursor pagination, state/peer_id filter, sort order
  • New params: limit (max 200), cursor (exclusive keyset), state, peer_id, sort
  • Response: has_more, next_cursor, total fields
  • Addresses the gap noted in A2A issue #1667 discussion

Added (2026-03-23 — DCUtR NAT 穿透初版实现)

  • DCUtR 风格 UDP 打洞 NAT 穿透 — Level 2 连接策略(v1.4 特性,初版实装)
  • 新增 STUNClient 类 (~120 行):stdlib-only STUN Binding Request 客户端
    • 支持 RFC 5389 / RFC 8489(XOR-MAPPED-ADDRESS 优先,MAPPED-ADDRESS 兜底)
    • 使用公共 STUN 服务器 stun.l.google.com:19302
    • 3s 超时,失败静默返回 None(不抛异常)
    • 运行在 executor 中,不阻塞 asyncio event loop
  • 新增 DCUtRPuncher 类 (~200 行):UDP 打洞状态机
    • attempt(relay_ws, local_port) — 发起方:发 dcutr_connect → 等 dcutr_sync → 双方同时发 UDP 包 → 等回包
    • listen_for_dcutr(relay_ws, local_port) — 响应方:等 dcutr_connect → 回 dcutr_sync → 执行打洞
    • 打洞成功后自动关闭 Relay 连接(后续通信完全直连)
    • 所有超时/失败均静默降级,不抛异常到上层
  • 新增 connect_with_holepunch() 函数 (~60 行):对外公开 API
    • 返回 (websocket, is_direct: bool)
    • Level 1: 直连(3s timeout)→ Level 2: UDP 打洞(5s 信令 + 3s 探测)→ Level 3: Relay 永久中转
  • 新增 3 种 ACP 控制消息类型:dcutr_connect / dcutr_sync / dcutr_result
    • 在 Relay WebSocket 上传输,不影响业务消息
  • stdlib onlyasyncio, socket, struct, os, time, uuid — 无新增第三方依赖
  • 向后兼容acp:// 链接格式不变,NAT 穿透对上层完全透明
  • 文档:新建 docs/nat-traversal.md(用户指南),更新 spec/nat-traversal-v1.4.md(完整规范)

Fixed (commit 638f778 — 2026-03-23, scenario-C ring pipeline testing)

  • BUG-007 part 2 (P1)/message:send with peer_id still routed to wrong peer
  • Root cause: BUG-007 part 1 (commit 3a1c499) added the ambiguity guard but did not update the actual send dispatch — _ws_send_sync(msg) continued to use _peer_ws (the last-connected peer) even when peer_id was explicitly provided in the body.
  • Fix: _ws_send(msg, peer_id=None) and _ws_send_sync(msg, peer_id=None) now accept an optional peer_id parameter. When supplied, they look up _peers[peer_id]["ws"] and route directly to that WebSocket, also updating the per-peer messages_sent counter. Both the sync and async paths of /message:send now pass _req_peer_id.
  • Legacy behavior (no peer_id → use _peer_ws) preserved for backward compatibility.
  • Verified with Scenario C (A→B→C→A ring pipeline): 8/8 checks pass ✅.

Tested — Scenario C: A→B→C→A Ring Pipeline (2026-03-23)

Full end-to-end 3-agent ring pipeline validated: - Ring topology established: A→B, B→C, C→A (6 peer connections total, 2 per agent) ✅ - A injects payload (raw=[1,2,3,4,5]) → B via peer_id-directed /message:send ✅ - B receives, processes (doubled=[2,4,6,8,10]), forwards to C ✅ - C receives, finalizes (sum=30), sends result back to A ✅ - A receives complete pipeline result ✅ - Task state machine (pipeline_001completed) ✅ - Per-agent send/recv stats correct (A:2/1, B:1/1, C:1/1) ✅ - Result: 8/8 PASS 🎉

Fixed (commit 3a1c499 — 2026-03-23, 3-agent scenario-B testing)

Two bugs discovered during Orchestrator → Worker1 + Worker2 multi-peer test:

  • BUG-007 (P1)/message:send silently routed to wrong peer when multiple peers connected
  • When ≥2 peers are connected and no peer_id is supplied, /message:send previously sent to _peer_ws (the most recently connected peer) with no indication of ambiguity.
  • Fix: if len(connected_peers) > 1 and peer_id is absent in the request body, return HTTP 400 ERR_AMBIGUOUS_PEER with a connected_peers list guiding the caller to use POST /peer/{id}/send for directed delivery. If peer_id IS supplied in the body, the message is routed to that specific peer (single-peer path unchanged).
  • Verified: ERR_AMBIGUOUS_PEER returned with peer list ✅; peer_id routing ✅; single-peer agents unaffected ✅.

  • BUG-008 (P2) — Task action endpoints had inconsistent naming convention

  • :cancel used A2A-aligned colon style; /update, /wait, /continue used slash style.
  • Fix: router now accepts both colon and slash variants for all three endpoints: POST /tasks/{id}:update / /tasks/{id}/update, GET /tasks/{id}:wait / /tasks/{id}/wait, POST /tasks/{id}:continue / /tasks/{id}/continue. Old slash-style paths remain fully supported (backward-compatible).
  • Spec will be updated to recommend colon style; both accepted indefinitely.
  • Verified: /update slash ✅, :update colon ✅, :wait colon ✅.

Known Issues (discovered 2026-03-23, not yet fixed)

  • BUG-009 (P1) — SSE /stream event delivery latency ~950 ms
  • Root cause: the /stream and /tasks/{id}:subscribe handlers poll the event queue using time.sleep(1) in a busy-wait loop. On average, an event arriving mid-sleep waits ~500 ms; worst case 1 s. Measured avg 950 ms across 8 trials.
  • Impact: SSE push is unsuitable for latency-sensitive use cases until fixed.
  • Planned fix: replace time.sleep(1) with threading.Event.wait(timeout=0.05); _broadcast_sse_event calls event.set() to wake subscribers immediately. Expected result: SSE delivery latency < 10 ms.
  • Priority: P1 — fix in next development round.

Fixed (commit 643450c — 2026-03-23, real dual-agent testing)

Six bugs discovered during first live AlphaAgent↔BetaAgent P2P communication session:

  • BUG-001 (P0) — SSE /stream never delivered message events (only keepalive)
  • Root cause 1: HTTPServer is single-threaded; the /stream blocking loop blocked all subsequent HTTP requests including /message:send. Fix: use ThreadingHTTPServer.
  • Root cause 2: BaseHTTP defaults to HTTP/1.0 and sets close_connection = True after handle_one_request() returns, silently closing the SSE connection before any events are sent. Fix: self.close_connection = False + X-Accel-Buffering: no header.
  • Root cause 3: /message:send outbound path never called _broadcast_sse_event. Fix: add broadcast with direction: "outbound" after _ws_send_sync.
  • Test fix: tests/compat/test_stream.py raw-socket reader returns 0 bytes against HTTP/1.0 keep-alive connections; replaced with http.client streaming reader.

  • BUG-002 (P0) — Task :cancel endpoint returned status: "failed" instead of "canceled"

  • Added TASK_CANCELED = "canceled" constant; added to TERMINAL_STATES; cancel handler now uses the constant.

  • BUG-003 (P1)/peers/connect for the same link created duplicate peer entries

  • Two-layer fix: (1) /peers/connect checks existing connected peers before registering; returns already_connected: true on match. (2) guest_mode() WS connect reuses pre-registered peer entry (matched by token link) instead of calling _register_peer() again, which had created a second entry.

  • BUG-004 (P1)/message:send response body missing server_seq field

  • Captured seq = msg["server_seq"] before _ws_send_sync; included in both sync (reply) and async (fire-and-forget) response paths.

  • BUG-005 (P1)peer.messages_received counter never incremented

  • _on_message() now looks up sender peer by msg.get("from") name; falls back to single connected peer when from field absent; increments messages_received.

  • BUG-006 (P2) — Client-supplied task_id in POST /tasks body was ignored

  • _create_task() now accepts optional task_id parameter; if the ID already exists, returns the existing task (idempotent). /tasks handler passes body.get("task_id").

Added

  • Extension mechanism — URI-identified AgentCard extensions (commit 88d00fc)
  • New optional extensions array in AgentCard: [{uri, required, params?}]
  • capabilities.extensions: true flag when at least one extension declared
  • Runtime APIs:
    • GET /extensions — list all declared extensions with count
    • POST /extensions/register — register new extension at runtime (no restart)
    • POST /extensions/unregister — remove extension by URI at runtime
  • Merge semantics: URI-keyed; re-registering the same URI updates in-place
  • Extensions omitted from AgentCard when none declared (clean opt-in)
  • tests/unit: +5 TestExtensions tests (card absent/present, capabilities flag, register/unregister)
  • docs/integration-guide.md: full Extension mechanism section with curl examples
  • docs/comparison.md: ACP Extensions vs A2A extensions[] comparison row
  • Design: aligned with A2A extension model (URI-identified, required flag), zero-config when unused

  • did:acp: DID Identity — stable, self-sovereign Agent identifier (commit 6595e39)

  • Derives did:acp:<base64url(ed25519-pubkey)> from existing --identity keypair
  • No external registry; the DID is the key (key-based method)
  • AgentCard gains did field when identity enabled; omitted otherwise
  • New endpoint GET /.well-known/did.json — W3C-compatible DID Document:
    • verificationMethod[] with publicKeyMultibase (Ed25519VerificationKey2020)
    • authentication, assertionMethod relationships
    • Returns 404 when --identity not configured
  • capabilities.did_identity: true flag when --identity provided
  • Outbound AgentCard includes did field for peer verification
  • tests/unit: +5 TestDidAcp tests (derivation, AgentCard embed, DID Document structure)
  • docs/integration-guide.md: full DID Identity section (format, AgentCard sample, /.well-known/did.json sample, Python peer-verification snippet, design notes)
  • docs/comparison.md: DID identifier + DID Document rows — did:acp: (key-based, no DNS) vs ANP did:wba: (domain-based, requires DNS)
  • docs/README.zh-CN.md: v1.3 status 规划中 → 🚧 进行中, all three items ✅

  • Official Docker image v1.3 + GHCR CI publish pipeline (commit 1f0b7e5)

  • Dockerfile version label bumped 1.2.01.3.0
  • New run examples in Dockerfile header: v1.3 Extension + DID identity flags
  • GHCR pull instructions: docker pull ghcr.io/kickflip73/agent-communication-protocol/acp-relay:latest
  • .github/workflows/docker-publish.yml — automated multi-arch build & push:
    • Triggers: push to main, semver tags (v*.*.*), manual workflow_dispatch
    • Matrix: base (no extra deps) + full (websockets + cryptography)
    • Registry: GitHub Container Registry (ghcr.io)
    • Tags: :latest, :vX.Y.Z, :sha-<short>, -full variant suffix
    • Platforms: linux/amd64 + linux/arm64 (multi-arch)
    • GHA layer cache (cache-from/to: type=gha) for fast incremental rebuilds
    • Smoke-test job: pull :latest, start container, verify /.well-known/acp.json returns valid AgentCard
  • docker-compose.yml v1.3 additions:
    • Commented DID Identity pair example (requires acp-relay:full, persistent acp-identity volume)
    • Commented Extension registration demo example
    • volumes.acp-identity declaration for stable Ed25519 keypair across container restarts

Notes

  • v1.3 introduces two orthogonal extensibility layers: Extensions (capability advertisement) + DID (identity layer)
  • Both are fully opt-in: no breaking changes to v1.0/v1.2 deployments
  • Unit test total: 92 (v1.2) + 10 (v1.3 TestExtensions + TestDidAcp) = 102 PASS
  • tests/unit/test_relay_core.py: 121 def test_ entries (includes v1.3 classes)
  • ACP now has 4 extensibility dimensions: HMAC security · Ed25519 identity · availability scheduling · URI-identified Extensions — all opt-in, zero-config default
  • v1.1 Backlog fully closed: failed_message_id ✅ · replay-window ✅ · Rust SDK ✅ · DID ✅ · Docker CI ✅ (only HTTP/2 transport binding remains open as optional long-term item)

[1.2.0-dev] — 2026-03-22

Added

  • AgentCard availability block — heartbeat/cron agent scheduling metadata (commit c10c230)
  • New optional availability object in AgentCard; omitted when not configured (opt-in)
  • Fields: mode (persistent|heartbeat|cron|manual), interval_seconds, next_active_at, last_active_at (auto-stamped from startup time), task_latency_max_seconds
  • capabilities.availability: true flag when block is present
  • CLI flags: --availability-mode, --heartbeat-interval, --next-active-at
  • Config-file keys: availability-mode, heartbeat-interval, next-active-at
  • ACP is the first Agent communication protocol to support scheduling metadata natively (A2A issue #1667, 2026-03-21: A2A AgentCard has no scheduling fields)
  • tests/unit: +10 TestAgentCardAvailability tests; total 83 PASS
  • PATCH /.well-known/acp.json — live availability update API (commit cd67181)
  • Heartbeat agents can stamp next_active_at / last_active_at on each wake without restarting the relay
  • Merge semantics: only patched fields are updated; others preserved
  • Whitelist validation: allowed fields enforced; unknown fields → 400
  • Mode enum validation; missing availability key → 400
  • Supports both /card and /.well-known/acp.json paths
  • tests/unit: +9 TestPatchAvailability tests; total 92 PASS
  • docs/cli-reference.md updated to v1.2
  • New section: "Live availability update (PATCH)" with curl examples, response schema, PATCH rules summary, macOS/Linux date command portability note

  • Rust SDKsdk/rust/acp-relay-sdk v1.2 (commit bed7884)

  • Thin blocking HTTP client (reqwest 0.12 + serde + thiserror)
  • RelayClient::new(base_url) — validates URL scheme; strips trailing slash
  • send_message(MessageRequest)MessageResponse
    • MessageRequest::user/agent(text) helpers; .with_message_id(id); .sync_timeout(secs) for blocking request-response
  • agent_card()AgentCardResponse (self + optional peer, with Availability)
  • patch_availability(AvailabilityPatch) → live update scheduling metadata (v1.2)
  • status(), link(), ping() utility methods
  • AcpError enum: Http / Relay { code, message } / InvalidUrl / Json
  • 8 unit tests (helpers, URL validation, skip_serializing_if behaviour)
  • sdk/rust/README.md: quick-start, heartbeat example, API table
  • docs/integration-guide.md — new full Rust SDK section (send, card, PATCH, error handling)
  • Added Go SDK section header to match Python/Node/Rust consistency

Notes

  • Inspired by A2A issue #1667: A2A protocol has no mechanism for heartbeat/cron agents to advertise scheduling intent. ACP v1.2 fills this gap with a clean, opt-in design.
  • Multi-language SDK matrix now complete: Python ✅ · Go ✅ · Node.js ✅ · Rust ✅

[1.1.0-dev] — 2026-03-22

Added

  • HMAC replay-window (--hmac-window <seconds>, default 300 s) (commit e263f52)
  • New _hmac_check_replay_window(ts_str) helper: parses ISO-8601 UTC timestamp, checks |server_now − msg_ts| ≤ window; returns (ok, reason) for clean logging
  • Inbound WS handler: when --secret is set, out-of-window messages are hard-rejected (dropped) before any processing — prevents replay attacks
  • Signature mismatch remains warn-only for graceful interop with legacy agents
  • Configurable via --hmac-window <seconds> CLI flag or hmac-window config-file key
  • Graceful degradation: when --secret is not set, replay-window check is a no-op
  • docs/security.md: HMAC audit result PARTIAL → ✅ PASS; new §1.3 replay-window docs; audit history v1.1.0 = 9 PASS, 0 PARTIAL
  • tests/unit: +10 TestHMACReplayWindow tests; unit test total 63 → 73 PASS

Security

  • HMAC-SHA256 audit now fully PASS (9/9, 0 PARTIAL)
  • Previous PARTIAL item: "no server-side timestamp window check" — now resolved

[1.0.0] — 2026-03-21

Added (P0 — Specification & Versioning)

  • spec/core-v1.0.md: authoritative v1.0 specification (631 lines) (commit 20aa1ed)
  • Supersedes spec/core-v0.8.md
  • Stability annotations: stable / experimental per endpoint and field
  • §1.1: role MUST-level validation rules (v0.9 breaking change formally recorded)
  • §4: complete HTTP API stability matrix (17 endpoints)
  • §6: ERR_INVALID_REQUEST formal definition (incl. role trigger)
  • §11: CLI reference (12 flags, stability annotations)
  • §12: package distribution (pip install acp-relay, npm install acp-relay-client)
  • §13: v1.0 compatibility guarantees (4 MUST requirements)
  • Appendix A: version history through v0.9 + v1.0
  • Appendix B: ACP vs A2A comparison table (refs #876, #883)
  • API stability annotations in acp_relay.py (commit 19b3627)
  • [stable] (13 endpoints): /.well-known/acp.json, /status, /peers, /recv, /tasks, /stream, /message:send, /send (legacy), /peers/connect, /tasks/{id}/continue, /tasks/{id}:cancel, /skills/query
  • [experimental] (1 endpoint): /discover (mDNS, platform-dependent)
  • docs/security.md: complete security model documentation (commit a3ee229)
  • §1 HMAC-SHA256: mechanism, audit findings table (replay-window later resolved in v1.1)
  • §2 Ed25519: mechanism, audit findings table, HMAC coexistence
  • §3 HMAC vs Ed25519 side-by-side comparison
  • §4 Transport security recommendations (nginx/Caddy/Cloudflare Tunnel)
  • §5 Known limitations summary (severity + roadmap)
  • §6 Audit history
  • Go SDK stub (sdk/go/) (commit bcf6b75)
  • Package acprelay — stdlib-only, zero external dependencies (Go 1.21+)
  • Client struct with 6 stable methods: Send, Recv, GetStatus, GetTasks, CancelTask, QuerySkills
  • 16 tests via net/http/httptest.Server
  • sdk/go/README.md with install + quick start + API reference table

Changed (P0)

  • Version bumped to 1.0.0 across all package files (commit ddfaf07)
  • relay/acp_relay.py: VERSION = "0.8-dev""1.0.0"
  • pyproject.toml: 0.9.0.dev01.0.0
  • sdk/python/setup.py: 0.9.0.dev01.0.0
  • sdk/node/package.json: 0.9.0-dev.01.0.0

Security (P1 — Audit)

  • HMAC-SHA256 audit (commit a3ee229)
  • ✅ PASS: hmac.compare_digest constant-time comparison
  • ✅ PASS: no timing oracle in error path
  • ✅ PASS: message_id unpredictability (secrets.token_hex(8))
  • ✅ PASS: secret never written to disk
  • ⚠️ PARTIAL: no server-side replay-window timestamp check (resolved in v1.1 --hmac-window)
  • Ed25519 identity audit (commit a3ee229)
  • ✅ PASS: key file permissions enforced (chmod 0600)
  • ✅ PASS: canonical form deterministic (sort_keys=True + compact separators)
  • ✅ PASS: identity.sig excluded from signing payload correctly
  • ✅ PASS: InvalidSignature exception handling (no exception leaks)
  • ✅ PASS: graceful fallback when cryptography not installed
  • ✅ PASS: key generation from OS CSPRNG (Ed25519PrivateKey.generate())

Release Tag

  • v1.0.0-rc.1 pushed (commit ddfaf07)

[0.9.0] — 2026-03-21

Added (P0 — Developer UX)

  • CLI --version: prints acp_relay.py <version> and exits (commit e74afdf)
  • CLI --verbose / -v: switch root logger from INFO → DEBUG at startup
  • CLI --config <FILE>: load defaults from a JSON or YAML config file
  • JSON: stdlib json.loads
  • YAML: stdlib-only flat key-value parser (no PyYAML required); bool/int coercion
  • Precedence: CLI flags > config file > hardcoded defaults
  • All 12 flags supported; clear error + exit(1) on missing file
  • Example config files: relay/examples/config.json, config-relay.json, config-secure.yaml
  • docs/cli-reference.md: comprehensive CLI reference (all flags, port layout, 8 usage patterns, config file section)
  • spec/core-v0.8.md: single authoritative specification (515 lines, supersedes core-v0.5.md) (commit 4728b0e)
  • 11 chapters: principles, message envelope, Part model, Task FSM, AgentCard, error codes, extensions, transport, peer registration, skill query, versioning
  • Appendix A: full version history v0.1–v0.8
  • Appendix B: A2A v1.0 comparison table

Changed (P0)

  • AsyncRelayClient rewritten — stdlib-only, zero external dependencies (removed aiohttp) (commit 7bcb907)
  • Implementation: asyncio.get_event_loop().run_in_executor() offloads urllib calls to thread pool
  • New methods: connect_peer, discover, card, link, get_task, continue_task, cancel_task, wait_for_task, async stream generator
  • send(): adds context_id (v0.7), task_id, create_task, sync mode
  • update_task(): new artifact parameter
  • query_skills(): adds query free-text + limit params
  • wait_for_peer(): converted to async
  • 35 new tests in sdk/python/tests/test_async_relay_client.py — all passing
  • Python SDK __version__: 0.6.00.8.0
  • acp-research/ROADMAP.md: full rewrite — all v0.1–v0.8 milestones marked complete

Added (P1 — Quality & Docs)

  • /message:send server-side required field validation (commit bb1c80e)
  • Missing role400 ERR_INVALID_REQUEST with descriptive error message
  • Invalid role value (not user/agent) → 400 ERR_INVALID_REQUEST
  • Replaces silent default "user" fallback; addresses A2A issue #876 gap
  • 7 new MUST-level test cases in tests/compat/test_message_send.py
  • CHANGELOG.md (this file): complete version history v0.1.0–v0.9.0-dev (commit b48e9d5)
  • docs/integration-guide.md comprehensive rewrite (commit 2a74d3e)
  • Covers P2P / Relay / mDNS transport options; port layout (WS :7801 + HTTP :7901)
  • Task CRUD, multi-peer sessions, HMAC signing, Ed25519 identity
  • Python sync + async SDK examples; Node.js SDK examples
  • Multi-language quick-start (curl / Go / Java / Rust)
  • Troubleshooting table (503 / 400 / 413 + solutions)
  • tests/unit/test_relay_core.py: 63 unit tests covering all internal helpers (commit ac9846c)
  • TestErrHelper, TestIdGenerators, TestPartConstructors, TestValidatePart/Parts, TestHMACHelpers, TestTaskStateConstants, TestLoadConfigFile, TestParseLink, TestVersion

Added (P2 — Package Distribution)

  • pyproject.toml: pip install acp-relay support (commit 0fb0c9e)
  • Package name: acp-relay; version: 0.9.0.dev0
  • Required dep: websockets>=12.0 only
  • Optional [identity]: cryptography>=42.0; Optional [dev]: pytest + httpx
  • CLI entry-point: acp-relay = 'acp_relay:main'
  • relay/py.typed PEP 561 marker
  • Node.js SDK renamed to acp-relay-client (commit 9c1b0d9)
  • ESM entry-point src/index.mjs (createRequire bridge, export default RelayClient)
  • package.json: full npm metadata, exports field (ESM + CJS + types), files whitelist
  • .npmignore: excludes tests/ from published package
  • LICENSE: Apache-2.0 (aligned with repo root)
  • 19 tests passing

[0.8.0] — 2026-03-21

Added

  • Ed25519 optional identity extension (--identity [path]) (commit 1a13dec)
  • Self-sovereign keypair: auto-generated at ~/.acp/identity.json (chmod 0600)
  • Every outbound message includes identity.sig (base64url-encoded Ed25519 signature)
  • AgentCard publishes identity.public_key for peer verification
  • Graceful fallback: identity block omitted when cryptography not installed
  • Requires: pip install cryptography
  • Node.js SDK (sdk/node/) (commit fd8c02a)
  • RelayClient class — zero external dependencies, TypeScript types
  • All v0.8 endpoints: send, recv, tasks, peers, skills, stream (SSE)
  • 19 tests passing
  • Compatibility test suite (tests/compat/) (commit 98197cf)
  • Black-box spec compliance runner: parameterized by ACP_BASE_URL
  • Covers: AgentCard structure, /message:send response shape, SSE events, Task lifecycle, error code format, idempotency
  • spec/core-v0.8.md: consolidated authoritative specification (515 lines) supersedes spec/core-v0.5.md and spec/transports.md

Changed

  • README overhauled for v0.8: dependency table, full feature matrix, updated quickstart

[0.7.0] — 2026-03-20

Added

  • HMAC-SHA256 optional message signing (--secret <key>) (commit 87dad51)
  • sig = HMAC-SHA256(secret, message_id + ":" + timestamp)
  • Verification is warn-only (never drops messages) for graceful interop
  • AgentCard trust.scheme: "hmac-sha256" | "none"
  • mDNS LAN peer discovery (--advertise-mdns) (commit aabfae5)
  • Pure stdlib UDP multicast 224.0.0.251:5354 — no zeroconf library required
  • GET /discover: returns list of LAN peers with acp:// links
  • SSE event type=mdns for real-time new-peer notifications
  • context_id multi-turn conversation grouping (commit aabfae5)
  • Optional field on /message:send — client-generated, server-echoed
  • Groups related messages across multiple Task cycles
  • AgentCard capability: context_id: true
  • spec/transports.md v0.3: Protocol Bindings vs Extensions separation (commit 68db641)

Changed

  • AgentCard capabilities block: hmac_signing, lan_discovery, context_id fields

[0.6.0] — 2026-03-20

Added

  • Multi-session peer registry (commit ad7e1c4)
  • GET /peers: list all connected peers
  • GET /peer/{id}: get a specific peer's info
  • POST /peer/{id}/send: send a message to a specific peer
  • POST /peers/connect: connect to a new peer via acp:// link
  • AgentCard capability: multi_session: true
  • Standardized error codes (commit c816cb5)
  • 6 codes: ERR_NOT_CONNECTED / ERR_MSG_TOO_LARGE / ERR_NOT_FOUND / ERR_INVALID_REQUEST / ERR_TIMEOUT / ERR_INTERNAL
  • Unified response: {ok, error_code, error, failed_message_id}
  • failed_message_id: enables precise client-side retries (inspired by ANP)
  • Reference: spec/error-codes.md
  • Minimal agent spec (spec/v0.6-minimal-agent.md): 3-endpoint minimum to join ACP network
  • GET /.well-known/acp.json (AgentCard)
  • POST /message:send (receive inbound)
  • GET /stream (SSE outbound, optional)
  • Python SDK v0.6 (sdk/python/) (commit 430a97f)
  • RelayClient: sync HTTP client, all v0.6 endpoints, stdlib-only
  • RelayClient.stream(): SSE generator using urllib
  • Cloudflare Worker v2.0 (commit 8e8b771)
  • Multi-room concurrent sessions
  • Sliding TTL (30 min inactivity expiry)
  • Cursor-based poll (no duplicate messages)
  • DELETE /acp/{token} cleanup endpoint
  • Transport C: HTTP polling relay (acp+wss:// scheme) (commit 907c729)
  • Fallback for K8s/firewall environments with no inbound TCP
  • Auto-fallback: P2P timeout (10 s) → relay (commit fd74394)
  • Composite link: single acp:// token pre-registered on relay; transparent upgrade/fallback
  • Proxy-aware WebSocket connector (commit 4f392b8)
  • Reads http_proxy / HTTPS_PROXY env vars; routes WS through HTTP CONNECT tunnel

Removed

  • GitHub Issues relay transport (acp+gh://) permanently deleted (commit bc25ab7)
  • Reason: required both-side GitHub tokens; violated zero-registration principle

[0.5.0] — 2026-03-19

Added

  • Task state machine — 5 states (commit cd9545e, bb6aba3)
submitted → working → completed
                   → failed
                   → input_required  (resumable via /tasks/{id}/continue)

New endpoints: | Endpoint | Method | Description | |----------|--------|-------------| | /tasks | GET | List tasks; ?status= filter | | /tasks/{id} | GET | Get single task | | /tasks/{id}/wait | GET | Long-poll until terminal state (?timeout=N) | | /tasks/{id}/update | POST | Update state + optional artifact | | /tasks/{id}/continue | POST | Resume from input_required | | /tasks/{id}:cancel | POST | Cancel → failed | | /tasks/{id}:subscribe | GET | Per-task SSE stream |

  • Bilateral task synchronization: create_task: true on /message:send auto-registers same-id task on the receiving peer; state updates propagate back via task.updated messages
  • Structured Part model — three types:
    {"type": "text",  "content": "Hello"}
    {"type": "file",  "url": "https://...", "media_type": "image/png", "filename": "photo.png"}
    {"type": "data",  "content": {...}}
    
  • Message idempotency
  • message_id: client-generated UUID, server deduplicates per session
  • server_seq: monotonically increasing counter; clients can detect gaps/reordering
  • QuerySkill API (commit 710aade)
  • POST /skills/query: runtime capability query (skill_id, capability filter)
  • GET /.well-known/acp.json: standard AgentCard discovery endpoint
  • Structured SSE event types: status | artifact | message | peer
  • /message:send endpoint (A2A-aligned) alongside legacy /send
  • spec/core-v0.5.md: initial formal specification

[0.4.0] — 2026-03-18

Added

  • A2A-aligned AgentCard (commit 83ca11b)
  • /.well-known/acp.json: name, description, version, capabilities, skills
  • session_id field on all messages
  • Safety limits: --max-msg-size flag (default 1 MiB); ERR_MSG_TOO_LARGE on violation
  • --relay flag for host mode: one-command relay session start (commit 07f38ff)
  • SKILL.md v2: full SOP runbook with InStreet-style observable verification

Fixed

  • Unbounded consumption risk: max message size enforcement
  • Critical NameError in peer-equal architecture refactor (commit af73415)

[0.3.0] — 2026-03-18

Added

  • Four communication modes (commit 4f7e242)
  • Standard (request-response)
  • Streaming (SSE events)
  • Task delegation (fire-and-forget with status polling)
  • Broadcast (one-to-many)
  • Explicit connection lifecycle: connect / disconnect events; clean teardown
  • Lightweight explicit session management: session tokens in AgentCard

[0.2.0] — 2026-03-05

Added

  • ACP P2P v0.2: decentralized group chat support
  • Skill guide: how to expose and invoke agent capabilities
  • acp_relay.py: local daemon replacing central relay server architecture
  • Zero-code-change design: Agents connect by passing a single link
  • Human-as-messenger pattern: acp://IP:PORT/TOKEN link shared by human

Changed

  • Architecture shift: from centralized relay → true P2P direct connect (commit 183c425)

[0.1.0] — 2026-03-05

Added

  • Initial ACP v0.1 specification (spec/)
  • Python SDK skeleton (sdk/python/)
  • Gateway server reference implementation
  • Framework integration examples (LangChain, AutoGen, CrewAI stubs)
  • Bilingual README (EN + ZH)
  • Design principles established:
  • Lightweight & zero-config
  • True P2P — no middleman
  • Practical — curl-compatible
  • Personal/team focus
  • Standardization (Agent↔Agent, like MCP for Agent↔Tool)

Version Summary

Version Date Theme Key Feature
0.9.0-dev 2026-03-21 Developer UX + Distribution CLI flags, async SDK stdlib-only, unit tests, pip install acp-relay, acp-relay-client npm
0.8.0 2026-03-21 Ecosystem Ed25519 identity, Node.js SDK, compat test suite
0.7.0 2026-03-20 Trust + Discovery HMAC signing, mDNS LAN discovery, context_id
0.6.0 2026-03-20 Multi-peer + Reliability Peer registry, error codes, HTTP relay, Python SDK
0.5.0 2026-03-19 Structure Task state machine, Part model, idempotency, QuerySkill
0.4.0 2026-03-18 Safety AgentCard v2, max-msg-size, SKILL.md SOP
0.3.0 2026-03-18 Modes 4 communication modes, explicit lifecycle
0.2.0 2026-03-05 P2P True P2P relay, Skill guide, zero-code-change
0.1.0 2026-03-05 Foundation Initial spec, Python SDK, design principles