BeamAgent.Telemetry (beam_agent_ex v0.1.0)

Copy Markdown View Source

OpenTelemetry-style span and event emission for the BeamAgent SDK.

All five backend session handlers emit telemetry events via this module so that consuming applications can attach handlers once and observe every backend uniformly. No OTLP export or collector is built in — this module follows the Erlang/OTP :telemetry convention: the library emits events, applications handle them.

Optional dependency

The :telemetry library is an optional dependency. When present, events are emitted via :telemetry.execute/3. When absent, all emission is a silent no-op with zero overhead. To enable telemetry, add {:telemetry, "~> 1.3"} to your application's deps in mix.exs and ensure the :telemetry application is started.

When to use directly vs through BeamAgent

Backends call this module internally during session operation. Use it directly only when implementing a custom backend adapter or adding instrumentation to a session handler.

Event namespace

All events are published under the [:beam_agent, ...] prefix. The agent parameter (an atom such as :claude or :codex) becomes the second element of the event name list:

[:beam_agent, :claude, :query, :start]
[:beam_agent, :claude, :query, :stop]
[:beam_agent, :claude, :query, :exception]
[:beam_agent, :session, :state_change]   # always at this fixed path
[:beam_agent, :run, :state_change]       # canonical run lifecycle
[:beam_agent, :buffer, :overflow]        # always at this fixed path

Span lifecycle example

start_time = BeamAgent.Telemetry.span_start(:claude, :query, %{session_id: id})
# ... do work ...
BeamAgent.Telemetry.span_stop(:claude, :query, start_time)

Attaching handlers

Use the standard :telemetry.attach/4 or :telemetry.attach_many/4 call in your application startup:

:telemetry.attach_many(
  :my_handler,
  [
    [:beam_agent, :claude, :query, :start],
    [:beam_agent, :claude, :query, :stop]
  ],
  &MyTelemetryHandler.handle/4,
  []
)

See the :telemetry library documentation for handler function signature details.

Summary

Functions

Emit a buffer overflow warning when accumulated transport data exceeds the limit.

Emit a span exception event when a unit of work fails.

Emit a span exception event with additional metadata.

Emit a span start event and return a monotonic start time.

Emit a span stop event, computing duration from the start time.

Emit a span stop event with additional metadata.

Emit a state change event for a gen_statem transition.

Emit a state change event for a non-session BeamAgent domain.

Functions

buffer_overflow(buffer_size, max)

@spec buffer_overflow(pos_integer(), pos_integer()) :: :ok

Emit a buffer overflow warning when accumulated transport data exceeds the limit.

The event is published at the fixed path [:beam_agent, :buffer, :overflow]. This event fires when the session engine's inbound buffer grows beyond the configured maximum, which typically signals a misbehaving backend or extremely large responses.

Parameters:

  • buffer_size — current size of the buffer in bytes
  • max — the configured maximum in bytes

span_exception(agent, suffix, reason)

@spec span_exception(atom(), atom(), term()) :: :ok

Emit a span exception event when a unit of work fails.

The event is published at [:beam_agent, agent, event_suffix, :exception]. Call this instead of span_stop/3 when the work raised an error or exception.

Parameters:

  • agent — backend atom
  • suffix — operation atom, must match the span_start/3 call
  • reason — the error reason or exception term

span_exception(agent, suffix, reason, metadata)

@spec span_exception(atom(), atom(), term(), map()) :: :ok

Emit a span exception event with additional metadata.

Same as span_exception/3 but merges caller-supplied metadata into the emitted exception event.

span_start(agent, suffix, metadata)

@spec span_start(atom(), atom(), map()) :: integer()

Emit a span start event and return a monotonic start time.

The returned integer must be passed unchanged to span_stop/3 or span_exception/3 so the duration can be computed.

Parameters:

  • agent — backend atom, e.g. :claude, :codex, :gemini
  • event_suffix — atom labelling the operation, e.g. :query, :connect
  • metadata — arbitrary map attached to the telemetry event

Returns a monotonic integer (result of :erlang.monotonic_time/0).

Example

t = BeamAgent.Telemetry.span_start(:claude, :query, %{session_id: id})
# work...
BeamAgent.Telemetry.span_stop(:claude, :query, t)

span_stop(agent, suffix, start_time)

@spec span_stop(atom(), atom(), integer()) :: :ok

Emit a span stop event, computing duration from the start time.

The event is published at [:beam_agent, agent, event_suffix, :stop] with a duration measurement in native time units.

Parameters:

span_stop(agent, suffix, start_time, metadata)

@spec span_stop(atom(), atom(), integer(), map()) :: :ok

Emit a span stop event with additional metadata.

Same as span_stop/3 but merges caller-supplied metadata into the stop event.

state_change(agent, from_state, to_state)

@spec state_change(atom(), atom(), atom()) :: :ok

Emit a state change event for a gen_statem transition.

The event is published at the fixed path [:beam_agent, :session, :state_change]. Backend session handlers call this on every state machine transition so that consumers can observe the full session lifecycle.

Parameters:

  • agent — backend atom identifying which session handler fired
  • from_state — the state the session is leaving (e.g. :connecting, :ready)
  • to_state — the state the session is entering

Valid state atoms: :connecting, :initializing, :ready, :active_query, :error.

state_change(domain, from_state, to_state, metadata)

@spec state_change(atom(), atom(), atom(), map()) :: :ok

Emit a state change event for a non-session BeamAgent domain.

The event is published at [:beam_agent, domain, :state_change]. Canonical BeamAgent domains such as runs, steps, and routines use this helper to report lifecycle transitions without inventing a second telemetry style.