@warlock.js/ai-panoptic
@warlock.js/ai-panoptic is the observability companion to @warlock.js/ai. The core package already produces a rich report tree on every run — timings, status, rolled-up cost, the full nested trace. Panoptic takes that tree and ships it where your dashboards live: OpenTelemetry, Langfuse, the console, a JSON-Lines file, or an in-memory queryable store.
Separate, optional package.
@warlock.js/aihas no dependency on it. Install it only when you want traces exported.
npm install @warlock.js/ai-panopticThis page is the section overview — it covers the pipeline, the panoptic() entry point, and the exporters at a glance. The three companion pages go deep on each stage: what a trace looks like, what each exporter emits, and how to query the in-memory store.
Prefer zero-wiring setup? Skip the imperative
panoptic()/attach()plumbing and turn observability on declaratively withai.config({ panoptic })— exporters, observe-all, and an optional zero-setup local dashboard for eyeballing your traces in the browser. The imperative API below still powers it and remains available for fine-grained control.
The pipeline
Section titled “The pipeline” run a primitive project fan out┌──────────────────┐ ┌──────────────┐ ┌─────────────────────┐│ BaseReport tree │ ──▶ │ collector │ ──▶ │ console / file ││ (from ai.*) │ │ → Trace │ │ otel / langfuse │└──────────────────┘ └──────────────┘ │ in-memory store │ └─────────────────────┘A Trace is a 1:1 projection of the core BaseReport tree into the span vocabulary shared by OTEL, Langfuse, and similar backends — identity, timing, outcome, and rolled-up cost. The collector flattens a report into spans without consulting anything else. See What Panoptic traces look like for the full Trace / TraceSpan shape and the projection rules.
panoptic() — the one-call entry point
Section titled “panoptic() — the one-call entry point”panoptic({ exporters }) builds a collector, registers the exporters, and hands back a subscriber with three ways to feed it — all converging on the same collector, so a trace reaches every exporter exactly once per run:
import { ai } from "@warlock.js/ai";import { panoptic, consoleExporter, otelExporter } from "@warlock.js/ai-panoptic";
const observe = panoptic({ exporters: [consoleExporter(), otelExporter({ tracerName: "app" })],});The three feed methods, in brief:
attach(target)— subscribe to a primitive’s terminal*.completedevents so every run is captured automatically. Returns a detach function. Safe on agents, workflows, and supervisors; narrow or widen withcompletedEvents.middleware()— anAgentMiddlewarethat feeds the collector from the agent’sexecute.after/onErrorhooks, for apps that prefer the middleware pipeline over events. The hooks never mutate the result.collect(report)— feed aBaseReportdirectly. This is how you capture an orchestrator turn, whoseorchestrator.turn.*events carry only session identity and no result-bearing report:
const result = await orchestrator.execute(input, { sessionId });await observe.collect(result.report);The subscriber also exposes use(exporter) (register more exporters), toTrace(report) (pure projection, no dispatch), flush(), and shutdown(). Call shutdown() on process teardown to flush durable exporters.
Exporters at a glance
Section titled “Exporters at a glance”| Exporter | Ships | Optional peer |
|---|---|---|
consoleExporter(opts?) | Trace summary, or the full span tree with { tree: true } | none (zero-dep) |
fileExporter({ path, flushEvery? }) | Buffered JSON-Lines append; flushEvery batches writes | none (zero-dep) |
otelExporter(opts?) | Maps spans to OpenTelemetry GenAI semantic conventions (gen_ai.*) | @opentelemetry/api |
langfuseExporter(opts) | Maps spans to Langfuse traces, generations, and spans | langfuse |
The OTEL and Langfuse exporters lazily import their SDKs — installing @warlock.js/ai-panoptic pulls in neither. Each throws an install-instruction error only if you build the exporter without the peer present.
import { consoleExporter, fileExporter, otelExporter, langfuseExporter,} from "@warlock.js/ai-panoptic";
panoptic({ exporters: [ consoleExporter({ tree: true }), fileExporter({ path: "./traces.jsonl", flushEvery: 50 }), otelExporter({ tracerName: "my-app" }), // or pass a pre-built { tracer } langfuseExporter({ publicKey, secretKey }), // or pass a pre-built { client } ],});For exactly what each exporter writes — the console tree format, the JSON-Lines record, the gen_ai.* attribute mapping, and the Langfuse object shapes — see Exporter output.
Querying traces
Section titled “Querying traces”createInMemoryTraceStore() is both a queryable TraceStoreContract and an ExporterContract, so you can register it as an exporter and then query() / aggregate() the traces it has captured — by traceId, sessionId, status, and time window. Use it for tests, local dashboards, or a lightweight in-process spend monitor; reach for the otel / langfuse exporters for durable backends.
createCacheTraceStore(cache, options?) is the same query surface backed by any @warlock.js/cache CacheDriver, so traces survive a process restart — reads stay synchronous (served from an in-memory mirror), writes go through to the cache, and ready() re-hydrates on boot. The query / aggregate API and the persistent store are covered in Querying traces.
Continue reading
Section titled “Continue reading”- Configuring Panoptic — the declarative
ai.config({ panoptic })path:exporters,observeAll, the per-flowobserveoption, and how the side-effect import registers the collector for you. - The local dashboard — the zero-setup loopback UI started by
ai.config({ panoptic: { dashboard } })(ordashboard(store, options)): theme, a two-pane call-tree / timeline drawer, cost heatmap, search / filter / group-by, deep-links, its options, and its read-only JSON API. - What Panoptic traces look like — the
Trace/TraceSpan/TraceSpanErrorshape, how aBaseReportprojects into spans, the building-block projections (reportToTrace(),reportToSpan(),extractSpanAttributes()), and content capture (captureContent/fullHistory). - Exporter output — what each of
consoleExporter,fileExporter,otelExporter, andlangfuseExporteremits, plus the exporter utilities (toGenAiAttributes(),walkSpans(),totalCostUsd(),GEN_AI_ATTRIBUTES/WARLOCK_ATTRIBUTES). - Querying traces — the in-memory and cache-persistent stores’
query()/aggregate()API, theTraceQueryfilter fields, theTraceAggregateresult shape, and the exported trace-list folds (filterTraces/groupBySession/groupByPrompt/rollupCost/ heatmap) the dashboard mirrors.
Related
Section titled “Related”- Orchestrators — collect orchestrator turns via
collect(). - Log AI calls — the
log-channel side of observability. - Cost tracking recipe — reading rolled-up
usagedirectly off a report.