Subsystemssubsystems/tracing

Tracing

src/tracing is the cross-cutting observability layer — a homegrown, OTel-free span tracer with a deliberately fresh vocabulary. Imported as indusagi/tracing, or as the tracing namespace from indusagi.

A trace is built from immutable Segment values (kinds run / inference / action / recall / custom) driven by a functional SegmentHandle (note / child / fail / close). A Recorder mints handles onto a SignalChannel async-iterable; sinks drain that channel and write somewhere. Sampled-out work collapses to the single frozen NOOP_HANDLE. No OpenTelemetry SDK is used — the data model re-derives the relevant W3C trace concepts.

Table of Contents

Public exports

src/tracing/index.ts re-exports seven layer barrels (export * over signal, channel, redaction, sinks, recorder, registry, adapter):

Export Kind Layer Purpose
Recorder class recorder The tracer that mints SegmentHandles onto a channel
SampleGate class recorder Admission control (SampleStrategy / RatioStrategy)
openSegment, withAttributes, closeSegment functions signal The Segment value constructors/derivations
openHandle, NOOP_HANDLE fn / const signal The sampled-in handle factory and the shared no-op
freshId, freshTraceId, freshSegmentId functions signal Id minting
SignalChannel class channel The async-iterable transport for TraceSignals
SecretScrubber processor redaction Attribute scrubbing before data leaves the process
ConsoleSink, FileSink, StreamSink classes sinks Terminal consumers that drain a channel
TelemetryHub, getDefaultHub, setDefaultHub class / fns registry The named-recorder registry + process default
traceAgentRun function adapter The optional runtime RunEvent bridge

Types include Segment, SegmentKind, SegmentStatus, TraceRecord, SegmentHandle, SignalSink; TraceSignal (OpenSignal / UpdateSignal / CloseSignal); Sink, ConsoleSinkOptions, FileSinkOptions, StreamSinkOptions; SampleStrategy, RatioStrategy, OpenOptions, RecorderOptions; and RunEventSubscribe.

Sub-directories

Directory Holds
signal/ segment.ts (the Segment data model), handle.ts (SegmentHandle, NOOP_HANDLE)
channel/ signal.ts — the SignalChannel transport and the TraceSignal union
recorder/ recorder.ts (Recorder), sampling.ts (SampleGate)
redaction/ secret-scrubber.ts — the SecretScrubber processor and its default rules
sinks/ base.ts (Sink), console.ts, file.ts, stream.ts
registry/ hub.ts — the TelemetryHub and default-hub accessors
adapter/ runtime-trace.tstraceAgentRun, the runtime bridge

Recording a trace

A Recorder opens a root SegmentHandle; child nests, note annotates, fail marks an error, and close ends a segment, emitting a TraceSignal on the channel:

import { Recorder, ConsoleSink } from "indusagi/tracing";

const recorder = new Recorder(/* RecorderOptions */);
const sink = new ConsoleSink(/* ConsoleSinkOptions */);
// drain recorder's channel into the sink …

const run = recorder.open("run", "build");
const step = run.child("action", "edit");
step.note({ file: "x.ts" });
step.close();
run.close();

SampleGate admission control decides whether work is recorded; sampled-out work gets the frozen NOOP_HANDLE, so the call sites are uniform regardless of sampling.

Sinks, redaction, and the registry

A Sink (drain / optional flush / close) is a terminal consumer of a SignalChannel: ConsoleSink writes one pretty line per closed segment, FileSink appends NDJSON TraceRecords to a path, and StreamSink writes JSON lines to an injected Writable. The SecretScrubber processor sanitizes segment attributes before they leave the process. TelemetryHub is a registry of named recorders, with getDefaultHub / setDefaultHub for an opt-in process default.

The runtime adapter

traceAgentRun is the optional bridge that projects a runtime RunEvent stream onto a Recorder as nested run / inference / action segments. The adapter is the one place tracing knows the runtime's event vocabulary; the rest of the subsystem stays runtime-agnostic:

import { Recorder, traceAgentRun } from "indusagi/tracing";

const recorder = new Recorder();
traceAgentRun(recorder, agent.subscribe);  // agent from indusagi/runtime

Relationship to neighbors

Tracing is cross-cutting: it consumes the Runtime's RunEvent stream through the adapter but never the other way around — the runtime has no dependency on tracing. Every other layer can be observed by recording its own segments.

Back to the Architecture overview.