# `PtcRunner.TraceContext`
[🔗](https://github.com/andreasronge/ptc_runner/blob/main/lib/ptc_runner/trace_context.ex#L1)

Centralizes process dictionary access for tracing context.

All tracing-related process dictionary keys are managed through this module,
providing a single point of access for collector stacks, span stacks, and
child step pass-through data.

## Collector Stack

Supports nested trace collection. Each `TraceLog.start/1` pushes a collector
onto the stack, and `TraceLog.stop/1` pops it. Events are routed to all
active collectors.

## Span Stack

Maintains parent-child span relationships for telemetry correlation.
Used by `PtcRunner.SubAgent.Telemetry` to track nested spans.

## Cross-Process Propagation

Use `capture/0` and `attach/1` to propagate trace context across process
boundaries (e.g., `Task.async_stream/3`).

## Child Step Pass-Through

Used by `PtcRunner.Lisp.Eval` to smuggle child execution metadata through
the process dictionary without polluting the Lisp value space.

## See Also

- [Observability Guide](subagent-observability.md) — cross-process tracing section
- `PtcRunner.TraceLog` — JSONL trace capture
- `PtcRunner.SubAgent.Telemetry` — telemetry event emission

# `attach`

```elixir
@spec attach(map()) :: :ok
```

Restores trace context from a captured map in a child process.

Merges collectors (filtering dead PIDs) and restores the span stack.

# `capture`

```elixir
@spec capture() :: map()
```

Captures the current trace context into a portable map.

Use with `attach/1` to propagate context to child processes.

# `clear_lisp_prelude_trace`

```elixir
@spec clear_lisp_prelude_trace() :: :ok
```

Clears the stashed Lisp `prelude_trace` (called at turn start).

# `collectors`

```elixir
@spec collectors() :: [pid()]
```

Returns all active collectors (innermost first).

# `current_collector`

```elixir
@spec current_collector() :: pid() | nil
```

Returns the innermost (current) collector, or `nil`.

# `current_span_id`

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

Returns the current (innermost) span ID, or `nil`.

# `lisp_prelude_trace`

```elixir
@spec lisp_prelude_trace() :: map() | nil
```

Returns the current turn's stashed Lisp `prelude_trace`, or nil.

# `memory_sinks`

```elixir
@spec memory_sinks() :: [pid()]
```

Returns all active in-memory sinks (innermost first).

# `merge_collectors`

```elixir
@spec merge_collectors([pid()]) :: :ok
```

Merges collectors from another process into the current stack.

Filters out dead processes and deduplicates while preserving order.

# `merge_memory_sinks`

```elixir
@spec merge_memory_sinks([pid()]) :: :ok
```

Merges in-memory sinks from another process into the current stack.

Filters out dead processes and deduplicates while preserving order.

# `parent_span_id`

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

Returns the parent span ID (second element on the stack), or `nil`.

# `pop_collector`

```elixir
@spec pop_collector() :: {pid(), String.t()} | nil
```

Pops the innermost collector and handler ID from the stack.

Returns `{collector, handler_id}` or `nil` if the stack is empty.

# `pop_span`

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

Pops the current span from the stack.

# `push_collector`

```elixir
@spec push_collector(pid(), String.t()) :: :ok
```

Pushes a collector and its handler ID onto the stack.

# `push_memory_sink`

```elixir
@spec push_memory_sink(pid()) :: :ok
```

Pushes an in-memory turn-log sink onto the stack.

Mirrors the collector stack: turn-event emitters route to every active
in-memory sink in addition to the JSONL collectors.

# `push_span`

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

Pushes a span ID onto the stack. Returns the parent span ID (previous top).

# `put_child_result`

```elixir
@spec put_child_result(String.t() | nil, term()) :: :ok
```

Stores a child execution result for pass-through.

# `put_lisp_prelude_trace`

```elixir
@spec put_lisp_prelude_trace(map() | nil) :: :ok
```

Stashes the just-executed turn's Lisp `prelude_trace` (or nil).

# `remove_collector`

```elixir
@spec remove_collector(pid()) :: {pid(), String.t()} | nil
```

Removes a specific collector from the stack (not necessarily the top).

Returns `{collector, handler_id}` if found, or `nil`.

# `remove_memory_sink`

```elixir
@spec remove_memory_sink(pid()) :: pid() | nil
```

Removes a specific in-memory sink from the stack. Returns the pid or `nil`.

# `set_parent_span`

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

Sets the initial parent span for this process (for cross-process propagation).

Only sets if the span stack is empty (does not override existing context).

# `span_context`

```elixir
@spec span_context() :: %{span_id: String.t() | nil, parent_span_id: String.t() | nil}
```

Returns the span context map with `:span_id` and `:parent_span_id`.

# `take_child_result`

```elixir
@spec take_child_result() :: {String.t() | nil, term()} | nil
```

Takes (gets and deletes) the child execution result. One-shot read.

Returns `{trace_id, step}` or `nil` if no child result is stored.

---

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