@warlock.js/ai-panoptic 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/LICENSE +21 -0
  3. package/README.md +111 -0
  4. package/cjs/index.cjs +1370 -0
  5. package/cjs/index.cjs.map +1 -0
  6. package/esm/collector/collector.d.mts +19 -0
  7. package/esm/collector/collector.d.mts.map +1 -0
  8. package/esm/collector/collector.mjs +89 -0
  9. package/esm/collector/collector.mjs.map +1 -0
  10. package/esm/collector/extract-span-attributes.d.mts +29 -0
  11. package/esm/collector/extract-span-attributes.d.mts.map +1 -0
  12. package/esm/collector/extract-span-attributes.mjs +76 -0
  13. package/esm/collector/extract-span-attributes.mjs.map +1 -0
  14. package/esm/collector/index.d.mts +5 -0
  15. package/esm/collector/normalize-error.d.mts +27 -0
  16. package/esm/collector/normalize-error.d.mts.map +1 -0
  17. package/esm/collector/normalize-error.mjs +49 -0
  18. package/esm/collector/normalize-error.mjs.map +1 -0
  19. package/esm/collector/report-to-span.d.mts +31 -0
  20. package/esm/collector/report-to-span.d.mts.map +1 -0
  21. package/esm/collector/report-to-span.mjs +53 -0
  22. package/esm/collector/report-to-span.mjs.map +1 -0
  23. package/esm/collector/report-to-trace.d.mts +35 -0
  24. package/esm/collector/report-to-trace.d.mts.map +1 -0
  25. package/esm/collector/report-to-trace.mjs +53 -0
  26. package/esm/collector/report-to-trace.mjs.map +1 -0
  27. package/esm/contracts/collector.contract.d.mts +63 -0
  28. package/esm/contracts/collector.contract.d.mts.map +1 -0
  29. package/esm/contracts/exporter.contract.d.mts +76 -0
  30. package/esm/contracts/exporter.contract.d.mts.map +1 -0
  31. package/esm/contracts/index.d.mts +3 -0
  32. package/esm/contracts/trace.type.d.mts +122 -0
  33. package/esm/contracts/trace.type.d.mts.map +1 -0
  34. package/esm/exporters/console/console-exporter.d.mts +25 -0
  35. package/esm/exporters/console/console-exporter.d.mts.map +1 -0
  36. package/esm/exporters/console/console-exporter.mjs +79 -0
  37. package/esm/exporters/console/console-exporter.mjs.map +1 -0
  38. package/esm/exporters/console/console-exporter.type.d.mts +36 -0
  39. package/esm/exporters/console/console-exporter.type.d.mts.map +1 -0
  40. package/esm/exporters/console/format-span-line.d.mts +17 -0
  41. package/esm/exporters/console/format-span-line.d.mts.map +1 -0
  42. package/esm/exporters/console/format-span-line.mjs +41 -0
  43. package/esm/exporters/console/format-span-line.mjs.map +1 -0
  44. package/esm/exporters/console/index.d.mts +3 -0
  45. package/esm/exporters/file/file-exporter.d.mts +23 -0
  46. package/esm/exporters/file/file-exporter.d.mts.map +1 -0
  47. package/esm/exporters/file/file-exporter.mjs +93 -0
  48. package/esm/exporters/file/file-exporter.mjs.map +1 -0
  49. package/esm/exporters/file/file-exporter.type.d.mts +39 -0
  50. package/esm/exporters/file/file-exporter.type.d.mts.map +1 -0
  51. package/esm/exporters/file/index.d.mts +2 -0
  52. package/esm/exporters/index.d.mts +12 -0
  53. package/esm/exporters/index.mjs +12 -0
  54. package/esm/exporters/langfuse/index.d.mts +2 -0
  55. package/esm/exporters/langfuse/index.mjs +3 -0
  56. package/esm/exporters/langfuse/langfuse-exporter.d.mts +27 -0
  57. package/esm/exporters/langfuse/langfuse-exporter.d.mts.map +1 -0
  58. package/esm/exporters/langfuse/langfuse-exporter.mjs +158 -0
  59. package/esm/exporters/langfuse/langfuse-exporter.mjs.map +1 -0
  60. package/esm/exporters/langfuse/langfuse-exporter.type.d.mts +90 -0
  61. package/esm/exporters/langfuse/langfuse-exporter.type.d.mts.map +1 -0
  62. package/esm/exporters/otel/index.d.mts +2 -0
  63. package/esm/exporters/otel/index.mjs +3 -0
  64. package/esm/exporters/otel/otel-exporter.d.mts +29 -0
  65. package/esm/exporters/otel/otel-exporter.d.mts.map +1 -0
  66. package/esm/exporters/otel/otel-exporter.mjs +142 -0
  67. package/esm/exporters/otel/otel-exporter.mjs.map +1 -0
  68. package/esm/exporters/otel/otel-exporter.type.d.mts +39 -0
  69. package/esm/exporters/otel/otel-exporter.type.d.mts.map +1 -0
  70. package/esm/exporters/utils/gen-ai-attributes.d.mts +65 -0
  71. package/esm/exporters/utils/gen-ai-attributes.d.mts.map +1 -0
  72. package/esm/exporters/utils/gen-ai-attributes.mjs +88 -0
  73. package/esm/exporters/utils/gen-ai-attributes.mjs.map +1 -0
  74. package/esm/exporters/utils/index.d.mts +3 -0
  75. package/esm/exporters/utils/total-cost.d.mts +20 -0
  76. package/esm/exporters/utils/total-cost.d.mts.map +1 -0
  77. package/esm/exporters/utils/total-cost.mjs +23 -0
  78. package/esm/exporters/utils/total-cost.mjs.map +1 -0
  79. package/esm/exporters/utils/walk-spans.d.mts +18 -0
  80. package/esm/exporters/utils/walk-spans.d.mts.map +1 -0
  81. package/esm/exporters/utils/walk-spans.mjs +21 -0
  82. package/esm/exporters/utils/walk-spans.mjs.map +1 -0
  83. package/esm/index.d.mts +31 -0
  84. package/esm/index.mjs +22 -0
  85. package/esm/panoptic/index.d.mts +4 -0
  86. package/esm/panoptic/panoptic-middleware.d.mts +38 -0
  87. package/esm/panoptic/panoptic-middleware.d.mts.map +1 -0
  88. package/esm/panoptic/panoptic-middleware.mjs +68 -0
  89. package/esm/panoptic/panoptic-middleware.mjs.map +1 -0
  90. package/esm/panoptic/panoptic-target.type.d.mts +53 -0
  91. package/esm/panoptic/panoptic-target.type.d.mts.map +1 -0
  92. package/esm/panoptic/panoptic.d.mts +37 -0
  93. package/esm/panoptic/panoptic.d.mts.map +1 -0
  94. package/esm/panoptic/panoptic.mjs +134 -0
  95. package/esm/panoptic/panoptic.mjs.map +1 -0
  96. package/esm/panoptic/panoptic.type.d.mts +100 -0
  97. package/esm/panoptic/panoptic.type.d.mts.map +1 -0
  98. package/esm/store/in-memory-trace-store.d.mts +32 -0
  99. package/esm/store/in-memory-trace-store.d.mts.map +1 -0
  100. package/esm/store/in-memory-trace-store.mjs +134 -0
  101. package/esm/store/in-memory-trace-store.mjs.map +1 -0
  102. package/esm/store/index.d.mts +6 -0
  103. package/esm/store/index.mjs +5 -0
  104. package/esm/store/match-trace.d.mts +21 -0
  105. package/esm/store/match-trace.d.mts.map +1 -0
  106. package/esm/store/match-trace.mjs +44 -0
  107. package/esm/store/match-trace.mjs.map +1 -0
  108. package/esm/store/sum-usage.d.mts +34 -0
  109. package/esm/store/sum-usage.d.mts.map +1 -0
  110. package/esm/store/sum-usage.mjs +65 -0
  111. package/esm/store/sum-usage.mjs.map +1 -0
  112. package/esm/store/trace-aggregate.type.d.mts +51 -0
  113. package/esm/store/trace-aggregate.type.d.mts.map +1 -0
  114. package/esm/store/trace-query.type.d.mts +45 -0
  115. package/esm/store/trace-query.type.d.mts.map +1 -0
  116. package/esm/store/trace-store.contract.d.mts +55 -0
  117. package/esm/store/trace-store.contract.d.mts.map +1 -0
  118. package/llms-full.txt +450 -0
  119. package/llms.txt +11 -0
  120. package/package.json +40 -0
  121. package/skills/README.md +17 -0
  122. package/skills/export-traces/SKILL.md +152 -0
  123. package/skills/observe-with-panoptic/SKILL.md +128 -0
  124. package/skills/query-traces/SKILL.md +152 -0
package/llms-full.txt ADDED
@@ -0,0 +1,450 @@
1
+ # Warlock AI Panoptic — full skills
2
+
3
+ > Package: `@warlock.js/ai-panoptic`
4
+
5
+ > Generated artifact. Concatenates every SKILL.md and reference file under `@warlock.js/ai-panoptic/skills/`. Re-run `node scripts/generate-llms.mjs` after any change.
6
+
7
+ ## export-traces `@warlock.js/ai-panoptic/export-traces/SKILL.md`
8
+
9
+ ---
10
+ name: export-traces
11
+ description: 'Send @warlock.js/ai-panoptic traces to an observability backend via pluggable exporters. Triggers: `consoleExporter`, `fileExporter`, `otelExporter`, `langfuseExporter`, `ExporterContract`, `toGenAiAttributes`, `walkSpans`, `totalCostUsd`, `GEN_AI_ATTRIBUTES`, `WARLOCK_ATTRIBUTES`, `reportToTrace`, `reportToSpan`, `extractSpanAttributes`, `normalizeError`, `formatSpanLine`, `createPanopticMiddleware`; ''export AI traces to OpenTelemetry'', ''send traces to Langfuse'', ''log traces to console / a file'', ''gen_ai semantic conventions'', ''write a custom exporter''; typical import `import { otelExporter, langfuseExporter, consoleExporter, fileExporter } from "@warlock.js/ai-panoptic"`. Skip: wiring the subscriber/collector into a run — `@warlock.js/ai-panoptic/observe-with-panoptic/SKILL.md`; the core report shape — `@warlock.js/ai/run-ai-agent/SKILL.md`.'
12
+ ---
13
+
14
+ # Export traces — the sink end of Panoptic
15
+
16
+ An **exporter** translates a vendor-neutral `Trace` (produced by the collector) into a backend's wire format. The collector fans every completed trace out to each registered exporter via the `ExporterContract`:
17
+
18
+ ```ts
19
+ export interface ExporterContract {
20
+ readonly name: string;
21
+ export(trace: Trace): void | Promise<void>;
22
+ exportSpan?(span: TraceSpan): void | Promise<void>; // optional live streaming
23
+ flush?(): void | Promise<void>;
24
+ shutdown?(): void | Promise<void>;
25
+ }
26
+ ```
27
+
28
+ Four exporters ship today. Each is a **factory** returning an `ExporterContract` — callers never `new`.
29
+
30
+ ## Pick an exporter
31
+
32
+ | Exporter | Backend | Deps | Use it for |
33
+ |---|---|---|---|
34
+ | `consoleExporter()` | stdout/stderr | none | local dev, CI logs |
35
+ | `fileExporter()` | JSON-Lines file | none | durable local sink, replay, out-of-band shipping |
36
+ | `otelExporter()` | OpenTelemetry | `@opentelemetry/api` (optional peer) | OTel-native stacks, GenAI semantic conventions |
37
+ | `langfuseExporter()` | Langfuse | `langfuse` (optional peer) | LLM-specific dashboards, generations |
38
+
39
+ > `@opentelemetry/api` and `langfuse` are **OPTIONAL peer dependencies**, lazily imported by their exporter. Importing `@warlock.js/ai-panoptic` never pulls them in; a missing SDK surfaces as a curated *"install this"* error on first export, never a boot-time stack trace.
40
+
41
+ ## Console
42
+
43
+ ```ts
44
+ import { consoleExporter } from "@warlock.js/ai-panoptic";
45
+
46
+ collector.use(consoleExporter()); // one summary line per trace
47
+ collector.use(consoleExporter({ tree: true })); // full indented span tree
48
+ collector.use(consoleExporter({ streaming: true })); // also print each span as it finalizes
49
+ ```
50
+
51
+ Failed/cancelled spans route to `console.error`; everything else to `console.log`. Inject a sink (`{ console: mySink }`) to redirect or to capture in tests.
52
+
53
+ ## File
54
+
55
+ ```ts
56
+ import { fileExporter } from "@warlock.js/ai-panoptic";
57
+
58
+ collector.use(fileExporter({ path: "storage/traces.jsonl" })); // write every trace
59
+ collector.use(fileExporter({ path: "...", flushEvery: 50 })); // batch 50 traces per write
60
+ collector.use(fileExporter({ path: "...", pretty: true })); // multi-line records
61
+ ```
62
+
63
+ Appends one JSON record per line (a `TraceRecord` = `{ type: "trace", exportedAt, trace }`). Buffers in memory and drains on `flush()` / `shutdown()` — call `collector.shutdown()` on teardown so the last batch lands. The parent directory is created on first write.
64
+
65
+ ## OpenTelemetry
66
+
67
+ The exporter emits spans onto a `Tracer`; **the host app owns the SDK wiring** (`TracerProvider`, processors, span exporters), exactly as with any OTel instrumentation.
68
+
69
+ ```ts
70
+ import { otelExporter } from "@warlock.js/ai-panoptic";
71
+
72
+ // app already configured @opentelemetry/sdk-trace-base + a global provider
73
+ collector.use(otelExporter({ tracerName: "my-app", system: "openai" }));
74
+
75
+ // or pass a Tracer directly
76
+ collector.use(otelExporter({ tracer: myTracer }));
77
+ ```
78
+
79
+ Each `TraceSpan` becomes one OTel span with the source start/end times and parent relationship reconstructed, so the emitted tree matches the original execution tree. Attributes follow the [GenAI semantic conventions](https://opentelemetry.io/docs/specs/semconv/gen-ai/):
80
+
81
+ - `gen_ai.usage.input_tokens` / `gen_ai.usage.output_tokens` / `gen_ai.usage.total_tokens` (from the span's typed `usage` rollup)
82
+ - `gen_ai.usage.cached_tokens` / `gen_ai.usage.reasoning_tokens` (when the span reported them)
83
+ - `gen_ai.conversation.id` (from the span's `sessionId`)
84
+ - `gen_ai.system` / `gen_ai.request.model` / `gen_ai.operation.name` — **never invented**: these appear only when the span's free-form `attributes` bag already carried that exact key. `otelExporter`'s `system` option backfills `gen_ai.system` when the span supplied none (and never overrides one it did).
85
+ - `warlock.report.type` / `warlock.version` / `warlock.duration_ms` / `warlock.cost.usd`
86
+ - the collector's own per-primitive keys ride along verbatim — `agent.model.name` / `agent.model.provider` / `agent.trips`, `workflow.steps`, `supervisor.iterations` / `supervisor.terminatedBy`, `tool.tripIndex`, `retries`, … — since `toGenAiAttributes` copies every scalar entry of the span's `attributes` bag.
87
+
88
+ Failed/cancelled spans get OTel `ERROR` status plus a recorded exception.
89
+
90
+ ## Langfuse
91
+
92
+ ```ts
93
+ import { langfuseExporter } from "@warlock.js/ai-panoptic";
94
+
95
+ collector.use(langfuseExporter({ publicKey: "pk-...", secretKey: "sk-...", baseUrl: "..." }));
96
+ // or reuse an existing Langfuse client
97
+ collector.use(langfuseExporter({ client: myLangfuse }));
98
+ ```
99
+
100
+ - The root span becomes a Langfuse **trace**.
101
+ - A token-producing span (`usage.total > 0`) becomes a **generation** (carries `usage`); a pure tool/callback becomes a plain **span**.
102
+ - Children nest under their parent observation; timing, `version`, and status (failed → `ERROR` level) are mapped.
103
+ - `flush()` / `shutdown()` delegate to the client's `flushAsync()` / `shutdownAsync()` — call `collector.shutdown()` on teardown so buffered observations are sent.
104
+
105
+ ## Shared utilities
106
+
107
+ Reusable when authoring a **custom exporter**:
108
+
109
+ ```ts
110
+ import { walkSpans, toGenAiAttributes, totalCostUsd, GEN_AI_ATTRIBUTES, WARLOCK_ATTRIBUTES } from "@warlock.js/ai-panoptic";
111
+
112
+ for (const span of walkSpans(trace.root)) { // depth-first pre-order
113
+ const attributes = toGenAiAttributes(span); // gen_ai.* / warlock.* attribute map
114
+ const cost = totalCostUsd(span.usage); // single USD scalar, or undefined
115
+ }
116
+ ```
117
+
118
+ `toGenAiAttributes` copies only **scalar** entries from a span's free-form `attributes` bag (objects/arrays are skipped — backend attribute values must be primitives) and lets an explicit collector attribute override a derived one.
119
+
120
+ ## Write a custom exporter
121
+
122
+ Implement `ExporterContract` directly — `name` + `export` are the only required members:
123
+
124
+ ```ts
125
+ import type { ExporterContract, Trace } from "@warlock.js/ai-panoptic";
126
+
127
+ const myExporter: ExporterContract = {
128
+ name: "my-backend",
129
+ async export(trace: Trace) {
130
+ await sendSomewhere(trace);
131
+ },
132
+ };
133
+
134
+ collector.use(myExporter);
135
+ ```
136
+
137
+ Wrapping a third-party SDK? Lazily `await import(...)` it **inside** the export path (never at module top) so it stays an optional peer — copy the loader pattern from `otelExporter` / `langfuseExporter`. The collector isolates a throwing exporter, so one backend's failure never crashes the originating AI run.
138
+
139
+ ## Lower-level building blocks
140
+
141
+ Most apps only need `panoptic(...)`, an exporter, and the store. These lower-level exports exist for **custom collectors / exporters and bespoke pipelines** — each is a pure function over the report tree:
142
+
143
+ ```ts
144
+ import {
145
+ reportToTrace, // (report: BaseReport) => Trace — project a finished report into a vendor-neutral Trace
146
+ reportToSpan, // (report: BaseReport) => TraceSpan — project one report node into a span (recurses into children)
147
+ extractSpanAttributes, // (report: BaseReport) => Record<string, unknown> | undefined — the per-primitive attribute bag (agent.model.name, workflow.steps, …)
148
+ normalizeError, // (error: unknown) => TraceSpanError | undefined — coerce any thrown value into the typed span-error shape
149
+ formatSpanLine, // (span: TraceSpan, depth?: number) => string — render one indented console line (consoleExporter's tree mode uses this)
150
+ createPanopticMiddleware, // (collector: CollectorContract, name?: string) => AgentMiddleware — build a middleware from a collector you already hold (panoptic(...) wires this for you)
151
+ } from "@warlock.js/ai-panoptic";
152
+ ```
153
+
154
+ Reach for `reportToTrace` when you already hold a finished `result.report` and want a `Trace` without the live subscriber; `formatSpanLine` to build a custom console renderer; `createPanopticMiddleware` when you assemble the agent's `middleware` array by hand instead of letting `panoptic(...)` do it.
155
+
156
+ ## See also
157
+
158
+ - [`@warlock.js/ai-panoptic/observe-with-panoptic/SKILL.md`](@warlock.js/ai-panoptic/observe-with-panoptic/SKILL.md) — the source end: wiring the `panoptic(...)` subscriber into a run (`attach` / `middleware` / `collect`). The `collector` these exporters register on comes from there.
159
+ - [`@warlock.js/ai-panoptic/query-traces/SKILL.md`](@warlock.js/ai-panoptic/query-traces/SKILL.md) — the in-memory trace store: a sibling sink that *retains* traces (instead of shipping them out) so you can `query` / `aggregate` them after the fact.
160
+ - [`@warlock.js/ai/run-ai-agent/SKILL.md`](@warlock.js/ai/run-ai-agent/SKILL.md) — running the agent and the `BaseReport` shape Panoptic projects into a `Trace`.
161
+
162
+
163
+ ## observe-with-panoptic `@warlock.js/ai-panoptic/observe-with-panoptic/SKILL.md`
164
+
165
+ ---
166
+ name: observe-with-panoptic
167
+ description: 'Wire @warlock.js/ai-panoptic into an agent/workflow/supervisor/orchestrator with the one-call `panoptic({ exporters })` subscriber. Triggers: `panoptic`, `Panoptic`, `PanopticOptions`, `.attach`, `.middleware`, `.collect`, `.toTrace`, `observe.attach(agent)`, `panoptic().middleware()`, `completedEvents`, `middlewareName`; ''observe a warlock agent'', ''trace an agent run'', ''attach observability to a workflow/supervisor'', ''add a tracing middleware'', ''collect an orchestrator turn report'', ''wire OTel/Langfuse into my agent''; typical import `import { panoptic } from "@warlock.js/ai-panoptic"`. Skip: writing/choosing an exporter (the sink end) — `@warlock.js/ai-panoptic/export-traces/SKILL.md`; the core report shape / running the agent itself — `@warlock.js/ai/run-ai-agent/SKILL.md`.'
168
+ ---
169
+
170
+ # Observe a run with Panoptic — the one-call subscriber
171
+
172
+ `panoptic(...)` is the entry point. It builds a collector, registers your exporters on it, and hands back a subscriber with **three ways to feed traces** in. All three converge on the same collector, so each run reaches every exporter exactly once.
173
+
174
+ ```ts
175
+ import { panoptic } from "@warlock.js/ai-panoptic";
176
+ import { consoleExporter, otelExporter } from "@warlock.js/ai-panoptic";
177
+
178
+ const observe = panoptic({
179
+ exporters: [consoleExporter(), otelExporter({ tracerName: "my-app" })],
180
+ });
181
+ ```
182
+
183
+ ## Three feed paths
184
+
185
+ | Path | Use when | API |
186
+ |---|---|---|
187
+ | **Events** | You want every run captured automatically and the primitive emits a `*.completed` event (agent / workflow / supervisor). | `observe.attach(target)` |
188
+ | **Middleware** | The app already composes cross-cutting concerns through the agent middleware pipeline. | `panoptic().middleware()` |
189
+ | **Direct** | The primitive carries no result-bearing event (the orchestrator), or you captured a report out of band. | `observe.collect(report)` |
190
+
191
+ ### 1. Attach to the event stream
192
+
193
+ `attach` subscribes to the terminal `*.completed` events and returns a **detach** function:
194
+
195
+ ```ts
196
+ const agent = ai.agent({ model });
197
+ const detach = observe.attach(agent); // agent | workflow | supervisor
198
+
199
+ await agent.execute("Summarize this");
200
+
201
+ // ...later, on teardown:
202
+ detach();
203
+ await observe.shutdown();
204
+ ```
205
+
206
+ By default Panoptic subscribes to `agent.completed`, `workflow.completed`, and `supervisor.completed` — the terminal events that carry the finalized `result.report`. These fire **once per run regardless of outcome** (the matching `*.error` fires first on failure, then `*.completed` still fires), so attaching captures completed, failed, and cancelled runs alike. Panoptic silently ignores the names a given target doesn't emit, so the same subscriber attaches to any of the three primitives.
207
+
208
+ Narrow or widen the surface with `completedEvents`:
209
+
210
+ ```ts
211
+ panoptic({ exporters, completedEvents: ["agent.completed"] }); // agents only
212
+ ```
213
+
214
+ ### 2. Wire it as agent middleware
215
+
216
+ ```ts
217
+ const observe = panoptic({ exporters: [langfuseExporter({ /* ... */ })] });
218
+
219
+ const agent = ai.agent({
220
+ model,
221
+ middleware: [observe.middleware()],
222
+ });
223
+ ```
224
+
225
+ The middleware feeds the collector from the `execute`-level `after` (success) and `onError` (failure) hooks. It never returns a value, so it never mutates the agent's result. Register more than one Panoptic middleware on the same agent by passing a distinct `middlewareName`:
226
+
227
+ ```ts
228
+ panoptic({ middlewareName: "panoptic-secondary" }).middleware();
229
+ ```
230
+
231
+ ### 3. Collect a report directly
232
+
233
+ The **orchestrator** emits `orchestrator.turn.*` events that carry only session identity — no result. Feed its report straight in:
234
+
235
+ ```ts
236
+ const result = await orchestrator.execute(input, { sessionId });
237
+ await observe.collect(result.report);
238
+ ```
239
+
240
+ `collect(report)` also covers any report captured out of band (e.g. replayed from a store).
241
+
242
+ ## Failure isolation
243
+
244
+ An observability fault never escapes onto the AI run's hot path:
245
+
246
+ - The collector wraps every exporter invocation in `Promise.allSettled`, so a throwing exporter can't crash the run or block its siblings.
247
+ - The event handler and the middleware additionally swallow the `collect` rejection, so even a collector-level fault stays contained.
248
+
249
+ A broken backend degrades to *missing telemetry*, never a failed agent run.
250
+
251
+ ## Lifecycle
252
+
253
+ ```ts
254
+ await observe.flush(); // drain buffered exporters (between batches / checkpoints)
255
+ await observe.shutdown(); // flush + release every exporter (process teardown)
256
+ ```
257
+
258
+ Call `shutdown()` on teardown so buffered exporters (`fileExporter` batches, the Langfuse client) send their last records before exit.
259
+
260
+ ## Bring your own collector or store
261
+
262
+ The in-memory trace store doubles as an `ExporterContract`, so the simplest way to retain runs for later querying is to pass it as an exporter — it then receives every trace alongside your other sinks:
263
+
264
+ ```ts
265
+ import { panoptic, createInMemoryTraceStore } from "@warlock.js/ai-panoptic";
266
+
267
+ const store = createInMemoryTraceStore(); // an ExporterContract + queryable store
268
+ const observe = panoptic({ exporters: [store, consoleExporter()] });
269
+
270
+ // store now receives every trace — query/aggregate it later:
271
+ store.query({ status: "failed" });
272
+ store.aggregate({ sessionId });
273
+ ```
274
+
275
+ Pass a pre-built `collector` instead when you want to share one collector across several subscribers (each subscriber's `attach` / `middleware` feeds the same exporters):
276
+
277
+ ```ts
278
+ import { panoptic, createCollector } from "@warlock.js/ai-panoptic";
279
+
280
+ const collector = createCollector().use(store);
281
+ const observe = panoptic({ collector, exporters: [consoleExporter()] });
282
+ ```
283
+
284
+ Add exporters after construction with `observe.use(exporter)` (chainable, deduped by `ExporterContract.name`). `observe.toTrace(report)` projects a report into a `Trace` **without** dispatching — handy for inspection or tests.
285
+
286
+ > The store's full query / aggregate / capacity surface is its own skill — see [`query-traces/SKILL.md`](@warlock.js/ai-panoptic/query-traces/SKILL.md).
287
+
288
+ ## See also
289
+
290
+ - [`@warlock.js/ai-panoptic/export-traces/SKILL.md`](@warlock.js/ai-panoptic/export-traces/SKILL.md) — the sink end: choosing/writing an exporter, the `gen_ai.*` / `warlock.*` attribute mapping.
291
+ - [`@warlock.js/ai-panoptic/query-traces/SKILL.md`](@warlock.js/ai-panoptic/query-traces/SKILL.md) — retaining traces in the queryable in-memory store and slicing them (`query` / `aggregate` / `capacity`).
292
+ - [`@warlock.js/ai/run-ai-agent/SKILL.md`](@warlock.js/ai/run-ai-agent/SKILL.md) — running the agent and the `BaseReport` shape Panoptic consumes.
293
+
294
+
295
+ ## query-traces `@warlock.js/ai-panoptic/query-traces/SKILL.md`
296
+
297
+ ---
298
+ name: query-traces
299
+ description: 'Retain @warlock.js/ai-panoptic traces in a queryable in-memory store and slice them after the fact — get one run by id, list a session, filter failed runs in a window, roll up usage + cost. Triggers: `createInMemoryTraceStore`, `TraceStoreContract`, `TraceQuery`, `TraceAggregate`, `InMemoryTraceStoreOptions`, `store.query`, `store.aggregate`, `store.get`, `store.add`, `store.clear`, `store.size`, `capacity`, `sumUsage`, `emptyUsage`, `matchTrace`; ''query collected traces'', ''aggregate AI cost per session'', ''how much did this session spend'', ''list failed runs'', ''retain traces in memory'', ''roll up token usage''; typical import `import { createInMemoryTraceStore } from "@warlock.js/ai-panoptic"`. Skip: sending traces to an external backend (OTel/Langfuse/console/file) — `@warlock.js/ai-panoptic/export-traces/SKILL.md`; wiring the subscriber into a run — `@warlock.js/ai-panoptic/observe-with-panoptic/SKILL.md`.'
300
+ ---
301
+
302
+ # Query traces — the in-memory queryable store
303
+
304
+ The collector fans every completed `Trace` out to its exporters. A **trace store** is one such sink that *retains* traces so you can answer observability questions after the fact: pull one run by id, list every run for a session, slice failed runs in a time window, or roll usage + cost across any of those slices.
305
+
306
+ `createInMemoryTraceStore()` returns an object that satisfies **both** `TraceStoreContract` and `ExporterContract` — so wiring it into a collector is a single `collector.use(store)` and the store fills as traces complete.
307
+
308
+ ```ts
309
+ import { panoptic, createInMemoryTraceStore } from "@warlock.js/ai-panoptic";
310
+
311
+ const store = createInMemoryTraceStore();
312
+
313
+ // The store doubles as an exporter — register it like any other sink.
314
+ const observe = panoptic({ exporters: [store] });
315
+
316
+ const agent = ai.agent({ model });
317
+ observe.attach(agent);
318
+
319
+ await agent.execute("Summarize this");
320
+
321
+ // ...later, off the hot path:
322
+ const failed = store.query({ status: "failed" });
323
+ const spend = store.aggregate({ sessionId: "session-42" });
324
+ ```
325
+
326
+ > The store is also a valid `collector.use(...)` target directly — `panoptic({ collector: createCollector().use(store) })` — see [`observe-with-panoptic/SKILL.md`](@warlock.js/ai-panoptic/observe-with-panoptic/SKILL.md) for bring-your-own-collector wiring.
327
+
328
+ ## The store contract
329
+
330
+ `TraceStoreContract` is the queryable surface; the in-memory implementation adds the `ExporterContract` `name` + `export` so it can be a sink.
331
+
332
+ ```ts
333
+ interface TraceStoreContract {
334
+ add(trace: Trace): void; // ingest; overwrites same traceId
335
+ get(traceId: string): Trace | undefined; // one run by id
336
+ query(filter?: TraceQuery): Trace[]; // matching traces, newest-started first
337
+ aggregate(filter?: TraceQuery): TraceAggregate; // usage + cost + status rollup
338
+ readonly size: number; // retained count
339
+ clear(): void; // drop everything
340
+ }
341
+ ```
342
+
343
+ - **`add` overwrites** any existing trace with the same `traceId` — re-collecting a run replaces rather than duplicates it.
344
+ - **`export(trace)` is an alias for `add`** — that is what makes the store a drop-in exporter (`store.name === "in-memory-trace-store"`).
345
+ - **`query` returns newest-started first** (sorted by root `startedAt`, descending). An empty / omitted filter returns every retained trace.
346
+
347
+ ## Filtering with `TraceQuery`
348
+
349
+ Every field is optional and **ANDed** together — an absent field is "don't care", so `{}` matches everything. The same filter type drives both `query` and `aggregate`.
350
+
351
+ ```ts
352
+ type TraceQuery = {
353
+ traceId?: string; // exact one run
354
+ sessionId?: string; // every run for a session
355
+ status?: ReportStatus | ReportStatus[]; // single status or membership set
356
+ startedAfter?: string | Date; // inclusive lower bound on root startedAt
357
+ startedBefore?: string | Date; // inclusive upper bound on root startedAt
358
+ };
359
+ ```
360
+
361
+ ```ts
362
+ // One run by id:
363
+ store.query({ traceId: "run-abc" });
364
+
365
+ // Every run for a conversation:
366
+ store.query({ sessionId: "session-42" });
367
+
368
+ // Failed or cancelled runs since midnight (status accepts an array):
369
+ store.query({
370
+ status: ["failed", "cancelled"],
371
+ startedAfter: "2026-06-18T00:00:00.000Z",
372
+ });
373
+
374
+ // A Date works anywhere a time bound is accepted:
375
+ store.query({ startedAfter: new Date(Date.now() - 3_600_000) }); // last hour
376
+ ```
377
+
378
+ Time bounds compare against each trace's root `startedAt`, **inclusive on both ends**, accepting either an ISO-8601 string or a `Date`. Status is exact equality (or array membership). The pure matcher behind this is `matchTrace(trace, filter)` — exported for testing or filtering a trace list you hold yourself.
379
+
380
+ ## Rolling up with `aggregate`
381
+
382
+ `aggregate(filter?)` answers the headline questions — how many runs, how many tokens, how much did it cost, how many failed — for whatever slice the filter selects:
383
+
384
+ ```ts
385
+ type TraceAggregate = {
386
+ traces: number; // matched count
387
+ completed: number; // root status === "completed"
388
+ failed: number; // root status === "failed"
389
+ cancelled: number; // root status === "cancelled"
390
+ usage: Usage; // summed token usage (input/output/total always present)
391
+ cost?: ModelPricing; // merged per-channel USD, or undefined if no trace was priced
392
+ totalDuration: number; // summed wall-clock ms
393
+ };
394
+ ```
395
+
396
+ ```ts
397
+ const stats = store.aggregate({ sessionId: "session-42" });
398
+
399
+ console.log(`${stats.traces} runs, ${stats.usage.total} tokens, ${stats.failed} failed`);
400
+
401
+ const totalUsd =
402
+ (stats.cost?.input ?? 0) +
403
+ (stats.cost?.output ?? 0) +
404
+ (stats.cost?.cachedInput ?? 0) +
405
+ (stats.cost?.cachedOutput ?? 0);
406
+ ```
407
+
408
+ Notes that matter when reading the numbers:
409
+
410
+ - **`usage`** sums each trace's *root* `Usage` (already a rollup of own cost + children), so the totals reflect the whole run tree without re-walking spans. `input` / `output` / `total` are always present (zero when nothing matched); the optional `cachedTokens` / `cacheWriteTokens` / `reasoningTokens` channels are summed only when at least one matched trace reported them.
411
+ - **`cost`** is a per-channel `ModelPricing` breakdown (not one opaque number), merged with the framework's own cost-rollup logic — an unpriced trace never erases the cost of priced ones. It stays `undefined` only when **no** matched trace carried pricing.
412
+ - **`completed` + `failed` + `cancelled` need not equal `traces`** — non-terminal statuses (`awaiting-input`, `max-iterations`) count toward `traces` but none of the three headline counters.
413
+ - An empty match returns a zeroed aggregate (`usage` all `0`, `cost` `undefined`, `totalDuration` `0`).
414
+
415
+ ## Bounding retention with `capacity`
416
+
417
+ Unbounded by default (keep everything until `clear()`). For a long-lived process, cap it — the oldest-ingested trace is evicted FIFO once the cap is exceeded:
418
+
419
+ ```ts
420
+ type InMemoryTraceStoreOptions = { capacity?: number }; // absent / 0 = unbounded
421
+
422
+ const store = createInMemoryTraceStore({ capacity: 1000 });
423
+ ```
424
+
425
+ Overwriting an existing `traceId` refreshes its insertion position, so a re-collected run is treated as newest for eviction — eviction stays honest under overwrite.
426
+
427
+ > The in-memory store has no secondary indexes: `get` / `add` are O(1), `query` / `aggregate` are O(n) scans. That is the right trade-off for dev/test and modest-volume runtime use. For high-volume or durable retention, write a custom `TraceStoreContract` (or export to a backend with [`export-traces`](@warlock.js/ai-panoptic/export-traces/SKILL.md)) backed by a real datastore.
428
+
429
+ ## Summing usage yourself
430
+
431
+ `sumUsage` / `emptyUsage` are the pure folds `aggregate` is built on — reuse them to roll a `Usage` set the store didn't produce (e.g. a list of traces you filtered by hand):
432
+
433
+ ```ts
434
+ import { emptyUsage, sumUsage } from "@warlock.js/ai-panoptic";
435
+
436
+ let total = emptyUsage(); // { input: 0, output: 0, total: 0 } — optional channels absent
437
+ for (const trace of store.query({ sessionId })) {
438
+ total = sumUsage(total, trace.usage);
439
+ }
440
+ ```
441
+
442
+ `sumUsage` is pure (returns a fresh `Usage`, mutates neither argument): token channels always sum; the optional cache/reasoning channels sum only when a side reported them (so a never-metered channel stays absent rather than fabricating a `0`); `cost` merges with the framework's `accumulateCost` so an unpriced contributor never erases a priced one.
443
+
444
+ ## See also
445
+
446
+ - [`@warlock.js/ai-panoptic/observe-with-panoptic/SKILL.md`](@warlock.js/ai-panoptic/observe-with-panoptic/SKILL.md) — wiring the `panoptic(...)` subscriber and bring-your-own-collector so the store fills as runs complete.
447
+ - [`@warlock.js/ai-panoptic/export-traces/SKILL.md`](@warlock.js/ai-panoptic/export-traces/SKILL.md) — sending traces to an external backend instead of (or alongside) retaining them in memory; writing a custom sink.
448
+ - [`@warlock.js/ai/run-ai-agent/SKILL.md`](@warlock.js/ai/run-ai-agent/SKILL.md) — the `BaseReport` the collector projects into the `Trace` this store retains.
449
+
450
+
package/llms.txt ADDED
@@ -0,0 +1,11 @@
1
+ # Warlock AI Panoptic
2
+
3
+ > Package: `@warlock.js/ai-panoptic`
4
+
5
+ > Observability collector + exporters for @warlock.js/ai
6
+
7
+ ## Skills
8
+
9
+ - [export-traces](@warlock.js/ai-panoptic/export-traces/SKILL.md): Send @warlock.js/ai-panoptic traces to an observability backend via pluggable exporters. Triggers: `consoleExporter`, `fileExporter`, `otelExporter`, `langfuseExporter`, `ExporterContract`, `toGenAiAttributes`, `walkSpans`, `totalCostUsd`, `GEN_AI_ATTRIBUTES`, `WARLOCK_ATTRIBUTES`, `reportToTrace`, `reportToSpan`, `extractSpanAttributes`, `normalizeError`, `formatSpanLine`, `createPanopticMiddleware`; 'export AI traces to OpenTelemetry', 'send traces to Langfuse', 'log traces to console / a file', 'gen_ai semantic conventions', 'write a custom exporter'; typical import `import { otelExporter, langfuseExporter, consoleExporter, fileExporter } from "@warlock.js/ai-panoptic"`. Skip: wiring the subscriber/collector into a run — `@warlock.js/ai-panoptic/observe-with-panoptic/SKILL.md`; the core report shape — `@warlock.js/ai/run-ai-agent/SKILL.md`.
10
+ - [observe-with-panoptic](@warlock.js/ai-panoptic/observe-with-panoptic/SKILL.md): Wire @warlock.js/ai-panoptic into an agent/workflow/supervisor/orchestrator with the one-call `panoptic({ exporters })` subscriber. Triggers: `panoptic`, `Panoptic`, `PanopticOptions`, `.attach`, `.middleware`, `.collect`, `.toTrace`, `observe.attach(agent)`, `panoptic().middleware()`, `completedEvents`, `middlewareName`; 'observe a warlock agent', 'trace an agent run', 'attach observability to a workflow/supervisor', 'add a tracing middleware', 'collect an orchestrator turn report', 'wire OTel/Langfuse into my agent'; typical import `import { panoptic } from "@warlock.js/ai-panoptic"`. Skip: writing/choosing an exporter (the sink end) — `@warlock.js/ai-panoptic/export-traces/SKILL.md`; the core report shape / running the agent itself — `@warlock.js/ai/run-ai-agent/SKILL.md`.
11
+ - [query-traces](@warlock.js/ai-panoptic/query-traces/SKILL.md): Retain @warlock.js/ai-panoptic traces in a queryable in-memory store and slice them after the fact — get one run by id, list a session, filter failed runs in a window, roll up usage + cost. Triggers: `createInMemoryTraceStore`, `TraceStoreContract`, `TraceQuery`, `TraceAggregate`, `InMemoryTraceStoreOptions`, `store.query`, `store.aggregate`, `store.get`, `store.add`, `store.clear`, `store.size`, `capacity`, `sumUsage`, `emptyUsage`, `matchTrace`; 'query collected traces', 'aggregate AI cost per session', 'how much did this session spend', 'list failed runs', 'retain traces in memory', 'roll up token usage'; typical import `import { createInMemoryTraceStore } from "@warlock.js/ai-panoptic"`. Skip: sending traces to an external backend (OTel/Langfuse/console/file) — `@warlock.js/ai-panoptic/export-traces/SKILL.md`; wiring the subscriber into a run — `@warlock.js/ai-panoptic/observe-with-panoptic/SKILL.md`.
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@warlock.js/ai-panoptic",
3
+ "description": "Observability collector + exporters for @warlock.js/ai",
4
+ "keywords": [
5
+ "warlock",
6
+ "ai",
7
+ "observability",
8
+ "tracing",
9
+ "opentelemetry",
10
+ "langfuse"
11
+ ],
12
+ "author": "Hasan Zohdy",
13
+ "license": "MIT",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/warlockjs/ai-panoptic"
17
+ },
18
+ "peerDependencies": {
19
+ "@warlock.js/ai": "4.3.0",
20
+ "@opentelemetry/api": "*",
21
+ "@opentelemetry/sdk-trace-base": "*",
22
+ "langfuse": "*"
23
+ },
24
+ "version": "4.3.0",
25
+ "main": "./cjs/index.cjs",
26
+ "module": "./esm/index.mjs",
27
+ "types": "./esm/index.d.mts",
28
+ "exports": {
29
+ ".": {
30
+ "import": {
31
+ "types": "./esm/index.d.mts",
32
+ "default": "./esm/index.mjs"
33
+ },
34
+ "require": {
35
+ "types": "./esm/index.d.mts",
36
+ "default": "./cjs/index.cjs"
37
+ }
38
+ }
39
+ }
40
+ }
@@ -0,0 +1,17 @@
1
+ # `@warlock.js/ai-panoptic` — skills index
2
+
3
+ Per-task skills. All cross-references use the form `@warlock.js/<pkg>/<skill>/SKILL.md`.
4
+
5
+ ## Skills
6
+
7
+ ### [`observe-with-panoptic/`](./observe-with-panoptic/SKILL.md)
8
+
9
+ Wire Panoptic into a run with the one-call `panoptic({ exporters })` subscriber — `attach()` to an agent/workflow/supervisor event stream (every run captured automatically), `middleware()` for the agent pipeline, or `collect()` a report directly (the orchestrator turn). Covers the three feed paths, the default `*.completed` event surface and `completedEvents` override, failure isolation, `flush()` / `shutdown()`, and bringing your own collector/store. Load when attaching observability to a primitive, adding a tracing middleware, or collecting an orchestrator turn report.
10
+
11
+ ### [`export-traces/`](./export-traces/SKILL.md)
12
+
13
+ Send Panoptic traces to an observability backend via pluggable exporters — `consoleExporter` / `fileExporter` (zero-dep) and `otelExporter` (OpenTelemetry GenAI semantic conventions) / `langfuseExporter` (both lazily import their optional-peer SDK). Covers picking an exporter, the `ExporterContract`, the `gen_ai.*` / `warlock.*` attribute mapping, the shared `walkSpans` / `toGenAiAttributes` / `totalCostUsd` utilities, and authoring a custom exporter. Load when wiring `collector.use(...)` with an exporter, exporting to OTel / Langfuse / console / a file, or writing a custom sink.
14
+
15
+ ### [`query-traces/`](./query-traces/SKILL.md)
16
+
17
+ Retain traces in the queryable in-memory store and slice them after the fact — `createInMemoryTraceStore` (doubles as an `ExporterContract`), `get` / `query` / `aggregate` / `clear` / `size`, the `TraceQuery` filter (id / session / status / time window), the `TraceAggregate` usage + cost + status rollup, `capacity` FIFO eviction, and the pure `sumUsage` / `emptyUsage` / `matchTrace` helpers. Load when querying collected traces, aggregating AI cost/usage per session, listing failed runs, or retaining traces in memory.