A2A Protocol Telemetry
BMO emits paired structured events for every A2A invocation it participates in,
on both the client path (invoke_a2a, real_client) and the server
path (bmo serve JSON-RPC executor):
a2a.firedat the start of each invocationa2a.actionat every terminal or composition decision
Records are written to slog and a process-scoped ring buffer (capacity 16) so operators and agents can inspect recent activity without scraping logs. The protocol surface itself is documented in A2A Protocol integration; this page covers the observability contract.
Bounded actions
Section titled “Bounded actions”The full enum lives in internal/a2a/a2a_emit.go. Adding an action requires
updating severityForA2AAction and the exhaustive enum test.
| Action | Severity | Lane | Meaning |
|---|---|---|---|
client_invoke_started | info | client | invoke_a2a resolved a target and started a remote task |
client_invoke_completed | info | client | Remote returned a terminal state (completed, input-required, auth-required, failed) |
client_invoke_failed | warn | client | Transport, parse, or non-recoverable client failure |
server_task_started | info | server | bmo serve accepted an inbound A2A task and started executing |
server_task_completed | info | server | Inbound task finished with completed |
server_task_failed | warn | server | Executor rejected or aborted the inbound task |
server_task_input_required | info | server | Inbound task paused for caller input or approval |
server_task_cancelled | info | server | Caller cancelled the inbound task (operator-initiated) |
mesh_fallback_attempt | warn | composition | Capability-resolved peer failed; mesh advanced to next candidate |
disabled_rejected | warn | composition | invoke_a2a short-circuited because options.a2a.enabled = false |
a2a.fired is metadata-only. Common fields: invocation_id, session_id,
task_id, context_id, target_url, capability. Outbound message bodies,
agent-card JSON, and bearer tokens are never emitted on either record. The
target_url attribute is redacted to scheme + host[:port] at emit time
(path, query, fragment, and userinfo are dropped) so operator dashboards group
cleanly on host without leaking task ids or auth material.
Operator surfaces
Section titled “Operator surfaces”/a2a (TUI)
Section titled “/a2a (TUI)”Aliases: /agent-to-agent. Read-only. Renders enablement, agent name and
default agent-card URL, HTTP listen (when options.http_server.enabled), the
ring capacity and recent count, the last few a2a.fired/a2a.action pairs,
and the canonical a2a.fired / a2a.action slog filter strings.
bmo config show-a2a (CLI)
Section titled “bmo config show-a2a (CLI)”Same snapshot as /a2a for the current process. CLI and TUI share the ring
only when run in the same process (the ring is process-global, not persisted).
Sidebar
Section titled “Sidebar”When options.a2a.enabled = true, the TUI sidebar shows A2A: with one of:
idle— enabled, no events recorded yet.<n> events— onlya2a.firedrecords present (no terminal action yet).last: <action>— lasta2a.actionwas Info severity.warn: <action>— lasta2a.actionwas Warn (failure, mesh fallback, or disabled rejection).
The label is scoped to the current TUI session (events without a session
are also included, so cross-session client invokes still surface). When A2A
is disabled, the sidebar item is greyed out (no description); use
bmo config show-a2a for the explicit “disabled” snapshot.
Agent tool
Section titled “Agent tool”list_recent_a2a_events returns JSON { "ring_capacity", "returned", "events" } with snake_case event fields. Optional session_id filter and
limit (default: full ring; capped at ring capacity, currently 16). Use this
tool from inside an agent session for JSON export or programmatic filtering;
the same data drives /a2a and bmo config show-a2a.
Failure modes the lane catches
Section titled “Failure modes the lane catches”- Disabled invoke (client) — agent calls
invoke_a2abut operator did not enable A2A. Surfaces asa2a.action action=disabled_rejected(Warn) and increments the sidebarwarn:badge instead of failing silently. - Disabled server (HTTP) — when
options.a2a.enabled = false, every A2A HTTP route on the embedded server (the agent-card endpoint and the JSON-RPC handler) returnsHTTP 404 Not Found. Callers see a clean “feature disabled” signal rather than401/500ambiguity. Noa2a.*records are emitted on the server side because the routes are not mounted; toggleoptions.a2a.enabled = trueto bring the lane online. - Mesh fallback — capability resolved to peer A; A failed; mesh tried peer
B. Each transition emits
mesh_fallback_attempt(Warn) with the failed target so operators can attribute capacity issues to specific peers. - Server task failure — inbound JSON-RPC
tasks/sendaccepted but the executor failed. Pair the inbounda2a.firedwithserver_task_failedusing the sharedinvocation_idto reconstruct the failure. - Operator-initiated cancel —
tasks/cancelfrom the caller emitsserver_task_cancelledat Info severity. The lane intentionally distinguishes this fromserver_task_failedso cancel storms do not pollute warn filters.
Privacy posture
Section titled “Privacy posture”Lifecycle records carry only metadata. Specifically out of scope:
- A2A message body text (inbound or outbound)
- Agent-card JSON or
well-knowndiscovery payloads Authorization: Bearer …token values- File-part contents from inbound tasks
The a2a.* slog records put the bounded enum on the action attribute and
reserve outcome for short, human-readable free-text context (e.g.
"missing target", "permission denied"). The outcome value is truncated
to 256 bytes (a2aOutcomeMaxLen) so cardinality stays predictable. If a
future arm needs a structured signal, add it as a new A2AAction enum
constant — do not stuff data into outcome.
Related
Section titled “Related”- A2A Protocol integration — protocol semantics, client/server config, and authentication.
- Agent Mesh — capability-based delegation that the
composition arms (
mesh_fallback_attempt,disabled_rejected) protect. - Maintainer detail: agent tracing
for the full
a2a.*jq recipes and decision-reconstruction playbook.