Agent-to-Agent Protocol
How Agent Mesh Manages Workloads names the three workload classes. This page is the wire-format reference: the JSON-RPC envelope, the topic naming rules, the event broker header user properties, the signal taxonomy, and the JSON Web Token (JWT) trust chain that they use to talk to each other.
A2A is a JSON-RPC 2.0 dialect carried over event broker topics. The shape is identical to Python Agent Mesh: both implementations accept each other's messages without translation. This is a hard constraint, not a goal. When the Go code disagrees with the Python wire, the Python wire wins and the Go code is the bug.
The Envelope
Every A2A payload is a JSON-RPC 2.0 message. Requests and responses both carry the task ID in the JSON-RPC id field.
{
"jsonrpc": "2.0",
"id": "<taskID>",
"method": "message/stream",
"params": {
"message": {
"kind": "message",
"messageId": "<messageID>",
"role": "user",
"parts": [{ "kind": "text", "text": "Summarize quarterly earnings" }],
"contextId": "<sessionID>"
}
}
}
The success response is a JSON-RPC result; an error response is a JSON-RPC error with {code, message, data?}. The result payload is an A2A task, message, or status-update event (the polymorphism is method-dependent).
The JSON-RPC method names on the wire:
| Method | From | To | Use |
|---|---|---|---|
message/stream
|
Entrypoint Executor | Agent-Workflow Executor | Submit a streaming task |
message/send
|
Entrypoint Executor | Agent-Workflow Executor | Non-streaming submit |
message/send
|
Agent-Workflow Executor | peer agent | Delegation to a peer agent, subtask forwarding, and resuming an auth-required or input-required pause |
tasks/cancel
|
Entrypoint Executor | Agent-Workflow Executor | Cancel an in-flight task |
sam_remote_tool/invoke
|
Agent-Workflow Executor | Secure Tool Runtime | Invoke a remote tool |
sam_remote_tool/status
|
Secure Tool Runtime | Agent-Workflow Executor | Tool progress update |
sam_remote_tool/init
|
Secure Tool Runtime | Agent-Workflow Executor | Tool registration broadcast (one per tool at startup) |
sam_remote_tool/init_request
|
Agent-Workflow Executor | Secure Tool Runtime | Request a config-aware schema for a specific tool |
sam_remote_tool/removed
|
Secure Tool Runtime | Platform | Tool removed from the manifest |
sam_remote_tool/failed_discovery
|
Secure Tool Runtime | Platform | --schema invocation failed for a tool binary |
Topic Conventions
Every A2A topic follows the prefix <namespace>/a2a/v1/<service>/... where <namespace> is the configurable event broker namespace (default: solace-agent-mesh).
| Topic | Publisher | Subscriber |
|---|---|---|
<ns>/a2a/v1/agent/request/<agentName>
|
Entrypoint Executor | Agent-Workflow Executor |
<ns>/a2a/v1/gateway/status/<gatewayID>/<taskID>
|
Agent-Workflow Executor | Entrypoint Executor |
<ns>/a2a/v1/gateway/response/<gatewayID>/<taskID>
|
Agent-Workflow Executor | Entrypoint Executor |
<ns>/a2a/v1/agent/status/<delegatingAgent>/<subTaskID>
|
Peer agent | delegating agent |
<ns>/a2a/v1/agent/response/<delegatingAgent>/<subTaskID>
|
Peer agent | delegating agent |
<ns>/a2a/v1/sam_remote_tool/invoke/<toolName>
|
Agent-Workflow Executor | Secure Tool Runtime |
<ns>/a2a/v1/sam_remote_tool/response/<agentName>/<corrID>
|
Secure Tool Runtime | Agent-Workflow Executor |
<ns>/a2a/v1/sam_remote_tool/status/<agentName>/<corrID>
|
Secure Tool Runtime | Agent-Workflow Executor |
<ns>/a2a/v1/sam_remote_tool/cancel/<corrID>
|
Agent-Workflow Executor | every Secure Tool Runtime worker (via wildcard) |
<ns>/a2a/v1/discovery/agentcards
|
Agent-Workflow Executor | Entrypoint Executor and peer agents |
<ns>/a2a/v1/discovery/gatewaycards
|
Entrypoint Executor | clients |
<ns>/a2a/v1/trust/<componentType>/<componentID>
|
Every component | trust manager |
The <namespace> segment is validated at config load. Broker wildcards (*, >), path-traversal segments (..), and leading slashes are rejected so a misconfigured namespace fails fast instead of producing topics that escape the intended prefix.
User Properties
The event broker carries a flat map of string key/value pairs alongside every payload (in Solace terms, "user properties"). A2A uses this map for routing and auth metadata that has to be visible without parsing the JSON-RPC body.
| Key | Set by | Purpose |
|---|---|---|
clientId
|
originating entrypoint | Entrypoint ID, used for response topic routing |
userId
|
entrypoint | End-user identifier |
replyTo
|
entrypoint | Topic the agent publishes the terminal response to |
a2aStatusTopic
|
entrypoint | Topic the agent publishes status events to |
traceId
|
entrypoint (minted as UUIDv7) | Immutable per-task trace ID, forwarded on every republish |
authToken
|
entrypoint | Per-task JWT signed by the entrypoint's trust manager |
a2aUserConfig
|
entrypoint | JSON blob carrying _enterprise_capabilities, user_profile, enrichment outcome |
a2aExtensions
|
originating publisher | Comma-separated A2A extension URIs the publisher is using |
gatewayCapabilities
|
entrypoint | Capability flags peers forward unchanged. interactive_plan_verification: entrypoint can render the deep-research plan card. interactive_user_input: entrypoint can render a generic A2UI user_input_request surface (ask_user_question, tool approval, volume prompt_user) and round-trip a response. Web sets both; Slack/Teams set interactive_user_input; email sets it only when a magic-link form is configured; MCP/event-mesh leave it unset, so those prompts return a clean error (or, for tool approval, a fail-safe denial) instead of deadlocking. |
callDepth
|
delegating agent | Agent-to-agent recursion depth, gated by the per-agent peer-recursion limit |
timestamp
|
every publisher | Publication time in epoch milliseconds |
delegating_agent_name
|
delegating agent | Set on peer delegation; triggers verify-without-task-binding on the receiver |
All keys are camelCase except delegating_agent_name, which stays snake_case for Python Agent Mesh wire parity. Reading the map with the wrong case is the most common A2A bug.
Signal Taxonomy
Streaming status events carry an Agent Mesh-specific signal type in the status-update event's metadata. The most common signals group as follows:
| Category | Signals | Emitted when |
|---|---|---|
| LLM | llm_invocation, llm_response, thinking_content |
The agent calls the LLM, the LLM responds, an extended-thinking model emits its reasoning |
| Agent progress | agent_progress_update
|
The agent emits a free-form progress message |
| Tool invocation | tool_invocation_start, tool_result |
Tool dispatch begins, tool result arrives |
| Artifact lifecycle | artifact_creation_progress, artifact_saved |
A tool is producing an artifact, an artifact write commits |
| Templates / structured | template_block, structured_invocation_request, structured_invocation_result |
A fenced template block resolves, a structured-output sub-call begins / completes |
| Workflow | workflow_execution_start, workflow_execution_result, workflow_node_execution_start, workflow_node_execution_result, workflow_map_progress |
The workflow engine and each typed node emit their lifecycle |
| Deep research | deep_research_progress, deep_research_plan, deep_research_plan_stale, deep_research_report |
The deep-research tool publishes iteration progress, the verifiable plan, and the final report |
| Auth | authentication_required, auth_response |
A tool needs OAuth, the user has completed the consent flow |
| Human-in-the-loop | user_input_request, user_input_response |
The agent pauses for typed user input, the user has answered |
| Retrieval-Augmented Generation (RAG) | rag_info_update
|
A retrieval step emits its source metadata |
| AI Builder | builder_component_progress
|
The Platform service's AI Builder reports a component-generation step |
Signal-type values are snake_case lowercase on the wire: llm_invocation, not LLMInvocation and not llm-invocation.
Task State Vocabulary
A2A defines a closed set of task states, carried in the state field of every status-update event and on the final task body.
| State | Meaning |
|---|---|
submitted
|
Accepted by the agent, not yet started |
working
|
Actively in the LLM loop or executing a tool |
completed
|
Reached natural termination |
failed
|
Unrecoverable error |
canceled
|
The client called tasks/cancel |
input-required
|
Paused for human-in-the-loop input |
auth-required
|
Paused for OAuth consent |
rejected
|
Refused at admission (for example, RBAC scope failure) |
unknown
|
Unrecognized state from an older or newer wire version |
The terminal status-update event for a task has final: true. After that, the agent publishes the final response on the response topic.
Snake_Case on the Wire vs CamelCase in HTTP
The entrypoint's HTTP API uses camelCase (per the Solace REST API ADRs), but the A2A broker wire uses snake_case for inner blobs. The two conventions coexist at different layers: A2A wire parity with Python Agent Mesh is non-negotiable; REST ADR parity for the HTTP API is non-negotiable.
The canonical example is task_metadata: the same logical record flips case depending on whether it is travelling on the broker or in an HTTP data transfer object (DTO).
On the A2A wire (inside message.metadata or the parameters of a tool invocation):
{
"task_metadata": {
"agent_name": "Orchestrator",
"project_id": "${PROJECT_ID}",
"session_id": "${SESSION_ID}"
}
}
In an HTTP DTO returned by the entrypoint:
{
"taskMetadata": {
"agentName": "Orchestrator",
"projectId": "${PROJECT_ID}",
"sessionId": "${SESSION_ID}"
}
}
The rule is: if the bytes are going through the broker, snake_case; if they are going through HTTP, camelCase. Do not try to unify; the conversion happens at the entrypoint boundary.
JWT Signing and Trust
A2A uses a single signed channel for both authentication and authorization:
-
The entrypoint resolves the user's roles to scopes and signs a per-task JWT carrying both the user identity claims and the resolved
scopesclaim. The signed JWT is attached asauthTokenin the broker user properties. -
The agent's trust manager verifies the JWT (proving the request came from a legitimate entrypoint) and reads the resolved scopes directly from the verified claims. The agent does not run its own role-based access control (RBAC) resolution.
Each component holds a signing key for itself and verification keys for every other component, distributed via the trust-card topic. The signing key is bound to the broker client-username, and broker ACLs guarantee topic authenticity on the trust-card topic. That is what prevents a compromised peer from impersonating an entrypoint.
The unsigned a2aUserConfig._enterprise_capabilities body field exists for historical reasons but must not be used to populate scopes. Trusting it would let a compromised peer agent re-publish a legitimately signed entrypoint JWT alongside an inflated _enterprise_capabilities body, and the receiving agent would honor the inflated scopes. Authorization decisions must derive from the cryptographically verified JWT claims only.
In practice the receiving agent reconciles the unsigned body against the signed claims via a soft-subset assert: narrowing is allowed, widening is dropped with a structured warn. This is a deliberate migration accommodation, not a design feature; a future strict-mode configuration will reject mismatches outright.
Three Patterns the Protocol Uses
The protocol layers three independent patterns on top of the same envelope.
Discovery. Every agent and entrypoint publishes its card (agent card or entrypoint card) on the discovery topics on startup, then republishes when its capability set changes. Subscribers (peer agents, the entrypoint agent-card endpoint) build a local view of who is on the mesh. A component that stops publishing is reaped from the local view by a TTL sweep. Trust cards on <ns>/a2a/v1/trust/<componentType>/<componentID> follow the same publish-on-startup pattern for signing keys.
Request-response. One request on the request topic, one terminal response on the reply topic. Used for tasks/cancel, sam_remote_tool/invoke, and the non-streaming message/send. The request carries replyTo in user properties; the responder publishes exactly once and then stops.
Stream-status. Used for message/stream. The responder accepts the request, then emits a sequence of status-update events on the status topic until it finishes, at which point it emits one event with final: true and publishes a single response on the reply topic. Clients (the SSE event log, peer agents) follow the status stream live and use the terminal event to close their subscription.
Python Agent Mesh Parity
The other implementation of A2A is the Python Agent Mesh runtime: same envelope, same topic conventions, same user-property semantics. A Go Agent-Workflow Executor can serve a Python entrypoint's request; a Python Agent-Workflow Executor can serve a Go entrypoint's request. Mixed deployments are explicitly supported.
When the Go code and the Python code disagree on the wire, the Python repo is the cross-implementation contract. The fix is to change the Go code, not to invent a Go-specific message shape.
Next Steps
-
How Agent Mesh Manages Workloads: the three workload classes that exchange these messages.