/**
* Trace — a UI-free, JSON-lossless snapshot of a run for OFFLINE REPLAY.
*
* `localObservability()` (Tier-3 / Debug) retains a live model during a run.
* `serializeTrace()` freezes that model into a `Trace` — plain JSON you can
* persist (file, Redis, a bug report) and later rehydrate WITHOUT re-running
* the agent. `agentfootprint-lens`'s `` consumes it and
* draws the flowchart via the existing translators.
*
* A `Trace` stores the domain-event log (the single source of truth the Lens
* already reads) plus, since 7.8, the run's footprintjs snapshot. The step
* graph is ALWAYS a derived projection of those events (footprint.js's "graph
* is derived, never post-processed" principle) — it is rebuilt at render time,
* never stored. Storing a derived graph would be redundant AND a redaction
* hazard: a second content surface a per-event `redact` could never reach.
*
* WHAT A TRACE CAN AND CANNOT DRIVE
* ─────────────────────────────────
* `` draws the chart from `structure`, and that is all it
* draws: a static shape, not a lit-up path. It does not read `trace.events` —
* time-travel over them is a planned refinement, and until it lands an offline
* `` is a strictly smaller view than the live ``, not a match
* for it.
*
* For the full viewers — ``, ExplainableShell, WhereFrom, the commit
* axis — what you want is a RECORDING: `{ snapshot, events, structure }`, the
* shape `recordRun()` produces and lens's `observeRecording()` consumes. Note
* `events` there means the TYPED agentfootprint stream (`runner.on('*')`),
* which is not the domain-event log a Trace carries: the two are different
* projections of the same run, and a Trace holds only the second. So a Trace
* with a `snapshot` can drive the snapshot-fed surfaces, but it is not itself
* a recording — reach for `recordRun()` when the destination is a viewer.
*
* PII / trust boundary: the event log carries real content — `llm.end.content`,
* `tool.start.args`, `tool.end.result`, `context.injected.contentSummary`,
* `run`/`subflow` `payload`, `decision.branch.rationale`. A live, in-process
* model is fine, but **serializing is a trust-boundary crossing** (the trace
* can travel). So redaction is applied HERE, at serialize time, via a
* consumer `redact` function — PII never enters the `Trace`. `redactContent`
* is a ready-made redactor covering every content field. The result is
* self-describing: `trace.redaction`. See
* `docs/design/local-observability-and-pii.md`.
*
* Because `getEvents()` is FLAT (parent + every subflow), one `redact` pass
* covers the whole tree — no per-subflow inheritance needed here. (The engine's
* `RedactionPolicy` separately propagates to subflows for the OBSERVER mirror.)
*/
import type { DomainEvent } from './BoundaryRecorder.js';
import { type StepGraph } from './FlowchartRecorder.js';
/**
* How a `Trace` was redacted before serialization.
* - `'none'` — raw content (no `redact`). A `` UI may warn.
* - `'pii'` — a consumer `redact` ran (the default label when one is given).
* - `'policy'` — produced from a declarative `RedactionPolicy` (future).
*/
export type TraceRedaction = 'none' | 'pii' | 'policy';
/** Cheap headline rollup, so a consumer can show totals without folding `events`. */
export interface TraceSummary {
readonly tokens: {
readonly input: number;
readonly output: number;
};
readonly llmCalls: number;
readonly toolCalls: number;
readonly durationMs?: number;
}
/**
* A JSON-lossless, UI-free snapshot of one run. Persist it, ship it, replay it.
* `events` ARE the timeline (the graph is a derived projection, rebuilt at
* render); `structure` is the chart; `snapshot` is the state the run left.
*/
export interface Trace {
/** Schema version. Bump on a breaking shape change. */
readonly version: 1;
/** The domain-event log — the whole timeline. Already redacted if `redact` ran. */
readonly events: readonly DomainEvent[];
/**
* The serialized STATIC chart structure (footprint.js `buildTimeStructure`).
* Design-time data (stage ids/names/types/edges) — UI-free. `` draws
* the flowchart from this. NOT runtime-redacted (it carries no user data; the
* `redact` function targets runtime events).
*
* Absent, `` has nothing to draw and says so.
*/
readonly structure?: unknown;
/**
* The run's footprintjs snapshot (`runner.getLastSnapshot()`) — shared
* state, the commit log, and every attached recorder's data.
*
* This is the piece the snapshot-fed surfaces need and the event log cannot
* supply: ExplainableShell's memory and provenance panels, WhereFrom, and
* the commit axis all read the commit log, which is a footprintjs artifact
* and appears nowhere in `events`. Without it a Trace draws a chart and a
* timeline and nothing else — which is what every Trace captured before 7.8
* is, and why they replay as a static picture.
*
* `unknown` on purpose: this is footprintjs's shape, not agentfootprint's,
* and a Trace is a transport — it should not pin a peer's type into its own
* schema. Cast at the consumer.
*
* NOT redacted by `redact`, which runs per domain event and cannot reach
* inside a snapshot. This field carries whatever the run's own footprintjs
* redaction policy (`setRedactionPolicy()`) let through, so a run whose
* state holds secrets wants that policy set at run time — which is why
* `enable.localObservability({ includeSnapshot: true })` is opt-in and a
* redacted Trace does not quietly grow a second content surface.
*/
readonly snapshot?: unknown;
/** Optional headline totals. */
readonly summary?: TraceSummary;
/** Self-describing redaction state — travels with the trace. */
readonly redaction: TraceRedaction;
/** Wall-clock capture time, stamped by the caller (the engine has no clock here). */
readonly capturedAtMs?: number;
}
export interface SerializeTraceOptions {
/**
* Consumer redaction — runs once per domain event at the serialize boundary,
* so PII never enters the `Trace`. Return a scrubbed COPY (do not mutate the
* input — the live model still references it). Use `redactContent` for a
* ready-made redactor. When omitted, content is raw.
*/
readonly redact?: (event: DomainEvent) => DomainEvent;
/** Override the `redaction` label. Defaults to `'pii'` when `redact` is given, else `'none'`. */
readonly redactionLabel?: TraceRedaction;
/** The serialized static chart (`getSpec().buildTimeStructure`) — what `` draws. */
readonly structure?: unknown;
/**
* The run's footprintjs snapshot (`runner.getLastSnapshot()`) — the commit
* log and recorder data the snapshot-fed panels read. `enable.localObservability()`
* fills this for you; pass it explicitly when calling `serializeTrace` by hand.
*/
readonly snapshot?: unknown;
/** Optional precomputed headline rollup. */
readonly summary?: TraceSummary;
/** Wall-clock capture time. Pass `Date.now()` from the call site. */
readonly capturedAtMs?: number;
}
/**
* Ready-made redactor: replaces every content-bearing field with a marker,
* keeping structure/counts for a useful replay. Covers ALL `DomainEvent`
* content surfaces — pass it to `getTrace({ redact: redactContent })`.
*
* Returns a copy only when it changes something, so unaffected events stay
* referentially identical (cheap) and the caller's live model is never mutated.
*/
export declare function redactContent(event: DomainEvent): DomainEvent;
/**
* Freeze a live run model into a `Trace`. Pure: pass the `BoundaryRecorder`'s
* `getEvents()` output.
*
* const trace = serializeTrace(handle.boundary.getEvents(), {
* redact: redactContent, // PII stripped before it enters the trace
* structure: agent.getSpec().buildTimeStructure, // the chart
* snapshot: agent.getLastSnapshot(), // state + commit log + recorders
* capturedAtMs: Date.now(),
* });
* fs.writeFileSync('run.trace.json', JSON.stringify(trace));
*
* `enable.localObservability().getTrace()` passes all three for you.
*/
export declare function serializeTrace(events: readonly DomainEvent[], options?: SerializeTraceOptions): Trace;
/**
* Rebuild the step graph from a `Trace` — the offline half of replay. The graph
* is ALWAYS a derived projection of `trace.events`; because those events were
* already redacted at serialize time, the rebuilt graph is clean too (no extra
* redaction needed — that's exactly why the graph is never stored). UI-free:
* `agentfootprint-lens`'s `` translates this `StepGraph` into its
* xyflow render model.
*/
export declare function traceToStepGraph(trace: Trace): StepGraph;