@tangle-network/agent-eval 0.117.1 → 0.118.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.
- package/CHANGELOG.md +23 -0
- package/dist/analyst/index.d.ts +2772 -21
- package/dist/analyst/index.js +7 -6
- package/dist/analyst/index.js.map +1 -1
- package/dist/belief-state/index.d.ts +706 -10
- package/dist/belief-state/index.js +2 -1
- package/dist/belief-state/index.js.map +1 -1
- package/dist/benchmarks/index.d.ts +958 -14
- package/dist/benchmarks/index.js +12 -10
- package/dist/builder-eval/index.d.ts +449 -4
- package/dist/builder-eval/index.js +4 -3
- package/dist/builder-eval/index.js.map +1 -1
- package/dist/campaign/index.d.ts +4275 -73
- package/dist/campaign/index.js +12 -10
- package/dist/{chunk-VF3XSYTI.js → chunk-33JA4TFA.js} +6 -6
- package/dist/{chunk-4JLWXDYA.js → chunk-3EHHMC6E.js} +2 -2
- package/dist/{chunk-CCZIVI3F.js → chunk-BFW56GTT.js} +2 -2
- package/dist/{chunk-YZPO4UHR.js → chunk-DP3BBMYJ.js} +100 -143
- package/dist/chunk-DP3BBMYJ.js.map +1 -0
- package/dist/{chunk-E4BUPP7Z.js → chunk-HKUCJ437.js} +38 -66
- package/dist/chunk-HKUCJ437.js.map +1 -0
- package/dist/{chunk-HZHNRYHK.js → chunk-K6N6XJJX.js} +2 -2
- package/dist/chunk-KSDQVPLR.js +286 -0
- package/dist/chunk-KSDQVPLR.js.map +1 -0
- package/dist/chunk-MA6HLL3S.js +65 -0
- package/dist/chunk-MA6HLL3S.js.map +1 -0
- package/dist/{chunk-MGEHEHSN.js → chunk-OIIMMLRB.js} +11 -11
- package/dist/{chunk-DXZRATT5.js → chunk-OYZAPX5G.js} +3 -3
- package/dist/chunk-PXE2VKMX.js +140 -0
- package/dist/chunk-PXE2VKMX.js.map +1 -0
- package/dist/{chunk-JSJZ4PJ6.js → chunk-Q442S5AS.js} +17 -17
- package/dist/{chunk-ODVOOEWQ.js → chunk-QBRSJK47.js} +2 -2
- package/dist/{chunk-S2F4J57L.js → chunk-QKEGNI5B.js} +77 -32
- package/dist/chunk-QKEGNI5B.js.map +1 -0
- package/dist/{chunk-5UF54T55.js → chunk-S3UZOQ5Y.js} +34 -7
- package/dist/chunk-S3UZOQ5Y.js.map +1 -0
- package/dist/{chunk-FQNLDL4D.js → chunk-SVH2ANFD.js} +136 -4
- package/dist/chunk-SVH2ANFD.js.map +1 -0
- package/dist/{chunk-GQCZRZ7L.js → chunk-U5CHZ5M3.js} +9 -9
- package/dist/{chunk-TVVP3ZZQ.js → chunk-VQMK5FMP.js} +2 -1
- package/dist/{chunk-TVVP3ZZQ.js.map → chunk-VQMK5FMP.js.map} +1 -1
- package/dist/chunk-WDHBCA3M.js +31 -0
- package/dist/chunk-WDHBCA3M.js.map +1 -0
- package/dist/{chunk-HQPHZGL6.js → chunk-YLMUS4MM.js} +9 -9
- package/dist/{chunk-LQUTGLOZ.js → chunk-ZET2UAYW.js} +15 -65
- package/dist/chunk-ZET2UAYW.js.map +1 -0
- package/dist/contract/index.d.ts +4012 -38
- package/dist/contract/index.js +56 -29
- package/dist/contract/index.js.map +1 -1
- package/dist/control.d.ts +1013 -9
- package/dist/control.js +4 -3
- package/dist/fuzz.d.ts +194 -4
- package/dist/fuzz.js +3 -3
- package/dist/hosted/index.d.ts +498 -17
- package/dist/index.d.ts +10982 -1286
- package/dist/index.js +97 -80
- package/dist/index.js.map +1 -1
- package/dist/matrix/index.d.ts +139 -4
- package/dist/meta-eval/index.d.ts +862 -15
- package/dist/meta-eval/index.js +2 -1
- package/dist/meta-eval/index.js.map +1 -1
- package/dist/multishot/index.d.ts +214 -14
- package/dist/openapi.json +1 -1
- package/dist/pipelines/index.d.ts +392 -7
- package/dist/pipelines/index.js +5 -3
- package/dist/pipelines/index.js.map +1 -1
- package/dist/reporting.d.ts +1277 -17
- package/dist/rl.d.ts +2359 -28
- package/dist/rl.js +6 -5
- package/dist/rl.js.map +1 -1
- package/dist/storyboard/index.d.ts +86 -1
- package/dist/trace-attributes.d.ts +16 -0
- package/dist/trace-attributes.js +32 -0
- package/dist/trace-attributes.js.map +1 -0
- package/dist/traces.d.ts +1970 -697
- package/dist/traces.js +49 -32
- package/dist/wire/index.d.ts +655 -9
- package/docs/insight-report.md +44 -0
- package/package.json +9 -3
- package/dist/adversarial-B7loGVVX.d.ts +0 -19
- package/dist/analyst-C8HHvfJp.d.ts +0 -88
- package/dist/analyze-runs--2x39HZ7.d.ts +0 -81
- package/dist/baseline-DKq3gJpP.d.ts +0 -141
- package/dist/calibration-C8MTS7cw.d.ts +0 -101
- package/dist/chunk-5UF54T55.js.map +0 -1
- package/dist/chunk-E4BUPP7Z.js.map +0 -1
- package/dist/chunk-FQNLDL4D.js.map +0 -1
- package/dist/chunk-LQUTGLOZ.js.map +0 -1
- package/dist/chunk-S2F4J57L.js.map +0 -1
- package/dist/chunk-YZPO4UHR.js.map +0 -1
- package/dist/code-agent-session-CjZsVd19.d.ts +0 -87
- package/dist/control-6vuGfmDH.d.ts +0 -258
- package/dist/cost-ledger-DWy3XdJc.d.ts +0 -183
- package/dist/dataset-NENEzRgk.d.ts +0 -115
- package/dist/default-registry-DaK8b3fv.d.ts +0 -155
- package/dist/emitter-CjD7vUwv.d.ts +0 -122
- package/dist/errors-oeQrLqXC.d.ts +0 -74
- package/dist/failure-cluster-DOAcSJ87.d.ts +0 -76
- package/dist/feedback-trajectory-BUnM58xL.d.ts +0 -348
- package/dist/gepa-eESocoDi.d.ts +0 -642
- package/dist/index-PdX4VnPA.d.ts +0 -423
- package/dist/insight-report-DY4nDW9Q.d.ts +0 -310
- package/dist/integrity-DqlBiLyK.d.ts +0 -81
- package/dist/judge-calibration-7C-IDmKr.d.ts +0 -145
- package/dist/kind-factory-ClZmO25A.d.ts +0 -171
- package/dist/llm-client-qoDd18Qz.d.ts +0 -289
- package/dist/multi-layer-verifier-BsqKuLyN.d.ts +0 -150
- package/dist/off-policy-DiwuKKg7.d.ts +0 -132
- package/dist/outcome-store-rnXLEqSn.d.ts +0 -63
- package/dist/policy-edit-wG9uFEFm.d.ts +0 -455
- package/dist/pre-registration-BWQhJ3vz.d.ts +0 -761
- package/dist/provenance-DpjwyseI.d.ts +0 -541
- package/dist/query-CF7PG61p.d.ts +0 -35
- package/dist/raw-provider-sink-C46HDghv.d.ts +0 -132
- package/dist/release-report-C8G2i5Xi.d.ts +0 -236
- package/dist/researcher-C8XyxQsu.d.ts +0 -387
- package/dist/rubric-predictive-validity-p49lLVrE.d.ts +0 -105
- package/dist/run-record-BDH49H2E.d.ts +0 -360
- package/dist/runtime-trajectory-DGBIUt4B.d.ts +0 -49
- package/dist/schema-B3Q3l9Z_.d.ts +0 -201
- package/dist/semantic-concept-judge-CXnPEJbf.d.ts +0 -723
- package/dist/sequential-5iSVfzl2.d.ts +0 -139
- package/dist/series-convergence-D5OWMBg6.d.ts +0 -33
- package/dist/statistics-KUnG73jH.d.ts +0 -494
- package/dist/storage-DrX3v_5B.d.ts +0 -50
- package/dist/store-C1YxJDEK.d.ts +0 -248
- package/dist/store-DGqD0Pyo.d.ts +0 -116
- package/dist/summary-report-C5bKFfm-.d.ts +0 -445
- package/dist/test-graded-scenario-B0ybnPY7.d.ts +0 -166
- package/dist/types-BSw1rOUB.d.ts +0 -634
- package/dist/types-BUxNaJ8c.d.ts +0 -108
- package/dist/types-BkfcQnxV.d.ts +0 -313
- package/dist/verdict-C9MlYujm.d.ts +0 -35
- /package/dist/{chunk-VF3XSYTI.js.map → chunk-33JA4TFA.js.map} +0 -0
- /package/dist/{chunk-4JLWXDYA.js.map → chunk-3EHHMC6E.js.map} +0 -0
- /package/dist/{chunk-CCZIVI3F.js.map → chunk-BFW56GTT.js.map} +0 -0
- /package/dist/{chunk-HZHNRYHK.js.map → chunk-K6N6XJJX.js.map} +0 -0
- /package/dist/{chunk-MGEHEHSN.js.map → chunk-OIIMMLRB.js.map} +0 -0
- /package/dist/{chunk-DXZRATT5.js.map → chunk-OYZAPX5G.js.map} +0 -0
- /package/dist/{chunk-JSJZ4PJ6.js.map → chunk-Q442S5AS.js.map} +0 -0
- /package/dist/{chunk-ODVOOEWQ.js.map → chunk-QBRSJK47.js.map} +0 -0
- /package/dist/{chunk-GQCZRZ7L.js.map → chunk-U5CHZ5M3.js.map} +0 -0
- /package/dist/{chunk-HQPHZGL6.js.map → chunk-YLMUS4MM.js.map} +0 -0
package/dist/traces.d.ts
CHANGED
|
@@ -1,678 +1,786 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { P as ProviderRedactor, R as RawProviderSink, d as RawProviderEvent } from './raw-provider-sink-C46HDghv.js';
|
|
3
|
-
export { F as FileSystemRawProviderSink, a as FileSystemRawProviderSinkOptions, I as InMemoryRawProviderSink, b as InMemoryRawProviderSinkOptions, N as NoopRawProviderSink, c as RawProviderDirection, e as RawProviderSinkFilter, f as defaultProviderRedactor, p as providerFromBaseUrl } from './raw-provider-sink-C46HDghv.js';
|
|
4
|
-
import { a as RunCompleteHookContext, R as RunCompleteHook } from './emitter-CjD7vUwv.js';
|
|
5
|
-
export { S as SpanHandle, T as TraceEmitter, b as TraceEmitterOptions, l as llmSpanFromProvider } from './emitter-CjD7vUwv.js';
|
|
6
|
-
export { b as RunIntegrityError, R as RunIntegrityExpectations, c as RunIntegrityIssue, d as RunIntegrityIssueCode, a as RunIntegrityReport, e as assertRunCaptured, t as throwIfRunIncomplete } from './integrity-DqlBiLyK.js';
|
|
7
|
-
import { T as TraceStore } from './store-DGqD0Pyo.js';
|
|
8
|
-
export { E as EventFilter, F as FileSystemTraceStore, a as FileSystemTraceStoreOptions, I as InMemoryTraceStore, R as RunFilter, S as SpanFilter } from './store-DGqD0Pyo.js';
|
|
9
|
-
import { T as ToolSpan, R as Run } from './schema-B3Q3l9Z_.js';
|
|
10
|
-
export { A as Artifact, B as BudgetLedgerEntry, h as BudgetSpec, E as EventKind, i as FAILURE_CLASSES, F as FailureClass, G as GenericSpan, J as JudgeSpan, L as LlmSpan, M as Message, c as RetrievalSpan, g as RunLayer, a as RunOutcome, f as RunStatus, d as SandboxSpan, S as Span, j as SpanBase, b as SpanKind, k as SpanStatus, l as TRACE_SCHEMA_VERSION, e as TraceEvent, m as isJudgeSpan, n as isLlmSpan, o as isRetrievalSpan, p as isSandboxSpan, q as isToolSpan } from './schema-B3Q3l9Z_.js';
|
|
11
|
-
export { a as aggregateLlm, b as argHash, g as groupBy, h as hasCapturedToolArgs, j as judgeSpans, l as llmSpans, r as runFailureClass, c as runsForScenario, t as toolSpans } from './query-CF7PG61p.js';
|
|
12
|
-
import { A as AnalyzeTracesOptions, b as AnalyzeTracesResult } from './analyst-C8HHvfJp.js';
|
|
13
|
-
export { a as AnalyzeTracesInput, c as AnalyzeTracesTurnSnapshot, d as analyzeTraces } from './analyst-C8HHvfJp.js';
|
|
14
|
-
import { h as TraceAnalystSpanKind, i as TraceAnalystSpanStatus, T as TraceAnalysisStore, g as TraceAnalystFilters, b as DatasetOverview, Q as QueryTracesPage, l as ViewTraceResult, V as ViewSpansResult, c as SearchTraceResult, S as SearchSpanResult } from './store-C1YxJDEK.js';
|
|
15
|
-
export { D as DEFAULT_TRACE_ANALYST_BUDGETS, E as ErrorCluster, d as SpanMatchRecord, e as TRACE_ANALYST_TRUNCATION_MARKER_PREFIX, f as TraceAnalystByteBudgets, a as TraceAnalystSpan, j as TraceAnalystTraceSummary, k as ViewTraceOversized } from './store-C1YxJDEK.js';
|
|
16
|
-
import { a as RunSplitTag, c as RunTokenUsage, R as RunRecord } from './run-record-BDH49H2E.js';
|
|
17
|
-
import { AxFunction } from '@ax-llm/ax';
|
|
18
|
-
import '@tangle-network/agent-interface';
|
|
1
|
+
import { AxAIService, AxFunction } from '@ax-llm/ax';
|
|
19
2
|
|
|
20
|
-
/**
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
declare
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Error taxonomy for `@tangle-network/agent-eval`.
|
|
5
|
+
*
|
|
6
|
+
* Every error this package throws as part of its *public contract* extends
|
|
7
|
+
* `AgentEvalError`. Consumers can pattern-match by `instanceof <Subclass>` or
|
|
8
|
+
* by the stable string `code` carried on the base class.
|
|
9
|
+
*
|
|
10
|
+
* The codes are stable across minor versions; new codes can be added, but
|
|
11
|
+
* existing codes never change meaning. New subclasses are non-breaking.
|
|
12
|
+
*
|
|
13
|
+
* Internal invariant guards (`throw new Error('this should never happen')`)
|
|
14
|
+
* remain plain `Error`s on purpose — they're programmer-mistake assertions,
|
|
15
|
+
* not consumer-catchable contract failures.
|
|
16
|
+
*/
|
|
17
|
+
type AgentEvalErrorCode = 'validation' | 'not_found' | 'config' | 'capture_integrity' | 'judge' | 'verification' | 'replay' | 'backend_integrity' | 'profile_matrix';
|
|
18
|
+
/**
|
|
19
|
+
* Base class for every contract error this package throws — carries the stable
|
|
20
|
+
* string `code` taxonomy so consumers can `instanceof`-match or switch on `code`.
|
|
21
|
+
*/
|
|
22
|
+
declare class AgentEvalError extends Error {
|
|
23
|
+
/** Stable string code. Survives minification; safe to switch on. */
|
|
24
|
+
readonly code: AgentEvalErrorCode;
|
|
25
|
+
constructor(code: AgentEvalErrorCode, message: string, options?: {
|
|
26
|
+
cause?: unknown;
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/** A named resource (run, span, rubric, scenario, dataset row, route) does not exist. */
|
|
30
|
+
declare class NotFoundError extends AgentEvalError {
|
|
31
|
+
constructor(message: string, options?: {
|
|
32
|
+
cause?: unknown;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* A run is missing the artifacts a launch-grade check requires:
|
|
37
|
+
* raw HTTP capture absent, no LLM spans, route assertion failed, run-end
|
|
38
|
+
* assertion tripped. Block ship on this; do not catch and move on.
|
|
39
|
+
*/
|
|
40
|
+
declare class CaptureIntegrityError extends AgentEvalError {
|
|
41
|
+
constructor(message: string, options?: {
|
|
42
|
+
cause?: unknown;
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/** Replay cache cannot satisfy a request: miss with no fallback, sink lacks list(), unsupported URL. */
|
|
46
|
+
declare class ReplayError extends AgentEvalError {
|
|
47
|
+
constructor(message: string, options?: {
|
|
48
|
+
cause?: unknown;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
43
51
|
|
|
44
52
|
/**
|
|
45
|
-
*
|
|
53
|
+
* RawProviderSink — first-class persistence for the actual HTTP-level
|
|
54
|
+
* request/response bodies of every LLM provider call.
|
|
46
55
|
*
|
|
47
|
-
*
|
|
48
|
-
* direct matrix run produces an analysis artifact without an out-of-band
|
|
49
|
-
* step. Designed for the case where the consumer reports "the analyst
|
|
50
|
-
* never ran" — the cause is almost always orchestration, not the analyst.
|
|
56
|
+
* Why this is a separate sink from the structured `LlmSpan`:
|
|
51
57
|
*
|
|
52
|
-
*
|
|
58
|
+
* - `LlmSpan` records the *intent* — model name, messages, output text,
|
|
59
|
+
* usage. It's what dashboards read; it's NOT enough for forensics.
|
|
60
|
+
* - When a downstream consumer reports "the verifier used the wrong route"
|
|
61
|
+
* or "tokens look right but reasoning was missing," the only way to
|
|
62
|
+
* answer is the raw HTTP body. Span fields can lie (a proxy can echo
|
|
63
|
+
* a different `model` value than what actually answered); the raw
|
|
64
|
+
* response is ground truth.
|
|
53
65
|
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
66
|
+
* Default behaviour: opt-in. Pass `rawSink` to `LlmClientOptions` (or the
|
|
67
|
+
* matrix runner / BuilderSession sets it up automatically) and every
|
|
68
|
+
* request, response, and error is recorded — including retries, with the
|
|
69
|
+
* attempt index attached so a flaky call's full event chain is recoverable.
|
|
57
70
|
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
71
|
+
* Redaction is enforced at sink time. The default redactor strips
|
|
72
|
+
* `Authorization`, `X-Api-Key`, `X-Auth-Token`, `Cookie` headers and any
|
|
73
|
+
* payload field whose key matches `apiKey | api_key | bearer | password |
|
|
74
|
+
* secret | token` (case-insensitive). Override via the sink constructor or
|
|
75
|
+
* the per-call `redactor`. The `redactedFields` array on the persisted
|
|
76
|
+
* event lets a reviewer see what was stripped without exposing the values.
|
|
61
77
|
*/
|
|
62
|
-
|
|
63
|
-
interface
|
|
64
|
-
/**
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
analyze: Omit<AnalyzeTracesOptions, 'source'> & {
|
|
70
|
-
source?: AnalyzeTracesOptions['source'];
|
|
71
|
-
};
|
|
72
|
-
/**
|
|
73
|
-
* Override the question. The default is intentionally generic:
|
|
74
|
-
* "Summarise what happened in this run, surface any failure modes,
|
|
75
|
-
* surprising findings, or evidence the verdict is wrong."
|
|
76
|
-
*/
|
|
77
|
-
question?: string;
|
|
78
|
-
/**
|
|
79
|
-
* Persist the result. The hook calls this with the analysis output and
|
|
80
|
-
* the run context. Common implementations write to a TraceAnalysisStore
|
|
81
|
-
* or append to a per-run JSONL.
|
|
82
|
-
*/
|
|
83
|
-
save?: (result: AnalyzeTracesResult, ctx: RunCompleteHookContext) => Promise<void>;
|
|
84
|
-
/**
|
|
85
|
-
* Predicate gating execution per run. Default: every completed run.
|
|
86
|
-
* Use to skip aborted runs, debug runs, or runs without LLM activity.
|
|
87
|
-
*/
|
|
88
|
-
shouldRun?: (ctx: RunCompleteHookContext) => boolean;
|
|
78
|
+
type RawProviderDirection = 'request' | 'response' | 'error';
|
|
79
|
+
interface RawProviderEvent {
|
|
80
|
+
/** Stable id. Generated by the sink if omitted. */
|
|
81
|
+
eventId: string;
|
|
82
|
+
/** Trace context populated by `LlmClient` when the call is wrapped in a span. */
|
|
83
|
+
runId?: string;
|
|
84
|
+
spanId?: string;
|
|
89
85
|
/**
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
86
|
+
* Logical provider name. Free-form so callers can use whatever id matches
|
|
87
|
+
* their topology (`'openai'`, `'anthropic'`, `'tangle-router'`, …). When
|
|
88
|
+
* omitted, derived from `baseUrl` in `LlmClientOptions`.
|
|
93
89
|
*/
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
90
|
+
provider: string;
|
|
91
|
+
model: string;
|
|
92
|
+
/** Endpoint path, e.g. `'/v1/chat/completions'`. */
|
|
93
|
+
endpoint: string;
|
|
94
|
+
/** Base URL used for the call (already-normalised — no trailing slash). */
|
|
95
|
+
baseUrl: string;
|
|
96
|
+
/** 0-indexed retry attempt. The first attempt is 0; a retried call gets 1, 2, … */
|
|
97
|
+
attemptIndex: number;
|
|
98
|
+
direction: RawProviderDirection;
|
|
99
|
+
/** Unix ms. */
|
|
100
|
+
timestamp: number;
|
|
101
|
+
/** Wall-clock duration of the call leg. Set on `response` and `error` events; null on `request`. */
|
|
102
|
+
durationMs?: number;
|
|
103
|
+
statusCode?: number;
|
|
104
|
+
requestHeaders?: Record<string, string>;
|
|
105
|
+
requestBody?: unknown;
|
|
106
|
+
responseHeaders?: Record<string, string>;
|
|
107
|
+
responseBody?: unknown;
|
|
108
|
+
/** Set on `direction: 'error'` events. */
|
|
109
|
+
errorMessage?: string;
|
|
110
|
+
/** Field paths the redactor stripped from this event ('header:Authorization', 'body.apiKey', …). */
|
|
111
|
+
redactedFields: string[];
|
|
112
112
|
}
|
|
113
|
-
interface
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
proposedFixClass?: string;
|
|
113
|
+
interface RawProviderSinkFilter {
|
|
114
|
+
runId?: string;
|
|
115
|
+
spanId?: string;
|
|
116
|
+
direction?: RawProviderDirection;
|
|
117
|
+
attemptIndex?: number;
|
|
119
118
|
}
|
|
120
|
-
interface
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
119
|
+
interface RawProviderSink {
|
|
120
|
+
record(event: RawProviderEvent): Promise<void>;
|
|
121
|
+
/** Optional listing — implementations that durably persist (file, db) should support this. */
|
|
122
|
+
list?(filter?: RawProviderSinkFilter): Promise<RawProviderEvent[]>;
|
|
123
|
+
/** Optional teardown for backed implementations. */
|
|
124
|
+
close?(): Promise<void>;
|
|
124
125
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
126
|
+
type ProviderRedactor = (event: RawProviderEvent) => RawProviderEvent;
|
|
127
|
+
/**
|
|
128
|
+
* Default redactor — strips well-known auth headers and any body field whose
|
|
129
|
+
* key matches the credential pattern. Records every redacted path on
|
|
130
|
+
* `event.redactedFields` so a downstream reviewer can see what was removed.
|
|
131
|
+
*/
|
|
132
|
+
declare function defaultProviderRedactor(event: RawProviderEvent): RawProviderEvent;
|
|
133
|
+
interface InMemoryRawProviderSinkOptions {
|
|
134
|
+
redactor?: ProviderRedactor;
|
|
129
135
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
+
declare class InMemoryRawProviderSink implements RawProviderSink {
|
|
137
|
+
private events;
|
|
138
|
+
private redactor;
|
|
139
|
+
constructor(opts?: InMemoryRawProviderSinkOptions);
|
|
140
|
+
record(event: RawProviderEvent): Promise<void>;
|
|
141
|
+
list(filter?: RawProviderSinkFilter): Promise<RawProviderEvent[]>;
|
|
142
|
+
size(): number;
|
|
136
143
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
declare class NoopRawProviderSink implements RawProviderSink {
|
|
145
|
+
record(): Promise<void>;
|
|
146
|
+
/**
|
|
147
|
+
* Returns an empty array. Implemented so `assertRunCaptured` does not
|
|
148
|
+
* trip the `no_raw_sink` issue when a caller explicitly opts out of
|
|
149
|
+
* capture by passing this sink — opt-out is a deliberate choice, not a
|
|
150
|
+
* misconfiguration.
|
|
151
|
+
*/
|
|
152
|
+
list(): Promise<RawProviderEvent[]>;
|
|
146
153
|
}
|
|
147
|
-
interface
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
154
|
+
interface FileSystemRawProviderSinkOptions {
|
|
155
|
+
/** Directory the NDJSON file is written into. Created if missing. */
|
|
156
|
+
dir: string;
|
|
157
|
+
/** File name; default `'raw-provider-events.ndjson'`. */
|
|
158
|
+
fileName?: string;
|
|
159
|
+
/** Bytes after which the writer rolls over to a new file (default 32 MiB). */
|
|
160
|
+
rollAtBytes?: number;
|
|
161
|
+
redactor?: ProviderRedactor;
|
|
153
162
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
163
|
+
declare class FileSystemRawProviderSink implements RawProviderSink {
|
|
164
|
+
private dir;
|
|
165
|
+
private fileName;
|
|
166
|
+
private rollAtBytes;
|
|
167
|
+
private redactor;
|
|
168
|
+
private bytesWritten;
|
|
169
|
+
private rollIndex;
|
|
170
|
+
private initPromise;
|
|
171
|
+
constructor(opts: FileSystemRawProviderSinkOptions);
|
|
172
|
+
private ensureInit;
|
|
173
|
+
private currentPath;
|
|
174
|
+
record(event: RawProviderEvent): Promise<void>;
|
|
175
|
+
list(filter?: RawProviderSinkFilter): Promise<RawProviderEvent[]>;
|
|
158
176
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
declare function
|
|
164
|
-
declare function buildTraceInsightContext(input: TraceInsightPromptInput): TraceInsightContext;
|
|
165
|
-
declare function scoreTraceInsightReadiness(context: TraceInsightContext): TraceInsightReadiness;
|
|
166
|
-
declare function defaultTraceInsightPanel(): TraceInsightPanelRole[];
|
|
167
|
-
declare function buildTraceInsightPrompt(input: TraceInsightPromptInput): string;
|
|
177
|
+
/**
|
|
178
|
+
* Best-effort provider id from a base URL. Falls back to the URL host when
|
|
179
|
+
* none of the well-known patterns match.
|
|
180
|
+
*/
|
|
181
|
+
declare function providerFromBaseUrl(baseUrl: string): string;
|
|
168
182
|
|
|
169
183
|
/**
|
|
170
|
-
*
|
|
171
|
-
*
|
|
184
|
+
* Replay-from-raw-events — turn every captured campaign run into a
|
|
185
|
+
* re-runnable artifact.
|
|
172
186
|
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
187
|
+
* `RawProviderSink` captures every provider HTTP envelope; `runEvalCampaign`
|
|
188
|
+
* makes that capture the default. Together they make every past run a
|
|
189
|
+
* complete fingerprint of what happened on the wire — enough to replay
|
|
190
|
+
* the run without burning new LLM cost.
|
|
176
191
|
*
|
|
177
|
-
*
|
|
192
|
+
* Three use cases this primitive enables:
|
|
193
|
+
*
|
|
194
|
+
* 1. **Post-hoc judging** — apply a new judge / rubric / scoring callback
|
|
195
|
+
* to last week's runs without re-calling any LLM. The cost of trying
|
|
196
|
+
* a new rubric drops from "another full sweep" to a CPU-bound replay.
|
|
197
|
+
* 2. **Determinism audits** — replay the same campaign and verify the
|
|
198
|
+
* raw responses match byte-for-byte. Any drift is a non-determinism
|
|
199
|
+
* bug (in the harness, the prompt builder, the sandbox, …).
|
|
200
|
+
* 3. **Free judge calibration** — run two judges on identical responses
|
|
201
|
+
* and measure inter-judge agreement without doubling LLM spend.
|
|
202
|
+
*
|
|
203
|
+
* The interface is deliberately fetch-shaped. Inject `createReplayFetch`
|
|
204
|
+
* into `LlmClientOptions.fetch` and every `callLlm` transparently reads
|
|
205
|
+
* from the cache instead of calling the network. No new code path through
|
|
206
|
+
* the LLM client is needed; the cache hit is invisible to the runner.
|
|
178
207
|
*/
|
|
179
208
|
|
|
180
|
-
declare
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
interface OtlpSpan {
|
|
185
|
-
traceId: string;
|
|
186
|
-
spanId: string;
|
|
187
|
-
parentSpanId?: string;
|
|
188
|
-
name: string;
|
|
189
|
-
kind: number;
|
|
190
|
-
startTimeUnixNano: string;
|
|
191
|
-
endTimeUnixNano: string;
|
|
192
|
-
attributes: Array<{
|
|
193
|
-
key: string;
|
|
194
|
-
value: {
|
|
195
|
-
stringValue?: string;
|
|
196
|
-
intValue?: string;
|
|
197
|
-
doubleValue?: number;
|
|
198
|
-
boolValue?: boolean;
|
|
199
|
-
};
|
|
200
|
-
}>;
|
|
201
|
-
events?: Array<{
|
|
202
|
-
timeUnixNano: string;
|
|
203
|
-
name: string;
|
|
204
|
-
attributes?: OtlpSpan['attributes'];
|
|
205
|
-
}>;
|
|
206
|
-
status?: {
|
|
207
|
-
code: number;
|
|
208
|
-
message?: string;
|
|
209
|
-
};
|
|
209
|
+
declare class ReplayCacheMissError extends ReplayError {
|
|
210
|
+
readonly url: string;
|
|
211
|
+
readonly requestKey: string;
|
|
212
|
+
constructor(url: string, requestKey: string, message?: string);
|
|
210
213
|
}
|
|
211
|
-
interface
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
};
|
|
215
|
-
scopeSpans: Array<{
|
|
216
|
-
scope: typeof OTEL_AGENT_EVAL_SCOPE;
|
|
217
|
-
spans: OtlpSpan[];
|
|
218
|
-
}>;
|
|
214
|
+
interface ReplayCacheEntry {
|
|
215
|
+
request: RawProviderEvent;
|
|
216
|
+
response: RawProviderEvent;
|
|
219
217
|
}
|
|
220
|
-
interface
|
|
221
|
-
|
|
218
|
+
interface ReplayCacheStats {
|
|
219
|
+
total: number;
|
|
220
|
+
byProvider: Record<string, number>;
|
|
221
|
+
byModel: Record<string, number>;
|
|
222
|
+
/** Spans for which we have a request but no response (run aborted mid-call). */
|
|
223
|
+
orphanRequests: number;
|
|
222
224
|
}
|
|
223
|
-
/** Export a single run's spans + events in OTLP/JSON. */
|
|
224
|
-
declare function exportRunAsOtlp(store: TraceStore, runId: string, resourceAttrs?: Record<string, string | number | boolean>): Promise<OtlpExport>;
|
|
225
|
-
|
|
226
225
|
/**
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
* `
|
|
230
|
-
* flatteners with one canonical projection.
|
|
226
|
+
* In-memory deterministic cache of (request → response) keyed on a stable
|
|
227
|
+
* hash of the request body. Built from a `RawProviderSink` containing
|
|
228
|
+
* paired `request` and `response` events from a previous run.
|
|
231
229
|
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
230
|
+
* The cache is the source of truth for replay; `createReplayFetch` is a
|
|
231
|
+
* thin wrapper that reads from it.
|
|
234
232
|
*/
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
233
|
+
declare class ReplayCache {
|
|
234
|
+
private byKey;
|
|
235
|
+
private orphans;
|
|
236
|
+
private byProvider;
|
|
237
|
+
private byModel;
|
|
238
|
+
/**
|
|
239
|
+
* Build a cache from a sink's events. The sink must implement `list()`.
|
|
240
|
+
* Filter by `runId` / `spanId` to scope to a specific replay.
|
|
241
|
+
*/
|
|
242
|
+
static fromSink(sink: RawProviderSink, filter?: {
|
|
243
|
+
runId?: string;
|
|
244
|
+
spanId?: string;
|
|
245
|
+
}): Promise<ReplayCache>;
|
|
246
|
+
/** Build a cache from an in-memory event list. */
|
|
247
|
+
static fromEvents(events: RawProviderEvent[]): Promise<ReplayCache>;
|
|
248
|
+
/** Number of cacheable (request, response) pairs in the cache. */
|
|
249
|
+
size(): number;
|
|
250
|
+
stats(): ReplayCacheStats;
|
|
251
|
+
/** Iterate every cached `(request, response)` pair in insertion order. */
|
|
252
|
+
entries(): IterableIterator<ReplayCacheEntry>;
|
|
253
|
+
/**
|
|
254
|
+
* Look up a cached response by hashing the (model, messages, temperature,
|
|
255
|
+
* maxTokens, response_format) shape. Returns `undefined` on miss; the
|
|
256
|
+
* caller decides whether to throw, fall back to the network, or skip.
|
|
257
|
+
*/
|
|
258
|
+
lookup(requestBody: unknown): Promise<ReplayCacheEntry | undefined>;
|
|
257
259
|
}
|
|
258
|
-
interface
|
|
259
|
-
/**
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
260
|
+
interface ReplayFetchOptions {
|
|
261
|
+
/**
|
|
262
|
+
* Behaviour on cache miss. Default `'throw'`. `'fallback'` calls the
|
|
263
|
+
* `fallbackFetch` (typically `globalThis.fetch`) so a partial replay can
|
|
264
|
+
* still complete; `'fail-closed'` returns a synthetic 599 response so the
|
|
265
|
+
* call site sees a non-retriable failure.
|
|
266
|
+
*/
|
|
267
|
+
onMiss?: 'throw' | 'fallback' | 'fail-closed';
|
|
268
|
+
fallbackFetch?: typeof fetch;
|
|
269
|
+
/** Optional callback fired once per replayed call (for telemetry / counters). */
|
|
270
|
+
onHit?: (info: {
|
|
271
|
+
url: string;
|
|
272
|
+
provider: string;
|
|
273
|
+
model: string;
|
|
274
|
+
}) => void;
|
|
275
|
+
/** Optional callback fired on cache miss before the `onMiss` policy applies. */
|
|
276
|
+
onMissNotify?: (info: {
|
|
277
|
+
url: string;
|
|
278
|
+
requestBody: unknown;
|
|
279
|
+
}) => void;
|
|
265
280
|
}
|
|
266
|
-
declare function flattenOtlpExportToNdjson(otlpExport: OtlpExport, opts?: FlattenOtlpOptions): OtlpFlatLine[];
|
|
267
|
-
|
|
268
281
|
/**
|
|
269
|
-
*
|
|
270
|
-
*
|
|
271
|
-
* `
|
|
272
|
-
* emit via their OpenInference OTLP exporter).
|
|
282
|
+
* Build a `fetch`-shaped function that serves cached responses out of a
|
|
283
|
+
* `ReplayCache` for any URL ending in `/chat/completions`. Pass through
|
|
284
|
+
* `LlmClientOptions.fetch` and `callLlm` becomes free.
|
|
273
285
|
*
|
|
274
|
-
* `
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
|
|
286
|
+
* Non-`/chat/completions` URLs are passed straight to the fallback fetch
|
|
287
|
+
* (default: `globalThis.fetch`). This matters because non-LLM HTTP work
|
|
288
|
+
* (judge HTTP servers, sandbox callbacks) sometimes flows through the same
|
|
289
|
+
* `fetch` and shouldn't be intercepted.
|
|
290
|
+
*/
|
|
291
|
+
declare function createReplayFetch(cache: ReplayCache, opts?: ReplayFetchOptions): typeof fetch;
|
|
292
|
+
/**
|
|
293
|
+
* Convenience iterator over `(request, response)` pairs in a sink — for
|
|
294
|
+
* post-hoc scoring that doesn't need a `fetch` shim. The judge or scorer
|
|
295
|
+
* runs purely in-process over cached LLM outputs.
|
|
296
|
+
*/
|
|
297
|
+
declare function iterateRawCalls(sink: RawProviderSink, filter?: {
|
|
298
|
+
runId?: string;
|
|
299
|
+
spanId?: string;
|
|
300
|
+
}): AsyncGenerator<ReplayCacheEntry>;
|
|
301
|
+
|
|
302
|
+
type AgentProfileCellSchemaVersion = 'agent-profile-cell/v1';
|
|
303
|
+
type AgentProfileDimensionValue = string | number | boolean | null;
|
|
304
|
+
interface AgentProfileSource {
|
|
305
|
+
/** Runtime/profile contract being fingerprinted, e.g. `agent-interface-profile`. */
|
|
306
|
+
kind: string;
|
|
307
|
+
/** sha256 over the canonical source profile object. */
|
|
308
|
+
hash: string;
|
|
309
|
+
}
|
|
310
|
+
interface AgentProfileHarness {
|
|
311
|
+
id: string;
|
|
312
|
+
version?: string;
|
|
313
|
+
hash?: string;
|
|
314
|
+
}
|
|
315
|
+
interface AgentProfileCell {
|
|
316
|
+
schemaVersion: AgentProfileCellSchemaVersion;
|
|
317
|
+
cellId: string;
|
|
318
|
+
profileId: string;
|
|
319
|
+
sourceProfile: AgentProfileSource;
|
|
320
|
+
harness?: AgentProfileHarness;
|
|
321
|
+
model?: string;
|
|
322
|
+
promptHash?: string;
|
|
323
|
+
dimensions?: Record<string, AgentProfileDimensionValue>;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* TraceSchema v1 — the canonical data model for agent-eval.
|
|
279
328
|
*
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
* - the substrate's own `llm.*` / `tool.*` / `span.kind` attributes
|
|
283
|
-
* (`flattenSpanAttributes` in `trace/otel.ts`), and
|
|
284
|
-
* - the OpenInference / inference-export attributes AppWorld / HALO
|
|
285
|
-
* emit (`openinference.span.kind`, `inference.observation_kind`,
|
|
286
|
-
* `inference.llm.input_tokens`, `llm.token_count.prompt`, …).
|
|
329
|
+
* Every score, every failure class, every pipeline in the framework is
|
|
330
|
+
* a view over this data. Shape it once, live with it.
|
|
287
331
|
*
|
|
288
|
-
*
|
|
332
|
+
* Wire-compatible with OpenTelemetry span semantics (see trace/otel.ts)
|
|
333
|
+
* but extended with agent-specific span kinds (llm, tool, retrieval,
|
|
334
|
+
* judge, sandbox) and first-class BudgetLedger / Artifact / JudgeVerdict
|
|
335
|
+
* entities that OTEL leaves as free-form attributes.
|
|
289
336
|
*/
|
|
290
|
-
|
|
337
|
+
declare const TRACE_SCHEMA_VERSION = "1.0.0";
|
|
338
|
+
type RunStatus = 'running' | 'completed' | 'failed' | 'aborted';
|
|
339
|
+
interface BudgetSpec {
|
|
340
|
+
tokens?: number;
|
|
341
|
+
wallMs?: number;
|
|
342
|
+
calls?: number;
|
|
343
|
+
usd?: number;
|
|
344
|
+
}
|
|
345
|
+
interface RunOutcome$1 {
|
|
346
|
+
score?: number;
|
|
347
|
+
pass?: boolean;
|
|
348
|
+
failureClass?: FailureClass;
|
|
349
|
+
notes?: string;
|
|
350
|
+
}
|
|
291
351
|
/**
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
*
|
|
352
|
+
* Layer — optional classification in a nested build workflow.
|
|
353
|
+
* `builder`: the meta-agent editing a project (e.g. agent-builder Forge chat).
|
|
354
|
+
* `app-build`: sandbox harness that compiled + tested the generated scaffold.
|
|
355
|
+
* `app-runtime`: a run of the generated agent against a domain scenario.
|
|
356
|
+
* `meta`: any meta-eval (judge replay, correlation analysis).
|
|
296
357
|
*/
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
358
|
+
type RunLayer = 'builder' | 'app-build' | 'app-runtime' | 'meta' | 'custom';
|
|
359
|
+
interface Run {
|
|
360
|
+
runId: string;
|
|
361
|
+
/**
|
|
362
|
+
* Stable identifier of the scenario being executed.
|
|
363
|
+
*
|
|
364
|
+
* Always populated on the persisted Run — but `TraceEmitter.startRun` accepts
|
|
365
|
+
* input WITHOUT this field, substituting a sensible default
|
|
366
|
+
* (`run.layer ?? run.tags?.['kind'] ?? 'runtime'`) when the caller has no
|
|
367
|
+
* curated scenario to anchor to (runtime / operator / meta-eval runs). This
|
|
368
|
+
* keeps the persisted shape unambiguous for downstream filters + aggregations
|
|
369
|
+
* while removing the boilerplate of inventing placeholder ids at the call site.
|
|
370
|
+
*/
|
|
371
|
+
scenarioId: string;
|
|
372
|
+
variantId?: string;
|
|
373
|
+
datasetVersion?: string;
|
|
374
|
+
/** Git SHA of agent code at run time. */
|
|
375
|
+
codeSha?: string;
|
|
376
|
+
/** Hash of the prompt template + any system prompt. */
|
|
377
|
+
promptSha?: string;
|
|
378
|
+
/** Model id + date + system-prompt hash, concatenated. */
|
|
379
|
+
modelFingerprint?: string;
|
|
380
|
+
seed?: number;
|
|
381
|
+
/** Arbitrary environment markers (shell, docker version, tz). */
|
|
382
|
+
envFingerprint?: Record<string, string>;
|
|
383
|
+
/** Version of the redaction rules applied to this run. */
|
|
384
|
+
redactionVersion?: string;
|
|
385
|
+
/** Parent run in a nested build workflow. A builder run's children are
|
|
386
|
+
* app-build runs; those children are app-runtime runs. */
|
|
387
|
+
parentRunId?: string;
|
|
388
|
+
/** Stable project identifier — groups runs across chats + sessions. */
|
|
389
|
+
projectId?: string;
|
|
390
|
+
/** Chat/conversation identifier within a project. */
|
|
391
|
+
chatId?: string;
|
|
392
|
+
/** Layer classification — hint for aggregation; not enforced. */
|
|
393
|
+
layer?: RunLayer;
|
|
394
|
+
startedAt: number;
|
|
395
|
+
endedAt?: number;
|
|
396
|
+
status: RunStatus;
|
|
397
|
+
outcome?: RunOutcome$1;
|
|
398
|
+
budget?: BudgetSpec;
|
|
399
|
+
/** Free-form labels for downstream grouping. */
|
|
400
|
+
tags?: Record<string, string>;
|
|
401
|
+
}
|
|
402
|
+
type SpanKind = 'agent' | 'llm' | 'tool' | 'retrieval' | 'judge' | 'sandbox' | 'custom';
|
|
403
|
+
type SpanStatus = 'ok' | 'error';
|
|
404
|
+
interface SpanBase {
|
|
405
|
+
spanId: string;
|
|
406
|
+
parentSpanId?: string;
|
|
407
|
+
runId: string;
|
|
408
|
+
kind: SpanKind;
|
|
301
409
|
name: string;
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
410
|
+
startedAt: number;
|
|
411
|
+
endedAt?: number;
|
|
412
|
+
status?: SpanStatus;
|
|
413
|
+
error?: string;
|
|
414
|
+
/** Anything not covered by typed fields. Kept deliberately free-form. */
|
|
415
|
+
attributes?: Record<string, unknown>;
|
|
416
|
+
}
|
|
417
|
+
interface Message {
|
|
418
|
+
role: 'system' | 'user' | 'assistant' | 'tool';
|
|
419
|
+
content: string;
|
|
420
|
+
tokens?: number;
|
|
421
|
+
/** Multi-modal content descriptors; blobs themselves live in Artifacts. */
|
|
422
|
+
images?: Array<{
|
|
423
|
+
artifactId?: string;
|
|
424
|
+
url?: string;
|
|
425
|
+
mime?: string;
|
|
426
|
+
}>;
|
|
427
|
+
}
|
|
428
|
+
interface LlmSpan extends SpanBase {
|
|
429
|
+
kind: 'llm';
|
|
430
|
+
model: string;
|
|
431
|
+
messages: Message[];
|
|
432
|
+
output?: string;
|
|
433
|
+
inputTokens?: number;
|
|
434
|
+
/** All generated tokens, including the reasoning subset when present. */
|
|
435
|
+
outputTokens?: number;
|
|
436
|
+
cachedTokens?: number;
|
|
437
|
+
cacheWriteTokens?: number;
|
|
438
|
+
/** Reasoning-token subset of `outputTokens`. */
|
|
439
|
+
reasoningTokens?: number;
|
|
440
|
+
costUsd?: number;
|
|
441
|
+
finishReason?: string;
|
|
442
|
+
}
|
|
443
|
+
interface ToolSpan extends SpanBase {
|
|
444
|
+
kind: 'tool';
|
|
445
|
+
toolName: string;
|
|
446
|
+
args: unknown;
|
|
447
|
+
/** False when the source observed the call but did not capture its arguments. */
|
|
448
|
+
argsCaptured?: boolean;
|
|
449
|
+
result?: unknown;
|
|
450
|
+
latencyMs?: number;
|
|
314
451
|
}
|
|
452
|
+
interface RetrievalSpan extends SpanBase {
|
|
453
|
+
kind: 'retrieval';
|
|
454
|
+
query: string;
|
|
455
|
+
hits: Array<{
|
|
456
|
+
docId: string;
|
|
457
|
+
score: number;
|
|
458
|
+
content?: string;
|
|
459
|
+
}>;
|
|
460
|
+
}
|
|
461
|
+
interface JudgeSpan extends SpanBase {
|
|
462
|
+
kind: 'judge';
|
|
463
|
+
judgeId: string;
|
|
464
|
+
/** Span this judgment applies to. */
|
|
465
|
+
targetSpanId: string;
|
|
466
|
+
dimension: string;
|
|
467
|
+
/** Numeric score (free-range; interpretation up to the judge). */
|
|
468
|
+
score: number;
|
|
469
|
+
rationale?: string;
|
|
470
|
+
evidence?: string;
|
|
471
|
+
}
|
|
472
|
+
interface SandboxSpan extends SpanBase {
|
|
473
|
+
kind: 'sandbox';
|
|
474
|
+
image?: string;
|
|
475
|
+
command?: string;
|
|
476
|
+
exitCode?: number;
|
|
477
|
+
testsTotal?: number;
|
|
478
|
+
testsPassed?: number;
|
|
479
|
+
stdoutHash?: string;
|
|
480
|
+
stderrHash?: string;
|
|
481
|
+
/** Duration in ms; the harness fills this explicitly (endedAt - startedAt may miss setup). */
|
|
482
|
+
wallMs?: number;
|
|
483
|
+
}
|
|
484
|
+
interface GenericSpan extends SpanBase {
|
|
485
|
+
kind: 'agent' | 'custom';
|
|
486
|
+
}
|
|
487
|
+
type Span = LlmSpan | ToolSpan | RetrievalSpan | JudgeSpan | SandboxSpan | GenericSpan;
|
|
488
|
+
type EventKind = 'log' | 'error' | 'budget_decrement' | 'budget_breach' | 'state_mutation' | 'policy_violation' | 'redaction_applied' | 'custom';
|
|
489
|
+
interface TraceEvent {
|
|
490
|
+
eventId: string;
|
|
491
|
+
runId: string;
|
|
492
|
+
spanId?: string;
|
|
493
|
+
kind: EventKind;
|
|
494
|
+
timestamp: number;
|
|
495
|
+
payload: Record<string, unknown>;
|
|
496
|
+
}
|
|
497
|
+
interface BudgetLedgerEntry {
|
|
498
|
+
runId: string;
|
|
499
|
+
dimension: keyof BudgetSpec;
|
|
500
|
+
limit: number;
|
|
501
|
+
consumed: number;
|
|
502
|
+
remaining: number;
|
|
503
|
+
timestamp: number;
|
|
504
|
+
breached: boolean;
|
|
505
|
+
/** Span that triggered this entry, if any. */
|
|
506
|
+
spanId?: string;
|
|
507
|
+
}
|
|
508
|
+
interface Artifact {
|
|
509
|
+
artifactId: string;
|
|
510
|
+
runId: string;
|
|
511
|
+
spanId?: string;
|
|
512
|
+
contentType: string;
|
|
513
|
+
sizeBytes: number;
|
|
514
|
+
/** sha256 in hex. */
|
|
515
|
+
hash: string;
|
|
516
|
+
/** External storage URL (R2, S3, filesystem path). */
|
|
517
|
+
storageUrl?: string;
|
|
518
|
+
/** Inline content for small blobs — keep under ~64KB. */
|
|
519
|
+
inlineContent?: string;
|
|
520
|
+
}
|
|
521
|
+
type FailureClass = 'success' | 'reasoning_error' | 'tool_selection_error' | 'tool_argument_error' | 'tool_recovery_failure' | 'hallucination' | 'instruction_following' | 'safety_refusal_miss' | 'policy_violation' | 'budget_exceeded' | 'format_drift' | 'permission_escalation' | 'pii_leak' | 'cost_overrun' | 'timeout' | 'sandbox_failure' | 'missing_user_data' | 'missing_domain_data' | 'missing_codebase_context' | 'missing_runtime_context' | 'missing_credentials' | 'missing_integration_connection' | 'missing_integration_scope' | 'integration_approval_required' | 'integration_auth_expired' | 'integration_provider_failure' | 'bad_integration_manifest' | 'unsafe_integration_write_denied' | 'stale_external_data' | 'bad_retrieval' | 'insufficient_evidence' | 'contradictory_evidence' | 'ambiguous_user_intent' | 'knowledge_readiness_blocked' | 'unknown';
|
|
522
|
+
declare const FAILURE_CLASSES: readonly FailureClass[];
|
|
523
|
+
declare function isLlmSpan(s: Span): s is LlmSpan;
|
|
524
|
+
declare function isToolSpan(s: Span): s is ToolSpan;
|
|
525
|
+
declare function isRetrievalSpan(s: Span): s is RetrievalSpan;
|
|
526
|
+
declare function isJudgeSpan(s: Span): s is JudgeSpan;
|
|
527
|
+
declare function isSandboxSpan(s: Span): s is SandboxSpan;
|
|
528
|
+
|
|
315
529
|
/**
|
|
316
|
-
*
|
|
317
|
-
*
|
|
530
|
+
* Paper-grade RunRecord schema + runtime validator.
|
|
531
|
+
*
|
|
532
|
+
* Every run that participates in a promotion gate, paper table, or
|
|
533
|
+
* researcher loop SHOULD be recorded as a `RunRecord`. The mandatory
|
|
534
|
+
* fields are exactly those the paper "Two Loops, Three Roles" requires
|
|
535
|
+
* for reproducibility: who/what/when/cost/seed/hash, plus the search vs
|
|
536
|
+
* holdout split tag and either a `searchScore` or a `holdoutScore`.
|
|
537
|
+
*
|
|
538
|
+
* This is intentionally NOT a replacement for the rich `Run` /
|
|
539
|
+
* `ProposeReviewReport` / `ScenarioResult` types already in the
|
|
540
|
+
* package. Those are runtime structures with full provenance. A
|
|
541
|
+
* `RunRecord` is the analysis-time projection — the JSON-friendly
|
|
542
|
+
* row you'd put in a parquet file or paste into a notebook.
|
|
543
|
+
*
|
|
544
|
+
* Validate at the boundary:
|
|
545
|
+
*
|
|
546
|
+
* const rec = validateRunRecord(rawJson) // throws on missing
|
|
547
|
+
* const ok = isRunRecord(rawJson) // boolean check
|
|
548
|
+
* const rec = parseRunRecordSafe(rawJson) // { ok, value | error }
|
|
549
|
+
*
|
|
550
|
+
* The validator runs in pure TS — zod is intentionally NOT a
|
|
551
|
+
* dependency. Round-trip tested in `tests/run-record.test.ts`.
|
|
318
552
|
*/
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
553
|
+
|
|
554
|
+
/** Search/dev/holdout split tag. 'search' is the paper-grade alias for the
|
|
555
|
+
* combined train+test pool that the optimizer is allowed to read. */
|
|
556
|
+
type RunSplitTag = 'search' | 'dev' | 'holdout';
|
|
557
|
+
interface RunTokenUsage {
|
|
558
|
+
input: number;
|
|
559
|
+
/** All generated tokens charged as output, including reasoning tokens. */
|
|
560
|
+
output: number;
|
|
561
|
+
/** Reasoning-token subset of `output`, when the provider reports it. */
|
|
562
|
+
reasoning?: number;
|
|
563
|
+
/** Prompt tokens served from a provider cache. */
|
|
564
|
+
cached?: number;
|
|
565
|
+
/** Prompt tokens written into a provider cache. */
|
|
566
|
+
cacheWrite?: number;
|
|
567
|
+
}
|
|
325
568
|
/**
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
569
|
+
* How a run's USD amount was obtained.
|
|
570
|
+
*
|
|
571
|
+
* `costUsd` remains mandatory for wire compatibility. New producers should
|
|
572
|
+
* always populate this discriminated union so a missing bill is never
|
|
573
|
+
* mistaken for an observed zero-dollar run. For `uncaptured`, `costUsd` uses
|
|
574
|
+
* the legacy `0` sentinel while this field carries the truthful null.
|
|
329
575
|
*/
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
576
|
+
type RunCostProvenance = {
|
|
577
|
+
kind: 'observed';
|
|
578
|
+
usd: number;
|
|
579
|
+
} | {
|
|
580
|
+
kind: 'estimated';
|
|
581
|
+
usd: number;
|
|
582
|
+
} | {
|
|
583
|
+
kind: 'uncaptured';
|
|
584
|
+
usd: null;
|
|
585
|
+
};
|
|
586
|
+
interface RunJudgeMetadata {
|
|
587
|
+
model: string;
|
|
588
|
+
promptVersion: string;
|
|
589
|
+
/** [0,1] confidence the judge declared. Constant judge confidence
|
|
590
|
+
* across many runs is a fallback signal (see `canary.ts`). */
|
|
591
|
+
confidence: number;
|
|
592
|
+
/** True if the judge degraded to a fallback path (rules-only,
|
|
593
|
+
* prior-call cache, etc.). The canary uses this to alert. */
|
|
594
|
+
fallback: boolean;
|
|
595
|
+
}
|
|
340
596
|
/**
|
|
341
|
-
*
|
|
342
|
-
*
|
|
343
|
-
* same shape `flattenOtlpExportToNdjson` produces) into validated
|
|
344
|
-
* `RunRecord[]` — one record per `trace_id` (one trace == one task).
|
|
597
|
+
* Per-judge / per-dimension breakdown for runs scored by an ensemble of
|
|
598
|
+
* judges over a multi-dimensional rubric.
|
|
345
599
|
*
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
600
|
+
* The collapsed `outcome.searchScore` / `holdoutScore` carries the
|
|
601
|
+
* composite the gate uses. The full breakdown belongs here so consumers
|
|
602
|
+
* can answer "which judge disagreed?", "which dimension dragged the
|
|
603
|
+
* composite down?", and "did half the panel fail?" without re-running.
|
|
349
604
|
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
352
|
-
*
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
* loud `raw.cost_unpriced = 1` marker so a missing price is visible, not
|
|
356
|
-
* a silent zero folded into a gate.
|
|
357
|
-
* - failureMode: the first `STATUS_CODE_ERROR` span's normalized status
|
|
358
|
-
* message (carries the real failure signature, not a generic class).
|
|
359
|
-
* - model: the dominant LLM model in the trace (snapshot-padded to satisfy
|
|
360
|
-
* `validateRunRecord` when the trace's model is a bare alias).
|
|
361
|
-
* - outcome score: `opts.scoreForTrace` (AppWorld `world.evaluate()` →
|
|
362
|
-
* TGC/SGC) when supplied; else 1 when the trace had no error span, 0
|
|
363
|
-
* when it did — a defensible default the caller can override.
|
|
364
|
-
* - prompt / completion: carried into `raw` as token-count signals and,
|
|
365
|
-
* when the first/last LLM span exposes `input.value` / `output.value`,
|
|
366
|
-
* the verbatim text is preserved on the optional `promptText` /
|
|
367
|
-
* `completionText` of the returned `OtlpTraceRunRecord`.
|
|
605
|
+
* `perJudge[judgeId][dim]` is the canonical source; `perDimMean` and
|
|
606
|
+
* `composite` are convenience projections — derivable but precomputed so
|
|
607
|
+
* downstream IRR primitives (`interRaterReliability`,
|
|
608
|
+
* `corpusInterRaterAgreement`) and reporters don't pay the same
|
|
609
|
+
* aggregation twice.
|
|
368
610
|
*
|
|
369
|
-
* Fail-loud:
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
* rather than silently producing a half-record.
|
|
611
|
+
* Fail-loud discipline: judges that errored out land in `failedJudges`
|
|
612
|
+
* by id. A missing key in `perJudge` is ambiguous (silent zero vs not
|
|
613
|
+
* run); the explicit list makes a partial-failure recorded as such.
|
|
373
614
|
*/
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
615
|
+
interface JudgeScoresRecord {
|
|
616
|
+
/** Per-judge per-dimension scores. `{ "kimi-k2.6": { helpfulness: 0.8, clarity: 0.7 }, ... }`. */
|
|
617
|
+
perJudge: Record<string, Record<string, number>>;
|
|
618
|
+
/** Per-dim mean across judges. Convenience — derivable from `perJudge`. */
|
|
619
|
+
perDimMean: Record<string, number>;
|
|
620
|
+
/** Composite mean across all dims and judges. Mirrors the score
|
|
621
|
+
* the gate sees on `outcome.searchScore` / `holdoutScore`. */
|
|
622
|
+
composite: number;
|
|
623
|
+
/** Judges that errored or returned an unparseable verdict. Recorded
|
|
624
|
+
* by id (e.g. `['glm-5.1']`) so a partial-failure case is explicit,
|
|
625
|
+
* not inferred from missing keys in `perJudge`. */
|
|
626
|
+
failedJudges?: string[];
|
|
627
|
+
/** Free-form notes the judges emitted (joined across judges or
|
|
628
|
+
* first-judge only — consumer's choice). */
|
|
629
|
+
notes?: string;
|
|
630
|
+
}
|
|
631
|
+
interface RunOutcome {
|
|
632
|
+
/** Score on the search/optimization split. Optional because a
|
|
633
|
+
* holdout-only evaluation only fills `holdoutScore`. */
|
|
634
|
+
searchScore?: number;
|
|
635
|
+
/** Score on the held-out split. Optional because a search-only run
|
|
636
|
+
* only fills `searchScore`. At least one must be present. */
|
|
637
|
+
holdoutScore?: number;
|
|
638
|
+
/** Bag of any other metric the run produced — judge dimensions,
|
|
639
|
+
* pass/fail counters, latency stats, etc. Numeric only — keeps
|
|
640
|
+
* reporters honest. */
|
|
641
|
+
raw: Record<string, number>;
|
|
642
|
+
/** Per-judge / per-dim breakdown. Consumers writing ensemble
|
|
643
|
+
* judgements populate this; substrate primitives like
|
|
644
|
+
* `interRaterReliability` and `corpusInterRaterAgreement` accept
|
|
645
|
+
* these records as input. Optional — single-judge or scalar-only
|
|
646
|
+
* runs leave it unset. */
|
|
647
|
+
judgeScores?: JudgeScoresRecord;
|
|
648
|
+
/** Authenticity / realness verdict — did the run build the REAL thing on the
|
|
649
|
+
* intended infra, or fake it (see `./authenticity`)? Optional: only domains
|
|
650
|
+
* with an authenticity config populate it. Carried in the corpus so the
|
|
651
|
+
* flywheel / off-policy learning can optimize for real completion, not gamed
|
|
652
|
+
* pass-rate. `score` is 0-1; `gated` is the anti-Goodhart flag — a gated run
|
|
653
|
+
* must not count as a real success regardless of `score`. */
|
|
654
|
+
realness?: {
|
|
655
|
+
score: number;
|
|
656
|
+
gated: boolean;
|
|
657
|
+
reason?: string;
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Mandatory paper-grade fields for a single evaluation run. Optional
|
|
662
|
+
* fields are extension points; mandatory fields throw if missing.
|
|
663
|
+
*
|
|
664
|
+
* Hash discipline:
|
|
665
|
+
* - `promptHash` is the sha256 of the EFFECTIVE prompt sent to the
|
|
666
|
+
* model (after any steering bundle merge).
|
|
667
|
+
* - `configHash` is the sha256 of the effective run config (model,
|
|
668
|
+
* temperature, tools, judges, splits). The pair (promptHash,
|
|
669
|
+
* configHash) uniquely identifies an experiment cell.
|
|
670
|
+
*
|
|
671
|
+
* Model snapshot discipline:
|
|
672
|
+
* - `model` MUST encode a snapshot version. Bare aliases like
|
|
673
|
+
* `claude-sonnet-4` or `gpt-4o` are banned — they remap silently.
|
|
674
|
+
* Use `claude-sonnet-4-6@2025-04-15` or `gpt-4o-2024-11-20`.
|
|
675
|
+
*/
|
|
676
|
+
interface RunRecord {
|
|
677
|
+
/** UUID for the run. */
|
|
678
|
+
runId: string;
|
|
679
|
+
/** Logical experiment grouping (a treatment vs a baseline within
|
|
680
|
+
* the same sweep should share `experimentId`). */
|
|
377
681
|
experimentId: string;
|
|
378
|
-
/**
|
|
379
|
-
*
|
|
682
|
+
/** Stable identifier for the candidate (variant) being run. The
|
|
683
|
+
* promotion gate compares two `candidateId`s on matched items. */
|
|
380
684
|
candidateId: string;
|
|
381
|
-
/**
|
|
382
|
-
*
|
|
383
|
-
|
|
384
|
-
/**
|
|
385
|
-
commitSha?: string;
|
|
386
|
-
/** sha256 of the effective prompt surface. Default `'unknown'`. */
|
|
387
|
-
promptHash?: string;
|
|
388
|
-
/** sha256 of the effective config. Default `'unknown'`. */
|
|
389
|
-
configHash?: string;
|
|
390
|
-
/** RNG seed recorded on every row. Default 0. */
|
|
391
|
-
seed?: number;
|
|
392
|
-
/**
|
|
393
|
-
* Fallback model snapshot when the trace exposes no LLM model attribute
|
|
394
|
-
* OR exposes a bare alias `validateRunRecord` would reject. The trace's
|
|
395
|
-
* own model wins when it already carries a snapshot. Default
|
|
396
|
-
* `'unknown@otlp'` (opaque-snapshot form the validator accepts).
|
|
397
|
-
*/
|
|
398
|
-
fallbackModel?: string;
|
|
399
|
-
/**
|
|
400
|
-
* USD per total token (input+output) used to price a trace when no
|
|
401
|
-
* per-span cost attribute is present. When unset, an unpriced trace
|
|
402
|
-
* records `costUsd: 0` AND `raw.cost_unpriced = 1` — the zero is flagged,
|
|
403
|
-
* never silent.
|
|
404
|
-
*/
|
|
405
|
-
priceUsdPerToken?: number;
|
|
406
|
-
/**
|
|
407
|
-
* Score for a trace's outcome (AppWorld `world.evaluate()` → TGC/SGC, or
|
|
408
|
-
* any [0,1] task-success signal). Keyed by `trace_id`; falls through to
|
|
409
|
-
* the error-derived default (1 = no error span, 0 = had one) when the map
|
|
410
|
-
* has no entry or the function returns undefined.
|
|
411
|
-
*/
|
|
412
|
-
scoreForTrace?: (traceId: string, span: TraceAggregate) => number | undefined;
|
|
413
|
-
/**
|
|
414
|
-
* Per-record judge metadata when an external judge produced the score.
|
|
415
|
-
* Keyed by `trace_id`.
|
|
416
|
-
*/
|
|
417
|
-
judgeMetadataForTrace?: (traceId: string) => RunRecord['judgeMetadata'] | undefined;
|
|
418
|
-
}
|
|
419
|
-
/** A `RunRecord` plus the verbatim prompt/completion text when the trace's
|
|
420
|
-
* LLM spans exposed it. The text is NOT on the validated `RunRecord`
|
|
421
|
-
* (`outcome.raw` is numeric-only) but consumers ingesting full traces want
|
|
422
|
-
* it — so it rides alongside. */
|
|
423
|
-
interface OtlpTraceRunRecord {
|
|
424
|
-
record: RunRecord;
|
|
425
|
-
/** Verbatim first-LLM-span `input.value`, when present. */
|
|
426
|
-
promptText?: string;
|
|
427
|
-
/** Verbatim last-LLM-span `output.value`, when present. */
|
|
428
|
-
completionText?: string;
|
|
429
|
-
}
|
|
430
|
-
/** Per-trace rollup the score callback can inspect. */
|
|
431
|
-
interface TraceAggregate {
|
|
432
|
-
traceId: string;
|
|
433
|
-
spanCount: number;
|
|
434
|
-
llmSpanCount: number;
|
|
435
|
-
toolSpanCount: number;
|
|
436
|
-
agentSpanCount: number;
|
|
437
|
-
errorSpanCount: number;
|
|
438
|
-
tokenUsage: RunTokenUsage;
|
|
439
|
-
/** First error span's normalized status message, if any. */
|
|
440
|
-
firstErrorMessage?: string;
|
|
685
|
+
/** RNG seed for the run. Always recorded — silent re-seeding is
|
|
686
|
+
* the most common cause of non-reproducible numbers. */
|
|
687
|
+
seed: number;
|
|
688
|
+
/** Model identifier WITH snapshot version. */
|
|
441
689
|
model: string;
|
|
442
|
-
|
|
443
|
-
|
|
690
|
+
/** sha256 of the effective prompt (post-steering). */
|
|
691
|
+
promptHash: string;
|
|
692
|
+
/** sha256 of the effective config. */
|
|
693
|
+
configHash: string;
|
|
694
|
+
/** Git SHA the harness was run from. */
|
|
695
|
+
commitSha: string;
|
|
696
|
+
/** End-to-end wall-clock duration in milliseconds. */
|
|
444
697
|
wallMs: number;
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
*/
|
|
451
|
-
|
|
452
|
-
/**
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
/**
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
/**
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
* without re-parsing the whole file)
|
|
475
|
-
* - a `TraceAnalystTraceSummary` rollup
|
|
476
|
-
* - sets of services / agents / models / tools / has_errors
|
|
477
|
-
* - byte size of the trace's JSONL slab
|
|
478
|
-
*
|
|
479
|
-
* Memory bound. The index keeps span metadata only — names, kinds,
|
|
480
|
-
* offsets, status. Attribute payloads stay on disk until requested.
|
|
481
|
-
* For a 50MB JSONL with 50k spans, the index is ~5MB.
|
|
482
|
-
*
|
|
483
|
-
* Concurrency. The store builds the index once on first read and
|
|
484
|
-
* caches it. Subsequent reads reuse the index. The file is opened on
|
|
485
|
-
* each read; we never hold a long-lived FD.
|
|
486
|
-
*/
|
|
487
|
-
|
|
488
|
-
interface OtlpFileTraceStoreOptions {
|
|
489
|
-
/** Path to the OTLP-JSONL file. */
|
|
490
|
-
path: string;
|
|
491
|
-
/** Override the discovery (`viewTrace`) per-attribute byte cap. */
|
|
492
|
-
perAttributeViewBudget?: number;
|
|
493
|
-
/** Override the surgical (`viewSpans`) per-attribute byte cap. */
|
|
494
|
-
perAttributeSpanBudget?: number;
|
|
495
|
-
/** Override the per-call ceiling that triggers oversized summaries. */
|
|
496
|
-
perCallByteCeiling?: number;
|
|
497
|
-
/** Override the per-match text budget. */
|
|
498
|
-
perMatchTextBudget?: number;
|
|
698
|
+
/** Time spent queued before execution started, if known. */
|
|
699
|
+
queueMs?: number;
|
|
700
|
+
/** Total USD cost. Mandatory — runs without a cost number are
|
|
701
|
+
* unbounded by definition and must not be admitted into the gate.
|
|
702
|
+
* `0` is retained as the compatibility sentinel for an uncaptured amount;
|
|
703
|
+
* inspect `costProvenance` before treating it as observed. */
|
|
704
|
+
costUsd: number;
|
|
705
|
+
/** Observed, model-priced estimate, or genuinely uncaptured USD amount.
|
|
706
|
+
* Optional only so existing serialized RunRecords remain valid. */
|
|
707
|
+
costProvenance?: RunCostProvenance;
|
|
708
|
+
/** Token usage breakdown. */
|
|
709
|
+
tokenUsage: RunTokenUsage;
|
|
710
|
+
/** Judge-side metadata, if a judge was used. */
|
|
711
|
+
judgeMetadata?: RunJudgeMetadata;
|
|
712
|
+
/** Per-split scores + raw bag. */
|
|
713
|
+
outcome: RunOutcome;
|
|
714
|
+
/** Canonical, cross-agent failure class drawn from the shared
|
|
715
|
+
* `FAILURE_CLASSES` taxonomy. This is the aggregation key that makes
|
|
716
|
+
* "which failure dominates across the whole fleet" answerable in ONE
|
|
717
|
+
* vocabulary — every agent classifies against the same enum. Producers
|
|
718
|
+
* set it via the substrate classifier; leave unset only when the failure
|
|
719
|
+
* genuinely can't be classified. */
|
|
720
|
+
failureClass?: FailureClass;
|
|
721
|
+
/** Free-form domain-specific failure detail, scoped UNDER `failureClass`
|
|
722
|
+
* (e.g. failureClass='tool_recovery_failure', failureMode='forge_build_unsatisfied').
|
|
723
|
+
* The within-agent drill-down; `failureClass` is the cross-agent key. */
|
|
724
|
+
failureMode?: string;
|
|
725
|
+
/** Which split this run was drawn from. */
|
|
726
|
+
splitTag: RunSplitTag;
|
|
499
727
|
/**
|
|
500
|
-
*
|
|
501
|
-
*
|
|
502
|
-
*
|
|
503
|
-
*
|
|
504
|
-
*
|
|
728
|
+
* Stable scenario identifier the run was scored against. Optional for
|
|
729
|
+
* backwards compatibility, but **strongly recommended**: every primitive
|
|
730
|
+
* that pairs runs by scenario (preferences, paired stats, BT tournament)
|
|
731
|
+
* keys on this. The campaign artifact populates it canonically; legacy
|
|
732
|
+
* runs without it fall back to inference from `outcome.raw.scenario_id`
|
|
733
|
+
* or `experimentId`.
|
|
505
734
|
*/
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
private indexPromise?;
|
|
516
|
-
/** Cached UTF-8 buffer of the file. We pin it once because every
|
|
517
|
-
* read needs slice access and re-reading on each call balloons the
|
|
518
|
-
* syscall count. */
|
|
519
|
-
private bufferPromise?;
|
|
520
|
-
constructor(opts: OtlpFileTraceStoreOptions);
|
|
521
|
-
getOverview(filters?: TraceAnalystFilters): Promise<DatasetOverview>;
|
|
522
|
-
queryTraces(opts: {
|
|
523
|
-
filters?: TraceAnalystFilters;
|
|
524
|
-
limit: number;
|
|
525
|
-
offset?: number;
|
|
526
|
-
}): Promise<QueryTracesPage>;
|
|
527
|
-
countTraces(filters?: TraceAnalystFilters): Promise<number>;
|
|
528
|
-
viewTrace(opts: {
|
|
529
|
-
trace_id: string;
|
|
530
|
-
per_attribute_byte_cap?: number;
|
|
531
|
-
}): Promise<ViewTraceResult>;
|
|
532
|
-
viewSpans(opts: {
|
|
533
|
-
trace_id: string;
|
|
534
|
-
span_ids: readonly string[];
|
|
535
|
-
per_attribute_byte_cap?: number;
|
|
536
|
-
}): Promise<ViewSpansResult>;
|
|
537
|
-
searchTrace(opts: {
|
|
538
|
-
trace_id: string;
|
|
539
|
-
regex_pattern: string;
|
|
540
|
-
max_matches?: number;
|
|
541
|
-
}): Promise<SearchTraceResult>;
|
|
542
|
-
searchSpan(opts: {
|
|
543
|
-
trace_id: string;
|
|
544
|
-
span_id: string;
|
|
545
|
-
regex_pattern: string;
|
|
546
|
-
max_matches?: number;
|
|
547
|
-
}): Promise<SearchSpanResult>;
|
|
548
|
-
/** Force the index to materialise. Useful to amortise startup cost
|
|
549
|
-
* before the first agent call. */
|
|
550
|
-
ensureIndexed(): Promise<void>;
|
|
551
|
-
private buffer;
|
|
552
|
-
/** Stat-then-read so an oversized file fails loud BEFORE we allocate a
|
|
553
|
-
* multi-hundred-MB Buffer and OOM the process. A missing file surfaces
|
|
554
|
-
* as TraceFileMissingError; any other stat/read error propagates. */
|
|
555
|
-
private readGuarded;
|
|
556
|
-
private index;
|
|
557
|
-
private buildIndex;
|
|
558
|
-
private matchedTraces;
|
|
559
|
-
private toSummary;
|
|
560
|
-
private projectSpan;
|
|
561
|
-
private buildOversizedSummary;
|
|
562
|
-
private scanSpanForMatches;
|
|
563
|
-
}
|
|
564
|
-
declare class TraceFileMissingError extends NotFoundError {
|
|
565
|
-
constructor(path: string);
|
|
566
|
-
}
|
|
567
|
-
declare class TraceNotFoundError extends NotFoundError {
|
|
568
|
-
readonly trace_id: string;
|
|
569
|
-
constructor(trace_id: string);
|
|
570
|
-
}
|
|
571
|
-
declare class SpanNotFoundError extends NotFoundError {
|
|
572
|
-
readonly trace_id: string;
|
|
573
|
-
readonly span_id: string;
|
|
574
|
-
constructor(trace_id: string, span_id: string);
|
|
735
|
+
scenarioId?: string;
|
|
736
|
+
/**
|
|
737
|
+
* Canonical identity for the agent profile cell that produced this row:
|
|
738
|
+
* profile artifact hash plus optional harness/model/prompt/reporting
|
|
739
|
+
* dimensions. Use `agentProfile.cellId` to group persona sweeps and
|
|
740
|
+
* longitudinal reports by the complete source profile, not by a loose
|
|
741
|
+
* candidate label or opaque config hash.
|
|
742
|
+
*/
|
|
743
|
+
agentProfile?: AgentProfileCell;
|
|
575
744
|
}
|
|
576
745
|
|
|
577
746
|
/**
|
|
578
|
-
*
|
|
579
|
-
* agent calls from generated JS code via `traces.<name>(...)`.
|
|
580
|
-
*
|
|
581
|
-
* Discovery → narrow → deep-read protocol. Tool names + ordering
|
|
582
|
-
* support RLM discovery:
|
|
583
|
-
*
|
|
584
|
-
* 1. `getDatasetOverview` (cheap) — first call, sizes the dataset
|
|
585
|
-
* 2. `queryTraces` — paginated summaries with `raw_jsonl_bytes`
|
|
586
|
-
* 3. `countTraces` — cheap pre-flight before regex
|
|
587
|
-
* 4. `viewTrace` — full span list, oversized → summary
|
|
588
|
-
* 5. `viewSpans` — surgical 16KB-cap reads
|
|
589
|
-
* 6. `searchTrace` / `searchSpan` — bounded regex hits
|
|
747
|
+
* Provider response and SSE usage extraction.
|
|
590
748
|
*
|
|
591
|
-
*
|
|
592
|
-
*
|
|
593
|
-
* into actor-visible `[ERROR]` strings so the analyst can adjust on
|
|
594
|
-
* the next turn instead of looping.
|
|
595
|
-
*/
|
|
596
|
-
|
|
597
|
-
interface BuildTraceAnalystToolsOpts {
|
|
598
|
-
store: TraceAnalysisStore;
|
|
599
|
-
/** Override the default sample-trace-id slot count (20). Mostly for tests. */
|
|
600
|
-
sampleTraceLimit?: number;
|
|
601
|
-
}
|
|
602
|
-
/**
|
|
603
|
-
* Build the trace-analyst function set. Pass the result into
|
|
604
|
-
* `agent(...).functions.local`.
|
|
749
|
+
* Missing usage returns `null`; reported zeroes and cache-only activity remain
|
|
750
|
+
* distinguishable from absent telemetry.
|
|
605
751
|
*/
|
|
606
|
-
declare function buildTraceAnalystTools(opts: BuildTraceAnalystToolsOpts): AxFunction[];
|
|
607
|
-
/**
|
|
608
|
-
* Convenience: same shape as `buildTraceAnalystTools` but returns the
|
|
609
|
-
* grouped form expected when registering trace tools alongside other
|
|
610
|
-
* agent function modules. */
|
|
611
|
-
declare function traceAnalystFunctionGroup(opts: BuildTraceAnalystToolsOpts): {
|
|
612
|
-
namespace: string;
|
|
613
|
-
title: string;
|
|
614
|
-
selectionCriteria: string;
|
|
615
|
-
description: string;
|
|
616
|
-
functions: AxFunction[];
|
|
617
|
-
};
|
|
618
752
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
* re-implement the same `usage` parser on top of the captured response (the
|
|
625
|
-
* OpenAI `prompt_tokens`/`completion_tokens` shape, the Anthropic
|
|
626
|
-
* `input_tokens`/`output_tokens` shape, and the camelCase variants), plus an
|
|
627
|
-
* SSE accumulator that sums the per-chunk `usage` deltas. This is the one
|
|
628
|
-
* canonical version.
|
|
629
|
-
*
|
|
630
|
-
* Both functions return `null` (not a silent `{ input: 0, output: 0 }`) when no
|
|
631
|
-
* usage is present, so a caller can tell "no usage reported" from "zero tokens".
|
|
632
|
-
*/
|
|
633
|
-
interface ExtractedUsage {
|
|
634
|
-
input: number;
|
|
635
|
-
output: number;
|
|
636
|
-
/** Cached prompt tokens, when the provider reports them. */
|
|
637
|
-
cached?: number;
|
|
753
|
+
type ExtractedUsage = RunTokenUsage;
|
|
754
|
+
type SseUsageMode = 'cumulative' | 'delta';
|
|
755
|
+
interface ExtractUsageFromSseOptions {
|
|
756
|
+
/** Provider usage events are cumulative snapshots unless explicitly marked as deltas. */
|
|
757
|
+
mode?: SseUsageMode;
|
|
638
758
|
}
|
|
639
759
|
/**
|
|
640
|
-
* Pull `{ input, output, cached? }` from a parsed
|
|
760
|
+
* Pull `{ input, output, cached?, cacheWrite? }` from a parsed response
|
|
641
761
|
* body. Accepts a top-level `usage` object (the common case) or a body that IS
|
|
642
|
-
* the usage object. Returns null when
|
|
643
|
-
* present — callers must inspect for null rather than treat it as zero cost.
|
|
762
|
+
* the usage object. Returns null only when none of those categories is present.
|
|
644
763
|
*/
|
|
645
764
|
declare function extractUsage(body: unknown): ExtractedUsage | null;
|
|
646
765
|
/**
|
|
647
|
-
*
|
|
648
|
-
*
|
|
649
|
-
*
|
|
650
|
-
* usage — distinguishing a usage-less stream from a genuine zero.
|
|
651
|
-
*
|
|
652
|
-
* Providers report SSE usage in two ways: a single terminal chunk with the full
|
|
653
|
-
* totals, or incremental per-chunk deltas. Summing is correct for the delta
|
|
654
|
-
* case and harmless for the terminal case (one non-null chunk ⇒ the total).
|
|
766
|
+
* Extract token usage from a complete SSE response body using the shared SSE
|
|
767
|
+
* frame parser. Cumulative snapshots are the fail-safe default because summing
|
|
768
|
+
* them inflates billing; callers with explicit delta events opt into `mode: 'delta'`.
|
|
655
769
|
*/
|
|
656
|
-
declare function extractUsageFromSse(text: string): ExtractedUsage | null;
|
|
770
|
+
declare function extractUsageFromSse(text: string, options?: ExtractUsageFromSseOptions): ExtractedUsage | null;
|
|
657
771
|
/**
|
|
658
772
|
* Extract usage from an HTTP `Response` without consuming the caller's body:
|
|
659
773
|
* clones, reads the text, and tries the JSON parser first, then the SSE
|
|
660
774
|
* accumulator. Best-effort — returns null on any read/parse miss so a usage tee
|
|
661
775
|
* never takes down the underlying call.
|
|
662
776
|
*/
|
|
663
|
-
declare function extractUsageFromResponse(response: Response): Promise<ExtractedUsage | null>;
|
|
777
|
+
declare function extractUsageFromResponse(response: Response, sseOptions?: ExtractUsageFromSseOptions): Promise<ExtractedUsage | null>;
|
|
664
778
|
|
|
665
779
|
/**
|
|
666
|
-
*
|
|
667
|
-
*
|
|
668
|
-
*
|
|
669
|
-
*
|
|
670
|
-
*
|
|
671
|
-
* The returned value is a plain `typeof fetch` — pass it as the `fetchImpl` to
|
|
672
|
-
* any OpenAI-compatible backend factory. Capture is best-effort by default: a
|
|
673
|
-
* sink write that throws does NOT take down the underlying LLM call (set
|
|
674
|
-
* `failClosed` to change that). Uses the existing `defaultProviderRedactor` +
|
|
675
|
-
* `providerFromBaseUrl` — no new redaction policy.
|
|
780
|
+
* Wrap a provider `fetch` and record request, response, and error events.
|
|
781
|
+
*
|
|
782
|
+
* The returned value is a plain `typeof fetch`. Capture is best-effort by
|
|
783
|
+
* default; set `failClosed` when telemetry loss must stop the provider call.
|
|
676
784
|
*/
|
|
677
785
|
|
|
678
786
|
interface CaptureFetchContext {
|
|
@@ -706,19 +814,419 @@ interface CaptureFetchOptions {
|
|
|
706
814
|
* unless `failClosed` is set.
|
|
707
815
|
*/
|
|
708
816
|
onUsage?: (usage: ExtractedUsage, ctx: CaptureFetchContext) => void;
|
|
817
|
+
/** How repeated SSE usage events should be reconciled. Default `cumulative`. */
|
|
818
|
+
sseUsageMode?: SseUsageMode;
|
|
709
819
|
}
|
|
710
820
|
declare function captureFetchToRawSink(fetch: typeof globalThis.fetch, sink: RawProviderSink, ctx: CaptureFetchContext, opts?: CaptureFetchOptions): typeof globalThis.fetch;
|
|
711
821
|
|
|
822
|
+
interface RunFilter {
|
|
823
|
+
scenarioId?: string;
|
|
824
|
+
variantId?: string;
|
|
825
|
+
status?: RunStatus;
|
|
826
|
+
since?: number;
|
|
827
|
+
until?: number;
|
|
828
|
+
tag?: {
|
|
829
|
+
key: string;
|
|
830
|
+
value: string;
|
|
831
|
+
};
|
|
832
|
+
parentRunId?: string;
|
|
833
|
+
projectId?: string;
|
|
834
|
+
chatId?: string;
|
|
835
|
+
layer?: RunLayer;
|
|
836
|
+
}
|
|
837
|
+
interface SpanFilter {
|
|
838
|
+
runId?: string;
|
|
839
|
+
parentSpanId?: string;
|
|
840
|
+
kind?: SpanKind;
|
|
841
|
+
name?: string;
|
|
842
|
+
toolName?: string;
|
|
843
|
+
judgeId?: string;
|
|
844
|
+
since?: number;
|
|
845
|
+
until?: number;
|
|
846
|
+
}
|
|
847
|
+
interface EventFilter {
|
|
848
|
+
runId?: string;
|
|
849
|
+
spanId?: string;
|
|
850
|
+
kind?: EventKind;
|
|
851
|
+
since?: number;
|
|
852
|
+
until?: number;
|
|
853
|
+
}
|
|
854
|
+
interface TraceStore {
|
|
855
|
+
appendRun(run: Run): Promise<void>;
|
|
856
|
+
updateRun(runId: string, patch: Partial<Run>): Promise<void>;
|
|
857
|
+
appendSpan(span: Span): Promise<void>;
|
|
858
|
+
updateSpan(spanId: string, patch: Partial<Span>): Promise<void>;
|
|
859
|
+
appendEvent(event: TraceEvent): Promise<void>;
|
|
860
|
+
appendArtifact(artifact: Artifact): Promise<void>;
|
|
861
|
+
appendBudgetEntry(entry: BudgetLedgerEntry): Promise<void>;
|
|
862
|
+
getRun(runId: string): Promise<Run | undefined>;
|
|
863
|
+
listRuns(filter?: RunFilter): Promise<Run[]>;
|
|
864
|
+
spans(filter?: SpanFilter): Promise<Span[]>;
|
|
865
|
+
events(filter?: EventFilter): Promise<TraceEvent[]>;
|
|
866
|
+
budget(runId: string): Promise<BudgetLedgerEntry[]>;
|
|
867
|
+
artifacts(runId: string): Promise<Artifact[]>;
|
|
868
|
+
}
|
|
869
|
+
declare class InMemoryTraceStore implements TraceStore {
|
|
870
|
+
private runs;
|
|
871
|
+
private allSpans;
|
|
872
|
+
private allEvents;
|
|
873
|
+
private allArtifacts;
|
|
874
|
+
private allBudget;
|
|
875
|
+
appendRun(run: Run): Promise<void>;
|
|
876
|
+
updateRun(runId: string, patch: Partial<Run>): Promise<void>;
|
|
877
|
+
appendSpan(span: Span): Promise<void>;
|
|
878
|
+
updateSpan(spanId: string, patch: Partial<Span>): Promise<void>;
|
|
879
|
+
appendEvent(event: TraceEvent): Promise<void>;
|
|
880
|
+
appendArtifact(artifact: Artifact): Promise<void>;
|
|
881
|
+
appendBudgetEntry(entry: BudgetLedgerEntry): Promise<void>;
|
|
882
|
+
getRun(runId: string): Promise<Run | undefined>;
|
|
883
|
+
listRuns(filter?: RunFilter): Promise<Run[]>;
|
|
884
|
+
spans(filter?: SpanFilter): Promise<Span[]>;
|
|
885
|
+
events(filter?: EventFilter): Promise<TraceEvent[]>;
|
|
886
|
+
budget(runId: string): Promise<BudgetLedgerEntry[]>;
|
|
887
|
+
artifacts(runId: string): Promise<Artifact[]>;
|
|
888
|
+
}
|
|
889
|
+
interface FileSystemTraceStoreOptions {
|
|
890
|
+
dir: string;
|
|
891
|
+
/** Roll over NDJSON files when they exceed this size in bytes. Default 32 MB. */
|
|
892
|
+
maxBytes?: number;
|
|
893
|
+
}
|
|
894
|
+
declare class FileSystemTraceStore implements TraceStore {
|
|
895
|
+
private dir;
|
|
896
|
+
private maxBytes;
|
|
897
|
+
/** Lazy in-memory index for queries — populated on first read. */
|
|
898
|
+
private index?;
|
|
899
|
+
/** Memoized index build — concurrent first reads share one build, and an
|
|
900
|
+
* append racing an in-flight load awaits this so its row isn't lost. */
|
|
901
|
+
private indexPromise?;
|
|
902
|
+
/** Strictly-increasing rollover stamp. Date.now() alone collides when two
|
|
903
|
+
* rollovers land in the same millisecond, overwriting a rolled file. */
|
|
904
|
+
private lastRolloverStamp;
|
|
905
|
+
/**
|
|
906
|
+
* Per-file append serialization. stat → conditional-rename → appendFile is a
|
|
907
|
+
* read-modify-write on the active file; without a lock two concurrent appends
|
|
908
|
+
* to the same `name` can both pass the size check (exceeding maxBytes) or one
|
|
909
|
+
* can append to a file the other just renamed away. Each entry chains the
|
|
910
|
+
* next append behind the prior one for that file.
|
|
911
|
+
*/
|
|
912
|
+
private appendLocks;
|
|
913
|
+
constructor(options: FileSystemTraceStoreOptions);
|
|
914
|
+
private ensureDir;
|
|
915
|
+
private append;
|
|
916
|
+
private appendLocked;
|
|
917
|
+
private insertInto;
|
|
918
|
+
private load;
|
|
919
|
+
private buildIndex;
|
|
920
|
+
appendRun(run: Run): Promise<void>;
|
|
921
|
+
updateRun(runId: string, patch: Partial<Run>): Promise<void>;
|
|
922
|
+
appendSpan(span: Span): Promise<void>;
|
|
923
|
+
updateSpan(spanId: string, patch: Partial<Span>): Promise<void>;
|
|
924
|
+
appendEvent(event: TraceEvent): Promise<void>;
|
|
925
|
+
appendArtifact(artifact: Artifact): Promise<void>;
|
|
926
|
+
appendBudgetEntry(entry: BudgetLedgerEntry): Promise<void>;
|
|
927
|
+
getRun(runId: string): Promise<Run | undefined>;
|
|
928
|
+
listRuns(filter?: RunFilter): Promise<Run[]>;
|
|
929
|
+
spans(filter?: SpanFilter): Promise<Span[]>;
|
|
930
|
+
events(filter?: EventFilter): Promise<TraceEvent[]>;
|
|
931
|
+
budget(runId: string): Promise<BudgetLedgerEntry[]>;
|
|
932
|
+
artifacts(runId: string): Promise<Artifact[]>;
|
|
933
|
+
}
|
|
934
|
+
|
|
712
935
|
/**
|
|
713
|
-
*
|
|
936
|
+
* TraceEmitter — hierarchical span builder that auto-parents using an
|
|
937
|
+
* internal stack. One emitter per Run; emitters do NOT share state.
|
|
714
938
|
*
|
|
715
|
-
*
|
|
716
|
-
*
|
|
717
|
-
*
|
|
718
|
-
*
|
|
939
|
+
* Convenience methods (`llm`, `tool`, `retrieval`, `judge`, `sandbox`)
|
|
940
|
+
* return a `SpanHandle` with `.end()` / `.fail()` so callers don't
|
|
941
|
+
* have to thread spanIds manually. For async workflows that can't use
|
|
942
|
+
* the stack (e.g. fan-out parallel calls), pass `parentSpanId`
|
|
943
|
+
* explicitly.
|
|
719
944
|
*/
|
|
720
945
|
|
|
721
|
-
interface
|
|
946
|
+
interface SpanHandle<S extends Span = Span> {
|
|
947
|
+
span: S;
|
|
948
|
+
end(patch?: Partial<S>): Promise<void>;
|
|
949
|
+
fail(error: string | Error, patch?: Partial<S>): Promise<void>;
|
|
950
|
+
}
|
|
951
|
+
interface RunCompleteHookContext {
|
|
952
|
+
runId: string;
|
|
953
|
+
emitter: TraceEmitter;
|
|
954
|
+
store: TraceStore;
|
|
955
|
+
/** Outcome the caller passed to `endRun` (undefined for `abortRun`). */
|
|
956
|
+
outcome?: RunOutcome$1;
|
|
957
|
+
/** Final run status. */
|
|
958
|
+
status: 'completed' | 'failed' | 'aborted';
|
|
959
|
+
}
|
|
960
|
+
type RunCompleteHook = (ctx: RunCompleteHookContext) => Promise<void> | void;
|
|
961
|
+
interface TraceEmitterOptions {
|
|
962
|
+
runId?: string;
|
|
963
|
+
/** Inject a clock for deterministic tests. */
|
|
964
|
+
now?: () => number;
|
|
965
|
+
/** Inject an id generator for deterministic tests. */
|
|
966
|
+
id?: () => string;
|
|
967
|
+
/**
|
|
968
|
+
* Hooks fired after `endRun` / `abortRun` writes the final run state.
|
|
969
|
+
* Designed for trace-analyst auto-execution, integrity assertions, and
|
|
970
|
+
* outbound notifications. Hooks run sequentially in the order supplied.
|
|
971
|
+
*
|
|
972
|
+
* By default a hook that throws is swallowed and logged as a `note` event
|
|
973
|
+
* on the run — auto-orchestration must not crash the underlying flow.
|
|
974
|
+
* Set `hookErrors: 'throw'` to propagate.
|
|
975
|
+
*/
|
|
976
|
+
onRunComplete?: RunCompleteHook[];
|
|
977
|
+
/** `'swallow'` (default) | `'throw'`. */
|
|
978
|
+
hookErrors?: 'swallow' | 'throw';
|
|
979
|
+
}
|
|
980
|
+
declare class TraceEmitter {
|
|
981
|
+
private store;
|
|
982
|
+
private stack;
|
|
983
|
+
private _runId;
|
|
984
|
+
private now;
|
|
985
|
+
private id;
|
|
986
|
+
private hooks;
|
|
987
|
+
private hookErrors;
|
|
988
|
+
constructor(store: TraceStore, options?: TraceEmitterOptions);
|
|
989
|
+
get runId(): string;
|
|
990
|
+
get traceStore(): TraceStore;
|
|
991
|
+
/** Append a hook after construction (e.g. attach the trace analyst). */
|
|
992
|
+
addRunCompleteHook(hook: RunCompleteHook): void;
|
|
993
|
+
/**
|
|
994
|
+
* Begin a Run.
|
|
995
|
+
*
|
|
996
|
+
* `scenarioId` is required on the persisted Run shape — every Run downstream
|
|
997
|
+
* gets a non-empty scenarioId so filters and aggregations stay simple — but
|
|
998
|
+
* the INPUT here accepts it as optional. When omitted, startRun substitutes
|
|
999
|
+
* a sensible default (`run.layer ?? run.tags?.['kind'] ?? 'runtime'`) so
|
|
1000
|
+
* runtime / operator / meta-eval runs that have no curated-scenario corpus
|
|
1001
|
+
* to anchor to don't have to invent placeholder strings at the call site.
|
|
1002
|
+
*/
|
|
1003
|
+
startRun(run: Omit<Run, 'runId' | 'scenarioId' | 'startedAt' | 'status'> & {
|
|
1004
|
+
scenarioId?: string;
|
|
1005
|
+
}): Promise<Run>;
|
|
1006
|
+
endRun(outcome?: RunOutcome$1): Promise<void>;
|
|
1007
|
+
abortRun(reason: string): Promise<void>;
|
|
1008
|
+
private runHooks;
|
|
1009
|
+
span<S extends Span = Span>(init: {
|
|
1010
|
+
kind: SpanKind;
|
|
1011
|
+
name: string;
|
|
1012
|
+
parentSpanId?: string;
|
|
1013
|
+
attributes?: Record<string, unknown>;
|
|
1014
|
+
} & Partial<Omit<S, 'spanId' | 'runId' | 'startedAt' | 'kind' | 'name'>>): Promise<SpanHandle<S>>;
|
|
1015
|
+
private handle;
|
|
1016
|
+
private pop;
|
|
1017
|
+
llm(init: Omit<LlmSpan, 'spanId' | 'runId' | 'kind' | 'startedAt'>): Promise<SpanHandle<LlmSpan>>;
|
|
1018
|
+
tool(init: Omit<ToolSpan, 'spanId' | 'runId' | 'kind' | 'startedAt'>): Promise<SpanHandle<ToolSpan>>;
|
|
1019
|
+
retrieval(init: Omit<RetrievalSpan, 'spanId' | 'runId' | 'kind' | 'startedAt'>): Promise<SpanHandle<RetrievalSpan>>;
|
|
1020
|
+
recordJudge(verdict: Omit<JudgeSpan, 'spanId' | 'runId' | 'kind' | 'startedAt' | 'endedAt'>): Promise<JudgeSpan>;
|
|
1021
|
+
sandbox(init: Omit<SandboxSpan, 'spanId' | 'runId' | 'kind' | 'startedAt'>): Promise<SpanHandle<SandboxSpan>>;
|
|
1022
|
+
emit(event: {
|
|
1023
|
+
kind: EventKind;
|
|
1024
|
+
spanId?: string;
|
|
1025
|
+
payload?: Record<string, unknown>;
|
|
1026
|
+
}): Promise<TraceEvent>;
|
|
1027
|
+
recordBudget(entry: Omit<BudgetLedgerEntry, 'runId' | 'timestamp'> & {
|
|
1028
|
+
timestamp?: number;
|
|
1029
|
+
}): Promise<BudgetLedgerEntry>;
|
|
1030
|
+
recordArtifact(artifact: Omit<Artifact, 'artifactId' | 'runId'>): Promise<Artifact>;
|
|
1031
|
+
/**
|
|
1032
|
+
* Runs `fn` inside a span; auto-ends on success, auto-fails on throw.
|
|
1033
|
+
* Returns the fn's return value. Use this for the 95% case.
|
|
1034
|
+
*/
|
|
1035
|
+
within<T>(init: Parameters<TraceEmitter['span']>[0], fn: (handle: SpanHandle) => Promise<T>): Promise<T>;
|
|
1036
|
+
}
|
|
1037
|
+
/** Helper to build an LLM span handle args object from a provider-shaped response. */
|
|
1038
|
+
declare function llmSpanFromProvider(args: {
|
|
1039
|
+
name?: string;
|
|
1040
|
+
model: string;
|
|
1041
|
+
messages: Message[];
|
|
1042
|
+
output: string;
|
|
1043
|
+
usage?: {
|
|
1044
|
+
inputTokens?: number;
|
|
1045
|
+
outputTokens?: number;
|
|
1046
|
+
cachedTokens?: number;
|
|
1047
|
+
cacheWriteTokens?: number;
|
|
1048
|
+
reasoningTokens?: number;
|
|
1049
|
+
};
|
|
1050
|
+
costUsd?: number;
|
|
1051
|
+
finishReason?: string;
|
|
1052
|
+
}): Omit<LlmSpan, 'spanId' | 'runId' | 'kind' | 'startedAt'>;
|
|
1053
|
+
|
|
1054
|
+
/**
|
|
1055
|
+
* Run-completion integrity check — at end of run, verify the expected event
|
|
1056
|
+
* types were actually captured. The point is the launch-review failure mode:
|
|
1057
|
+
* a run *appears* successful but the raw provider events were never written,
|
|
1058
|
+
* so a downstream reviewer can't reconstruct what happened.
|
|
1059
|
+
*
|
|
1060
|
+
* Pattern:
|
|
1061
|
+
*
|
|
1062
|
+
* const report = await assertRunCaptured(store, runId, {
|
|
1063
|
+
* llmSpansMin: 1,
|
|
1064
|
+
* judgeSpansMin: 1,
|
|
1065
|
+
* rawSink: providerSink, // must have ≥ 1 event for this run
|
|
1066
|
+
* requireRawCoverageOfLlmSpans: true, // every llm span has matching raw events
|
|
1067
|
+
* })
|
|
1068
|
+
* if (!report.ok) throwIfRunIncomplete(report) // or mark run failed and continue
|
|
1069
|
+
*
|
|
1070
|
+
* The function is read-only on the store and returns a structured report;
|
|
1071
|
+
* the caller chooses the failure mode (throw, mark run failed, log warning).
|
|
1072
|
+
* `throwIfRunIncomplete` is the convenient strict mode.
|
|
1073
|
+
*/
|
|
1074
|
+
|
|
1075
|
+
interface RunIntegrityExpectations {
|
|
1076
|
+
/** Minimum LLM span count. Default 0 (no requirement). */
|
|
1077
|
+
llmSpansMin?: number;
|
|
1078
|
+
/** Minimum judge span count. Default 0. */
|
|
1079
|
+
judgeSpansMin?: number;
|
|
1080
|
+
/** Minimum tool span count. Default 0. */
|
|
1081
|
+
toolSpansMin?: number;
|
|
1082
|
+
/**
|
|
1083
|
+
* Raw provider sink to consult for capture verification. When present,
|
|
1084
|
+
* the check requires at least one raw event for the run.
|
|
1085
|
+
*/
|
|
1086
|
+
rawSink?: RawProviderSink;
|
|
1087
|
+
/** Minimum raw provider event count. Default 0; ignored when `rawSink` absent. */
|
|
1088
|
+
rawProviderEventsMin?: number;
|
|
1089
|
+
/**
|
|
1090
|
+
* Every LLM span must have at least one matching raw `request` event
|
|
1091
|
+
* (matched by spanId). Catches the common bug where the structured span
|
|
1092
|
+
* was emitted but the raw HTTP capture was wired to a different sink.
|
|
1093
|
+
*/
|
|
1094
|
+
requireRawCoverageOfLlmSpans?: boolean;
|
|
1095
|
+
/** Run outcome must be set (not null/undefined). Default false. */
|
|
1096
|
+
requireOutcome?: boolean;
|
|
1097
|
+
}
|
|
1098
|
+
type RunIntegrityIssueCode = 'no_run' | 'missing_llm_spans' | 'missing_judge_spans' | 'missing_tool_spans' | 'missing_raw_events' | 'no_raw_sink' | 'orphan_llm_span' | 'missing_outcome';
|
|
1099
|
+
interface RunIntegrityIssue {
|
|
1100
|
+
code: RunIntegrityIssueCode;
|
|
1101
|
+
message: string;
|
|
1102
|
+
detail?: Record<string, unknown>;
|
|
1103
|
+
}
|
|
1104
|
+
interface RunIntegrityReport {
|
|
1105
|
+
ok: boolean;
|
|
1106
|
+
runId: string;
|
|
1107
|
+
llmSpanCount: number;
|
|
1108
|
+
judgeSpanCount: number;
|
|
1109
|
+
toolSpanCount: number;
|
|
1110
|
+
rawProviderEventCount: number;
|
|
1111
|
+
/**
|
|
1112
|
+
* Coverage of LLM spans by raw provider events keyed on spanId.
|
|
1113
|
+
* `total` is the number of LLM spans; `covered` is the count with at
|
|
1114
|
+
* least one matching `request` raw event.
|
|
1115
|
+
*/
|
|
1116
|
+
rawSpanCoverage: {
|
|
1117
|
+
covered: number;
|
|
1118
|
+
total: number;
|
|
1119
|
+
};
|
|
1120
|
+
issues: RunIntegrityIssue[];
|
|
1121
|
+
}
|
|
1122
|
+
declare class RunIntegrityError extends CaptureIntegrityError {
|
|
1123
|
+
readonly report: RunIntegrityReport;
|
|
1124
|
+
constructor(report: RunIntegrityReport);
|
|
1125
|
+
}
|
|
1126
|
+
declare function assertRunCaptured(store: TraceStore, runId: string, expectations?: RunIntegrityExpectations): Promise<RunIntegrityReport>;
|
|
1127
|
+
/** Strict mode: throws `RunIntegrityError` when the report isn't ok. */
|
|
1128
|
+
declare function throwIfRunIncomplete(report: RunIntegrityReport): void;
|
|
1129
|
+
|
|
1130
|
+
/**
|
|
1131
|
+
* OpenTelemetry JSON export — maps TraceSchema v1 to OTLP/JSON so
|
|
1132
|
+
* traces render natively in Jaeger / Honeycomb / Langfuse / Grafana.
|
|
1133
|
+
*
|
|
1134
|
+
* Wire format only. We do NOT depend on the @opentelemetry SDK — that
|
|
1135
|
+
* would drag in polyfills incompatible with Workers/Edge. Consumers
|
|
1136
|
+
* push the JSON to their collector of choice via HTTP.
|
|
1137
|
+
*
|
|
1138
|
+
* Reference: OTLP 1.3.2 (ResourceSpans / ScopeSpans / Span).
|
|
1139
|
+
*/
|
|
1140
|
+
|
|
1141
|
+
declare const OTEL_AGENT_EVAL_SCOPE: {
|
|
1142
|
+
name: string;
|
|
1143
|
+
version: string;
|
|
1144
|
+
};
|
|
1145
|
+
interface OtlpSpan {
|
|
1146
|
+
traceId: string;
|
|
1147
|
+
spanId: string;
|
|
1148
|
+
parentSpanId?: string;
|
|
1149
|
+
name: string;
|
|
1150
|
+
kind: number;
|
|
1151
|
+
startTimeUnixNano: string;
|
|
1152
|
+
endTimeUnixNano: string;
|
|
1153
|
+
attributes: Array<{
|
|
1154
|
+
key: string;
|
|
1155
|
+
value: {
|
|
1156
|
+
stringValue?: string;
|
|
1157
|
+
intValue?: string;
|
|
1158
|
+
doubleValue?: number;
|
|
1159
|
+
boolValue?: boolean;
|
|
1160
|
+
};
|
|
1161
|
+
}>;
|
|
1162
|
+
events?: Array<{
|
|
1163
|
+
timeUnixNano: string;
|
|
1164
|
+
name: string;
|
|
1165
|
+
attributes?: OtlpSpan['attributes'];
|
|
1166
|
+
}>;
|
|
1167
|
+
status?: {
|
|
1168
|
+
code: number;
|
|
1169
|
+
message?: string;
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
interface OtlpResourceSpans {
|
|
1173
|
+
resource: {
|
|
1174
|
+
attributes: OtlpSpan['attributes'];
|
|
1175
|
+
};
|
|
1176
|
+
scopeSpans: Array<{
|
|
1177
|
+
scope: typeof OTEL_AGENT_EVAL_SCOPE;
|
|
1178
|
+
spans: OtlpSpan[];
|
|
1179
|
+
}>;
|
|
1180
|
+
}
|
|
1181
|
+
interface OtlpExport {
|
|
1182
|
+
resourceSpans: OtlpResourceSpans[];
|
|
1183
|
+
}
|
|
1184
|
+
/** Export a single run's spans + events in OTLP/JSON. */
|
|
1185
|
+
declare function exportRunAsOtlp(store: TraceStore, runId: string, resourceAttrs?: Record<string, string | number | boolean>): Promise<OtlpExport>;
|
|
1186
|
+
|
|
1187
|
+
/** Canonical OpenInference-over-OTLP attribute names used at trace boundaries. */
|
|
1188
|
+
declare const OPENINFERENCE_SPAN_KIND = "openinference.span.kind";
|
|
1189
|
+
declare const LLM_MODEL_NAME = "llm.model_name";
|
|
1190
|
+
declare const LLM_INPUT_TOKENS = "llm.token_count.prompt";
|
|
1191
|
+
declare const LLM_OUTPUT_TOKENS = "llm.token_count.completion";
|
|
1192
|
+
declare const LLM_REASONING_TOKENS = "llm.token_count.reasoning";
|
|
1193
|
+
declare const LLM_CACHED_TOKENS = "llm.token_count.prompt_cache_hit";
|
|
1194
|
+
declare const LLM_CACHE_WRITE_TOKENS = "llm.token_count.prompt_cache_write";
|
|
1195
|
+
declare const LLM_COST_USD = "llm.cost_usd";
|
|
1196
|
+
declare const TOOL_NAME = "tool.name";
|
|
1197
|
+
declare const TOOL_ARGS_CAPTURED = "tool.args_captured";
|
|
1198
|
+
declare const TOOL_LATENCY_MS = "tool.latency_ms";
|
|
1199
|
+
declare const INPUT_VALUE = "input.value";
|
|
1200
|
+
declare const OUTPUT_VALUE = "output.value";
|
|
1201
|
+
|
|
1202
|
+
/** Canonical OpenInference-over-OTLP attribute vocabulary used at the trace boundary. */
|
|
1203
|
+
|
|
1204
|
+
declare const SPAN_KIND_ATTR_KEYS: readonly ["openinference.span.kind", "inference.observation_kind"];
|
|
1205
|
+
declare const LLM_MODEL_ATTR_KEYS: readonly ["llm.model_name", "inference.llm.model_name", "llm.model", ...string[], "tangle.model"];
|
|
1206
|
+
declare const LLM_INPUT_TOKEN_ATTR_KEYS: readonly ["llm.token_count.prompt", "inference.llm.input_tokens", "llm.input_tokens", ...string[], "tangle.tokens.in", "tokens.in"];
|
|
1207
|
+
declare const LLM_OUTPUT_TOKEN_ATTR_KEYS: readonly ["llm.token_count.completion", "inference.llm.output_tokens", "llm.output_tokens", ...string[], "tangle.tokens.out", "tokens.out"];
|
|
1208
|
+
/** Reasoning-token subset of output, when a producer exposes it separately. */
|
|
1209
|
+
declare const LLM_REASONING_TOKEN_ATTR_KEYS: readonly ["llm.token_count.reasoning", "inference.llm.reasoning_tokens", "llm.reasoning_tokens", "gen_ai.usage.reasoning_tokens", "gen_ai.usage.reasoning_output_tokens", "reasoning_tokens", "reasoning_output_tokens", "tangle.tokens.reasoning", "gen_ai.usage.output_tokens_details.reasoning_tokens", "gen_ai.usage.completion_tokens_details.reasoning_tokens"];
|
|
1210
|
+
declare const LLM_CACHED_TOKEN_ATTR_KEYS: readonly ["llm.token_count.prompt_cache_hit", "inference.llm.cached_tokens", "llm.cached_tokens", "gen_ai.usage.cached_tokens", "gen_ai.usage.prompt_tokens_details.cached_tokens", "gen_ai.usage.input_tokens_details.cached_tokens", "gen_ai.usage.cache_read_tokens", "gen_ai.usage.cache_read_input_tokens", "cache_read_tokens", "cache_read_input_tokens", "input_cache_read", "tangle.tokens.cached"];
|
|
1211
|
+
declare const LLM_CACHE_WRITE_TOKEN_ATTR_KEYS: readonly ["llm.token_count.prompt_cache_write", "inference.llm.cache_write_tokens", "llm.cache_write_tokens", "gen_ai.usage.cache_creation_tokens", "gen_ai.usage.cache_creation_input_tokens", "cache_creation_tokens", "cache_creation_input_tokens", "input_cache_creation", "tangle.tokens.cache_write"];
|
|
1212
|
+
declare const LLM_COST_ATTR_KEYS: readonly ["llm.cost_usd", "inference.llm.cost.total", "llm.cost.total", "gen_ai.usage.cost", "gen_ai.usage.cost_usd", "tangle.cost.usd", "cost.usd", "cost"];
|
|
1213
|
+
/** Explicit run-total cost keys safe to preserve on an untyped span. */
|
|
1214
|
+
declare const RUN_COST_ATTR_KEYS: readonly ["tangle.cost.usd", "cost.usd"];
|
|
1215
|
+
declare const TOOL_NAME_ATTR_KEYS: readonly ["tool.name", "inference.tool.name"];
|
|
1216
|
+
type ToolSpanOtlpInput = Pick<ToolSpan, 'toolName' | 'args' | 'argsCaptured' | 'result' | 'latencyMs'>;
|
|
1217
|
+
declare function applyToolSpanOtlpAttributes(attributes: Record<string, unknown>, span: ToolSpanOtlpInput): void;
|
|
1218
|
+
declare function traceSpanKindToOpenInferenceKind(kind: string): string;
|
|
1219
|
+
|
|
1220
|
+
/**
|
|
1221
|
+
* OTEL span exporter — streams spans to an OTLP/HTTP collector.
|
|
1222
|
+
*
|
|
1223
|
+
* Reads OTEL_EXPORTER_OTLP_ENDPOINT + OTEL_EXPORTER_OTLP_HEADERS from env
|
|
1224
|
+
* when no explicit config is given. Batches spans and flushes periodically
|
|
1225
|
+
* or when the batch fills. No @opentelemetry SDK dependency — minimal
|
|
1226
|
+
* OTLP/JSON serializer (~120 LOC) using the existing otel.ts helpers.
|
|
1227
|
+
*/
|
|
1228
|
+
|
|
1229
|
+
interface OtelExportConfig {
|
|
722
1230
|
/** OTLP endpoint. Reads OTEL_EXPORTER_OTLP_ENDPOINT env by default. */
|
|
723
1231
|
endpoint?: string;
|
|
724
1232
|
/** OTLP headers. Reads OTEL_EXPORTER_OTLP_HEADERS env by default. */
|
|
@@ -753,6 +1261,9 @@ interface ExportableSpan {
|
|
|
753
1261
|
model?: string;
|
|
754
1262
|
inputTokens?: number;
|
|
755
1263
|
outputTokens?: number;
|
|
1264
|
+
reasoningTokens?: number;
|
|
1265
|
+
cachedTokens?: number;
|
|
1266
|
+
cacheWriteTokens?: number;
|
|
756
1267
|
costUsd?: number;
|
|
757
1268
|
tool?: ToolSpanOtlpInput;
|
|
758
1269
|
attributes?: Record<string, unknown>;
|
|
@@ -790,6 +1301,38 @@ declare function otelRunCompleteHook(exporter: OtelExporter): RunCompleteHook;
|
|
|
790
1301
|
*/
|
|
791
1302
|
declare function createOtelTracingStore(inner: TraceStore, exporter: OtelExporter, traceId: string): TraceStore;
|
|
792
1303
|
|
|
1304
|
+
/**
|
|
1305
|
+
* Typed query helpers over TraceStore.
|
|
1306
|
+
*
|
|
1307
|
+
* Not a full SQL engine — a minimal, composable set of operators that
|
|
1308
|
+
* cover the canned-pipeline use cases. For ad-hoc analytics, persist to
|
|
1309
|
+
* NDJSON and point DuckDB at it; the schema is stable so external SQL
|
|
1310
|
+
* tooling works out of the box.
|
|
1311
|
+
*/
|
|
1312
|
+
|
|
1313
|
+
declare function runsForScenario(store: TraceStore, scenarioId: string): Promise<Run[]>;
|
|
1314
|
+
declare function llmSpans(store: TraceStore, runId?: string): Promise<LlmSpan[]>;
|
|
1315
|
+
declare function toolSpans(store: TraceStore, runId?: string, toolName?: string): Promise<ToolSpan[]>;
|
|
1316
|
+
/** Query judge-kind spans from the trace store, optionally scoped to a single run. */
|
|
1317
|
+
declare function judgeSpans(store: TraceStore, runId?: string): Promise<JudgeSpan[]>;
|
|
1318
|
+
/** Group spans by any key selector. */
|
|
1319
|
+
declare function groupBy<T, K extends string | number>(items: T[], key: (t: T) => K): Map<K, T[]>;
|
|
1320
|
+
/** Hash tool arguments to an orderless-key-stable string for de-duplication. */
|
|
1321
|
+
declare function argHash(args: unknown): string;
|
|
1322
|
+
/** Whether argument-based comparisons are valid for this tool call. */
|
|
1323
|
+
declare function hasCapturedToolArgs(span: ToolSpan): boolean;
|
|
1324
|
+
/** Sum an LLM-span array into aggregate token + cost. */
|
|
1325
|
+
declare function aggregateLlm(spans: LlmSpan[]): {
|
|
1326
|
+
inputTokens: number;
|
|
1327
|
+
outputTokens: number;
|
|
1328
|
+
cachedTokens: number;
|
|
1329
|
+
cacheWriteTokens: number;
|
|
1330
|
+
reasoningTokens: number;
|
|
1331
|
+
costUsd: number;
|
|
1332
|
+
};
|
|
1333
|
+
/** Pick the outcome's failure class when present, else derive 'success' from run status. */
|
|
1334
|
+
declare function runFailureClass(run: Run): FailureClass;
|
|
1335
|
+
|
|
793
1336
|
/**
|
|
794
1337
|
* Redaction — remove PII / secrets from trace payloads before persist.
|
|
795
1338
|
*
|
|
@@ -907,122 +1450,852 @@ interface TraceStoreToOtlpOptions {
|
|
|
907
1450
|
declare function convertTraceStoresToOtlp(source: string | TraceStoreSource | readonly TraceStoreSource[], outPath: string, opts?: TraceStoreToOtlpOptions): TracesToOtlpResult;
|
|
908
1451
|
|
|
909
1452
|
/**
|
|
910
|
-
*
|
|
911
|
-
* re-runnable artifact.
|
|
1453
|
+
* Shared types for the trace-analyst module.
|
|
912
1454
|
*
|
|
913
|
-
*
|
|
914
|
-
*
|
|
915
|
-
*
|
|
916
|
-
*
|
|
1455
|
+
* Wire format. The store interface speaks `OtlpSpanLike` rows — one JSONL
|
|
1456
|
+
* line per span, OTLP-shaped. We do NOT depend on a specific tracing
|
|
1457
|
+
* vendor at the type level. Adapter
|
|
1458
|
+
* layers map upstream shapes onto this interface.
|
|
917
1459
|
*
|
|
918
|
-
*
|
|
1460
|
+
* Design constraint. Every read operation that can return arbitrary
|
|
1461
|
+
* payload must carry a byte budget so the agent's tool result stays
|
|
1462
|
+
* bounded regardless of input trace size. Oversized responses
|
|
1463
|
+
* substitute a deterministic summary instead of bytes — see
|
|
1464
|
+
* `ViewTraceOversized`.
|
|
1465
|
+
*/
|
|
1466
|
+
/** OTLP span kind (subset we actually use). */
|
|
1467
|
+
type TraceAnalystSpanKind = 'AGENT' | 'LLM' | 'TOOL' | 'CHAIN' | 'GUARDRAIL' | 'SPAN' | 'UNKNOWN';
|
|
1468
|
+
type TraceAnalystSpanStatus = 'OK' | 'ERROR' | 'UNSET';
|
|
1469
|
+
/** Subset of OTLP span fields the analyst exposes to the agent. The
|
|
1470
|
+
* store's job is to project upstream's full span shape down to this
|
|
1471
|
+
* view — the analyst never sees vendor extensions directly. */
|
|
1472
|
+
interface TraceAnalystSpan {
|
|
1473
|
+
trace_id: string;
|
|
1474
|
+
span_id: string;
|
|
1475
|
+
parent_span_id: string | null;
|
|
1476
|
+
name: string;
|
|
1477
|
+
kind: TraceAnalystSpanKind;
|
|
1478
|
+
start_time: string;
|
|
1479
|
+
end_time: string;
|
|
1480
|
+
duration_ms: number;
|
|
1481
|
+
status: TraceAnalystSpanStatus;
|
|
1482
|
+
status_message?: string;
|
|
1483
|
+
service_name: string | null;
|
|
1484
|
+
agent_name: string | null;
|
|
1485
|
+
model_name: string | null;
|
|
1486
|
+
tool_name: string | null;
|
|
1487
|
+
/** Raw JSON-serialisable attribute map. May contain large strings;
|
|
1488
|
+
* callers must respect the per-attribute byte cap. */
|
|
1489
|
+
attributes: Record<string, unknown>;
|
|
1490
|
+
}
|
|
1491
|
+
interface TraceAnalystTraceSummary {
|
|
1492
|
+
trace_id: string;
|
|
1493
|
+
service_name: string | null;
|
|
1494
|
+
agent_name: string | null;
|
|
1495
|
+
span_count: number;
|
|
1496
|
+
has_errors: boolean;
|
|
1497
|
+
start_time: string;
|
|
1498
|
+
end_time: string;
|
|
1499
|
+
duration_ms: number;
|
|
1500
|
+
raw_jsonl_bytes: number;
|
|
1501
|
+
models: string[];
|
|
1502
|
+
tools: string[];
|
|
1503
|
+
}
|
|
1504
|
+
interface TraceAnalystFilters {
|
|
1505
|
+
/** Restrict to traces that contain at least one error span. */
|
|
1506
|
+
has_errors?: boolean;
|
|
1507
|
+
/** Match if any span's `service.name` is in this list. */
|
|
1508
|
+
service_names?: string[];
|
|
1509
|
+
/** Match if any span's `agent.name` is in this list. */
|
|
1510
|
+
agent_names?: string[];
|
|
1511
|
+
/** Match if any LLM span's `llm.model_name` is in this list. */
|
|
1512
|
+
model_names?: string[];
|
|
1513
|
+
/** Match if any tool span's `tool.name` is in this list. */
|
|
1514
|
+
tool_names?: string[];
|
|
1515
|
+
/** ISO-8601 lower bound on the trace's earliest start time. */
|
|
1516
|
+
start_time_after?: string;
|
|
1517
|
+
/** ISO-8601 upper bound on the trace's earliest start time. */
|
|
1518
|
+
start_time_before?: string;
|
|
1519
|
+
/** Single regex applied to raw JSONL bytes for the trace. Opt-in;
|
|
1520
|
+
* expensive on large datasets. Use the indexed filters above first. */
|
|
1521
|
+
regex_pattern?: string;
|
|
1522
|
+
}
|
|
1523
|
+
/** One distinct error signature across the dataset — the deterministic unit of
|
|
1524
|
+
* failure coverage. Signatures normalize volatile tokens (digits, hex/uuids,
|
|
1525
|
+
* paths, durations) out of the span `status_message` so semantically identical
|
|
1526
|
+
* failures collapse into one cluster. An analyst that accounts for every
|
|
1527
|
+
* cluster has, by construction, covered every distinct failure mode. */
|
|
1528
|
+
interface ErrorCluster {
|
|
1529
|
+
/** Normalized status_message — the cluster key. */
|
|
1530
|
+
signature: string;
|
|
1531
|
+
/** A verbatim, un-normalized exemplar message (for exact-string citation). */
|
|
1532
|
+
status_message_sample: string;
|
|
1533
|
+
/** The span name that most often carries this signature, if any. */
|
|
1534
|
+
span_name: string | null;
|
|
1535
|
+
/** The tool that most often carries this signature, if any. */
|
|
1536
|
+
tool_name: string | null;
|
|
1537
|
+
trace_count: number;
|
|
1538
|
+
span_count: number;
|
|
1539
|
+
/** trace_count / total error traces in the matched set (0..1). */
|
|
1540
|
+
prevalence: number;
|
|
1541
|
+
/** Real trace ids carrying this signature (capped), passable to view/search. */
|
|
1542
|
+
exemplar_trace_ids: string[];
|
|
1543
|
+
/** Real span ids carrying this signature (capped). */
|
|
1544
|
+
exemplar_span_ids: string[];
|
|
1545
|
+
}
|
|
1546
|
+
interface DatasetOverview {
|
|
1547
|
+
total_traces: number;
|
|
1548
|
+
raw_jsonl_bytes: number;
|
|
1549
|
+
services: string[];
|
|
1550
|
+
agents: string[];
|
|
1551
|
+
models: string[];
|
|
1552
|
+
tool_names: string[];
|
|
1553
|
+
/** Up to 20 real trace ids the agent may pass to view/search tools. */
|
|
1554
|
+
sample_trace_ids: string[];
|
|
1555
|
+
errors: {
|
|
1556
|
+
trace_count: number;
|
|
1557
|
+
span_count: number;
|
|
1558
|
+
};
|
|
1559
|
+
/** The COMPLETE deterministic error-signature population, sorted by
|
|
1560
|
+
* trace_count desc. This is the failure-coverage checklist: an analysis is
|
|
1561
|
+
* complete only when every cluster here is accounted for. Empty when the
|
|
1562
|
+
* matched set has no error spans. */
|
|
1563
|
+
error_clusters: ErrorCluster[];
|
|
1564
|
+
time_range: {
|
|
1565
|
+
earliest: string;
|
|
1566
|
+
latest: string;
|
|
1567
|
+
} | null;
|
|
1568
|
+
}
|
|
1569
|
+
interface QueryTracesPage {
|
|
1570
|
+
traces: TraceAnalystTraceSummary[];
|
|
1571
|
+
total: number;
|
|
1572
|
+
has_more: boolean;
|
|
1573
|
+
}
|
|
1574
|
+
/** Full-trace view. When the response would exceed the per-call byte
|
|
1575
|
+
* budget, `oversized` is populated INSTEAD of `spans` so the agent
|
|
1576
|
+
* knows to switch to `searchTrace` / `viewSpans`. */
|
|
1577
|
+
interface ViewTraceResult {
|
|
1578
|
+
trace_id: string;
|
|
1579
|
+
spans?: TraceAnalystSpan[];
|
|
1580
|
+
oversized?: ViewTraceOversized;
|
|
1581
|
+
}
|
|
1582
|
+
interface ViewTraceOversized {
|
|
1583
|
+
span_count: number;
|
|
1584
|
+
/** Names with their counts, sorted desc. Capped at 20 entries. */
|
|
1585
|
+
top_span_names: Array<[string, number]>;
|
|
1586
|
+
/** Largest single span body (bytes after attribute-cap projection). */
|
|
1587
|
+
span_response_bytes_max: number;
|
|
1588
|
+
error_span_count: number;
|
|
1589
|
+
}
|
|
1590
|
+
interface ViewSpansResult {
|
|
1591
|
+
trace_id: string;
|
|
1592
|
+
spans: TraceAnalystSpan[];
|
|
1593
|
+
/** Number of requested span ids that were not found in the trace. */
|
|
1594
|
+
missing_span_ids: string[];
|
|
1595
|
+
/** Number of attribute fields truncated to fit the per-attribute cap. */
|
|
1596
|
+
truncated_attribute_count: number;
|
|
1597
|
+
}
|
|
1598
|
+
interface SpanMatchRecord {
|
|
1599
|
+
trace_id: string;
|
|
1600
|
+
span_id: string;
|
|
1601
|
+
span_name: string;
|
|
1602
|
+
span_kind: TraceAnalystSpanKind;
|
|
1603
|
+
/** JSON pointer-style path to the matched value, e.g.
|
|
1604
|
+
* `attributes."llm.input_messages"[2].content`. */
|
|
1605
|
+
attribute_path: string;
|
|
1606
|
+
matched_text: string;
|
|
1607
|
+
context_before: string;
|
|
1608
|
+
context_after: string;
|
|
1609
|
+
match_offset: number;
|
|
1610
|
+
}
|
|
1611
|
+
interface SearchTraceResult {
|
|
1612
|
+
trace_id: string;
|
|
1613
|
+
hits: SpanMatchRecord[];
|
|
1614
|
+
total_matches: number;
|
|
1615
|
+
has_more: boolean;
|
|
1616
|
+
}
|
|
1617
|
+
interface SearchSpanResult {
|
|
1618
|
+
trace_id: string;
|
|
1619
|
+
span_id: string;
|
|
1620
|
+
hits: SpanMatchRecord[];
|
|
1621
|
+
total_matches: number;
|
|
1622
|
+
has_more: boolean;
|
|
1623
|
+
}
|
|
1624
|
+
/** Tunable byte budgets for bounded RLM tool output. */
|
|
1625
|
+
interface TraceAnalystByteBudgets {
|
|
1626
|
+
/** Max bytes any single tool response may emit. Hard ceiling enforced
|
|
1627
|
+
* by the store; oversized → summary. Default 150_000. */
|
|
1628
|
+
perCallByteCeiling: number;
|
|
1629
|
+
/** Per-attribute string truncation cap on `viewTrace` (discovery scan).
|
|
1630
|
+
* Default 4096. */
|
|
1631
|
+
perAttributeViewBudget: number;
|
|
1632
|
+
/** Per-attribute string truncation cap on `viewSpans` (surgical reads).
|
|
1633
|
+
* Default 16384. */
|
|
1634
|
+
perAttributeSpanBudget: number;
|
|
1635
|
+
/** Per-attribute cap on a single match record's `matched_text` and
|
|
1636
|
+
* context window. Default 1024. */
|
|
1637
|
+
perMatchTextBudget: number;
|
|
1638
|
+
}
|
|
1639
|
+
declare const DEFAULT_TRACE_ANALYST_BUDGETS: TraceAnalystByteBudgets;
|
|
1640
|
+
/** Marker substituted in place of truncated string payloads. Callers
|
|
1641
|
+
* parsing tool output can detect it deterministically. */
|
|
1642
|
+
declare const TRACE_ANALYST_TRUNCATION_MARKER_PREFIX = "[trace-analyst truncated:";
|
|
1643
|
+
|
|
1644
|
+
/**
|
|
1645
|
+
* `TraceAnalysisStore` — read-side interface the trace-analyst calls
|
|
1646
|
+
* through. Six operations, all bounded:
|
|
919
1647
|
*
|
|
920
|
-
*
|
|
921
|
-
*
|
|
922
|
-
*
|
|
923
|
-
*
|
|
924
|
-
*
|
|
925
|
-
*
|
|
926
|
-
*
|
|
927
|
-
* and measure inter-judge agreement without doubling LLM spend.
|
|
1648
|
+
* - `getOverview(filters?)` — dataset rollup + sample trace ids.
|
|
1649
|
+
* - `queryTraces(filters?, limit, offset)` — paginated summaries.
|
|
1650
|
+
* - `countTraces(filters?)` — cheap count without materialisation.
|
|
1651
|
+
* - `viewTrace(trace_id, perAttrCap)` — full span list, oversized → summary.
|
|
1652
|
+
* - `viewSpans(trace_id, span_ids, perAttrCap)` — surgical span fetch.
|
|
1653
|
+
* - `searchTrace(trace_id, regex, max_matches)` — bounded regex hits.
|
|
1654
|
+
* - `searchSpan(trace_id, span_id, regex, max_matches)` — single-span search.
|
|
928
1655
|
*
|
|
929
|
-
*
|
|
930
|
-
*
|
|
931
|
-
*
|
|
932
|
-
*
|
|
1656
|
+
* Multiple implementations ship in the core (`OtlpFileTraceStore`).
|
|
1657
|
+
* Downstream callers can supply their own — e.g. a DuckDB-backed
|
|
1658
|
+
* adapter or an in-memory adapter for tests — by implementing this
|
|
1659
|
+
* interface.
|
|
1660
|
+
*
|
|
1661
|
+
* Filters compose with AND semantics. Empty/undefined fields impose
|
|
1662
|
+
* no constraint. `regex_pattern` is the only opt-in raw-bytes scan —
|
|
1663
|
+
* implementations may skip it via `count`/`overview` when not set.
|
|
933
1664
|
*/
|
|
934
1665
|
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
1666
|
+
interface TraceAnalysisStore {
|
|
1667
|
+
getOverview(filters?: TraceAnalystFilters): Promise<DatasetOverview>;
|
|
1668
|
+
queryTraces(opts: {
|
|
1669
|
+
filters?: TraceAnalystFilters;
|
|
1670
|
+
limit: number;
|
|
1671
|
+
offset?: number;
|
|
1672
|
+
}): Promise<QueryTracesPage>;
|
|
1673
|
+
countTraces(filters?: TraceAnalystFilters): Promise<number>;
|
|
1674
|
+
viewTrace(opts: {
|
|
1675
|
+
trace_id: string;
|
|
1676
|
+
/** Override per-attribute byte cap. Defaults to discovery budget. */
|
|
1677
|
+
per_attribute_byte_cap?: number;
|
|
1678
|
+
}): Promise<ViewTraceResult>;
|
|
1679
|
+
viewSpans(opts: {
|
|
1680
|
+
trace_id: string;
|
|
1681
|
+
span_ids: readonly string[];
|
|
1682
|
+
/** Override per-attribute byte cap. Defaults to surgical budget. */
|
|
1683
|
+
per_attribute_byte_cap?: number;
|
|
1684
|
+
}): Promise<ViewSpansResult>;
|
|
1685
|
+
searchTrace(opts: {
|
|
1686
|
+
trace_id: string;
|
|
1687
|
+
regex_pattern: string;
|
|
1688
|
+
/** Hard cap on matches returned. Default 50. */
|
|
1689
|
+
max_matches?: number;
|
|
1690
|
+
}): Promise<SearchTraceResult>;
|
|
1691
|
+
searchSpan(opts: {
|
|
1692
|
+
trace_id: string;
|
|
1693
|
+
span_id: string;
|
|
1694
|
+
regex_pattern: string;
|
|
1695
|
+
max_matches?: number;
|
|
1696
|
+
}): Promise<SearchSpanResult>;
|
|
939
1697
|
}
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
1698
|
+
|
|
1699
|
+
interface AnalyzeTracesInput {
|
|
1700
|
+
/** The user-facing question. Domain framing belongs here, not in the
|
|
1701
|
+
* actor description. */
|
|
1702
|
+
question: string;
|
|
943
1703
|
}
|
|
944
|
-
interface
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
1704
|
+
interface AnalyzeTracesResult {
|
|
1705
|
+
/** The responder's prose answer. */
|
|
1706
|
+
answer: string;
|
|
1707
|
+
/** Bulleted findings extracted from the responder's structured output. */
|
|
1708
|
+
findings: string[];
|
|
1709
|
+
/** Per-actor-turn snapshots captured via `actorTurnCallback`. */
|
|
1710
|
+
turns: AnalyzeTracesTurnSnapshot[];
|
|
1711
|
+
/** Total turns the actor took. */
|
|
1712
|
+
turnCount: number;
|
|
1713
|
+
/** Token usage by role. */
|
|
1714
|
+
usage: TraceAnalystUsage;
|
|
1715
|
+
/** Full system + assistant + tool message log by role. */
|
|
1716
|
+
chatLog: TraceAnalystChatLog;
|
|
1717
|
+
/** Prompt version that produced this run. */
|
|
1718
|
+
actorPromptVersion: string;
|
|
1719
|
+
}
|
|
1720
|
+
interface TraceAnalystUsage {
|
|
1721
|
+
actor: TraceAnalystUsageEntry[];
|
|
1722
|
+
responder: TraceAnalystUsageEntry[];
|
|
1723
|
+
}
|
|
1724
|
+
interface TraceAnalystUsageEntry {
|
|
1725
|
+
[key: string]: unknown;
|
|
1726
|
+
}
|
|
1727
|
+
interface TraceAnalystChatLog {
|
|
1728
|
+
actor: TraceAnalystChatMessage[];
|
|
1729
|
+
responder: TraceAnalystChatMessage[];
|
|
1730
|
+
}
|
|
1731
|
+
interface TraceAnalystChatMessage {
|
|
1732
|
+
[key: string]: unknown;
|
|
1733
|
+
}
|
|
1734
|
+
interface AnalyzeTracesTurnSnapshot {
|
|
1735
|
+
turn: number;
|
|
1736
|
+
isError: boolean;
|
|
1737
|
+
/** The JS code the actor produced for this turn. */
|
|
1738
|
+
code: string;
|
|
1739
|
+
/** The formatted action-log entry the actor sees on the next turn. */
|
|
1740
|
+
output: string;
|
|
1741
|
+
/** Provider thought (when `actorOptions.showThoughts` is true and the
|
|
1742
|
+
* provider returns it). */
|
|
1743
|
+
thought?: string;
|
|
1744
|
+
}
|
|
1745
|
+
interface AnalyzeTracesOptions {
|
|
1746
|
+
/** Trace data source. Pass either an OTLP-JSONL path or a custom store. */
|
|
1747
|
+
source: string | TraceAnalysisStore;
|
|
1748
|
+
/** Caller-provided AxAIService. */
|
|
1749
|
+
ai: AxAIService;
|
|
1750
|
+
/** Model id forwarded to actor + responder. */
|
|
1751
|
+
model?: string;
|
|
1752
|
+
/** Recursion depth. 0 = no sub-agent dispatch. Default 1. */
|
|
1753
|
+
maxDepth?: number;
|
|
1754
|
+
/** Maximum actor turns. Default 12. */
|
|
1755
|
+
maxTurns?: number;
|
|
1756
|
+
/** Maximum parallel sub-agent calls in batched llmQuery. Default 2. */
|
|
1757
|
+
maxParallelSubagents?: number;
|
|
1758
|
+
/** Override the actor description. */
|
|
1759
|
+
actorDescription?: string;
|
|
1760
|
+
/** Override the subagent description. */
|
|
1761
|
+
subagentDescription?: string;
|
|
1762
|
+
/** Per-turn observability hook. */
|
|
1763
|
+
onTurn?: (turn: AnalyzeTracesTurnSnapshot) => void | Promise<void>;
|
|
1764
|
+
/** Override max runtime characters per turn. Default 6000. */
|
|
1765
|
+
maxRuntimeChars?: number;
|
|
1766
|
+
/** When set, every turn's snapshot is appended to this JSONL file
|
|
1767
|
+
* immediately. If the analyst crashes mid-loop (provider 503,
|
|
1768
|
+
* network error, validator reject) the partial reasoning is still
|
|
1769
|
+
* on disk. Replay the file with the responder afterward to recover
|
|
1770
|
+
* evidence. */
|
|
1771
|
+
progressLogPath?: string;
|
|
950
1772
|
}
|
|
951
1773
|
/**
|
|
952
|
-
*
|
|
953
|
-
* hash of the request body. Built from a `RawProviderSink` containing
|
|
954
|
-
* paired `request` and `response` events from a previous run.
|
|
1774
|
+
* Run the trace analyst.
|
|
955
1775
|
*
|
|
956
|
-
*
|
|
957
|
-
*
|
|
1776
|
+
* Throws:
|
|
1777
|
+
* - `TraceFileMissingError` if `source` is a path and doesn't exist.
|
|
1778
|
+
* - `AxAgentClarificationError` if the analyst asks for clarification.
|
|
1779
|
+
* - Provider errors (auth, rate limits) propagate from the AI service.
|
|
958
1780
|
*/
|
|
959
|
-
declare
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
1781
|
+
declare function analyzeTraces(input: AnalyzeTracesInput, options: AnalyzeTracesOptions): Promise<AnalyzeTracesResult>;
|
|
1782
|
+
|
|
1783
|
+
/**
|
|
1784
|
+
* Trace-analyst auto-execution hook.
|
|
1785
|
+
*
|
|
1786
|
+
* Wires `analyzeTraces` into a `TraceEmitter`'s `onRunComplete` so a
|
|
1787
|
+
* direct matrix run produces an analysis artifact without an out-of-band
|
|
1788
|
+
* step. Designed for the case where the consumer reports "the analyst
|
|
1789
|
+
* never ran" — the cause is almost always orchestration, not the analyst.
|
|
1790
|
+
*
|
|
1791
|
+
* Usage:
|
|
1792
|
+
*
|
|
1793
|
+
* const emitter = new TraceEmitter(store, {
|
|
1794
|
+
* onRunComplete: [traceAnalystOnRunComplete({ analyze: opts, save })],
|
|
1795
|
+
* })
|
|
1796
|
+
*
|
|
1797
|
+
* Hooks are best-effort by default — they never crash the underlying run.
|
|
1798
|
+
* The caller decides whether to gate the run on the analysis result via
|
|
1799
|
+
* the `gateOn` callback.
|
|
1800
|
+
*/
|
|
1801
|
+
|
|
1802
|
+
interface TraceAnalystHookOptions {
|
|
1803
|
+
/**
|
|
1804
|
+
* Options forwarded to `analyzeTraces`. The hook supplies the question
|
|
1805
|
+
* if you don't pass one — defaulting to a launch-grade prompt that asks
|
|
1806
|
+
* for failure modes, surprising findings, and a recommendation.
|
|
1807
|
+
*/
|
|
1808
|
+
analyze: Omit<AnalyzeTracesOptions, 'source'> & {
|
|
1809
|
+
source?: AnalyzeTracesOptions['source'];
|
|
1810
|
+
};
|
|
1811
|
+
/**
|
|
1812
|
+
* Override the question. The default is intentionally generic:
|
|
1813
|
+
* "Summarise what happened in this run, surface any failure modes,
|
|
1814
|
+
* surprising findings, or evidence the verdict is wrong."
|
|
1815
|
+
*/
|
|
1816
|
+
question?: string;
|
|
1817
|
+
/**
|
|
1818
|
+
* Persist the result. The hook calls this with the analysis output and
|
|
1819
|
+
* the run context. Common implementations write to a TraceAnalysisStore
|
|
1820
|
+
* or append to a per-run JSONL.
|
|
1821
|
+
*/
|
|
1822
|
+
save?: (result: AnalyzeTracesResult, ctx: RunCompleteHookContext) => Promise<void>;
|
|
1823
|
+
/**
|
|
1824
|
+
* Predicate gating execution per run. Default: every completed run.
|
|
1825
|
+
* Use to skip aborted runs, debug runs, or runs without LLM activity.
|
|
1826
|
+
*/
|
|
1827
|
+
shouldRun?: (ctx: RunCompleteHookContext) => boolean;
|
|
1828
|
+
/**
|
|
1829
|
+
* Optional gate: if set and returns false, the hook records the failure
|
|
1830
|
+
* as a log event on the run instead of staying quiet. The caller can
|
|
1831
|
+
* then trigger downstream alerts off `analyst_gate_failed` log events.
|
|
1832
|
+
*/
|
|
1833
|
+
gateOn?: (result: AnalyzeTracesResult, ctx: RunCompleteHookContext) => boolean;
|
|
1834
|
+
}
|
|
1835
|
+
declare function traceAnalystOnRunComplete(opts: TraceAnalystHookOptions): RunCompleteHook;
|
|
1836
|
+
|
|
1837
|
+
interface TraceInsightTask {
|
|
1838
|
+
id: string;
|
|
1839
|
+
name: string;
|
|
1840
|
+
prompt?: string;
|
|
1841
|
+
difficulty?: string;
|
|
1842
|
+
tags?: string[];
|
|
1843
|
+
outcome?: string;
|
|
1844
|
+
score?: number;
|
|
1845
|
+
gaps?: string[];
|
|
1846
|
+
}
|
|
1847
|
+
interface TraceInsightSuite {
|
|
1848
|
+
name: string;
|
|
1849
|
+
collectionId?: string;
|
|
1850
|
+
tasks: TraceInsightTask[];
|
|
1851
|
+
}
|
|
1852
|
+
interface TraceInsightFinding {
|
|
1853
|
+
kind: string;
|
|
1854
|
+
severity?: string;
|
|
1855
|
+
taskIds: string[];
|
|
1856
|
+
evidence?: string;
|
|
1857
|
+
proposedFixClass?: string;
|
|
1858
|
+
}
|
|
1859
|
+
interface TraceInsightQuestion {
|
|
1860
|
+
id: string;
|
|
1861
|
+
question: string;
|
|
1862
|
+
why: string;
|
|
1863
|
+
}
|
|
1864
|
+
interface TraceInsightPanelRole {
|
|
1865
|
+
id: string;
|
|
1866
|
+
name: string;
|
|
1867
|
+
responsibility: string;
|
|
1868
|
+
}
|
|
1869
|
+
interface TraceInsightPromptInput {
|
|
1870
|
+
suite: TraceInsightSuite;
|
|
1871
|
+
findings?: TraceInsightFinding[];
|
|
1872
|
+
agent?: Record<string, unknown>;
|
|
1873
|
+
totals?: Record<string, unknown>;
|
|
1874
|
+
maxRepresentativeTraces?: number;
|
|
1875
|
+
}
|
|
1876
|
+
interface TraceInsightContext {
|
|
1877
|
+
suite: TraceInsightSuite;
|
|
1878
|
+
scope: string;
|
|
1879
|
+
keywords: string[];
|
|
1880
|
+
questions: TraceInsightQuestion[];
|
|
1881
|
+
panel: TraceInsightPanelRole[];
|
|
1882
|
+
findings: TraceInsightFinding[];
|
|
1883
|
+
agent: Record<string, unknown> | null;
|
|
1884
|
+
totals: Record<string, unknown> | null;
|
|
1885
|
+
}
|
|
1886
|
+
interface TraceInsightQualityGate {
|
|
1887
|
+
id: string;
|
|
1888
|
+
label: string;
|
|
1889
|
+
passed: boolean;
|
|
1890
|
+
severity: 'critical' | 'high' | 'medium' | 'low';
|
|
1891
|
+
detail: string;
|
|
1892
|
+
}
|
|
1893
|
+
interface TraceInsightReadiness {
|
|
1894
|
+
score: number;
|
|
1895
|
+
grade: 'external-ready' | 'internal-review' | 'raw-analysis';
|
|
1896
|
+
gates: TraceInsightQualityGate[];
|
|
1897
|
+
}
|
|
1898
|
+
declare function tokenizeDomainWords(value: string): string[];
|
|
1899
|
+
declare function inferDomainKeywords(suite: TraceInsightSuite): string[];
|
|
1900
|
+
declare function domainEvidencePattern(keywords: string[]): RegExp;
|
|
1901
|
+
declare function describeTraceInsightScope(suite: TraceInsightSuite): string;
|
|
1902
|
+
declare function planTraceInsightQuestions(input: TraceInsightPromptInput): TraceInsightQuestion[];
|
|
1903
|
+
declare function buildTraceInsightContext(input: TraceInsightPromptInput): TraceInsightContext;
|
|
1904
|
+
declare function scoreTraceInsightReadiness(context: TraceInsightContext): TraceInsightReadiness;
|
|
1905
|
+
declare function defaultTraceInsightPanel(): TraceInsightPanelRole[];
|
|
1906
|
+
declare function buildTraceInsightPrompt(input: TraceInsightPromptInput): string;
|
|
1907
|
+
|
|
1908
|
+
/**
|
|
1909
|
+
* `flattenOtlpExportToNdjson` — flatten an `OtlpExport` (the shape
|
|
1910
|
+
* `exportRunAsOtlp` produces) into the per-line JSON the analyst's
|
|
1911
|
+
* `OtlpFileTraceStore` index reads. Replaces three per-consumer OTLP
|
|
1912
|
+
* flatteners with one canonical projection.
|
|
1913
|
+
*
|
|
1914
|
+
* Pure function, no I/O — the caller does `.map(JSON.stringify).join('\n')`
|
|
1915
|
+
* and writes the file (consumers want control over rotation + naming).
|
|
1916
|
+
*/
|
|
1917
|
+
|
|
1918
|
+
interface OtlpFlatLine {
|
|
1919
|
+
trace_id: string;
|
|
1920
|
+
span_id: string;
|
|
1921
|
+
parent_span_id: string | null;
|
|
1922
|
+
name: string;
|
|
1923
|
+
kind: string;
|
|
1924
|
+
start_time: string;
|
|
1925
|
+
end_time: string;
|
|
1926
|
+
status: {
|
|
1927
|
+
code: 'STATUS_CODE_OK' | 'STATUS_CODE_ERROR' | 'STATUS_CODE_UNSET';
|
|
1928
|
+
message?: string;
|
|
1929
|
+
};
|
|
1930
|
+
resource: {
|
|
1931
|
+
attributes: Record<string, string | number | boolean>;
|
|
1932
|
+
};
|
|
1933
|
+
attributes: Record<string, string | number | boolean>;
|
|
1934
|
+
events?: Array<{
|
|
1935
|
+
name: string;
|
|
1936
|
+
timeUnixNano?: string;
|
|
1937
|
+
attributes?: Record<string, unknown>;
|
|
1938
|
+
}>;
|
|
1939
|
+
}
|
|
1940
|
+
interface FlattenOtlpOptions {
|
|
1941
|
+
/** `'openinference'` (default) mirrors legacy per-span attributes into the
|
|
1942
|
+
* canonical OpenInference vocabulary the analyst readers consume. `'none'`
|
|
1943
|
+
* passes attributes through untouched. */
|
|
1944
|
+
attributeVocabulary?: 'openinference' | 'none';
|
|
1945
|
+
/** Override the numeric-kind → otlp-string mapping. */
|
|
1946
|
+
kindMap?: Partial<Record<number, string>>;
|
|
1947
|
+
}
|
|
1948
|
+
declare function flattenOtlpExportToNdjson(otlpExport: OtlpExport, opts?: FlattenOtlpOptions): OtlpFlatLine[];
|
|
1949
|
+
|
|
1950
|
+
/**
|
|
1951
|
+
* Canonical OTLP-flat-line readers shared by every consumer of the
|
|
1952
|
+
* OTLP-JSONL wire shape (one OTLP span per line; the form
|
|
1953
|
+
* `flattenOtlpExportToNdjson` produces and the form AppWorld / HALO
|
|
1954
|
+
* emit via their OpenInference OTLP exporter).
|
|
1955
|
+
*
|
|
1956
|
+
* `OtlpFileTraceStore` indexes spans with these; `otlpToRunRecords`
|
|
1957
|
+
* aggregates spans into `RunRecord`s with the same readers. One parser,
|
|
1958
|
+
* one vocabulary — a divergence between the analyst's view of a trace and
|
|
1959
|
+
* the RunRecord projected from it is a class of bug this consolidation
|
|
1960
|
+
* removes by construction.
|
|
1961
|
+
*
|
|
1962
|
+
* Vocabulary. The readers understand BOTH dialects that appear in the
|
|
1963
|
+
* wild:
|
|
1964
|
+
* - the substrate's own `llm.*` / `tool.*` / `span.kind` attributes
|
|
1965
|
+
* (`flattenSpanAttributes` in `trace/otel.ts`), and
|
|
1966
|
+
* - the OpenInference / inference-export attributes AppWorld / HALO
|
|
1967
|
+
* emit (`openinference.span.kind`, `inference.observation_kind`,
|
|
1968
|
+
* `inference.llm.input_tokens`, `llm.token_count.prompt`, …).
|
|
1969
|
+
*
|
|
1970
|
+
* Pure, no I/O.
|
|
1971
|
+
*/
|
|
1972
|
+
|
|
1973
|
+
/**
|
|
1974
|
+
* The structural fields a flat OTLP-JSONL line projects to. `attributes`
|
|
1975
|
+
* is the merged resource+span attribute map (span overrides resource);
|
|
1976
|
+
* the named fields are the pivots every reader of a trace needs without
|
|
1977
|
+
* paying the full attribute materialisation.
|
|
1978
|
+
*/
|
|
1979
|
+
interface ProjectedOtlpSpan {
|
|
1980
|
+
trace_id: string;
|
|
1981
|
+
span_id: string;
|
|
1982
|
+
parent_span_id: string | null;
|
|
1983
|
+
name: string;
|
|
1984
|
+
kind: TraceAnalystSpanKind;
|
|
1985
|
+
start_time: string;
|
|
1986
|
+
end_time: string;
|
|
1987
|
+
duration_ms: number;
|
|
1988
|
+
status: TraceAnalystSpanStatus;
|
|
1989
|
+
status_message: string | undefined;
|
|
1990
|
+
service_name: string | null;
|
|
1991
|
+
agent_name: string | null;
|
|
1992
|
+
model_name: string | null;
|
|
1993
|
+
tool_name: string | null;
|
|
1994
|
+
/** Merged resource + span attributes, span winning on overlap. */
|
|
1995
|
+
attributes: Record<string, unknown>;
|
|
1996
|
+
}
|
|
1997
|
+
/**
|
|
1998
|
+
* Project one parsed OTLP-JSONL object to `ProjectedOtlpSpan`, or `null`
|
|
1999
|
+
* when the line is missing the mandatory `trace_id` + `span_id`.
|
|
2000
|
+
*/
|
|
2001
|
+
declare function projectOtlpFlatLine(raw: Record<string, unknown>): ProjectedOtlpSpan | null;
|
|
2002
|
+
declare function readOtlpStatus(raw: Record<string, unknown>): {
|
|
2003
|
+
code: TraceAnalystSpanStatus;
|
|
2004
|
+
message: string | undefined;
|
|
2005
|
+
};
|
|
2006
|
+
declare function inferOtlpKind(attrs: Record<string, unknown>): TraceAnalystSpanKind;
|
|
2007
|
+
/**
|
|
2008
|
+
* Flatten OTLP `attributes` + `resource.attributes` into a single
|
|
2009
|
+
* dotted-key map. Span attributes override resource attributes when keys
|
|
2010
|
+
* overlap. Nested objects/arrays are preserved as-is.
|
|
2011
|
+
*/
|
|
2012
|
+
declare function extractOtlpAttributes(raw: Record<string, unknown>): Record<string, unknown>;
|
|
2013
|
+
declare function stringField(raw: Record<string, unknown>, key: string): string | undefined;
|
|
2014
|
+
declare function asString(v: unknown): string | null;
|
|
2015
|
+
/** Read a numeric attribute, tolerating numeric strings; `null` if absent/NaN. */
|
|
2016
|
+
declare function asNumber(v: unknown): number | null;
|
|
2017
|
+
/** First finite numeric value across a list of candidate attribute keys. */
|
|
2018
|
+
declare function firstNumberAttr(attrs: Record<string, unknown>, keys: readonly string[]): number | null;
|
|
2019
|
+
/** First non-empty string value across a list of candidate attribute keys. */
|
|
2020
|
+
declare function firstStringAttr(attrs: Record<string, unknown>, keys: readonly string[]): string | null;
|
|
2021
|
+
|
|
2022
|
+
/**
|
|
2023
|
+
* `otlpToRunRecords` — fold an OTLP traces.jsonl (one OTLP span per line;
|
|
2024
|
+
* the form AppWorld / HALO emit via their OpenInference OTLP exporter, the
|
|
2025
|
+
* same shape `flattenOtlpExportToNdjson` produces) into validated
|
|
2026
|
+
* `RunRecord[]` — one record per `trace_id` (one trace == one task).
|
|
2027
|
+
*
|
|
2028
|
+
* This is the offline ingestion primitive the AppWorld proposer bench and the
|
|
2029
|
+
* hosted Intelligence product both stand on: traces in, paper-grade rows
|
|
2030
|
+
* out, ready for `compareProposers` / `analyzeRuns` / the promotion gate.
|
|
2031
|
+
*
|
|
2032
|
+
* Aggregation per trace:
|
|
2033
|
+
* - tokenUsage: reconcile input, output, cache-read, and cache-write across
|
|
2034
|
+
* nested model-call wrappers without double-counting parent aggregates.
|
|
2035
|
+
* - costUsd: reconcile complete observed model-call cost when present; else priced via
|
|
2036
|
+
* `opts.priceUsdPerToken` from the aggregated tokens; else 0 with a
|
|
2037
|
+
* loud `raw.cost_unpriced = 1` marker so a missing price is visible, not
|
|
2038
|
+
* a silent zero folded into a gate.
|
|
2039
|
+
* - failureMode: the first `STATUS_CODE_ERROR` span's normalized status
|
|
2040
|
+
* message (carries the real failure signature, not a generic class).
|
|
2041
|
+
* - model: the dominant LLM model in the trace (snapshot-padded to satisfy
|
|
2042
|
+
* `validateRunRecord` when the trace's model is a bare alias).
|
|
2043
|
+
* - outcome score: `opts.scoreForTrace` (AppWorld `world.evaluate()` →
|
|
2044
|
+
* TGC/SGC) when supplied; else 1 when the trace had no error span, 0
|
|
2045
|
+
* when it did — a defensible default the caller can override.
|
|
2046
|
+
* - prompt / completion: carried into `raw` as token-count signals and,
|
|
2047
|
+
* when the first/last LLM span exposes `input.value` / `output.value`,
|
|
2048
|
+
* the verbatim text is preserved on the optional `promptText` /
|
|
2049
|
+
* `completionText` of the returned `OtlpTraceRunRecord`.
|
|
2050
|
+
*
|
|
2051
|
+
* Fail-loud: an OTLP file with zero valid spans throws. A trace with no
|
|
2052
|
+
* spans is impossible (a trace exists only because a span referenced it).
|
|
2053
|
+
* `validateRunRecord` runs on every row — a malformed projection throws
|
|
2054
|
+
* rather than silently producing a half-record.
|
|
2055
|
+
*/
|
|
2056
|
+
|
|
2057
|
+
interface OtlpToRunRecordsOptions {
|
|
2058
|
+
/** Logical experiment grouping for every produced record. */
|
|
2059
|
+
experimentId: string;
|
|
2060
|
+
/** Candidate (variant) id — the surface these traces exercised. The
|
|
2061
|
+
* bench passes the proposer label here so `compareProposers` can pair rows. */
|
|
2062
|
+
candidateId: string;
|
|
2063
|
+
/** Split assignment for every produced record. Default `'holdout'` —
|
|
2064
|
+
* ingested traces are evidence, not the optimizer's training pool. */
|
|
2065
|
+
splitTag?: RunSplitTag;
|
|
2066
|
+
/** Git SHA the traces were produced from. Default `'unknown'`. */
|
|
2067
|
+
commitSha?: string;
|
|
2068
|
+
/** sha256 of the effective prompt surface. Default `'unknown'`. */
|
|
2069
|
+
promptHash?: string;
|
|
2070
|
+
/** sha256 of the effective config. Default `'unknown'`. */
|
|
2071
|
+
configHash?: string;
|
|
2072
|
+
/** RNG seed recorded on every row. Default 0. */
|
|
2073
|
+
seed?: number;
|
|
2074
|
+
/**
|
|
2075
|
+
* Fallback model snapshot when the trace exposes no LLM model attribute
|
|
2076
|
+
* OR exposes a bare alias `validateRunRecord` would reject. The trace's
|
|
2077
|
+
* own model wins when it already carries a snapshot. Default
|
|
2078
|
+
* `'unknown@otlp'` (opaque-snapshot form the validator accepts).
|
|
2079
|
+
*/
|
|
2080
|
+
fallbackModel?: string;
|
|
964
2081
|
/**
|
|
965
|
-
*
|
|
966
|
-
*
|
|
2082
|
+
* USD per total token (input+output) used to price a trace when no
|
|
2083
|
+
* per-span cost attribute is present. When unset, an unpriced trace
|
|
2084
|
+
* records `costUsd: 0` AND `raw.cost_unpriced = 1` — the zero is flagged,
|
|
2085
|
+
* never silent.
|
|
967
2086
|
*/
|
|
968
|
-
|
|
969
|
-
runId?: string;
|
|
970
|
-
spanId?: string;
|
|
971
|
-
}): Promise<ReplayCache>;
|
|
972
|
-
/** Build a cache from an in-memory event list. */
|
|
973
|
-
static fromEvents(events: RawProviderEvent[]): Promise<ReplayCache>;
|
|
974
|
-
/** Number of cacheable (request, response) pairs in the cache. */
|
|
975
|
-
size(): number;
|
|
976
|
-
stats(): ReplayCacheStats;
|
|
977
|
-
/** Iterate every cached `(request, response)` pair in insertion order. */
|
|
978
|
-
entries(): IterableIterator<ReplayCacheEntry>;
|
|
2087
|
+
priceUsdPerToken?: number;
|
|
979
2088
|
/**
|
|
980
|
-
*
|
|
981
|
-
*
|
|
982
|
-
*
|
|
2089
|
+
* Score for a trace's outcome (AppWorld `world.evaluate()` → TGC/SGC, or
|
|
2090
|
+
* any [0,1] task-success signal). Keyed by `trace_id`; falls through to
|
|
2091
|
+
* the error-derived default (1 = no error span, 0 = had one) when the map
|
|
2092
|
+
* has no entry or the function returns undefined.
|
|
983
2093
|
*/
|
|
984
|
-
|
|
2094
|
+
scoreForTrace?: (traceId: string, span: TraceAggregate) => number | undefined;
|
|
2095
|
+
/**
|
|
2096
|
+
* Per-record judge metadata when an external judge produced the score.
|
|
2097
|
+
* Keyed by `trace_id`.
|
|
2098
|
+
*/
|
|
2099
|
+
judgeMetadataForTrace?: (traceId: string) => RunRecord['judgeMetadata'] | undefined;
|
|
985
2100
|
}
|
|
986
|
-
|
|
2101
|
+
/** A `RunRecord` plus the verbatim prompt/completion text when the trace's
|
|
2102
|
+
* LLM spans exposed it. The text is NOT on the validated `RunRecord`
|
|
2103
|
+
* (`outcome.raw` is numeric-only) but consumers ingesting full traces want
|
|
2104
|
+
* it — so it rides alongside. */
|
|
2105
|
+
interface OtlpTraceRunRecord {
|
|
2106
|
+
record: RunRecord;
|
|
2107
|
+
/** Verbatim first-LLM-span `input.value`, when present. */
|
|
2108
|
+
promptText?: string;
|
|
2109
|
+
/** Verbatim last-LLM-span `output.value`, when present. */
|
|
2110
|
+
completionText?: string;
|
|
2111
|
+
}
|
|
2112
|
+
/** Per-trace rollup the score callback can inspect. */
|
|
2113
|
+
interface TraceAggregate {
|
|
2114
|
+
traceId: string;
|
|
2115
|
+
spanCount: number;
|
|
2116
|
+
llmSpanCount: number;
|
|
2117
|
+
toolSpanCount: number;
|
|
2118
|
+
agentSpanCount: number;
|
|
2119
|
+
errorSpanCount: number;
|
|
2120
|
+
tokenUsage: RunTokenUsage;
|
|
2121
|
+
/** First error span's normalized status message, if any. */
|
|
2122
|
+
firstErrorMessage?: string;
|
|
2123
|
+
model: string;
|
|
2124
|
+
startTime: string;
|
|
2125
|
+
endTime: string;
|
|
2126
|
+
wallMs: number;
|
|
2127
|
+
}
|
|
2128
|
+
/**
|
|
2129
|
+
* Parse + aggregate an OTLP traces.jsonl string into validated
|
|
2130
|
+
* `RunRecord[]` (one per trace). Use {@link otlpToTraceRunRecords} when you
|
|
2131
|
+
* also want the verbatim prompt/completion text alongside each record.
|
|
2132
|
+
*/
|
|
2133
|
+
declare function otlpToRunRecords(otlpJsonl: string, opts: OtlpToRunRecordsOptions): RunRecord[];
|
|
2134
|
+
/** As {@link otlpToRunRecords} but returns the prompt/completion text too. */
|
|
2135
|
+
declare function otlpToTraceRunRecords(otlpJsonl: string, opts: OtlpToRunRecordsOptions): OtlpTraceRunRecord[];
|
|
2136
|
+
|
|
2137
|
+
/** Ax RLM prompt for bounded trace discovery and evidence-backed analysis. */
|
|
2138
|
+
declare const TRACE_ANALYST_ACTOR_DESCRIPTION = "You answer questions about an OTLP-shaped JSONL trace dataset using the trace tools provided in the `traces` namespace.\n\nDISCOVERY \u2192 NARROW \u2192 DEEP-READ protocol \u2014 follow exactly:\n\n1. ALWAYS call `traces.getDatasetOverview({})` FIRST without a regex_pattern. The result tells you total_traces, raw_jsonl_bytes, services, agents, models, and sample_trace_ids (real ids \u2014 never fabricate one).\n\n2. Use raw_jsonl_bytes to gauge how expensive raw scans will be. `filters.regex_pattern` is the one scan-heavy filter on getDatasetOverview / queryTraces / countTraces \u2014 narrow with indexed fields (has_errors, model_names, service_names, agent_names, time bounds) BEFORE adding a regex on a large dataset.\n\n3. To list more traces than the sample, call `traces.queryTraces({ filters?, limit, offset? })`. Each summary carries raw_jsonl_bytes \u2014 use it to choose between viewTrace and searchTrace BEFORE calling either.\n\n4. Per-trace inspection:\n - SMALL trace (raw_jsonl_bytes well under 150_000): call `traces.viewTrace({ trace_id })`. Returns all spans. Per-attribute payloads are head-capped at ~4KB; large `input.value` / `output.value` / `llm.input_messages` will show a `[trace-analyst truncated: N bytes]` marker.\n - LARGE trace (raw_jsonl_bytes near or above 150_000, or you saw an `oversized` response): use `traces.searchTrace({ trace_id, regex_pattern })` to get bounded SpanMatchRecords (span metadata + matched text + surrounding context). Then call `traces.viewSpans({ trace_id, span_ids: [...] })` for surgical reads (~16KB cap, 4\u00D7 higher than discovery), or `traces.searchSpan({ trace_id, span_id, regex_pattern })` for one large span. Stays bounded regardless of trace size.\n - Useful regex patterns: `STATUS_CODE_ERROR` (failures), tool names like `grep` or `view_trace`, error strings like `MaxTurnsExceeded`, model names, attribute keys.\n\n5. ONLY call viewTrace / viewSpans / searchTrace / searchSpan with trace/span ids you have already seen in sample_trace_ids, a queryTraces page, or a previous search result. Never invent ids.\n\n5a. **Result-shape contract** \u2014 searchTrace and searchSpan return `{ trace_id, hits, total_matches, has_more }`. Iterate `result.hits` (NOT result.matches). Each hit has `{ span_id, span_name, span_kind, attribute_path, matched_text, context_before, context_after, match_offset }`. viewTrace returns `{ trace_id, spans }` (or `oversized`). viewSpans returns `{ trace_id, spans, missing_span_ids, truncated_attribute_count }`. Never assume a field name \u2014 log the result shape first if unsure.\n\n6. If viewTrace returns an `oversized` summary instead of `spans`, DO NOT retry the same call. Read the summary's top_span_names, span_count, span_response_bytes_max, error_span_count to plan a follow-up: switch to searchTrace (or searchSpan for one large span), then viewSpans on a smaller, surgical span_ids set.\n\n7. If searchTrace or searchSpan returns has_more=true, REFINE the regex to be more specific rather than blindly raising max_matches.\n\n8. If a tool errors (invalid regex, range error), STOP and reconsider \u2014 don't retry with a guessed id or argument. Use the discovery tools above to recover.\n\n9. If a ~4KB-truncated payload from viewTrace / searchTrace matters for your answer, first try viewSpans on that span id (~16KB cap). If a 16KB-truncated payload from viewSpans still matters, narrow further with searchSpan against a more specific regex rather than asking for the full payload again.\n\n10. If maxDepth > 0 and the question splits into independent semantic branches, delegate well-defined subtasks to subagents using `await llmQuery(...)`. Pass narrow context and a focused query. Examples:\n\n const reviews = await llmQuery([\n { query: 'Drill into trace abc123 \u2014 what tool calls preceded the failure?', context: { trace_id: 'abc123' } },\n { query: 'Drill into trace def456 \u2014 same failure mode?', context: { trace_id: 'def456' } },\n ]);\n\nOBSERVABILITY rules:\n- Each non-final actor turn must emit at least one `console.log(...)` for evidence. Up to 3 logs per turn is fine when correlating multiple data sources (e.g. one log for findings list, one for source-file content, one for derived analysis).\n- Do NOT combine `console.log` with `final(...)` or `askClarification(...)` in the same turn \u2014 finish gathering data first, then call final on its own turn.\n- Reuse runtime variables across turns; don't recompute.\n- When done, call `await final(answer)` with the fully-formed report. The responder rewrites the answer into output fields; if you only pass a vague summary string the responder has nothing concrete to format.\n\nCRITICAL \u2014 `final()` payload contract for evidence-grounded analysis tasks:\n- Pass a STRUCTURED object as the second arg with the actual data the responder needs to format the answer. Do NOT pass abstract instructions; pass evidence.\n- Example for per-item verdict tasks:\n ```js\n await final(\"Format the per-item verdict report from the evidence below.\", {\n findings: [\n { id: 'sub-1-finding-1', claim: '...', verdict: 'TRUE-POSITIVE', evidence: 'lines 42-45 of contracts/X.sol show ...' },\n ...all items\n ],\n systemic_summary: '3 sentences I wrote based on the evidence above'\n });\n ```\n- Calling `final(\"answer\", {})` with no evidence is a failure mode \u2014 the responder will hallucinate or echo back the field names. Always include the gathered data.\n- Premature final after a single viewSpans call is INSUFFICIENT for per-finding analysis tasks. Read the requested attributes (e.g. `spans[i].attributes['redteam.finding.title']`), and for each one perform the requested cross-reference (e.g. read the source SPAN's `attributes['source.content']`).\n\nOUTPUT contract \u2014 your final answer must include:\n- A clear prose conclusion answering the user's question.\n- Trace ids and span ids cited as evidence for each claim.\n- Failure modes named in the user's domain language, with frequency and concrete examples.\n\nDo NOT invent trace ids, span ids, error messages, or model names. Every fact must be traceable to a tool result.";
|
|
2139
|
+
declare const TRACE_ANALYST_ACTOR_DESCRIPTION_VERSION = "trace-analyst-actor-v5-2026-05-06";
|
|
2140
|
+
/** Subagent prompt for focused trace-inspection subtasks. */
|
|
2141
|
+
declare const TRACE_ANALYST_SUBAGENT_DESCRIPTION = "You are a trace-analyst subagent. Your parent has delegated a focused trace-inspection question. Use the same DISCOVERY \u2192 NARROW \u2192 DEEP-READ protocol but stay tightly scoped: do exactly what was asked, return a concise compact answer, do NOT spawn further subagents unless the parent's question is genuinely multi-branch.\n\nCite trace ids and span ids for every claim. Do NOT invent ids.";
|
|
2142
|
+
|
|
2143
|
+
/**
|
|
2144
|
+
* `OtlpFileTraceStore` — read-only OTLP-JSONL trace store for the
|
|
2145
|
+
* trace-analyst.
|
|
2146
|
+
*
|
|
2147
|
+
* Wire shape. Each line of the input file is one OTLP-shaped span. The
|
|
2148
|
+
* store understands flattened OTLP JSONL plus the OpenInference vocab.
|
|
2149
|
+
* We project upstream's full
|
|
2150
|
+
* span shape down to `TraceAnalystSpan` lazily — full materialisation
|
|
2151
|
+
* only happens for the spans the agent actually requests.
|
|
2152
|
+
*
|
|
2153
|
+
* Indexing. On first read the store builds an in-memory index keyed
|
|
2154
|
+
* by `trace_id` carrying:
|
|
2155
|
+
* - byte offsets + lengths for each span line (for surgical reads
|
|
2156
|
+
* without re-parsing the whole file)
|
|
2157
|
+
* - a `TraceAnalystTraceSummary` rollup
|
|
2158
|
+
* - sets of services / agents / models / tools / has_errors
|
|
2159
|
+
* - byte size of the trace's JSONL slab
|
|
2160
|
+
*
|
|
2161
|
+
* Memory bound. The index keeps span metadata only — names, kinds,
|
|
2162
|
+
* offsets, status. Attribute payloads stay on disk until requested.
|
|
2163
|
+
* For a 50MB JSONL with 50k spans, the index is ~5MB.
|
|
2164
|
+
*
|
|
2165
|
+
* Concurrency. The store builds the index once on first read and
|
|
2166
|
+
* caches it. Subsequent reads reuse the index. The file is opened on
|
|
2167
|
+
* each read; we never hold a long-lived FD.
|
|
2168
|
+
*/
|
|
2169
|
+
|
|
2170
|
+
interface OtlpFileTraceStoreOptions {
|
|
2171
|
+
/** Path to the OTLP-JSONL file. */
|
|
2172
|
+
path: string;
|
|
2173
|
+
/** Override the discovery (`viewTrace`) per-attribute byte cap. */
|
|
2174
|
+
perAttributeViewBudget?: number;
|
|
2175
|
+
/** Override the surgical (`viewSpans`) per-attribute byte cap. */
|
|
2176
|
+
perAttributeSpanBudget?: number;
|
|
2177
|
+
/** Override the per-call ceiling that triggers oversized summaries. */
|
|
2178
|
+
perCallByteCeiling?: number;
|
|
2179
|
+
/** Override the per-match text budget. */
|
|
2180
|
+
perMatchTextBudget?: number;
|
|
987
2181
|
/**
|
|
988
|
-
*
|
|
989
|
-
*
|
|
990
|
-
*
|
|
991
|
-
*
|
|
2182
|
+
* Hard ceiling on the trace file size in bytes. The store reads the
|
|
2183
|
+
* whole file into one Buffer and indexes it in memory, so an
|
|
2184
|
+
* unbounded file OOMs the process. Above this size the store fails
|
|
2185
|
+
* loud with `TraceFileTooLargeError` instead of degrading silently.
|
|
2186
|
+
* Default 256 MiB.
|
|
992
2187
|
*/
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
2188
|
+
maxFileBytes?: number;
|
|
2189
|
+
}
|
|
2190
|
+
declare class OtlpFileTraceStore implements TraceAnalysisStore {
|
|
2191
|
+
private readonly path;
|
|
2192
|
+
private readonly perAttributeViewBudget;
|
|
2193
|
+
private readonly perAttributeSpanBudget;
|
|
2194
|
+
private readonly perCallByteCeiling;
|
|
2195
|
+
private readonly perMatchTextBudget;
|
|
2196
|
+
private readonly maxFileBytes;
|
|
2197
|
+
private indexPromise?;
|
|
2198
|
+
/** Cached UTF-8 buffer of the file. We pin it once because every
|
|
2199
|
+
* read needs slice access and re-reading on each call balloons the
|
|
2200
|
+
* syscall count. */
|
|
2201
|
+
private bufferPromise?;
|
|
2202
|
+
constructor(opts: OtlpFileTraceStoreOptions);
|
|
2203
|
+
getOverview(filters?: TraceAnalystFilters): Promise<DatasetOverview>;
|
|
2204
|
+
queryTraces(opts: {
|
|
2205
|
+
filters?: TraceAnalystFilters;
|
|
2206
|
+
limit: number;
|
|
2207
|
+
offset?: number;
|
|
2208
|
+
}): Promise<QueryTracesPage>;
|
|
2209
|
+
countTraces(filters?: TraceAnalystFilters): Promise<number>;
|
|
2210
|
+
viewTrace(opts: {
|
|
2211
|
+
trace_id: string;
|
|
2212
|
+
per_attribute_byte_cap?: number;
|
|
2213
|
+
}): Promise<ViewTraceResult>;
|
|
2214
|
+
viewSpans(opts: {
|
|
2215
|
+
trace_id: string;
|
|
2216
|
+
span_ids: readonly string[];
|
|
2217
|
+
per_attribute_byte_cap?: number;
|
|
2218
|
+
}): Promise<ViewSpansResult>;
|
|
2219
|
+
searchTrace(opts: {
|
|
2220
|
+
trace_id: string;
|
|
2221
|
+
regex_pattern: string;
|
|
2222
|
+
max_matches?: number;
|
|
2223
|
+
}): Promise<SearchTraceResult>;
|
|
2224
|
+
searchSpan(opts: {
|
|
2225
|
+
trace_id: string;
|
|
2226
|
+
span_id: string;
|
|
2227
|
+
regex_pattern: string;
|
|
2228
|
+
max_matches?: number;
|
|
2229
|
+
}): Promise<SearchSpanResult>;
|
|
2230
|
+
/** Force the index to materialise. Useful to amortise startup cost
|
|
2231
|
+
* before the first agent call. */
|
|
2232
|
+
ensureIndexed(): Promise<void>;
|
|
2233
|
+
private buffer;
|
|
2234
|
+
/** Stat-then-read so an oversized file fails loud BEFORE we allocate a
|
|
2235
|
+
* multi-hundred-MB Buffer and OOM the process. A missing file surfaces
|
|
2236
|
+
* as TraceFileMissingError; any other stat/read error propagates. */
|
|
2237
|
+
private readGuarded;
|
|
2238
|
+
private index;
|
|
2239
|
+
private buildIndex;
|
|
2240
|
+
private matchedTraces;
|
|
2241
|
+
private toSummary;
|
|
2242
|
+
private projectSpan;
|
|
2243
|
+
private buildOversizedSummary;
|
|
2244
|
+
private scanSpanForMatches;
|
|
1006
2245
|
}
|
|
2246
|
+
declare class TraceFileMissingError extends NotFoundError {
|
|
2247
|
+
constructor(path: string);
|
|
2248
|
+
}
|
|
2249
|
+
declare class TraceNotFoundError extends NotFoundError {
|
|
2250
|
+
readonly trace_id: string;
|
|
2251
|
+
constructor(trace_id: string);
|
|
2252
|
+
}
|
|
2253
|
+
declare class SpanNotFoundError extends NotFoundError {
|
|
2254
|
+
readonly trace_id: string;
|
|
2255
|
+
readonly span_id: string;
|
|
2256
|
+
constructor(trace_id: string, span_id: string);
|
|
2257
|
+
}
|
|
2258
|
+
|
|
1007
2259
|
/**
|
|
1008
|
-
*
|
|
1009
|
-
*
|
|
1010
|
-
* `LlmClientOptions.fetch` and `callLlm` becomes free.
|
|
2260
|
+
* Trace-analyst tool surface — six namespaced AxFunctions the analyst
|
|
2261
|
+
* agent calls from generated JS code via `traces.<name>(...)`.
|
|
1011
2262
|
*
|
|
1012
|
-
*
|
|
1013
|
-
*
|
|
1014
|
-
*
|
|
1015
|
-
* `
|
|
2263
|
+
* Discovery → narrow → deep-read protocol. Tool names + ordering
|
|
2264
|
+
* support RLM discovery:
|
|
2265
|
+
*
|
|
2266
|
+
* 1. `getDatasetOverview` (cheap) — first call, sizes the dataset
|
|
2267
|
+
* 2. `queryTraces` — paginated summaries with `raw_jsonl_bytes`
|
|
2268
|
+
* 3. `countTraces` — cheap pre-flight before regex
|
|
2269
|
+
* 4. `viewTrace` — full span list, oversized → summary
|
|
2270
|
+
* 5. `viewSpans` — surgical 16KB-cap reads
|
|
2271
|
+
* 6. `searchTrace` / `searchSpan` — bounded regex hits
|
|
2272
|
+
*
|
|
2273
|
+
* Failure mode. Tool handlers throw on bad input (invalid trace ids,
|
|
2274
|
+
* out-of-range pagination, malformed regex). Ax converts thrown errors
|
|
2275
|
+
* into actor-visible `[ERROR]` strings so the analyst can adjust on
|
|
2276
|
+
* the next turn instead of looping.
|
|
1016
2277
|
*/
|
|
1017
|
-
|
|
2278
|
+
|
|
2279
|
+
interface BuildTraceAnalystToolsOpts {
|
|
2280
|
+
store: TraceAnalysisStore;
|
|
2281
|
+
/** Override the default sample-trace-id slot count (20). Mostly for tests. */
|
|
2282
|
+
sampleTraceLimit?: number;
|
|
2283
|
+
}
|
|
1018
2284
|
/**
|
|
1019
|
-
*
|
|
1020
|
-
*
|
|
1021
|
-
* runs purely in-process over cached LLM outputs.
|
|
2285
|
+
* Build the trace-analyst function set. Pass the result into
|
|
2286
|
+
* `agent(...).functions.local`.
|
|
1022
2287
|
*/
|
|
1023
|
-
declare function
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
2288
|
+
declare function buildTraceAnalystTools(opts: BuildTraceAnalystToolsOpts): AxFunction[];
|
|
2289
|
+
/**
|
|
2290
|
+
* Convenience: same shape as `buildTraceAnalystTools` but returns the
|
|
2291
|
+
* grouped form expected when registering trace tools alongside other
|
|
2292
|
+
* agent function modules. */
|
|
2293
|
+
declare function traceAnalystFunctionGroup(opts: BuildTraceAnalystToolsOpts): {
|
|
2294
|
+
namespace: string;
|
|
2295
|
+
title: string;
|
|
2296
|
+
selectionCriteria: string;
|
|
2297
|
+
description: string;
|
|
2298
|
+
functions: AxFunction[];
|
|
2299
|
+
};
|
|
1027
2300
|
|
|
1028
|
-
export { AnalyzeTracesOptions, AnalyzeTracesResult, type CaptureFetchContext, type CaptureFetchOptions, DEFAULT_REDACTION_RULES, DatasetOverview, type ExportableSpan, type ExtractedUsage, type FlattenOtlpOptions, INPUT_VALUE, LLM_CACHED_TOKENS, LLM_CACHED_TOKEN_ATTR_KEYS, LLM_COST_ATTR_KEYS, LLM_COST_USD, LLM_INPUT_TOKENS, LLM_INPUT_TOKEN_ATTR_KEYS, LLM_MODEL_ATTR_KEYS, LLM_MODEL_NAME, LLM_OUTPUT_TOKENS, LLM_OUTPUT_TOKEN_ATTR_KEYS, OPENINFERENCE_SPAN_KIND, OTEL_AGENT_EVAL_SCOPE, OUTPUT_VALUE, type OtelExportConfig, type OtelExporter, type OtlpExport, OtlpFileTraceStore, type OtlpFileTraceStoreOptions, type OtlpFlatLine, type OtlpResourceSpans, type OtlpSpan, type OtlpToRunRecordsOptions, type OtlpTraceRunRecord, type ProjectedOtlpSpan, ProviderRedactor, QueryTracesPage, REDACTION_VERSION, RawProviderEvent, RawProviderSink, type RedactionReport, type RedactionRule, ReplayCache, type ReplayCacheEntry, ReplayCacheMissError, type ReplayCacheStats, type ReplayFetchOptions, Run, RunCompleteHook, RunCompleteHookContext, SPAN_KIND_ATTR_KEYS, SearchSpanResult, SearchTraceResult, SpanNotFoundError, TOOL_ARGS_CAPTURED, TOOL_LATENCY_MS, TOOL_NAME, TOOL_NAME_ATTR_KEYS, TRACE_ANALYST_ACTOR_DESCRIPTION, TRACE_ANALYST_ACTOR_DESCRIPTION_VERSION, TRACE_ANALYST_SUBAGENT_DESCRIPTION, ToolSpan, type ToolSpanOtlpInput, type TraceAggregate, TraceAnalysisStore, TraceAnalystFilters, type TraceAnalystHookOptions, TraceAnalystSpanKind, TraceAnalystSpanStatus, TraceFileMissingError, type TraceInsightContext, type TraceInsightFinding, type TraceInsightPanelRole, type TraceInsightPromptInput, type TraceInsightQualityGate, type TraceInsightQuestion, type TraceInsightReadiness, type TraceInsightSuite, type TraceInsightTask, TraceNotFoundError, TraceStore, type TraceStoreSource, type TraceStoreToOtlpOptions, type TracesToOtlpResult, ViewSpansResult, ViewTraceResult, applyToolSpanOtlpAttributes, asNumber, asString, buildTraceAnalystTools, buildTraceInsightContext, buildTraceInsightPrompt, captureFetchToRawSink, convertTraceStoresToOtlp, createOtelExporter, createOtelTracingStore, createReplayFetch, defaultTraceInsightPanel, describeTraceInsightScope, domainEvidencePattern, exportRunAsOtlp, extractOtlpAttributes, extractUsage, extractUsageFromResponse, extractUsageFromSse, firstNumberAttr, firstStringAttr, flattenOtlpExportToNdjson, inferDomainKeywords, inferOtlpKind, iterateRawCalls, otelRunCompleteHook, otlpToRunRecords, otlpToTraceRunRecords, planTraceInsightQuestions, projectOtlpFlatLine, readOtlpStatus, redactString, redactValue, scoreTraceInsightReadiness, stringField, tokenizeDomainWords, traceAnalystFunctionGroup, traceAnalystOnRunComplete, traceSpanKindToOpenInferenceKind };
|
|
2301
|
+
export { type AnalyzeTracesInput, type AnalyzeTracesOptions, type AnalyzeTracesResult, type AnalyzeTracesTurnSnapshot, type Artifact, type BudgetLedgerEntry, type BudgetSpec, type CaptureFetchContext, type CaptureFetchOptions, DEFAULT_REDACTION_RULES, DEFAULT_TRACE_ANALYST_BUDGETS, type DatasetOverview, type ErrorCluster, type EventFilter, type EventKind, type ExportableSpan, type ExtractUsageFromSseOptions, type ExtractedUsage, FAILURE_CLASSES, type FailureClass, FileSystemRawProviderSink, type FileSystemRawProviderSinkOptions, FileSystemTraceStore, type FileSystemTraceStoreOptions, type FlattenOtlpOptions, type GenericSpan, INPUT_VALUE, InMemoryRawProviderSink, type InMemoryRawProviderSinkOptions, InMemoryTraceStore, type JudgeSpan, LLM_CACHED_TOKENS, LLM_CACHED_TOKEN_ATTR_KEYS, LLM_CACHE_WRITE_TOKENS, LLM_CACHE_WRITE_TOKEN_ATTR_KEYS, LLM_COST_ATTR_KEYS, LLM_COST_USD, LLM_INPUT_TOKENS, LLM_INPUT_TOKEN_ATTR_KEYS, LLM_MODEL_ATTR_KEYS, LLM_MODEL_NAME, LLM_OUTPUT_TOKENS, LLM_OUTPUT_TOKEN_ATTR_KEYS, LLM_REASONING_TOKENS, LLM_REASONING_TOKEN_ATTR_KEYS, type LlmSpan, type Message, NoopRawProviderSink, OPENINFERENCE_SPAN_KIND, OTEL_AGENT_EVAL_SCOPE, OUTPUT_VALUE, type OtelExportConfig, type OtelExporter, type OtlpExport, OtlpFileTraceStore, type OtlpFileTraceStoreOptions, type OtlpFlatLine, type OtlpResourceSpans, type OtlpSpan, type OtlpToRunRecordsOptions, type OtlpTraceRunRecord, type ProjectedOtlpSpan, type ProviderRedactor, type QueryTracesPage, REDACTION_VERSION, RUN_COST_ATTR_KEYS, type RawProviderDirection, type RawProviderEvent, type RawProviderSink, type RawProviderSinkFilter, type RedactionReport, type RedactionRule, ReplayCache, type ReplayCacheEntry, ReplayCacheMissError, type ReplayCacheStats, type ReplayFetchOptions, type RetrievalSpan, type Run, type RunCompleteHook, type RunCompleteHookContext, type RunFilter, RunIntegrityError, type RunIntegrityExpectations, type RunIntegrityIssue, type RunIntegrityIssueCode, type RunIntegrityReport, type RunLayer, type RunOutcome$1 as RunOutcome, type RunStatus, SPAN_KIND_ATTR_KEYS, type SandboxSpan, type SearchSpanResult, type SearchTraceResult, type Span, type SpanBase, type SpanFilter, type SpanHandle, type SpanKind, type SpanMatchRecord, SpanNotFoundError, type SpanStatus, type SseUsageMode, TOOL_ARGS_CAPTURED, TOOL_LATENCY_MS, TOOL_NAME, TOOL_NAME_ATTR_KEYS, TRACE_ANALYST_ACTOR_DESCRIPTION, TRACE_ANALYST_ACTOR_DESCRIPTION_VERSION, TRACE_ANALYST_SUBAGENT_DESCRIPTION, TRACE_ANALYST_TRUNCATION_MARKER_PREFIX, TRACE_SCHEMA_VERSION, type ToolSpan, type ToolSpanOtlpInput, type TraceAggregate, type TraceAnalysisStore, type TraceAnalystByteBudgets, type TraceAnalystFilters, type TraceAnalystHookOptions, type TraceAnalystSpan, type TraceAnalystSpanKind, type TraceAnalystSpanStatus, type TraceAnalystTraceSummary, TraceEmitter, type TraceEmitterOptions, type TraceEvent, TraceFileMissingError, type TraceInsightContext, type TraceInsightFinding, type TraceInsightPanelRole, type TraceInsightPromptInput, type TraceInsightQualityGate, type TraceInsightQuestion, type TraceInsightReadiness, type TraceInsightSuite, type TraceInsightTask, TraceNotFoundError, type TraceStore, type TraceStoreSource, type TraceStoreToOtlpOptions, type TracesToOtlpResult, type ViewSpansResult, type ViewTraceOversized, type ViewTraceResult, aggregateLlm, analyzeTraces, applyToolSpanOtlpAttributes, argHash, asNumber, asString, assertRunCaptured, buildTraceAnalystTools, buildTraceInsightContext, buildTraceInsightPrompt, captureFetchToRawSink, convertTraceStoresToOtlp, createOtelExporter, createOtelTracingStore, createReplayFetch, defaultProviderRedactor, defaultTraceInsightPanel, describeTraceInsightScope, domainEvidencePattern, exportRunAsOtlp, extractOtlpAttributes, extractUsage, extractUsageFromResponse, extractUsageFromSse, firstNumberAttr, firstStringAttr, flattenOtlpExportToNdjson, groupBy, hasCapturedToolArgs, inferDomainKeywords, inferOtlpKind, isJudgeSpan, isLlmSpan, isRetrievalSpan, isSandboxSpan, isToolSpan, iterateRawCalls, judgeSpans, llmSpanFromProvider, llmSpans, otelRunCompleteHook, otlpToRunRecords, otlpToTraceRunRecords, planTraceInsightQuestions, projectOtlpFlatLine, providerFromBaseUrl, readOtlpStatus, redactString, redactValue, runFailureClass, runsForScenario, scoreTraceInsightReadiness, stringField, throwIfRunIncomplete, tokenizeDomainWords, toolSpans, traceAnalystFunctionGroup, traceAnalystOnRunComplete, traceSpanKindToOpenInferenceKind };
|