@tangle-network/agent-eval 0.119.0 → 0.120.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/CHANGELOG.md +25 -3
  2. package/README.md +0 -26
  3. package/dist/analyst/index.d.ts +30 -39
  4. package/dist/analyst/index.js +9 -5
  5. package/dist/analyst/index.js.map +1 -1
  6. package/dist/benchmarks/index.d.ts +11 -5
  7. package/dist/benchmarks/index.js +7 -7
  8. package/dist/campaign/index.d.ts +121 -66
  9. package/dist/campaign/index.js +26 -10
  10. package/dist/{chunk-RLCJQ6VZ.js → chunk-32BZXMSO.js} +385 -120
  11. package/dist/chunk-32BZXMSO.js.map +1 -0
  12. package/dist/{chunk-XJ7JVCHB.js → chunk-3A246TSA.js} +2 -2
  13. package/dist/{chunk-LMJ2TGWJ.js → chunk-JM2SKQMS.js} +2 -2
  14. package/dist/{chunk-F6YUH3L4.js → chunk-JN2FCO5W.js} +14 -168
  15. package/dist/chunk-JN2FCO5W.js.map +1 -0
  16. package/dist/{chunk-D7AEXSM5.js → chunk-PICTDURQ.js} +2 -2
  17. package/dist/{chunk-6QIM2EAP.js → chunk-PXD6ZFNY.js} +99 -1
  18. package/dist/chunk-PXD6ZFNY.js.map +1 -0
  19. package/dist/{chunk-GERDAIAL.js → chunk-QWMPPZ3X.js} +3 -3
  20. package/dist/{chunk-7A4LIMMY.js → chunk-S5TT5R3L.js} +229 -97
  21. package/dist/chunk-S5TT5R3L.js.map +1 -0
  22. package/dist/{chunk-ZYHJNKI3.js → chunk-ULOKLHIQ.js} +42 -5
  23. package/dist/chunk-ULOKLHIQ.js.map +1 -0
  24. package/dist/{chunk-FIUFRXP3.js → chunk-WW2A73HW.js} +49 -91
  25. package/dist/chunk-WW2A73HW.js.map +1 -0
  26. package/dist/{chunk-PAHNGS65.js → chunk-XDIRG3TO.js} +175 -11
  27. package/dist/chunk-XDIRG3TO.js.map +1 -0
  28. package/dist/contract/index.d.ts +76 -43
  29. package/dist/contract/index.js +596 -32
  30. package/dist/contract/index.js.map +1 -1
  31. package/dist/index.d.ts +92 -75
  32. package/dist/index.js +38 -35
  33. package/dist/index.js.map +1 -1
  34. package/dist/openapi.json +1 -1
  35. package/dist/rl.d.ts +11 -5
  36. package/dist/{run-campaign-OBXSN5WK.js → run-campaign-HNFPJET4.js} +2 -2
  37. package/dist/traces.d.ts +39 -25
  38. package/dist/traces.js +3 -5
  39. package/docs/trace-analysis.md +6 -2
  40. package/package.json +2 -7
  41. package/dist/chunk-6QIM2EAP.js.map +0 -1
  42. package/dist/chunk-7A4LIMMY.js.map +0 -1
  43. package/dist/chunk-F6YUH3L4.js.map +0 -1
  44. package/dist/chunk-FIUFRXP3.js.map +0 -1
  45. package/dist/chunk-PAHNGS65.js.map +0 -1
  46. package/dist/chunk-RLCJQ6VZ.js.map +0 -1
  47. package/dist/chunk-ZYHJNKI3.js.map +0 -1
  48. package/dist/primeintellect/index.d.ts +0 -311
  49. package/dist/primeintellect/index.js +0 -348
  50. package/dist/primeintellect/index.js.map +0 -1
  51. /package/dist/{chunk-XJ7JVCHB.js.map → chunk-3A246TSA.js.map} +0 -0
  52. /package/dist/{chunk-LMJ2TGWJ.js.map → chunk-JM2SKQMS.js.map} +0 -0
  53. /package/dist/{chunk-D7AEXSM5.js.map → chunk-PICTDURQ.js.map} +0 -0
  54. /package/dist/{chunk-GERDAIAL.js.map → chunk-QWMPPZ3X.js.map} +0 -0
  55. /package/dist/{run-campaign-OBXSN5WK.js.map → run-campaign-HNFPJET4.js.map} +0 -0
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  OtlpFileTraceStore,
3
- TraceFileMissingError,
4
- buildTraceAnalystTools
5
- } from "./chunk-6QIM2EAP.js";
3
+ buildTraceAnalystTools,
4
+ runTraceAnalysisLoop
5
+ } from "./chunk-PXD6ZFNY.js";
6
6
 
7
7
  // src/trace-analyst/prompts.ts
8
8
  var TRACE_ANALYST_ACTOR_DESCRIPTION = `You answer questions about an OTLP-shaped JSONL trace dataset using the trace tools provided in the \`traces\` namespace.
@@ -32,51 +32,33 @@ DISCOVERY \u2192 NARROW \u2192 DEEP-READ protocol \u2014 follow exactly:
32
32
 
33
33
  9. 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.
34
34
 
35
- 10. 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:
35
+ 10. If the question splits into independent reasoning branches, use bounded \`llmQuery(...)\` calls over evidence you already loaded. Subqueries cannot inspect the trace store, so pass the exact trace excerpts they need. Example:
36
36
 
37
37
  const reviews = await llmQuery([
38
- { query: 'Drill into trace abc123 \u2014 what tool calls preceded the failure?', context: { trace_id: 'abc123' } },
39
- { query: 'Drill into trace def456 \u2014 same failure mode?', context: { trace_id: 'def456' } },
38
+ { query: 'Classify the failure mechanism in this excerpt.', context: traceAbcExcerpt },
39
+ { query: 'Classify the failure mechanism in this excerpt.', context: traceDefExcerpt },
40
40
  ]);
41
41
 
42
42
  OBSERVABILITY rules:
43
- - 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).
44
- - 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.
43
+ - Each discovery turn must emit at least one concise \`console.log(...)\` showing what evidence was learned.
44
+ - Finish gathering evidence before submitting the analysis.
45
45
  - Reuse runtime variables across turns; don't recompute.
46
- - 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.
47
-
48
- CRITICAL \u2014 \`final()\` payload contract for evidence-grounded analysis tasks:
49
- - 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.
50
- - Example for per-item verdict tasks:
51
- \`\`\`js
52
- await final("Format the per-item verdict report from the evidence below.", {
53
- findings: [
54
- { id: 'sub-1-finding-1', claim: '...', verdict: 'TRUE-POSITIVE', evidence: 'lines 42-45 of contracts/X.sol show ...' },
55
- ...all items
56
- ],
57
- systemic_summary: '3 sentences I wrote based on the evidence above'
58
- });
59
- \`\`\`
60
- - 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.
61
- - 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']\`).
62
46
 
63
47
  OUTPUT contract \u2014 your final answer must include:
64
48
  - A clear prose conclusion answering the user's question.
65
49
  - Trace ids and span ids cited as evidence for each claim.
66
50
  - Failure modes named in the user's domain language, with frequency and concrete examples.
51
+ - A concise findings array containing only claims supported by inspected evidence.
67
52
 
68
53
  Do NOT invent trace ids, span ids, error messages, or model names. Every fact must be traceable to a tool result.`;
69
- var TRACE_ANALYST_ACTOR_DESCRIPTION_VERSION = "trace-analyst-actor-v5-2026-05-06";
70
- var 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.
71
-
72
- Cite trace ids and span ids for every claim. Do NOT invent ids.`;
54
+ var TRACE_ANALYST_ACTOR_DESCRIPTION_VERSION = "trace-analyst-actor-v6-2026-07-14";
73
55
 
74
56
  // src/trace-analyst/analyst.ts
75
- import { AxJSRuntime, agent } from "@ax-llm/ax";
76
57
  async function analyzeTraces(input, options) {
77
58
  if (!input.question || typeof input.question !== "string") {
78
59
  throw new TypeError("analyzeTraces: input.question must be a non-empty string");
79
60
  }
61
+ rejectRemovedOptions(options);
80
62
  const store = typeof options.source === "string" ? new OtlpFileTraceStore({ path: options.source }) : options.source;
81
63
  if (store instanceof OtlpFileTraceStore) {
82
64
  await store.ensureIndexed();
@@ -93,6 +75,7 @@ async function analyzeTraces(input, options) {
93
75
  }
94
76
  const actorTurnCallback = async (turn) => {
95
77
  const snap = {
78
+ stage: turn.stage,
96
79
  turn: turn.turn,
97
80
  isError: turn.isError,
98
81
  code: turn.code,
@@ -109,81 +92,57 @@ async function analyzeTraces(input, options) {
109
92
  }
110
93
  if (options.onTurn) await options.onTurn(snap);
111
94
  };
112
- const maxDepth = options.maxDepth ?? 1;
95
+ const maxSubqueries = options.maxSubqueries ?? 4;
113
96
  const maxTurns = options.maxTurns ?? 12;
114
- const maxParallelSubagents = options.maxParallelSubagents ?? 2;
97
+ const maxParallelSubqueries = options.maxParallelSubqueries ?? 2;
115
98
  const maxRuntimeChars = options.maxRuntimeChars ?? 6e3;
116
- const functions = tools;
117
- const analyst = agent(
118
- // `reasoning!` is an internal (Ax `!`) scratchpad field: generated first to
119
- // force reason-before-conclude, stripped from the returned output — so the
120
- // consumed shape stays { answer, findings }. Brings the trace-analyst to the
121
- // same prose-first CoT ordering the kind-factory gets from its `report` field.
122
- "question:string -> reasoning!:string, answer:string, findings:string[]",
123
- {
124
- agentIdentity: {
125
- name: "TraceAnalyst",
126
- description: "Analyzes OTLP-shaped JSONL traces using bounded discovery tools to identify systemic failure modes."
127
- },
128
- contextFields: ["question"],
129
- runtime: new AxJSRuntime({
130
- permissions: [],
131
- blockDynamicImport: true,
132
- allowedModules: [],
133
- freezeIntrinsics: true,
134
- blockShadowRealm: true,
135
- // RLM stdout mode relies on runtime bindings persisting across turns.
136
- preventGlobalThisExtensions: false
137
- }),
138
- mode: maxDepth > 0 ? "advanced" : "simple",
139
- recursionOptions: maxDepth > 0 ? { maxDepth } : void 0,
99
+ let completed;
100
+ try {
101
+ completed = await runTraceAnalysisLoop({
102
+ id: "TraceAnalyst",
103
+ description: "Analyzes OTLP-shaped JSONL traces using bounded discovery tools to identify systemic failure modes.",
104
+ prompt: `${options.actorDescription ?? TRACE_ANALYST_ACTOR_DESCRIPTION}
105
+
106
+ The report must answer the user's question, and findings must be an array of concise evidence-backed strings.`,
107
+ question: input.question,
108
+ ai: options.ai,
109
+ ...options.model ? { model: options.model } : {},
110
+ tools,
111
+ findingType: "string",
112
+ maxSubqueries,
113
+ maxParallelSubqueries,
140
114
  maxTurns,
141
115
  maxRuntimeChars,
142
- maxBatchedLlmQueryConcurrency: maxParallelSubagents,
143
- promptLevel: "detailed",
144
- // Trace analysis depends on exact prior tool results and runtime variables.
145
- contextPolicy: { preset: "full", budget: "balanced" },
146
- functions,
147
- actorOptions: {
148
- description: options.actorDescription ?? TRACE_ANALYST_ACTOR_DESCRIPTION,
149
- ...options.model ? { model: options.model } : {},
150
- // Keep actor messages tool-call/content shaped across reasoning models.
151
- showThoughts: false,
152
- thinkingTokenBudget: "none"
153
- },
154
- responderOptions: {
155
- ...options.model ? { model: options.model } : {},
156
- description: options.subagentDescription ?? TRACE_ANALYST_SUBAGENT_DESCRIPTION,
157
- showThoughts: false
158
- },
159
- actorTurnCallback,
160
- bubbleErrors: [TraceFileMissingError]
161
- }
162
- );
163
- let result;
164
- try {
165
- result = await analyst.forward(options.ai, { question: input.question });
116
+ ...options.signal ? { signal: options.signal } : {},
117
+ onTurn: actorTurnCallback
118
+ });
166
119
  } finally {
167
120
  if (progressFs) {
168
121
  await new Promise((resolve) => progressFs.end(() => resolve()));
169
122
  }
170
123
  }
171
124
  return {
172
- answer: typeof result.answer === "string" ? result.answer : String(result.answer ?? ""),
173
- findings: Array.isArray(result.findings) ? result.findings.filter((s) => typeof s === "string") : [],
125
+ answer: completed.report,
126
+ findings: completed.findings,
174
127
  turns,
175
128
  turnCount: turns.length,
176
- usage: normalizeRoleArrays(analyst.getUsage()),
177
- chatLog: normalizeRoleArrays(analyst.getChatLog()),
129
+ usage: { actor: normalizeRecordArray(completed.usage), responder: [] },
130
+ chatLog: { actor: normalizeRecordArray(completed.chatLog), responder: [] },
178
131
  actorPromptVersion: TRACE_ANALYST_ACTOR_DESCRIPTION_VERSION
179
132
  };
180
133
  }
181
- function normalizeRoleArrays(value) {
182
- const record = value && typeof value === "object" ? value : {};
183
- return {
184
- actor: normalizeRecordArray(record.actor),
185
- responder: normalizeRecordArray(record.responder)
186
- };
134
+ function rejectRemovedOptions(options) {
135
+ const supplied = options;
136
+ const migrations = [
137
+ ["maxDepth", "maxSubqueries"],
138
+ ["maxParallelSubagents", "maxParallelSubqueries"],
139
+ ["subagentDescription", "actorDescription"]
140
+ ];
141
+ for (const [removed, replacement] of migrations) {
142
+ if (removed in supplied) {
143
+ throw new TypeError(`analyzeTraces: '${removed}' is unsupported; use '${replacement}'`);
144
+ }
145
+ }
187
146
  }
188
147
  function normalizeRecordArray(value) {
189
148
  if (!Array.isArray(value)) return [];
@@ -195,7 +154,6 @@ function normalizeRecordArray(value) {
195
154
  export {
196
155
  TRACE_ANALYST_ACTOR_DESCRIPTION,
197
156
  TRACE_ANALYST_ACTOR_DESCRIPTION_VERSION,
198
- TRACE_ANALYST_SUBAGENT_DESCRIPTION,
199
157
  analyzeTraces
200
158
  };
201
- //# sourceMappingURL=chunk-FIUFRXP3.js.map
159
+ //# sourceMappingURL=chunk-WW2A73HW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/trace-analyst/prompts.ts","../src/trace-analyst/analyst.ts"],"sourcesContent":["/** Ax RLM prompt for bounded trace discovery and evidence-backed analysis. */\n\nexport 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 → NARROW → DEEP-READ protocol — 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 — 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 — 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 — 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× 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** — 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 — 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 — 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 the question splits into independent reasoning branches, use bounded \\`llmQuery(...)\\` calls over evidence you already loaded. Subqueries cannot inspect the trace store, so pass the exact trace excerpts they need. Example:\n\n const reviews = await llmQuery([\n { query: 'Classify the failure mechanism in this excerpt.', context: traceAbcExcerpt },\n { query: 'Classify the failure mechanism in this excerpt.', context: traceDefExcerpt },\n ]);\n\nOBSERVABILITY rules:\n- Each discovery turn must emit at least one concise \\`console.log(...)\\` showing what evidence was learned.\n- Finish gathering evidence before submitting the analysis.\n- Reuse runtime variables across turns; don't recompute.\n\nOUTPUT contract — 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- A concise findings array containing only claims supported by inspected evidence.\n\nDo NOT invent trace ids, span ids, error messages, or model names. Every fact must be traceable to a tool result.`\n\nexport const TRACE_ANALYST_ACTOR_DESCRIPTION_VERSION = 'trace-analyst-actor-v6-2026-07-14'\n","import type { AxAgentActorTurnCallbackArgs, AxAIService, AxFunction } from '@ax-llm/ax'\nimport { runTraceAnalysisLoop, type TraceAnalysisLoopResult } from './loop'\nimport { TRACE_ANALYST_ACTOR_DESCRIPTION, TRACE_ANALYST_ACTOR_DESCRIPTION_VERSION } from './prompts'\nimport type { TraceAnalysisStore } from './store'\nimport { OtlpFileTraceStore } from './store-otlp'\nimport { buildTraceAnalystTools } from './tools'\n\nexport interface AnalyzeTracesInput {\n /** The user-facing question. Domain framing belongs here, not in the\n * actor description. */\n question: string\n}\n\nexport interface AnalyzeTracesResult {\n /** The actor's submitted prose answer. */\n answer: string\n /** Bulleted findings from the actor's structured completion. */\n findings: string[]\n /** Per-turn snapshots captured via `actorTurnCallback`. */\n turns: AnalyzeTracesTurnSnapshot[]\n /** Total turns the actor took. */\n turnCount: number\n /** Token usage by role. */\n usage: TraceAnalystUsage\n /** Full system + assistant + tool message log by role. */\n chatLog: TraceAnalystChatLog\n /** Prompt version that produced this run. */\n actorPromptVersion: string\n}\n\nexport interface TraceAnalystUsage {\n actor: TraceAnalystUsageEntry[]\n responder: TraceAnalystUsageEntry[]\n}\n\nexport interface TraceAnalystUsageEntry {\n [key: string]: unknown\n}\n\nexport interface TraceAnalystChatLog {\n actor: TraceAnalystChatMessage[]\n responder: TraceAnalystChatMessage[]\n}\n\nexport interface TraceAnalystChatMessage {\n [key: string]: unknown\n}\n\nexport interface AnalyzeTracesTurnSnapshot {\n stage: AxAgentActorTurnCallbackArgs['stage']\n turn: number\n isError: boolean\n /** The JS code the actor produced for this turn. */\n code: string\n /** The formatted action-log entry the actor sees on the next turn. */\n output: string\n /** Provider thought (when `executorOptions.showThoughts` is true and the\n * provider returns it). */\n thought?: string\n}\n\nexport interface AnalyzeTracesOptions {\n /** Trace data source. Pass either an OTLP-JSONL path or a custom store. */\n source: string | TraceAnalysisStore\n /** Caller-provided AxAIService. */\n ai: AxAIService\n /** Model id forwarded to the actor. */\n model?: string\n /** Maximum model subqueries. 0 disables model fan-out. Default 4. */\n maxSubqueries?: number\n /** Maximum actor turns. Default 12. */\n maxTurns?: number\n /** Maximum parallel model subqueries. Default 2. */\n maxParallelSubqueries?: number\n /** Cancels in-flight model and tool work. */\n signal?: AbortSignal\n /** Override the actor description. */\n actorDescription?: string\n /** Per-turn observability hook. */\n onTurn?: (turn: AnalyzeTracesTurnSnapshot) => void | Promise<void>\n /** Override max runtime characters per turn. Default 6000. */\n maxRuntimeChars?: number\n /** When set, every turn's snapshot is appended to this JSONL file\n * immediately. If the analyst crashes mid-loop (provider 503,\n * network error, validator reject) the partial reasoning is still\n * on disk for diagnosis and recovery. */\n progressLogPath?: string\n}\n\n/**\n * Run the trace analyst.\n *\n * Throws:\n * - `TraceFileMissingError` if `source` is a path and doesn't exist.\n * - `AxAgentClarificationError` if the analyst asks for clarification.\n * - Provider errors (auth, rate limits) propagate from the AI service.\n */\nexport async function analyzeTraces(\n input: AnalyzeTracesInput,\n options: AnalyzeTracesOptions,\n): Promise<AnalyzeTracesResult> {\n if (!input.question || typeof input.question !== 'string') {\n throw new TypeError('analyzeTraces: input.question must be a non-empty string')\n }\n rejectRemovedOptions(options)\n\n const store: TraceAnalysisStore =\n typeof options.source === 'string'\n ? new OtlpFileTraceStore({ path: options.source })\n : options.source\n\n // Pre-warm file stores so missing inputs fail before the RLM starts.\n if (store instanceof OtlpFileTraceStore) {\n await store.ensureIndexed()\n }\n\n const tools: AxFunction[] = buildTraceAnalystTools({ store })\n const turns: AnalyzeTracesTurnSnapshot[] = []\n\n // Persist each turn as JSONL so interrupted analyst runs keep useful evidence.\n let progressFs: import('node:fs').WriteStream | undefined\n if (options.progressLogPath) {\n const { createWriteStream } = await import('node:fs')\n const { mkdir } = await import('node:fs/promises')\n const { dirname } = await import('node:path')\n await mkdir(dirname(options.progressLogPath), { recursive: true })\n progressFs = createWriteStream(options.progressLogPath, { flags: 'a' })\n }\n\n const actorTurnCallback = async (turn: AxAgentActorTurnCallbackArgs): Promise<void> => {\n const snap: AnalyzeTracesTurnSnapshot = {\n stage: turn.stage,\n turn: turn.turn,\n isError: turn.isError,\n code: turn.code,\n output: turn.output,\n thought: turn.thought,\n }\n turns.push(snap)\n if (progressFs) {\n try {\n progressFs.write(`${JSON.stringify({ ...snap, ts: Date.now() })}\\n`)\n } catch {\n // Progress logging must never fail the analyst.\n }\n }\n if (options.onTurn) await options.onTurn(snap)\n }\n\n const maxSubqueries = options.maxSubqueries ?? 4\n const maxTurns = options.maxTurns ?? 12\n const maxParallelSubqueries = options.maxParallelSubqueries ?? 2\n const maxRuntimeChars = options.maxRuntimeChars ?? 6000\n let completed: TraceAnalysisLoopResult<string>\n try {\n completed = await runTraceAnalysisLoop({\n id: 'TraceAnalyst',\n description:\n 'Analyzes OTLP-shaped JSONL traces using bounded discovery tools to identify systemic failure modes.',\n prompt: `${options.actorDescription ?? TRACE_ANALYST_ACTOR_DESCRIPTION}\n\nThe report must answer the user's question, and findings must be an array of concise evidence-backed strings.`,\n question: input.question,\n ai: options.ai,\n ...(options.model ? { model: options.model } : {}),\n tools,\n findingType: 'string',\n maxSubqueries,\n maxParallelSubqueries,\n maxTurns,\n maxRuntimeChars,\n ...(options.signal ? { signal: options.signal } : {}),\n onTurn: actorTurnCallback,\n })\n } finally {\n if (progressFs) {\n await new Promise<void>((resolve) => progressFs!.end(() => resolve()))\n }\n }\n\n return {\n answer: completed.report,\n findings: completed.findings,\n turns,\n turnCount: turns.length,\n usage: { actor: normalizeRecordArray(completed.usage), responder: [] },\n chatLog: { actor: normalizeRecordArray(completed.chatLog), responder: [] },\n actorPromptVersion: TRACE_ANALYST_ACTOR_DESCRIPTION_VERSION,\n }\n}\n\nfunction rejectRemovedOptions(options: AnalyzeTracesOptions): void {\n const supplied = options as unknown as Record<string, unknown>\n const migrations = [\n ['maxDepth', 'maxSubqueries'],\n ['maxParallelSubagents', 'maxParallelSubqueries'],\n ['subagentDescription', 'actorDescription'],\n ] as const\n for (const [removed, replacement] of migrations) {\n if (removed in supplied) {\n throw new TypeError(`analyzeTraces: '${removed}' is unsupported; use '${replacement}'`)\n }\n }\n}\n\nfunction normalizeRecordArray(value: unknown): Record<string, unknown>[] {\n if (!Array.isArray(value)) return []\n return value.map((item) =>\n item && typeof item === 'object' ? { ...(item as Record<string, unknown>) } : { value: item },\n )\n}\n"],"mappings":"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","names":[]}
@@ -21,13 +21,25 @@ var BackendIntegrityError = class extends AgentEvalError {
21
21
  }
22
22
  report;
23
23
  };
24
- function isStubRecord(rec) {
25
- return rec.tokenUsage.input === 0 && rec.tokenUsage.output === 0;
24
+ function summarizeBackendIntegrity(records) {
25
+ return summarizeBackendUsage(
26
+ records.map((record) => ({
27
+ inputTokens: record.tokenUsage.input,
28
+ outputTokens: record.tokenUsage.output,
29
+ costUsd: record.costUsd
30
+ }))
31
+ );
26
32
  }
27
- function isUncostedRecord(rec) {
28
- return rec.tokenUsage.output > 0 && rec.costUsd === 0;
33
+ function summarizeAgentReceiptIntegrity(receipts) {
34
+ return summarizeBackendUsage(
35
+ receipts.filter((receipt) => receipt.channel === "agent").map((receipt) => ({
36
+ inputTokens: receipt.inputTokens,
37
+ outputTokens: receipt.outputTokens,
38
+ costUsd: receipt.costUsd
39
+ }))
40
+ );
29
41
  }
30
- function summarizeBackendIntegrity(records) {
42
+ function summarizeBackendUsage(records) {
31
43
  const totalRecords = records.length;
32
44
  let stubRecords = 0;
33
45
  let realRecords = 0;
@@ -36,12 +48,12 @@ function summarizeBackendIntegrity(records) {
36
48
  let totalOutputTokens = 0;
37
49
  let totalCostUsd = 0;
38
50
  for (const rec of records) {
39
- totalInputTokens += rec.tokenUsage.input;
40
- totalOutputTokens += rec.tokenUsage.output;
51
+ totalInputTokens += rec.inputTokens;
52
+ totalOutputTokens += rec.outputTokens;
41
53
  totalCostUsd += rec.costUsd;
42
- if (isStubRecord(rec)) stubRecords++;
54
+ if (rec.inputTokens === 0 && rec.outputTokens === 0) stubRecords++;
43
55
  else realRecords++;
44
- if (isUncostedRecord(rec)) uncostedRecords++;
56
+ if (rec.outputTokens > 0 && rec.costUsd === 0) uncostedRecords++;
45
57
  }
46
58
  const verdict = totalRecords === 0 ? "stub" : stubRecords === totalRecords ? "stub" : stubRecords === 0 ? "real" : "mixed";
47
59
  const diagnosis = buildDiagnosis({
@@ -100,6 +112,13 @@ function buildDiagnosis(r) {
100
112
  }
101
113
  function assertRealBackend(records, opts = {}) {
102
114
  const report = summarizeBackendIntegrity(records);
115
+ return assertBackendReport(report, opts);
116
+ }
117
+ function assertRealAgentReceipts(receipts, opts = {}) {
118
+ const report = summarizeAgentReceiptIntegrity(receipts);
119
+ return assertBackendReport(report, opts);
120
+ }
121
+ function assertBackendReport(report, opts) {
103
122
  const allowMixed = opts.allowMixed ?? true;
104
123
  if (report.verdict === "stub") {
105
124
  throw new BackendIntegrityError(
@@ -245,6 +264,135 @@ function cachedJudge(judge, store, options) {
245
264
  return wrapped;
246
265
  }
247
266
 
267
+ // src/campaign/coverage.ts
268
+ function assertCampaignDesign(scenarios, reps) {
269
+ if (!Number.isSafeInteger(reps) || reps < 1) {
270
+ throw new Error("campaign design requires reps to be a positive safe integer");
271
+ }
272
+ const scenarioIds = /* @__PURE__ */ new Set();
273
+ for (const scenario of scenarios) {
274
+ if (typeof scenario.id !== "string" || scenario.id.trim().length === 0) {
275
+ throw new Error("campaign design requires every scenario to have a non-empty id");
276
+ }
277
+ if (scenarioIds.has(scenario.id)) {
278
+ throw new Error(`campaign design contains duplicate scenario id '${scenario.id}'`);
279
+ }
280
+ if (typeof scenario.kind !== "string" || scenario.kind.trim().length === 0) {
281
+ throw new Error("campaign design requires every scenario to have a non-empty kind");
282
+ }
283
+ scenarioIds.add(scenario.id);
284
+ }
285
+ }
286
+ function campaignScenarioIdentity(scenario) {
287
+ assertCampaignDesign([scenario], 1);
288
+ return {
289
+ id: scenario.id,
290
+ kind: scenario.kind,
291
+ scenarioDigest: `sha256:${contentHash(scenario)}`
292
+ };
293
+ }
294
+ function campaignSplitDigestFromIdentities(scenarios, reps) {
295
+ assertCampaignDesign(scenarios, reps);
296
+ for (const scenario of scenarios) {
297
+ if (!/^sha256:[a-f0-9]{64}$/.test(scenario.scenarioDigest)) {
298
+ throw new Error(`campaign scenario '${scenario.id}' has an invalid digest`);
299
+ }
300
+ }
301
+ return `sha256:${contentHash({
302
+ schema: "tangle.campaign-split",
303
+ scenarios: scenarios.map(({ id, kind, scenarioDigest }) => ({ id, kind, scenarioDigest })),
304
+ reps
305
+ })}`;
306
+ }
307
+ function campaignSplitDigest(scenarios, reps) {
308
+ assertCampaignDesign(scenarios, reps);
309
+ return campaignSplitDigestFromIdentities(scenarios.map(campaignScenarioIdentity), reps);
310
+ }
311
+ function assertCampaignSplitIdentity(scenarios, reps, splitDigest) {
312
+ if (campaignSplitDigestFromIdentities(scenarios, reps) !== splitDigest) {
313
+ throw new Error("campaign split digest does not match its retained scenario identities");
314
+ }
315
+ }
316
+ function campaignCoverage(cells, scenarios, reps, requireJudgeScore) {
317
+ assertCampaignDesign(scenarios, reps);
318
+ const expectedCellIds = designedCellIds(scenarios, reps);
319
+ const cellsById = /* @__PURE__ */ new Map();
320
+ for (const cell of cells) {
321
+ const matches = cellsById.get(cell.cellId) ?? [];
322
+ matches.push(cell);
323
+ cellsById.set(cell.cellId, matches);
324
+ }
325
+ const scorableCellIds = [];
326
+ const unscorableCells = [];
327
+ for (const cellId of expectedCellIds) {
328
+ const matches = cellsById.get(cellId) ?? [];
329
+ if (matches.length === 0) {
330
+ unscorableCells.push({ cellId, reason: "missing campaign cell" });
331
+ continue;
332
+ }
333
+ if (matches.length > 1) {
334
+ unscorableCells.push({ cellId, reason: `duplicate campaign cell (${matches.length})` });
335
+ continue;
336
+ }
337
+ const cell = matches[0];
338
+ const scoreEntries = Object.entries(cell.judgeScores);
339
+ const successfulScores = scoreEntries.map(([, score]) => score).filter((score) => score.failed !== true && Number.isFinite(score.composite));
340
+ const nonFiniteScores = scoreEntries.filter(
341
+ ([, score]) => score.failed !== true && (!Number.isFinite(score.composite) || Object.values(score.dimensions).some((value) => !Number.isFinite(value)))
342
+ );
343
+ const reasons = [];
344
+ if (cell.error) reasons.push(cell.error);
345
+ if (cell.artifact === null || cell.artifact === void 0) reasons.push("missing artifact");
346
+ if (!cell.error && requireJudgeScore && successfulScores.length === 0) {
347
+ reasons.push("no successful finite judge score");
348
+ }
349
+ if (scoreEntries.some(([, score]) => score.failed === true)) {
350
+ reasons.push("judge score marked failed");
351
+ }
352
+ if (nonFiniteScores.length > 0) {
353
+ reasons.push(
354
+ `non-finite judge score: ${nonFiniteScores.map(([name]) => name).sort().join(", ")}`
355
+ );
356
+ }
357
+ if (reasons.length > 0) {
358
+ unscorableCells.push({ cellId, reason: reasons.join("; ") });
359
+ } else {
360
+ scorableCellIds.push(cellId);
361
+ }
362
+ }
363
+ const expected = new Set(expectedCellIds);
364
+ for (const cell of cells) {
365
+ if (cell.cellId !== `${cell.scenarioId}:${cell.rep}`) {
366
+ unscorableCells.push({
367
+ cellId: cell.cellId,
368
+ reason: "campaign cell id does not match scenario id and rep"
369
+ });
370
+ continue;
371
+ }
372
+ if (!expected.has(cell.cellId)) {
373
+ unscorableCells.push({ cellId: cell.cellId, reason: "unexpected campaign cell" });
374
+ }
375
+ }
376
+ return {
377
+ complete: unscorableCells.length === 0 && scorableCellIds.length === expectedCellIds.length,
378
+ expectedCellIds,
379
+ scorableCellIds,
380
+ unscorableCells
381
+ };
382
+ }
383
+ function formatCoverageFailures(coverage) {
384
+ const shown = coverage.unscorableCells.slice(0, 3).map((cell) => `${cell.cellId}: ${cell.reason}`).join("; ");
385
+ const remainder = coverage.unscorableCells.length - Math.min(3, coverage.unscorableCells.length);
386
+ return remainder > 0 ? `${shown}; +${remainder} more` : shown || "unknown coverage failure";
387
+ }
388
+ function designedCellIds(scenarios, reps) {
389
+ const ids = [];
390
+ for (const scenario of scenarios) {
391
+ for (let rep = 0; rep < reps; rep++) ids.push(`${scenario.id}:${rep}`);
392
+ }
393
+ return ids;
394
+ }
395
+
248
396
  // src/campaign/run-dir.ts
249
397
  import { homedir } from "os";
250
398
  import { basename, isAbsolute, join } from "path";
@@ -527,6 +675,7 @@ async function runCampaign(opts) {
527
675
  const judges = opts.judges ?? [];
528
676
  const storage = opts.storage ?? fsCampaignStorage();
529
677
  const costPhase = opts.costPhase ?? "campaign";
678
+ assertCampaignDesign(opts.scenarios, reps);
530
679
  if (typeof opts.runDir !== "string" || opts.runDir.trim().length === 0) {
531
680
  throw new Error("runCampaign: runDir is required and must be a non-empty string");
532
681
  }
@@ -548,6 +697,7 @@ async function runCampaign(opts) {
548
697
  seed,
549
698
  reps
550
699
  });
700
+ const splitDigest = campaignSplitDigest(opts.scenarios, reps);
551
701
  const startedAt = now();
552
702
  const runAttemptId = globalThis.crypto.randomUUID();
553
703
  const cells = [];
@@ -609,7 +759,9 @@ async function runCampaign(opts) {
609
759
  );
610
760
  return {
611
761
  manifestHash,
762
+ splitDigest,
612
763
  seed,
764
+ reps,
613
765
  startedAt: startedAt.toISOString(),
614
766
  endedAt: endedAt.toISOString(),
615
767
  durationMs: endedAt.getTime() - startedAt.getTime(),
@@ -617,7 +769,7 @@ async function runCampaign(opts) {
617
769
  aggregates,
618
770
  runDir: opts.runDir,
619
771
  artifactsByPath,
620
- scenarios: opts.scenarios.map((s) => ({ id: s.id, kind: s.kind }))
772
+ scenarios: opts.scenarios.map(campaignScenarioIdentity)
621
773
  };
622
774
  }
623
775
  async function executeCell(args) {
@@ -826,6 +978,7 @@ function planCampaignRun(opts) {
826
978
  const reps = opts.reps ?? 1;
827
979
  const resumable = opts.resumable ?? true;
828
980
  const storage = opts.storage ?? fsCampaignStorage();
981
+ assertCampaignDesign(opts.scenarios, reps);
829
982
  if (typeof opts.runDir !== "string" || opts.runDir.trim().length === 0) {
830
983
  throw new Error("planCampaignRun: runDir is required and must be a non-empty string");
831
984
  }
@@ -837,6 +990,7 @@ function planCampaignRun(opts) {
837
990
  seed,
838
991
  reps
839
992
  });
993
+ const splitDigest = campaignSplitDigest(opts.scenarios, reps);
840
994
  const cells = buildCellSchedule(opts.scenarios, seed, reps).map((slot) => {
841
995
  const cachePath = join3(
842
996
  opts.runDir,
@@ -883,6 +1037,7 @@ function planCampaignRun(opts) {
883
1037
  const cellsCached = cells.filter((cell) => cell.status === "cached").length;
884
1038
  return {
885
1039
  manifestHash,
1040
+ splitDigest,
886
1041
  totalCells: cells.length,
887
1042
  cellsCached,
888
1043
  cellsToRun: cells.length - cellsCached,
@@ -1080,12 +1235,21 @@ function aggregate(samples, seed) {
1080
1235
  export {
1081
1236
  BackendIntegrityError,
1082
1237
  summarizeBackendIntegrity,
1238
+ summarizeAgentReceiptIntegrity,
1083
1239
  assertRealBackend,
1240
+ assertRealAgentReceipts,
1084
1241
  canonicalJson,
1085
1242
  contentHash,
1086
1243
  inMemoryVerdictCache,
1087
1244
  fileVerdictCache,
1088
1245
  cachedJudge,
1246
+ assertCampaignDesign,
1247
+ campaignScenarioIdentity,
1248
+ campaignSplitDigestFromIdentities,
1249
+ campaignSplitDigest,
1250
+ assertCampaignSplitIdentity,
1251
+ campaignCoverage,
1252
+ formatCoverageFailures,
1089
1253
  tangleTracesRoot,
1090
1254
  resolveRunDir,
1091
1255
  SearchLedgerError,
@@ -1099,4 +1263,4 @@ export {
1099
1263
  runCampaign,
1100
1264
  planCampaignRun
1101
1265
  };
1102
- //# sourceMappingURL=chunk-PAHNGS65.js.map
1266
+ //# sourceMappingURL=chunk-XDIRG3TO.js.map