Skip to content

Tracing & the HTML viewer

A chronicler stands in the circle: it records everything and changes nothing.

TraceRecorder is a Ward — opt-in, one per Invocation, zero overhead when absent:

from sanctum.omens import TraceRecorder, render_trace

recorder = TraceRecorder("run.sanctum-trace.json")
entity = summon(oracle, tome, wards=[recorder])
await entity.ainvoke(input, invocation_id="inv-7")

When the Rite manifests, run.sanctum-trace.json holds the complete story (format sanctum-trace/1): the graph, the superstep timeline with per-Sigil durations, every delta, spell calls correlated with their arguments and results, retries, repairs, rejections, and Seal ids. After a failure, call recorder.flush() to persist what was captured.

The viewer

python -m sanctum.trace render run.sanctum-trace.json

renders a single self-contained HTML file — no server, no JavaScript frameworks, zero external requests (verified by test):

  • a static SVG of the graph (hierarchical layers from START to END; cycles and conditional edges dashed),
  • the superstep timeline with duration bars per Sigil (retries marked),
  • expandable per-superstep detail: deltas, retries, Seals,
  • the spell-call table and the repairs section,
  • the final Aether.

Open it in any browser, attach it to a bug report, archive it next to the Seals — it will render identically offline in ten years.

Programmatic use: render_trace(trace_path) -> html_path.

Overhead, measured

The benchmark (python benchmarks/superstep_overhead.py) includes a TraceRecorder case: on the order of a hundred microseconds per superstep on a consumer laptop — noise next to model inference. Recording is in-memory until the single JSON write at the end.