# `PtcRunner.TraceLog.Introspection`
[🔗](https://github.com/andreasronge/ptc_runner/blob/main/lib/ptc_runner/trace_log/introspection.ex#L1)

Host-bound, read-only introspection over recorded turn-log sessions, packaged
as the `log/` capability prelude (plan P3, D4).

This is the proving consumer for "preludes are the sole opt-in mechanism": the
Elixir surface stays deliberately boring — plain data access over the turn log
(list sessions, fetch a session's turns/programs/tool-calls). Higher-level
analysis (dedup detection, cost aggregation, where-did-it-waste-turns) lives in
the PTC-Lisp programs that call these exports, not here.

## Wiring

    source = sink_pid_or_jsonl_path_or_event_list
    PtcRunner.Lisp.run(program,
      prelude: PtcRunner.TraceLog.Introspection.prelude_source(),
      tools: PtcRunner.TraceLog.Introspection.tools(source))

The `log/` prelude's exports invoke typed tools (`tool/log_sessions`, …), so
the compiler infers `tool:log_sessions`-style `requires`. Attach fails closed
unless the host grants those tools (the `tools/0` map). Read-only by design
(the two-grant rule, D4): there is no live-session control here.

## Memory model (P2 of `docs/plans/sandbox-heap-rebaseline.md`)

The granted closures are thin proxies: projections are computed host-side —
inside the `MemorySink` for pid sources, inside a
`PtcRunner.TraceLog.Introspection.Holder` started by `tools/2` for path and
list sources — so only each call's RESULT enters the sandbox and a program's
heap cost tracks result size, never log size. Call `tools/2` once per grant
(each path/list call starts a holder owned by the calling process; it stops
when that process goes down). A stopped sink/holder surfaces as a clear,
recoverable tool error, not a hang.

## Trust

Recorded sessions are **untrusted data** — they may contain adversarial or
junk programs. These tools return them as evidence to be analyzed, never as
instructions to follow.

# `source`

```elixir
@type source() :: pid() | String.t() | [map()]
```

A turn-log source: an in-memory sink pid, a JSONL trace path, or an already
loaded list of event maps.

# `prelude_source`

```elixir
@spec prelude_source() :: String.t()
```

The `log/` introspection prelude source. Attach it with the `tools/1` grant.

# `tools`

```elixir
@spec tools(
  source(),
  keyword()
) :: %{optional(String.t()) =&gt; (map() -&gt; term())}
```

Builds the granted, host-bound tool closures over `source`.

Grant these as `tools:`; the keys (`"log_sessions"`, `"log_turns"`,
`"log_programs"`, `"log_tool_calls"`) match the `tool:<name>` requirements the
`log/` prelude infers. All closures are read-only and return string-keyed data.

Path and list sources start a `Holder` owned by the calling process (see the
memory-model section above). Options: `:max_bytes` — the holder's
serialized-size load cap; raises `ArgumentError` for oversized logs.

---

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