Skip to content

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.fired at the start of each invocation
  • a2a.action at 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.

The full enum lives in internal/a2a/a2a_emit.go. Adding an action requires updating severityForA2AAction and the exhaustive enum test.

ActionSeverityLaneMeaning
client_invoke_startedinfoclientinvoke_a2a resolved a target and started a remote task
client_invoke_completedinfoclientRemote returned a terminal state (completed, input-required, auth-required, failed)
client_invoke_failedwarnclientTransport, parse, or non-recoverable client failure
server_task_startedinfoserverbmo serve accepted an inbound A2A task and started executing
server_task_completedinfoserverInbound task finished with completed
server_task_failedwarnserverExecutor rejected or aborted the inbound task
server_task_input_requiredinfoserverInbound task paused for caller input or approval
server_task_cancelledinfoserverCaller cancelled the inbound task (operator-initiated)
mesh_fallback_attemptwarncompositionCapability-resolved peer failed; mesh advanced to next candidate
disabled_rejectedwarncompositioninvoke_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.

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.

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).

When options.a2a.enabled = true, the TUI sidebar shows A2A: with one of:

  • idle — enabled, no events recorded yet.
  • <n> events — only a2a.fired records present (no terminal action yet).
  • last: <action> — last a2a.action was Info severity.
  • warn: <action> — last a2a.action was 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.

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.

  • Disabled invoke (client) — agent calls invoke_a2a but operator did not enable A2A. Surfaces as a2a.action action=disabled_rejected (Warn) and increments the sidebar warn: 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) returns HTTP 404 Not Found. Callers see a clean “feature disabled” signal rather than 401/500 ambiguity. No a2a.* records are emitted on the server side because the routes are not mounted; toggle options.a2a.enabled = true to 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/send accepted but the executor failed. Pair the inbound a2a.fired with server_task_failed using the shared invocation_id to reconstruct the failure.
  • Operator-initiated canceltasks/cancel from the caller emits server_task_cancelled at Info severity. The lane intentionally distinguishes this from server_task_failed so cancel storms do not pollute warn filters.

Lifecycle records carry only metadata. Specifically out of scope:

  • A2A message body text (inbound or outbound)
  • Agent-card JSON or well-known discovery 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.

  • 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.