# `PtcRunner.SubAgent.Telemetry`
[🔗](https://github.com/andreasronge/ptc_runner/blob/main/lib/ptc_runner/sub_agent/telemetry.ex#L1)

Telemetry event emission for SubAgent execution.

This module provides helpers for emitting telemetry events during SubAgent
execution, enabling integration with observability tools like Prometheus,
OpenTelemetry, and custom handlers.

## Events

All events are prefixed with `[:ptc_runner, :sub_agent]`.

| Event | Measurements | Metadata |
|-------|--------------|----------|
| `[:run, :start]` | `%{}` | agent, context, span_id, parent_span_id |
| `[:run, :stop]` | `%{duration: native_time}` | agent, step, status, span_id, parent_span_id |
| `[:run, :exception]` | `%{duration: native_time}` | agent, kind, reason, stacktrace, span_id, parent_span_id |
| `[:turn, :start]` | `%{}` | agent, turn, span_id, parent_span_id |
| `[:turn, :stop]` | `%{duration: native_time, tokens: n, input_tokens: n, output_tokens: n}` | agent, turn, program, result_preview, prints, type, span_id, parent_span_id |
| `[:llm, :start]` | `%{}` | agent, turn, messages, span_id, parent_span_id |
| `[:llm, :stop]` | `%{duration: native_time, tokens: n, input_tokens: n, output_tokens: n}` | agent, turn, response, span_id, parent_span_id |
| `[:tool, :start]` | `%{}` | agent, tool_name, args, span_id, parent_span_id |
| `[:tool, :stop]` | `%{duration: native_time}` | agent, tool_name, result, span_id, parent_span_id |
| `[:tool, :exception]` | `%{duration: native_time}` | agent, tool_name, kind, reason, stacktrace, span_id, parent_span_id |
| `[:compaction, :triggered]` | `%{messages_before, messages_after, estimated_tokens_before, estimated_tokens_after}` | agent, agent_name, agent_id, turn, strategy, reason, kept_initial_user?, kept_recent_turns, over_budget?, span_id, parent_span_id |

Note: `[:compaction, :triggered]` is a single emit (not a span) and only fires
on triggered compaction events. Non-triggered pressure checks are silent.

## Span Correlation

All events include `span_id` and `parent_span_id` for correlation:
- `span_id` - 8-character hex string, unique per span
- `parent_span_id` - The span_id of the parent span, or `nil` for root spans

Nested spans maintain a parent-child hierarchy. For example, a tool call within
a turn will have the turn's span_id as its parent_span_id.

## Usage

Attach handlers using `:telemetry.attach_many/4`:

    :telemetry.attach_many(
      "my-handler",
      [
        [:ptc_runner, :sub_agent, :run, :stop],
        [:ptc_runner, :sub_agent, :tool, :stop]
      ],
      &MyApp.Telemetry.handle_event/4,
      nil
    )

## Duration

Duration measurements use native time units via `System.monotonic_time/0`.
Convert to milliseconds with `System.convert_time_unit(duration, :native, :millisecond)`.

# `current_span_id`

```elixir
@spec current_span_id() :: String.t() | nil
```

Returns the current span ID, or nil if not within a span.

# `emit`

```elixir
@spec emit([atom()], map(), map()) :: :ok
```

Emit a telemetry event.

Automatically includes `span_id` and `parent_span_id` from the current span context.

## Parameters

- `event_suffix` - List of atoms to append to the prefix
- `measurements` - Map of measurements (default: `%{}`)
- `metadata` - Map of metadata

# `prefix`

```elixir
@spec prefix() :: [atom()]
```

Returns the telemetry event prefix.

## Examples

    iex> PtcRunner.SubAgent.Telemetry.prefix()
    [:ptc_runner, :sub_agent]

# `set_parent_span`

```elixir
@spec set_parent_span(String.t() | nil) :: :ok
```

Sets the initial span stack for this process.

Used for trace propagation across process boundaries. When spawning child
processes, capture the parent's current span ID and call this function in
the child to establish the parent-child span relationship.

## Parameters

  * `parent_span_id` - The span ID from the parent process (or nil)

## Example

    # In parent process
    parent_span = Telemetry.current_span_id()

    Task.async(fn ->
      Telemetry.set_parent_span(parent_span)
      # New spans in this process will have parent_span as their parent_span_id
    end)

# `span`

```elixir
@spec span([atom()], map(), (-&gt; {any(), map()} | {any(), map(), map()})) :: any()
```

Execute a function within a telemetry span.

Emits `:start`, `:stop`, and `:exception` events automatically.
The start metadata is passed as-is. The stop metadata receives
any additional measurements or metadata returned from the function.

All events include `span_id` and `parent_span_id` for correlation.

## Parameters

- `event_suffix` - List of atoms to append to the prefix (e.g., `[:run]`)
- `start_meta` - Metadata map for the start event
- `fun` - Zero-arity function to execute. Should return one of:
  - `{result, stop_meta}` - where `stop_meta` is merged into stop event metadata
  - `{result, extra_measurements, stop_meta}` - where `extra_measurements` is merged
    into stop measurements and `stop_meta` is merged into stop event metadata

---

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