defmodule AgentSea.Telemetry do
@moduledoc """
Telemetry events emitted across AgentSea. Attach handlers — a Logger, a
Prometheus exporter, or a Phoenix LiveView dashboard — to observe agent,
provider, tool, and crew activity without any bespoke event bus.
All events are under the `:agentsea` prefix.
Span events (emit `:start`, `:stop`, and `:exception` via `:telemetry.span/3`):
* `[:agentsea, :agent, :run, _]` — a full `AgentSea.Agent.run/3`
* metadata: `%{name, model}`; `:stop` adds `%{outcome, stop_reason}`
* `[:agentsea, :provider, :complete, _]` — one provider completion in the loop
* metadata: `%{provider, model, name, iteration}`;
`:stop` adds `%{outcome, stop_reason, input_tokens, output_tokens}`
* `[:agentsea, :tool, :run, _]` — one tool execution
* metadata: `%{tool, agent}`; `:stop` adds `%{outcome}`
Discrete events (emit `:start` and `:stop` via `:telemetry.execute/3`):
* `[:agentsea, :crew, :kickoff, :start | :stop]` — a crew run
* metadata: `%{crew, task_count}`; `:stop` adds `%{success}`,
measurements `%{duration}`
* `[:agentsea, :crew, :task, :start | :stop]` — one crew task
* metadata: `%{crew, task_id, agent}`; `:stop` carries `%{crew, task_id, outcome}`
* `[:agentsea, :gateway, :route, :stop]` — a gateway routing decision
* metadata: `%{provider, outcome}`; measurements `%{attempts, latency_ms}`
* `[:agentsea, :guardrail, :stop]` — a guardrail acted on content (emitted
only when a guardrail transforms or blocks, not on a plain pass)
* metadata: `%{guardrail, outcome}` where `outcome` is `:transform | :block`
"""
require Logger
@spans [
[:agentsea, :agent, :run],
[:agentsea, :provider, :complete],
[:agentsea, :tool, :run]
]
@discrete [
[:agentsea, :crew, :kickoff],
[:agentsea, :crew, :task],
[:agentsea, :gateway, :route],
[:agentsea, :guardrail]
]
@doc "Every event name AgentSea may emit."
@spec events() :: [[atom()]]
def events do
span_events = Enum.flat_map(@spans, &expand(&1, [:start, :stop, :exception]))
discrete_events = Enum.flat_map(@discrete, &expand(&1, [:start, :stop]))
span_events ++ discrete_events
end
defp expand(base, suffixes), do: Enum.map(suffixes, &(base ++ [&1]))
@doc "Attach a Logger handler for all AgentSea events (handy in dev)."
@spec attach_default_logger(Logger.level()) :: :ok | {:error, :already_exists}
def attach_default_logger(level \\ :debug) do
:telemetry.attach_many(
"agentsea-default-logger",
events(),
&__MODULE__.handle_event/4,
%{level: level}
)
end
@doc false
def handle_event(event, measurements, metadata, %{level: level}) do
Logger.log(level, fn ->
"#{Enum.join(event, ".")} #{inspect(measurements)} #{inspect(metadata)}"
end)
end
end