# `Nous.Telemetry`
[🔗](https://github.com/nyo16/nous/blob/v0.17.1/lib/nous/telemetry.ex#L1)

Telemetry integration for Nous AI.

Nous executes the following Telemetry events:

## Agent Events

  * `[:nous, :agent, :run, :start]` - Dispatched before agent execution starts
    * Measurement: `%{system_time: native_time, monotonic_time: monotonic_time}`
    * Metadata: `%{agent_name: string, model_provider: atom, model_name: string, tool_count: integer, has_tools: boolean}`

  * `[:nous, :agent, :run, :stop]` - Dispatched after agent execution completes
    * Measurement: `%{duration: native_time, total_tokens: integer, input_tokens: integer, output_tokens: integer, tool_calls: integer, requests: integer, iterations: integer}`
    * Metadata: `%{agent_name: string, model_provider: atom, model_name: string}`

  * `[:nous, :agent, :run, :exception]` - Dispatched when agent execution fails
    * Measurement: `%{duration: native_time}`
    * Metadata: `%{agent_name: string, model_provider: atom, kind: atom, reason: term, stacktrace: list}`

  * `[:nous, :agent, :iteration, :start]` - Dispatched before each agent iteration
    * Measurement: `%{system_time: native_time}`
    * Metadata: `%{agent_name: string, iteration: integer, max_iterations: integer}`

  * `[:nous, :agent, :iteration, :stop]` - Dispatched after each agent iteration
    * Measurement: `%{duration: native_time}`
    * Metadata: `%{agent_name: string, iteration: integer, tool_calls: integer, needs_response: boolean}`

## Provider Events

  * `[:nous, :provider, :request, :start]` - Dispatched before calling provider API
    * Measurement: `%{system_time: native_time, monotonic_time: monotonic_time}`
    * Metadata: `%{provider: atom, model_name: string, message_count: integer}`

  * `[:nous, :provider, :request, :stop]` - Dispatched after provider responds
    * Measurement: `%{duration: native_time, input_tokens: integer, output_tokens: integer, total_tokens: integer}`
    * Metadata: `%{provider: atom, model_name: string, has_tool_calls: boolean}`

  * `[:nous, :provider, :request, :exception]` - Dispatched when provider request fails
    * Measurement: `%{duration: native_time}`
    * Metadata: `%{provider: atom, model_name: string, kind: atom, reason: term}`

## Provider Streaming Events

  * `[:nous, :provider, :stream, :start]` - Dispatched before starting a streaming request
    * Measurement: `%{system_time: native_time, monotonic_time: monotonic_time}`
    * Metadata: `%{provider: atom, model_name: string, message_count: integer}`

  * `[:nous, :provider, :stream, :connected]` - Dispatched when stream connection is established
    * Measurement: `%{duration: native_time}`
    * Metadata: `%{provider: atom, model_name: string}`

  * `[:nous, :provider, :stream, :exception]` - Dispatched when streaming request fails
    * Measurement: `%{duration: native_time}`
    * Metadata: `%{provider: atom, model_name: string, kind: atom, reason: term}`

## Tool Events

  * `[:nous, :tool, :execute, :start]` - Dispatched before tool execution
    * Measurement: `%{system_time: native_time, monotonic_time: monotonic_time}`
    * Metadata: `%{tool_name: string, tool_module: module | nil, attempt: integer, max_retries: integer, has_timeout: boolean}`

  * `[:nous, :tool, :execute, :stop]` - Dispatched after tool completes
    * Measurement: `%{duration: native_time}`
    * Metadata: `%{tool_name: string, attempt: integer, success: boolean}`

  * `[:nous, :tool, :execute, :exception]` - Dispatched when tool fails
    * Measurement: `%{duration: native_time}`
    * Metadata: `%{tool_name: string, attempt: integer, will_retry: boolean, kind: atom, reason: term, stacktrace: list}`

  * `[:nous, :tool, :timeout]` - Dispatched when tool times out
    * Measurement: `%{timeout: integer}`
    * Metadata: `%{tool_name: string}`

## Context Events

  * `[:nous, :context, :update]` - Dispatched when context deps are updated by a tool
    * Measurement: `%{keys_updated: integer}`
    * Metadata: `%{agent_name: string, keys: list(atom)}`

## Callback Events

  * `[:nous, :callback, :execute]` - Dispatched when a callback is executed
    * Measurement: `%{duration: native_time}`
    * Metadata: `%{callback_type: atom, agent_name: string}`

## Fallback Events

  * `[:nous, :agent, :fallback, :used]` - Dispatched when an iteration switches
    to a fallback model (sticky-fallback). Use this to alert on provider
    degradation.
    * Measurement: `%{system_time: native_time}`
    * Metadata: `%{agent_name, original_provider, original_model, active_provider, active_model}`

  * `[:nous, :fallback, :activated]` - Dispatched when the fallback chain
    promotes to the next model after the primary returns an error.
    * Measurement: depends on the call site
    * Metadata: includes the active model and the reason for activation

## Hook Events

  * `[:nous, :hook, :execute, :start]` / `[:nous, :hook, :execute, :stop]`
    * Measurement: `%{system_time | duration}`
    * Metadata: `%{event, hook_name, hook_type, result?}`
  * `[:nous, :hook, :denied]` - emitted when a blocking hook returns :deny
    or when a fail_closed hook errors.
    * Metadata: `%{event, hook_name, hook_type, reason?}`

## Skill Events

  * `[:nous, :skill, :activate]` / `[:nous, :skill, :deactivate]`
    * Metadata: `%{skill_name, agent_name}`

## Workflow Events

  * `[:nous, :workflow, :run, :start]` / `[:nous, :workflow, :run, :stop]`
    / `[:nous, :workflow, :run, :exception]`
  * `[:nous, :workflow, :node, :start]` / `[:nous, :workflow, :node, :stop]`
    / `[:nous, :workflow, :node, :exception]`
    * See `Nous.Workflow.Telemetry` for full measurement/metadata payloads.

All times are in `:native` time unit. Use `System.convert_time_unit/3` to
convert to desired unit.

## Default Handler

Nous provides a default handler that logs events at appropriate levels:

    Nous.Telemetry.attach_default_handler()

This is useful for development and debugging.

## Custom Handlers

    :telemetry.attach(
      "my-nous-handler",
      [:nous, :agent, :run, :stop],
      fn _event, measurements, metadata, _config ->
        MyApp.Metrics.track_agent_run(
          metadata.agent_name,
          measurements.duration,
          measurements.total_tokens
        )
      end,
      nil
    )

## Metrics Integration

For production metrics, consider integrating with:
- `telemetry_metrics` for Prometheus/StatsD
- `telemetry_poller` for periodic metrics
- Phoenix LiveDashboard for visualization

    defmodule MyApp.Telemetry do
      use Supervisor
      import Telemetry.Metrics

      def metrics do
        [
          counter("nous.agent.run.start.count"),
          distribution("nous.agent.run.stop.duration",
            unit: {:native, :millisecond}
          ),
          sum("nous.agent.run.stop.total_tokens"),
          counter("nous.tool.execute.stop.count",
            tags: [:tool_name]
          ),
          counter("nous.tool.timeout.count",
            tags: [:tool_name]
          )
        ]
      end
    end

## Examples

Every event above is a plain `:telemetry` event, so attaching to one is just
`:telemetry.attach/4`. This handler forwards tool timeouts to the calling
process — the same shape you would use in a test to assert an event fired:

    iex> :telemetry.attach(
    ...>   "nous-doc-example",
    ...>   [:nous, :tool, :timeout],
    ...>   fn _event, measurements, metadata, pid ->
    ...>     send(pid, {:tool_timeout, metadata.tool_name, measurements.timeout})
    ...>   end,
    ...>   self()
    ...> )
    :ok
    iex> :telemetry.execute([:nous, :tool, :timeout], %{timeout: 5000}, %{tool_name: "bash"})
    :ok
    iex> receive do
    ...>   {:tool_timeout, tool_name, timeout} -> {tool_name, timeout}
    ...> after
    ...>   0 -> :no_event
    ...> end
    {"bash", 5000}
    iex> :telemetry.detach("nous-doc-example")
    :ok

Durations are in `:native` units, so convert before reporting them:

    iex> System.convert_time_unit(System.convert_time_unit(120, :millisecond, :native), :native, :millisecond)
    120

For local development, skip the wiring entirely and let Nous log everything
it emits:

    Nous.Telemetry.attach_default_handler()
    {:ok, result} = Nous.run(agent, "Summarise this file")
    # [Nous] Agent assistant completed in 1832ms (947 tokens, 1 tool calls, 2 iterations)
    Nous.Telemetry.detach_default_handler()

# `attach_default_handler`

```elixir
@spec attach_default_handler() :: :ok | {:error, :already_exists}
```

Attaches the default logging handler for Nous events.

This handler logs:
- Agent runs (info level)
- Provider requests (debug level)
- Tool executions (debug level)
- Exceptions (error level)

Returns `{:error, :already_exists}` if it is already attached.

## Example

    Nous.Telemetry.attach_default_handler()

# `detach_default_handler`

```elixir
@spec detach_default_handler() :: :ok | {:error, :not_found}
```

Detaches the default handler.

Returns `{:error, :not_found}` if it was never attached.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
