Legion.Telemetry (Legion v0.6.0)

View Source

Telemetry integration for Legion agents.

Legion emits the following telemetry events:

Agent Lifecycle Events

  • [:legion, :agent, :started] - emitted during init/1, before any persisted conversation is restored

    • Measurements: %{system_time: NaiveDateTime.t()}
    • Metadata: %{agent: module, agent_id: String.t()} (plus parent_agent_id: String.t() when the agent was started inside another agent's run)
    • agent_id names the conversation - stable across restarts, so a resumed conversation emits under the same id.
    • Not emitted if init/1 crashes before the event fires (e.g. while building the system prompt) - in that case :stopped is not emitted either, since GenServer does not call terminate/2 on init failure.
  • [:legion, :agent, :stopped] - agent process terminated via terminate/2

    • Measurements: %{system_time: NaiveDateTime.t()}
    • Metadata: %{agent: module, agent_id: String.t()} (plus parent_agent_id: String.t() when the agent was started inside another agent's run)

Agent Message Events (spans)

  • [:legion, :agent, :message, :start | :stop | :exception] - agent handling a message

    • Metadata includes: agent, agent_id, message
    • Stop adds: iterations (count of assistant turns in this message), status (:ok or :cancel), result (the value returned, or the cancellation reason such as :reached_max_iterations), and bindings (the variable bindings carried out of the turn)

Iteration Events (spans)

  • [:legion, :iteration, :start | :stop | :exception]

    • Metadata includes: agent, agent_id, iteration
    • Stop adds: action

LLM Request Events (spans)

  • [:legion, :llm, :request, :start | :stop | :exception]

    • Metadata includes: agent, agent_id, model, message_count, iteration
    • Stop adds: object or error, plus usage when a response was received (the string-keyed usage map with its "at" timestamp and "message_index", the position in the request's messages of the assistant message it produced, equal to message_count, or nil when the response had no usable object)

Sandbox Eval Events (spans)

  • [:legion, :sandbox, :eval, :start | :stop | :exception]

    • Metadata includes: agent, agent_id, code
    • Stop adds: success and result or error.

Eval Guard Events

  • [:legion, :eval_guard, :denied] - a guard refused generated code
    • Metadata: %{agent: module, agent_id: String.t(), guard: module, code: String.t(), reason: String.t()}

Rate Limit Events

  • [:legion, :rate_limit, :exceeded] - a rate limiter denied an agent's start, a turn, or a Legion.eval/3 call, before it ran; metadata carries the identity and policy of the rule that denied it, the usage measured for it, and the violations
    • Measurements: %{system_time: NaiveDateTime.t()}
    • Metadata: %{agent: module, agent_id: String.t(), identity: map, policy: Legion.RateLimiter.Policy.t(), usage: map, violations: [atom]}
    • violations names the limits that were reached, e.g. [:max_tokens].

MCP Events

A session of a Legion.MCP.Server is an agent, so it emits the agent, sandbox and rate limit events above. On top of those, every repl and help call is a span that ties them to the MCP session:

  • [:legion, :mcp, :call, :start | :stop | :exception] - one repl or help call. A repl span wraps the [:legion, :sandbox, :eval] span of the same agent_id; a denied call has no eval span, and a help call runs no eval at all.

    • Metadata: %{agent: module, agent_id: String.t(), session_id: String.t(), code: String.t()} for repl. A help call carries tool: String.t() | nil (nil lists every tool) instead of code, and its agent_id is nil until the session has an agent.
    • Stop adds: success, and error with the text the host's model was given when the code failed or the call was rate limited.

Default Logger

A default logger is provided that outputs human-readable telemetry to Logger. Attach it with Legion.Telemetry.attach_default_logger/1.

Summary

Functions

Attaches a default logger for Legion telemetry events.

Detaches the default logger.

Emits a single telemetry event. Injects agent_id from the process dictionary.

Wraps a function with :start / :stop / :exception telemetry events.

Functions

attach_default_logger(opts \\ [])

Attaches a default logger for Legion telemetry events.

It logs the full code of every evaluation and the first 80 characters of each message, so user data can end up in your logs.

Options

  • :level - log level, defaults to :info
  • :events - :all or a list of event categories (:agent, :message, :iteration, :llm, :sandbox, :mcp). Defaults to :all.

detach_default_logger()

Detaches the default logger.

emit(event, measurements \\ %{}, metadata)

Emits a single telemetry event. Injects agent_id from the process dictionary.

span(event_prefix, metadata, fun)

Wraps a function with :start / :stop / :exception telemetry events.

The function should return {result, extra_stop_metadata}. agent_id is automatically injected from the process dictionary.