跳到主要内容

A2A — Open Agent Federation

Suzent speaks A2A (Agent2Agent), the open agent-interoperability standard governed by the Linux Foundation. This is the half of the mesh that reaches agents we did not build — LangGraph, ADK, CrewAI, or anything else that implements the spec.

See nodes.md for the device mesh and security.md for how grants and tokens work.

1. Where A2A sits

LayerProtocolTalks toContract
ToolsMCPservices the agent usestransparent
DevicesSuzent Nodesyour hardwaretransparent capability manifest
AgentsA2Aother intelligencesopaque execution

Nodes are deliberately not A2A. A node advertises exactly what it can do (camera.snap, system.script) so the agent can pick a capability. A2A's model is the opposite: you delegate a goal and never see inside. Folding nodes into A2A would destroy the manifest that makes them useful.

The Suzent-native peer channel (/channels/suzent/*) also remains. It is the trusted fast path between two Suzent installs and carries things the standard does not: file attachments, pairing grants, mDNS discovery.

2. Wire format

We implement the JSON-RPC binding, which is what the reference SDK's JSON-RPC client speaks and what interoperates in practice.

SurfacePathAuth
Agent CardGET /.well-known/agent-card.jsonnone (public by design)
JSON-RPCPOST /a2a/v1bearer device grant (agent scope)
Local status/UI/a2a/status, /a2a/agents, /a2a/outboundloopback or full scope

Methods: message/send, message/stream (SSE), tasks/get, tasks/cancel, tasks/resubscribe. Push notification configs return -32003 (unsupported).

Version note. The card advertises protocolVersion: "0.3.0". A2A 1.0 renamed methods for the gRPC and REST bindings (SendMessage, /v1/message:send); the JSON-RPC binding still uses message/send. Advertising 0.3.0 with these method names is what the official SDK's JSON-RPC client expects.

3. Publishing is not authorizing

The Agent Card is served unauthenticated — that is what makes discovery work — but it is off by default (CONFIG.a2a_enabled). When off, the well-known path 404s, so a disabled device looks like one that never spoke A2A.

Publishing announces existence. It grants nothing. /a2a/v1 still requires the same per-peer bearer grant that the Suzent pairing flow mints, and an agent-scope token reaches only the agent surface — never /config, /nodes/devices, or /a2a/status.

The card includes the device name and OS environment (e.g. Windows 11 (AMD64)), so a delegating agent can judge where work will land.

4. Task lifecycle

A2A models work as a task with real states:

submitted → working → completed | failed | canceled
↘ input-required ↗ (client answers, task resumes)
↘ auth-required ↗

input-required is the state the one-shot peer channel could not express. A remote agent that hits an ambiguity stops and asks; the caller answers with a normal message/send carrying the same taskId, and the task resumes.

Terminal states are final — suzent.a2a.tasks refuses any transition out of them rather than silently reviving a settled task.

Durability. The conversation is persisted by the normal chat store (a task's contextId is a Suzent chat id, namespaced per authenticated caller so two remote agents cannot read each other's context). The task wrapper — state, artifacts, subscribers — is in-memory and bounded; eviction never drops a task that is still live.

5. Discovery

A2A defines four mechanisms: well-known URI, curated registries, direct configuration, and authenticated extended cards. There is no global public registry, and the spec has no LAN discovery at all.

That is why Suzent's own discovery is not redundant: mDNS and Tailscale find peers on your network with zero configuration, which the standard cannot do. External A2A agents are added by URL; we fetch the card to verify the address before storing it.

6. Code map

ModuleRole
suzent/a2a/types.pywire models (camelCase, spec-conformant)
suzent/a2a/card.pythis device's Agent Card
suzent/a2a/tasks.pytask store + state machine
suzent/a2a/executor.pybridges a task to a ChatProcessor turn
suzent/a2a/client.pyoutbound client (delegate to others)
suzent/a2a/store.pyregistry of external agents
suzent/a2a/outbound.pylocal view of delegated tasks
suzent/routes/a2a_routes.pyHTTP surface

The wire types are hand-rolled rather than taken from a2a-sdk at runtime: the SDK ships an opinionated server framework that would fight the Starlette route style used elsewhere. Conformance is not left to trust — tests/a2a_protocol/test_sdk_conformance.py drives our server with the official SDK client, which is a dev-only dependency.

7. Known gaps

  • Push notifications are unimplemented (-32003). Long-running delegation currently relies on streaming or polling tasks/get.
  • input-required is client-side only. We handle a remote agent asking us a question; our own server does not yet emit that state, because a Suzent turn runs headless for remote callers and has no way to pause for input.
  • The Suzent peer channel is still a parallel implementation. It should be rebuilt on this task engine, with attachments and grants as A2A extensions, so there is one lifecycle rather than two.
  • Agent Card signatures (AgentCardSignature) are not produced or verified.