Batonv0.3.9

A2A: Baton and other agents

Baton speaks Agent2Agent (A2A) in both directions:

Both sides follow A2A v1.0 (the current stable spec: SendMessage, SendStreamingMessage, GetTask, ListTasks, CancelTask, SubscribeToTask, the push-notification config methods, ProtoJSON payloads, A2A-Version: 1.0). They also speak v0.3 (message/send, message/stream, tasks/get, tasks/cancel, tasks/resubscribe, tasks/pushNotificationConfig/*, kind-tagged objects), which most agents deployed today still use.

bindingclientserver
JSON-RPC 2.0 (v1.0 and v0.3)yesyes, POST / (or /a2a)
HTTP+JSON / REST (v1.0)yesyes, /message:send, /tasks/{id}, … (also under /v1/…)
gRPCnono

gRPC is left out on purpose. It needs HTTP/2 trailers and a protobuf codec, and Bun's fetch and Bun.serve offer neither, so it would mean @grpc/grpc-js + protobufjs (a second HTTP stack and a generated codec). gRPC-web would need a proxy in front of every agent. The spec requires an agent to offer the same operations on every binding it lists, so an agent that offers gRPC almost always offers JSON-RPC or REST too.

Using a remote A2A agent

# .baton/config.toml
[agents.researcher]
adapter = "a2a"

[agents.researcher.options]
url = "https://research-agent.example.com"   # base URL or the agent-card URL
tokenEnv = "RESEARCH_AGENT_TOKEN"            # or token = "…" (bearer), or headers = { "x-api-key" = "…" }

Then /spawn researcher (or let the lead spawn it).

Any string option can be an env:NAME reference, for example clientSecret = "env:RESEARCH_SECRET". The agent's env is checked first, then the daemon's environment.

optionmeaning
urlAgent base URL or agent-card URL. args[0] works too.
endpointEndpoint override. Skips the card's interface choice, and the card becomes optional. JSON-RPC unless transport = "rest".
transport"auto" (default), "jsonrpc" or "rest". Auto takes the first v1.0 interface the card lists, because the card lists its preferred one first. It falls back to v0.3 JSON-RPC.
token / tokenEnvBearer token, or the name of the env var that holds it.
apiKeyAPI key for an apiKey security scheme. It goes where the card says: header, query or cookie.
basic"user:password" for an HTTP basic scheme.
clientId, clientSecretOAuth2 client credentials, for oauth2 and openIdConnect schemes.
tokenUrl, oidcUrl, scopes, audience, clientAuthOverrides for the token endpoint and the OIDC discovery URL, extra scopes, an audience, and clientAuth = "post" to send the client secret in the form body instead of HTTP basic.
securitySchemeUse this scheme from the card (by name) instead of picking one.
headersExtra HTTP headers. An Authorization header here wins over the schemes.
protocolVersion"1.0" or "0.3" to force a dialect. By default the adapter uses what the card offers, preferring 1.0.
streamingfalse turns streaming off even when the card supports it.
blockingNon-streaming only. true sends one blocking SendMessage. The default returns right away and polls GetTask, so an interrupt can always cancel.
pollIntervalMsPolling interval for GetTask (default 1000).
reconnectAttempts, reconnectDelayMsResubscribe attempts after a dropped stream (default 3), and the first backoff delay, which then doubles (default 500 ms).
cancelOnCloseCancel the running remote task when the agent is closed (default true).
requireSignedCardRefuse an agent whose card has no valid signature. Without it, an invalid signature only logs a warning.
cardJwksTrusted signing keys, as a JWKS object or a URL. Pins the key instead of trusting the card's jku.
pushUrl, pushToken, pushAuthRegister this webhook with every message, if the card supports push. pushAuth = { scheme = "Bearer", credentials = "…" }.
sendContextfalse stops the adapter from sending the session's system prompt as context (default true).
steerfalse queues mid-turn input instead of sending it to the running task.
permissionsfalse shows yes/no questions as plain input-required turns instead of approval prompts.

How it maps:

Exposing your team as an A2A agent

baton a2a                 # serve on 127.0.0.1, random port; prints the URL and token
baton a2a --port 4780     # fixed port
baton a2a --stop

Or set [a2a] autostart = true.

Security.

Example: Baton → Baton

# project B: use project A's team as one worker
[agents.team-a]
adapter = "a2a"
options = { url = "http://127.0.0.1:4780", tokenEnv = "TEAM_A_TOKEN" }

Known gaps

Baton 0.3.9 · MIT · Edit this page on GitHub