@tangle-network/agent-eval 0.86.0 → 0.90.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 +9 -1
- package/dist/adapters/http.d.ts +3 -3
- package/dist/adapters/langchain.d.ts +3 -3
- package/dist/adapters/otel.d.ts +6 -6
- package/dist/adversarial-DIVcDoI_.d.ts +88 -0
- package/dist/analyst/index.d.ts +11 -10
- package/dist/analyst/index.js +13 -8
- package/dist/analyst/index.js.map +1 -1
- package/dist/analyze-runs-DwCEkpO_.d.ts +81 -0
- package/dist/belief-state/index.d.ts +4 -4
- package/dist/belief-state/index.js +1 -1
- package/dist/benchmarks/index.d.ts +3 -3
- package/dist/campaign/index.d.ts +165 -18
- package/dist/campaign/index.js +289 -14
- package/dist/campaign/index.js.map +1 -1
- package/dist/chunk-45EEMHTC.js +35 -0
- package/dist/chunk-45EEMHTC.js.map +1 -0
- package/dist/{chunk-FZWAFVAA.js → chunk-4FBZZIYD.js} +2 -2
- package/dist/{chunk-YV7J7X5N.js → chunk-5HRORJQY.js} +22 -12
- package/dist/chunk-5HRORJQY.js.map +1 -0
- package/dist/{chunk-OTYQPHPL.js → chunk-6SOJM3VR.js} +5 -5
- package/dist/chunk-BOD4O7OF.js +40 -0
- package/dist/chunk-BOD4O7OF.js.map +1 -0
- package/dist/{chunk-Z7VFTS2J.js → chunk-CY6U5S3X.js} +2 -2
- package/dist/{chunk-VIDQF3F5.js → chunk-D3V5B42D.js} +5 -34
- package/dist/chunk-D3V5B42D.js.map +1 -0
- package/dist/{chunk-YGYXHNAQ.js → chunk-FIUKOSWI.js} +21 -8
- package/dist/chunk-FIUKOSWI.js.map +1 -0
- package/dist/{chunk-WJL2NJXN.js → chunk-GSH6QNNS.js} +2 -2
- package/dist/{chunk-RBNA5AZT.js → chunk-L3JOU6XM.js} +2 -2
- package/dist/{chunk-IDVBLYCY.js → chunk-LMZQ2Z4U.js} +56 -2
- package/dist/{chunk-IDVBLYCY.js.map → chunk-LMZQ2Z4U.js.map} +1 -1
- package/dist/{chunk-VUINJM5M.js → chunk-QAY5UIJO.js} +2 -193
- package/dist/chunk-QAY5UIJO.js.map +1 -0
- package/dist/{chunk-P2J6SOXT.js → chunk-QG2OVF2D.js} +5 -3
- package/dist/{chunk-P2J6SOXT.js.map → chunk-QG2OVF2D.js.map} +1 -1
- package/dist/chunk-REVYNR6C.js +100 -0
- package/dist/chunk-REVYNR6C.js.map +1 -0
- package/dist/chunk-STGVSCDH.js +202 -0
- package/dist/chunk-STGVSCDH.js.map +1 -0
- package/dist/{chunk-ZZ2HOPME.js → chunk-TWS7AZEY.js} +2 -2
- package/dist/chunk-UHMJT4T7.js +200 -0
- package/dist/chunk-UHMJT4T7.js.map +1 -0
- package/dist/chunk-UMMZHCPB.js +190 -0
- package/dist/chunk-UMMZHCPB.js.map +1 -0
- package/dist/chunk-VZSRQ272.js +149 -0
- package/dist/chunk-VZSRQ272.js.map +1 -0
- package/dist/{chunk-L5G7OUKD.js → chunk-XY4DDNEG.js} +8 -190
- package/dist/chunk-XY4DDNEG.js.map +1 -0
- package/dist/chunk-Y47J2LJ3.js +859 -0
- package/dist/chunk-Y47J2LJ3.js.map +1 -0
- package/dist/{chunk-BABOZOSN.js → chunk-ZFIBGEOL.js} +3 -3
- package/dist/chunk-ZFIBGEOL.js.map +1 -0
- package/dist/{code-agent-session-BRXmavYv.d.ts → code-agent-session-BO8nCnv3.d.ts} +1 -1
- package/dist/contract/index.d.ts +24 -95
- package/dist/contract/index.js +25 -764
- package/dist/contract/index.js.map +1 -1
- package/dist/{control-GeE8OhpN.d.ts → control-_Qb7skHX.d.ts} +2 -2
- package/dist/control.d.ts +5 -5
- package/dist/corpus-BoR-041R.d.ts +560 -0
- package/dist/cost-ledger-DuSqlw5B.d.ts +113 -0
- package/dist/counterfactual-Dwibr5IW.d.ts +85 -0
- package/dist/{dataset-B2kL-fSM.d.ts → dataset-BbGkaN2I.d.ts} +1 -1
- package/dist/{registry-DrEQ3Luj.d.ts → default-registry-zoGHUQEH.d.ts} +29 -2
- package/dist/diagnose.d.ts +251 -0
- package/dist/diagnose.js +381 -0
- package/dist/diagnose.js.map +1 -0
- package/dist/{errors-Dwqw-T_m.d.ts → errors-CzMUYo7b.d.ts} +1 -1
- package/dist/{feedback-trajectory-B3rErRsh.d.ts → feedback-trajectory-D9OVLrg9.d.ts} +1 -1
- package/dist/fuzz.d.ts +484 -0
- package/dist/fuzz.js +613 -0
- package/dist/fuzz.js.map +1 -0
- package/dist/governance/index.d.ts +4 -4
- package/dist/hosted/index.d.ts +6 -6
- package/dist/{index-DE3RXAXD.d.ts → index-Bx3gZ8xl.d.ts} +1 -1
- package/dist/index.d.ts +718 -455
- package/dist/index.js +1604 -793
- package/dist/index.js.map +1 -1
- package/dist/{insight-report-3ADTfClO.d.ts → insight-report-BBwvOh6x.d.ts} +2 -2
- package/dist/{integrity-CJzrpUua.d.ts → integrity-VJ9A7aST.d.ts} +1 -1
- package/dist/{judge-calibration-DilmB3Ml.d.ts → judge-calibration-0p2QcWNE.d.ts} +1 -1
- package/dist/{kind-factory-CVecZZG_.d.ts → kind-factory-5b7xXXOr.d.ts} +2 -2
- package/dist/{llm-client-CuUg2Mn3.d.ts → llm-client-BeEcAokY.d.ts} +1 -1
- package/dist/matrix/index.d.ts +2 -2
- package/dist/meta-eval/index.d.ts +177 -3
- package/dist/meta-eval/index.js +260 -1
- package/dist/meta-eval/index.js.map +1 -1
- package/dist/{multi-layer-verifier-DlWCXuxL.d.ts → multi-layer-verifier-DUZXrPDA.d.ts} +7 -1
- package/dist/multishot/index.d.ts +25 -11
- package/dist/multishot/index.js +36 -7
- package/dist/multishot/index.js.map +1 -1
- package/dist/openapi.json +1 -1
- package/dist/perf/index.d.ts +123 -0
- package/dist/perf/index.js +18 -0
- package/dist/pipelines/index.js +2 -2
- package/dist/{agent-profile-D0PBIWlV.d.ts → pre-registration-DELOEJ8v.d.ts} +144 -4
- package/dist/{provenance-DPpNIOJD.d.ts → provenance-LnqRT0sS.d.ts} +5 -5
- package/dist/{red-team-DW9Ca_tj.d.ts → red-team-BXHil6c8.d.ts} +1 -1
- package/dist/{release-report-hlNtD12q.d.ts → release-report-euXIV_Sk.d.ts} +3 -3
- package/dist/reporting.d.ts +8 -8
- package/dist/reporting.js +3 -3
- package/dist/{researcher-BLPHBbNV.d.ts → researcher-DE6Gpnb4.d.ts} +4 -4
- package/dist/rl.d.ts +194 -656
- package/dist/rl.js +231 -149
- package/dist/rl.js.map +1 -1
- package/dist/{rubric-predictive-validity-CnEl9Jc8.d.ts → rubric-predictive-validity-Cy_W-hWZ.d.ts} +1 -1
- package/dist/{run-campaign-4Y5V5CN3.js → run-campaign-RDGAM5KJ.js} +3 -3
- package/dist/run-campaign-RDGAM5KJ.js.map +1 -0
- package/dist/{run-improvement-loop-CNqQckTj.d.ts → run-improvement-loop-5z_l5zDz.d.ts} +2 -2
- package/dist/{run-record-De9VarXR.d.ts → run-record-e7vj1uZQ.d.ts} +1 -1
- package/dist/{runtime-trajectory-BLRiaifm.d.ts → runtime-trajectory-BDgfGZSr.d.ts} +1 -1
- package/dist/{semantic-concept-judge-DIEgr_6v.d.ts → semantic-concept-judge-Dn8Z6KEG.d.ts} +5 -31
- package/dist/series-convergence-D5OWMBg6.d.ts +33 -0
- package/dist/{statistics-CnC1FMbx.d.ts → statistics-C7PozGrZ.d.ts} +71 -2
- package/dist/{summary-report-Db0dDSWP.d.ts → summary-report-DGmUucwQ.d.ts} +1 -1
- package/dist/traces.d.ts +3 -3
- package/dist/traces.js +8 -6
- package/dist/{types-Cu3u_x59.d.ts → types-2VVIL04s.d.ts} +2 -2
- package/dist/{types-D7lLRYe9.d.ts → types-BU-7W85F.d.ts} +21 -1
- package/dist/{types-CqPax19X.d.ts → types-mn5Aqk7x.d.ts} +1 -1
- package/dist/{verdict-CeEgtjyI.d.ts → verdict-C9MlYujm.d.ts} +3 -0
- package/dist/wire/index.d.ts +3 -3
- package/dist/workflow/index.d.ts +12 -11
- package/dist/workflow/index.js +1 -1
- package/package.json +16 -1
- package/dist/chunk-BABOZOSN.js.map +0 -1
- package/dist/chunk-L5G7OUKD.js.map +0 -1
- package/dist/chunk-SHTXZ4O2.js +0 -113
- package/dist/chunk-SHTXZ4O2.js.map +0 -1
- package/dist/chunk-VIDQF3F5.js.map +0 -1
- package/dist/chunk-VUINJM5M.js.map +0 -1
- package/dist/chunk-YGYXHNAQ.js.map +0 -1
- package/dist/chunk-YV7J7X5N.js.map +0 -1
- /package/dist/{chunk-FZWAFVAA.js.map → chunk-4FBZZIYD.js.map} +0 -0
- /package/dist/{chunk-OTYQPHPL.js.map → chunk-6SOJM3VR.js.map} +0 -0
- /package/dist/{chunk-Z7VFTS2J.js.map → chunk-CY6U5S3X.js.map} +0 -0
- /package/dist/{chunk-WJL2NJXN.js.map → chunk-GSH6QNNS.js.map} +0 -0
- /package/dist/{chunk-RBNA5AZT.js.map → chunk-L3JOU6XM.js.map} +0 -0
- /package/dist/{chunk-ZZ2HOPME.js.map → chunk-TWS7AZEY.js.map} +0 -0
- /package/dist/{run-campaign-4Y5V5CN3.js.map → perf/index.js.map} +0 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AnalystRegistry,
|
|
3
|
+
DEFAULT_TRACE_ANALYST_KINDS,
|
|
4
|
+
createTraceAnalystKind
|
|
5
|
+
} from "./chunk-D3V5B42D.js";
|
|
6
|
+
import {
|
|
7
|
+
makeFinding
|
|
8
|
+
} from "./chunk-45EEMHTC.js";
|
|
9
|
+
|
|
10
|
+
// src/trace-analyst/behavioral-metrics.ts
|
|
11
|
+
var INPUT_GROWTH_FACTOR = 3;
|
|
12
|
+
var MIN_TOOL_CALLS = 3;
|
|
13
|
+
var VERIFY_RE = /verif|eval|inspect|check|assert|validat|review|confirm/i;
|
|
14
|
+
function num(v) {
|
|
15
|
+
return typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
16
|
+
}
|
|
17
|
+
function inputTokensOf(s) {
|
|
18
|
+
return num(s.attributes["llm.input_tokens"]) ?? num(s.attributes["llm.usage.input_tokens"]);
|
|
19
|
+
}
|
|
20
|
+
function outputTokensOf(s) {
|
|
21
|
+
return num(s.attributes["llm.output_tokens"]) ?? num(s.attributes["llm.usage.output_tokens"]);
|
|
22
|
+
}
|
|
23
|
+
function stepOf(s) {
|
|
24
|
+
return num(s.attributes.step);
|
|
25
|
+
}
|
|
26
|
+
function toolNameOf(s) {
|
|
27
|
+
if (s.tool_name) return s.tool_name;
|
|
28
|
+
const t = s.attributes["tool.name"];
|
|
29
|
+
return typeof t === "string" && t.length > 0 ? t : null;
|
|
30
|
+
}
|
|
31
|
+
function computeTraceMetrics(spans) {
|
|
32
|
+
const ordered = [...spans].sort((a, b) => {
|
|
33
|
+
const sa = stepOf(a);
|
|
34
|
+
const sb = stepOf(b);
|
|
35
|
+
if (sa !== null && sb !== null && sa !== sb) return sa - sb;
|
|
36
|
+
return a.start_time.localeCompare(b.start_time);
|
|
37
|
+
});
|
|
38
|
+
const inputTokenTrajectory = [];
|
|
39
|
+
const outputTokenTrajectory = [];
|
|
40
|
+
const toolHistogram = {};
|
|
41
|
+
let hasSelfVerification = false;
|
|
42
|
+
for (const s of ordered) {
|
|
43
|
+
const inT = inputTokensOf(s);
|
|
44
|
+
if (inT !== null) inputTokenTrajectory.push(inT);
|
|
45
|
+
const outT = outputTokensOf(s);
|
|
46
|
+
if (outT !== null) outputTokenTrajectory.push(outT);
|
|
47
|
+
const tool = toolNameOf(s);
|
|
48
|
+
if (tool) {
|
|
49
|
+
toolHistogram[tool] = (toolHistogram[tool] ?? 0) + 1;
|
|
50
|
+
if (VERIFY_RE.test(tool)) hasSelfVerification = true;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const totalToolCalls = Object.values(toolHistogram).reduce((a, b) => a + b, 0);
|
|
54
|
+
const distinctTools = Object.keys(toolHistogram).length;
|
|
55
|
+
const toolDiversityRatio = totalToolCalls === 0 ? 1 : distinctTools / totalToolCalls;
|
|
56
|
+
const signals = [];
|
|
57
|
+
if (inputTokenTrajectory.length >= 3) {
|
|
58
|
+
const first = inputTokenTrajectory[0];
|
|
59
|
+
const last = inputTokenTrajectory[inputTokenTrajectory.length - 1];
|
|
60
|
+
const growth = first > 0 ? last / first : 0;
|
|
61
|
+
if (last > first && growth >= INPUT_GROWTH_FACTOR) {
|
|
62
|
+
signals.push({
|
|
63
|
+
code: "monotonic-input-growth",
|
|
64
|
+
severity: "high",
|
|
65
|
+
detail: `LLM input tokens grew ${growth.toFixed(1)}x (${first}\u2192${last}) across ${inputTokenTrajectory.length} calls \u2014 full history re-sent each step with no compression.`,
|
|
66
|
+
evidence: {
|
|
67
|
+
first,
|
|
68
|
+
last,
|
|
69
|
+
growth_x: Number(growth.toFixed(2)),
|
|
70
|
+
calls: inputTokenTrajectory.length
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (outputTokenTrajectory.length >= 3) {
|
|
76
|
+
const first = outputTokenTrajectory[0];
|
|
77
|
+
const last = outputTokenTrajectory[outputTokenTrajectory.length - 1];
|
|
78
|
+
if (last < first) {
|
|
79
|
+
signals.push({
|
|
80
|
+
code: "output-length-decay",
|
|
81
|
+
severity: "medium",
|
|
82
|
+
detail: `LLM output tokens shrank ${first}\u2192${last} over ${outputTokenTrajectory.length} calls \u2014 less planning/reasoning per step as context grows.`,
|
|
83
|
+
evidence: { first, last, calls: outputTokenTrajectory.length }
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (totalToolCalls >= MIN_TOOL_CALLS && distinctTools === 1) {
|
|
88
|
+
const only = Object.keys(toolHistogram)[0];
|
|
89
|
+
signals.push({
|
|
90
|
+
code: "single-tool-dependency",
|
|
91
|
+
severity: "medium",
|
|
92
|
+
detail: `All ${totalToolCalls} tool calls are \`${only}\` \u2014 no tool diversity and no fallback path.`,
|
|
93
|
+
evidence: { tool: only, calls: totalToolCalls, distinct_tools: 1 }
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
if (totalToolCalls >= MIN_TOOL_CALLS && !hasSelfVerification) {
|
|
97
|
+
signals.push({
|
|
98
|
+
code: "no-self-verification",
|
|
99
|
+
severity: "medium",
|
|
100
|
+
detail: `${totalToolCalls} tool calls and none verify/inspect/check state \u2014 the agent never validates its own actions.`,
|
|
101
|
+
evidence: { tool_calls: totalToolCalls, verification_calls: 0 }
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
llmCallCount: inputTokenTrajectory.length,
|
|
106
|
+
inputTokenTrajectory,
|
|
107
|
+
outputTokenTrajectory,
|
|
108
|
+
toolHistogram,
|
|
109
|
+
totalToolCalls,
|
|
110
|
+
distinctTools,
|
|
111
|
+
toolDiversityRatio,
|
|
112
|
+
hasSelfVerification,
|
|
113
|
+
signals
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/analyst/behavioral-analyst.ts
|
|
118
|
+
var RECOMMENDED_ACTION = {
|
|
119
|
+
"monotonic-input-growth": "Add a context-budget instruction: once prior context exceeds a threshold, summarize earlier steps into a short status line instead of re-sending full history.",
|
|
120
|
+
"output-length-decay": "Require a minimum planning/reasoning budget per step so late steps do not degrade into terse, error-prone commands.",
|
|
121
|
+
"single-tool-dependency": "Direct the agent to use the full toolset (verify / inspect / alternate actions), not a single execute call, and to plan a fallback when a call returns an unexpected result.",
|
|
122
|
+
"no-self-verification": "After every state-mutating action, verify the result (eval / inspect / assert) before proceeding."
|
|
123
|
+
};
|
|
124
|
+
var ANALYST_ID = "efficiency-behavioral";
|
|
125
|
+
function deriveEfficiencyFindings(metrics, opts = {}) {
|
|
126
|
+
const analystId = opts.analystId ?? ANALYST_ID;
|
|
127
|
+
return metrics.signals.map(
|
|
128
|
+
(sig) => makeFinding({
|
|
129
|
+
analyst_id: analystId,
|
|
130
|
+
area: "efficiency",
|
|
131
|
+
subject: sig.code,
|
|
132
|
+
// kebab — passes the cluster grammar; stable key for diffFindings
|
|
133
|
+
claim: sig.detail,
|
|
134
|
+
severity: sig.severity,
|
|
135
|
+
// Deterministic arithmetic over spans, not a model judgment → certain.
|
|
136
|
+
confidence: 1,
|
|
137
|
+
evidence_refs: [
|
|
138
|
+
{
|
|
139
|
+
kind: "metric",
|
|
140
|
+
uri: `metric://efficiency/${sig.code}`,
|
|
141
|
+
excerpt: JSON.stringify(sig.evidence)
|
|
142
|
+
}
|
|
143
|
+
],
|
|
144
|
+
recommended_action: RECOMMENDED_ACTION[sig.code],
|
|
145
|
+
metadata: { deterministic: true, evidence: sig.evidence },
|
|
146
|
+
...opts.producedAt ? { produced_at: opts.producedAt } : {}
|
|
147
|
+
})
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
function behavioralAnalyst() {
|
|
151
|
+
return {
|
|
152
|
+
id: ANALYST_ID,
|
|
153
|
+
description: "Deterministic behavioral/efficiency findings over OTLP spans \u2014 token-growth, output-decay, tool-monoculture, missing self-verification. Zero LLM; model-agnostic by construction.",
|
|
154
|
+
inputKind: "trace-store",
|
|
155
|
+
cost: { kind: "deterministic" },
|
|
156
|
+
version: "1.0.0",
|
|
157
|
+
async analyze(store) {
|
|
158
|
+
const overview = await store.getOverview();
|
|
159
|
+
const spans = [];
|
|
160
|
+
for (const traceId of overview.sample_trace_ids) {
|
|
161
|
+
const viewed = await store.viewTrace({ trace_id: traceId });
|
|
162
|
+
if (viewed.spans) spans.push(...viewed.spans);
|
|
163
|
+
}
|
|
164
|
+
return deriveEfficiencyFindings(computeTraceMetrics(spans));
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// src/analyst/default-registry.ts
|
|
170
|
+
function buildDefaultAnalystRegistry(opts = {}) {
|
|
171
|
+
const registry = new AnalystRegistry(opts.registry);
|
|
172
|
+
if (opts.includeBehavioral !== false) {
|
|
173
|
+
registry.register(behavioralAnalyst());
|
|
174
|
+
}
|
|
175
|
+
if (opts.ai) {
|
|
176
|
+
const kinds = opts.kinds ?? DEFAULT_TRACE_ANALYST_KINDS;
|
|
177
|
+
for (const spec of kinds) {
|
|
178
|
+
registry.register(createTraceAnalystKind(spec, { ai: opts.ai, model: opts.model }));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return registry;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export {
|
|
185
|
+
computeTraceMetrics,
|
|
186
|
+
deriveEfficiencyFindings,
|
|
187
|
+
behavioralAnalyst,
|
|
188
|
+
buildDefaultAnalystRegistry
|
|
189
|
+
};
|
|
190
|
+
//# sourceMappingURL=chunk-UMMZHCPB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/trace-analyst/behavioral-metrics.ts","../src/analyst/behavioral-analyst.ts","../src/analyst/default-registry.ts"],"sourcesContent":["/**\n * Deterministic behavioral metrics over OTLP spans — pure arithmetic, no LLM.\n *\n * These are the model-independent multiplier: the four trace-quality signals a\n * tolerant analyzer (e.g. HALO) re-derives per run inside the model — token\n * growth, output decay, tool monoculture, missing self-verification — computed\n * here once, in TypeScript, with zero model judgment. A finding that falls out\n * of arithmetic is trivially model-agnostic and cannot hallucinate the trend.\n *\n * General, not trace-specific: the detectors key off token trajectories and\n * tool usage present in any agentic OTLP trace, not any one benchmark.\n */\n\nimport type { TraceAnalystSpan } from './types'\n\nexport type SuboptimalCode =\n | 'monotonic-input-growth'\n | 'output-length-decay'\n | 'single-tool-dependency'\n | 'no-self-verification'\n\nexport interface SuboptimalSignal {\n code: SuboptimalCode\n severity: 'high' | 'medium' | 'low'\n /** Human-readable claim, with the backing numbers inlined. */\n detail: string\n /** The exact figures the detector fired on — auditable, no model in the loop. */\n evidence: Record<string, number | string | boolean>\n}\n\nexport interface BehavioralMetrics {\n llmCallCount: number\n inputTokenTrajectory: number[]\n outputTokenTrajectory: number[]\n toolHistogram: Record<string, number>\n totalToolCalls: number\n distinctTools: number\n /** distinct/total tool calls; 1.0 when there are no tool calls. */\n toolDiversityRatio: number\n hasSelfVerification: boolean\n signals: SuboptimalSignal[]\n}\n\n/** ≥ this input-token growth ratio across a run, with no compression, fires. */\nconst INPUT_GROWTH_FACTOR = 3\n/** Tool-usage signals need at least this many calls to be meaningful. */\nconst MIN_TOOL_CALLS = 3\n/** Tool names matching this are self-verification, not state mutation. */\nconst VERIFY_RE = /verif|eval|inspect|check|assert|validat|review|confirm/i\n\nfunction num(v: unknown): number | null {\n return typeof v === 'number' && Number.isFinite(v) ? v : null\n}\nfunction inputTokensOf(s: TraceAnalystSpan): number | null {\n return num(s.attributes['llm.input_tokens']) ?? num(s.attributes['llm.usage.input_tokens'])\n}\nfunction outputTokensOf(s: TraceAnalystSpan): number | null {\n return num(s.attributes['llm.output_tokens']) ?? num(s.attributes['llm.usage.output_tokens'])\n}\nfunction stepOf(s: TraceAnalystSpan): number | null {\n return num(s.attributes.step)\n}\nfunction toolNameOf(s: TraceAnalystSpan): string | null {\n if (s.tool_name) return s.tool_name\n const t = s.attributes['tool.name']\n return typeof t === 'string' && t.length > 0 ? t : null\n}\n\n/**\n * Reduce a span list to behavioral metrics + fired suboptimality signals.\n * Pure + deterministic: same spans → same output, on any machine, no model.\n */\nexport function computeTraceMetrics(spans: readonly TraceAnalystSpan[]): BehavioralMetrics {\n // Order by step (when present) then start_time so trajectories reflect run order.\n const ordered = [...spans].sort((a, b) => {\n const sa = stepOf(a)\n const sb = stepOf(b)\n if (sa !== null && sb !== null && sa !== sb) return sa - sb\n return a.start_time.localeCompare(b.start_time)\n })\n\n const inputTokenTrajectory: number[] = []\n const outputTokenTrajectory: number[] = []\n const toolHistogram: Record<string, number> = {}\n let hasSelfVerification = false\n\n for (const s of ordered) {\n const inT = inputTokensOf(s)\n if (inT !== null) inputTokenTrajectory.push(inT)\n const outT = outputTokensOf(s)\n if (outT !== null) outputTokenTrajectory.push(outT)\n const tool = toolNameOf(s)\n if (tool) {\n toolHistogram[tool] = (toolHistogram[tool] ?? 0) + 1\n if (VERIFY_RE.test(tool)) hasSelfVerification = true\n }\n }\n\n const totalToolCalls = Object.values(toolHistogram).reduce((a, b) => a + b, 0)\n const distinctTools = Object.keys(toolHistogram).length\n const toolDiversityRatio = totalToolCalls === 0 ? 1 : distinctTools / totalToolCalls\n\n const signals: SuboptimalSignal[] = []\n\n if (inputTokenTrajectory.length >= 3) {\n const first = inputTokenTrajectory[0]!\n const last = inputTokenTrajectory[inputTokenTrajectory.length - 1]!\n const growth = first > 0 ? last / first : 0\n if (last > first && growth >= INPUT_GROWTH_FACTOR) {\n signals.push({\n code: 'monotonic-input-growth',\n severity: 'high',\n detail: `LLM input tokens grew ${growth.toFixed(1)}x (${first}→${last}) across ${inputTokenTrajectory.length} calls — full history re-sent each step with no compression.`,\n evidence: {\n first,\n last,\n growth_x: Number(growth.toFixed(2)),\n calls: inputTokenTrajectory.length,\n },\n })\n }\n }\n\n if (outputTokenTrajectory.length >= 3) {\n const first = outputTokenTrajectory[0]!\n const last = outputTokenTrajectory[outputTokenTrajectory.length - 1]!\n if (last < first) {\n signals.push({\n code: 'output-length-decay',\n severity: 'medium',\n detail: `LLM output tokens shrank ${first}→${last} over ${outputTokenTrajectory.length} calls — less planning/reasoning per step as context grows.`,\n evidence: { first, last, calls: outputTokenTrajectory.length },\n })\n }\n }\n\n if (totalToolCalls >= MIN_TOOL_CALLS && distinctTools === 1) {\n const only = Object.keys(toolHistogram)[0]!\n signals.push({\n code: 'single-tool-dependency',\n severity: 'medium',\n detail: `All ${totalToolCalls} tool calls are \\`${only}\\` — no tool diversity and no fallback path.`,\n evidence: { tool: only, calls: totalToolCalls, distinct_tools: 1 },\n })\n }\n\n if (totalToolCalls >= MIN_TOOL_CALLS && !hasSelfVerification) {\n signals.push({\n code: 'no-self-verification',\n severity: 'medium',\n detail: `${totalToolCalls} tool calls and none verify/inspect/check state — the agent never validates its own actions.`,\n evidence: { tool_calls: totalToolCalls, verification_calls: 0 },\n })\n }\n\n return {\n llmCallCount: inputTokenTrajectory.length,\n inputTokenTrajectory,\n outputTokenTrajectory,\n toolHistogram,\n totalToolCalls,\n distinctTools,\n toolDiversityRatio,\n hasSelfVerification,\n signals,\n }\n}\n","/**\n * `behavioralAnalyst` — a DETERMINISTIC analyst (cost.kind = 'deterministic',\n * never calls the LLM). It produces the efficiency/behavioral findings a\n * tolerant agentic analyzer (HALO) re-derives per run inside the model —\n * context bloat, output decay, tool monoculture, missing self-verification —\n * directly from arithmetic over spans (`computeTraceMetrics`).\n *\n * Why it matters: these findings are model-agnostic BY CONSTRUCTION (no model\n * in the loop), so they cannot return 0 on a weak model the way the Ax-RLM\n * does — and they are strictly more reliable than HALO, which spends tokens\n * re-deriving the same numbers and can hallucinate the trend. The agentic\n * RLM kinds remain for SEMANTIC findings that genuinely need a model; this\n * analyst owns the behavioral class.\n */\n\nimport {\n type BehavioralMetrics,\n computeTraceMetrics,\n type SuboptimalCode,\n} from '../trace-analyst/behavioral-metrics'\nimport type { TraceAnalysisStore } from '../trace-analyst/store'\nimport type { TraceAnalystSpan } from '../trace-analyst/types'\nimport { type Analyst, type AnalystFinding, makeFinding } from './types'\n\nconst RECOMMENDED_ACTION: Record<SuboptimalCode, string> = {\n 'monotonic-input-growth':\n 'Add a context-budget instruction: once prior context exceeds a threshold, summarize earlier steps into a short status line instead of re-sending full history.',\n 'output-length-decay':\n 'Require a minimum planning/reasoning budget per step so late steps do not degrade into terse, error-prone commands.',\n 'single-tool-dependency':\n 'Direct the agent to use the full toolset (verify / inspect / alternate actions), not a single execute call, and to plan a fallback when a call returns an unexpected result.',\n 'no-self-verification':\n 'After every state-mutating action, verify the result (eval / inspect / assert) before proceeding.',\n}\n\nconst ANALYST_ID = 'efficiency-behavioral'\n\n/**\n * Map computed signals → structured AnalystFindings. Pure: no LLM, no clock\n * dependence beyond `produced_at` (overridable for deterministic tests).\n */\nexport function deriveEfficiencyFindings(\n metrics: BehavioralMetrics,\n opts: { analystId?: string; producedAt?: string } = {},\n): AnalystFinding[] {\n const analystId = opts.analystId ?? ANALYST_ID\n return metrics.signals.map((sig) =>\n makeFinding({\n analyst_id: analystId,\n area: 'efficiency',\n subject: sig.code, // kebab — passes the cluster grammar; stable key for diffFindings\n claim: sig.detail,\n severity: sig.severity,\n // Deterministic arithmetic over spans, not a model judgment → certain.\n confidence: 1,\n evidence_refs: [\n {\n kind: 'metric',\n uri: `metric://efficiency/${sig.code}`,\n excerpt: JSON.stringify(sig.evidence),\n },\n ],\n recommended_action: RECOMMENDED_ACTION[sig.code],\n metadata: { deterministic: true, evidence: sig.evidence },\n ...(opts.producedAt ? { produced_at: opts.producedAt } : {}),\n }),\n )\n}\n\n/** The deterministic behavioral/efficiency analyst (no LLM, any-model). */\nexport function behavioralAnalyst(): Analyst<TraceAnalysisStore> {\n return {\n id: ANALYST_ID,\n description:\n 'Deterministic behavioral/efficiency findings over OTLP spans — token-growth, output-decay, tool-monoculture, missing self-verification. Zero LLM; model-agnostic by construction.',\n inputKind: 'trace-store',\n cost: { kind: 'deterministic' },\n version: '1.0.0',\n async analyze(store) {\n const overview = await store.getOverview()\n const spans: TraceAnalystSpan[] = []\n for (const traceId of overview.sample_trace_ids) {\n const viewed = await store.viewTrace({ trace_id: traceId })\n if (viewed.spans) spans.push(...viewed.spans)\n }\n return deriveEfficiencyFindings(computeTraceMetrics(spans))\n },\n }\n}\n","/**\n * `buildDefaultAnalystRegistry` — the canonical analyst suite, so consumers\n * stop hand-wiring `new AnalystRegistry()` + per-kind `createTraceAnalystKind`.\n *\n * The deterministic `behavioralAnalyst` is ALWAYS registered (it needs no\n * model and is model-agnostic by construction). The agentic RLM kinds are\n * registered only when an `ai` service is supplied — so a caller with no LLM\n * still gets the full behavioral/efficiency diagnosis, and the substrate's\n * \"any model (including no model)\" guarantee holds at the suite level.\n */\n\nimport type { AxAIService } from '@ax-llm/ax'\nimport { behavioralAnalyst } from './behavioral-analyst'\nimport { createTraceAnalystKind, type TraceAnalystKindSpec } from './kind-factory'\nimport { DEFAULT_TRACE_ANALYST_KINDS } from './kinds'\nimport { AnalystRegistry, type AnalystRegistryOptions } from './registry'\n\nexport interface DefaultAnalystRegistryOptions {\n /** Ax service for the agentic RLM kinds. Omit → only the deterministic analyst. */\n ai?: AxAIService\n /** Model for the agentic kinds (falls back to the ai service default). */\n model?: string\n /** Which agentic kinds to register when `ai` is present. Default = the shipped suite. */\n kinds?: readonly TraceAnalystKindSpec[]\n /** Set false to omit the deterministic behavioral analyst (default: include). */\n includeBehavioral?: boolean\n /** Forwarded to the AnalystRegistry constructor (signal, tags, priorFindings). */\n registry?: AnalystRegistryOptions\n}\n\nexport function buildDefaultAnalystRegistry(\n opts: DefaultAnalystRegistryOptions = {},\n): AnalystRegistry {\n const registry = new AnalystRegistry(opts.registry)\n if (opts.includeBehavioral !== false) {\n registry.register(behavioralAnalyst())\n }\n if (opts.ai) {\n const kinds = opts.kinds ?? DEFAULT_TRACE_ANALYST_KINDS\n for (const spec of kinds) {\n registry.register(createTraceAnalystKind(spec, { ai: opts.ai, model: opts.model }))\n }\n }\n return registry\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// src/rl/active-curriculum.ts
|
|
2
|
+
function varianceBasedCurriculum(observations, candidateCells, opts) {
|
|
3
|
+
const variancePrior = opts.variancePrior ?? 0.05;
|
|
4
|
+
const floor = opts.floorPerCell ?? 1;
|
|
5
|
+
const budget = opts.budget;
|
|
6
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
7
|
+
for (const o of observations) {
|
|
8
|
+
const k = `${o.variantId}::${o.scenarioId}`;
|
|
9
|
+
const arr = grouped.get(k) ?? [];
|
|
10
|
+
arr.push(o.score);
|
|
11
|
+
grouped.set(k, arr);
|
|
12
|
+
}
|
|
13
|
+
const cellStats = candidateCells.map((c) => {
|
|
14
|
+
const k = `${c.variantId}::${c.scenarioId}`;
|
|
15
|
+
const samples = grouped.get(k) ?? [];
|
|
16
|
+
const n = samples.length;
|
|
17
|
+
const mean = n === 0 ? 0.5 : samples.reduce((s, v) => s + v, 0) / n;
|
|
18
|
+
const variance = n < 2 ? variancePrior : samples.reduce((s, v) => s + (v - mean) ** 2, 0) / (n - 1) + variancePrior;
|
|
19
|
+
const weight = Math.sqrt(variance) + 1 / Math.sqrt(Math.max(1, n));
|
|
20
|
+
return { variantId: c.variantId, scenarioId: c.scenarioId, n, mean, variance, weight };
|
|
21
|
+
});
|
|
22
|
+
const floorTotal = floor * cellStats.length;
|
|
23
|
+
if (floorTotal >= budget) {
|
|
24
|
+
const each = Math.max(1, Math.floor(budget / Math.max(1, cellStats.length)));
|
|
25
|
+
return cellStats.map((c) => ({
|
|
26
|
+
variantId: c.variantId,
|
|
27
|
+
scenarioId: c.scenarioId,
|
|
28
|
+
count: each,
|
|
29
|
+
reason: `floor allocation (budget tight; n=${c.n})`
|
|
30
|
+
}));
|
|
31
|
+
}
|
|
32
|
+
const remaining = budget - floorTotal;
|
|
33
|
+
const totalWeight = cellStats.reduce((s, c) => s + c.weight, 0);
|
|
34
|
+
return cellStats.map((c) => {
|
|
35
|
+
const proportional = totalWeight === 0 ? 0 : Math.round(c.weight / totalWeight * remaining);
|
|
36
|
+
return {
|
|
37
|
+
variantId: c.variantId,
|
|
38
|
+
scenarioId: c.scenarioId,
|
|
39
|
+
count: floor + proportional,
|
|
40
|
+
reason: `variance ${c.variance.toFixed(3)} (n=${c.n}, mean=${c.mean.toFixed(3)})`
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function thompsonCurriculum(observations, candidateCells, opts) {
|
|
45
|
+
const threshold = opts.decisionThreshold ?? 0.5;
|
|
46
|
+
const alpha0 = opts.priorAlpha ?? 1;
|
|
47
|
+
const beta0 = opts.priorBeta ?? 1;
|
|
48
|
+
const rng = makeRng(opts.seed);
|
|
49
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
50
|
+
for (const o of observations) {
|
|
51
|
+
const k = `${o.variantId}::${o.scenarioId}`;
|
|
52
|
+
const cur = grouped.get(k) ?? { passes: 0, failures: 0 };
|
|
53
|
+
const pass = o.pass ?? o.score >= threshold;
|
|
54
|
+
if (pass) cur.passes += 1;
|
|
55
|
+
else cur.failures += 1;
|
|
56
|
+
grouped.set(k, cur);
|
|
57
|
+
}
|
|
58
|
+
const stats = candidateCells.map((c) => {
|
|
59
|
+
const k = `${c.variantId}::${c.scenarioId}`;
|
|
60
|
+
const cur = grouped.get(k) ?? { passes: 0, failures: 0 };
|
|
61
|
+
const a = alpha0 + cur.passes;
|
|
62
|
+
const b = beta0 + cur.failures;
|
|
63
|
+
const sampled = sampleBeta(a, b, rng);
|
|
64
|
+
const distance = Math.abs(sampled - threshold);
|
|
65
|
+
const variance = a * b / ((a + b) ** 2 * (a + b + 1));
|
|
66
|
+
const sigma = Math.max(0.05, Math.sqrt(variance));
|
|
67
|
+
const weight = Math.exp(-((distance / sigma) ** 2));
|
|
68
|
+
return {
|
|
69
|
+
variantId: c.variantId,
|
|
70
|
+
scenarioId: c.scenarioId,
|
|
71
|
+
n: cur.passes + cur.failures,
|
|
72
|
+
sampled,
|
|
73
|
+
sigma,
|
|
74
|
+
weight,
|
|
75
|
+
a,
|
|
76
|
+
b
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
const totalWeight = stats.reduce((s, c) => s + c.weight, 0);
|
|
80
|
+
return stats.map((c) => {
|
|
81
|
+
const proportional = totalWeight === 0 ? 0 : Math.round(c.weight / totalWeight * opts.budget);
|
|
82
|
+
return {
|
|
83
|
+
variantId: c.variantId,
|
|
84
|
+
scenarioId: c.scenarioId,
|
|
85
|
+
count: Math.max(0, proportional),
|
|
86
|
+
reason: `Beta(${c.a.toFixed(1)},${c.b.toFixed(1)}) sample=${c.sampled.toFixed(3)} (target ${threshold})`
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function observationsFromRunRecords(runs, opts = {}) {
|
|
91
|
+
const threshold = opts.passThreshold ?? 0.5;
|
|
92
|
+
const useHoldout = opts.useHoldout ?? true;
|
|
93
|
+
const out = [];
|
|
94
|
+
for (const r of runs) {
|
|
95
|
+
if (!r.scenarioId) continue;
|
|
96
|
+
const score = useHoldout ? r.outcome.holdoutScore ?? r.outcome.searchScore : r.outcome.searchScore ?? r.outcome.holdoutScore;
|
|
97
|
+
if (typeof score !== "number" || !Number.isFinite(score)) continue;
|
|
98
|
+
out.push({
|
|
99
|
+
variantId: r.candidateId,
|
|
100
|
+
scenarioId: r.scenarioId,
|
|
101
|
+
score,
|
|
102
|
+
pass: score >= threshold
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
function makeRng(seed) {
|
|
108
|
+
if (seed === void 0) return Math.random;
|
|
109
|
+
let s = seed >>> 0;
|
|
110
|
+
return () => {
|
|
111
|
+
s = s + 1831565813 >>> 0;
|
|
112
|
+
let t = s;
|
|
113
|
+
t = Math.imul(t ^ t >>> 15, t | 1);
|
|
114
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
115
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function sampleBeta(alpha, beta, rng) {
|
|
119
|
+
const a = Math.max(1, alpha);
|
|
120
|
+
const b = Math.max(1, beta);
|
|
121
|
+
const x = sampleGamma(a, rng);
|
|
122
|
+
const y = sampleGamma(b, rng);
|
|
123
|
+
return x / (x + y);
|
|
124
|
+
}
|
|
125
|
+
function sampleGamma(shape, rng) {
|
|
126
|
+
const d = shape - 1 / 3;
|
|
127
|
+
const c = 1 / Math.sqrt(9 * d);
|
|
128
|
+
while (true) {
|
|
129
|
+
let x;
|
|
130
|
+
let v;
|
|
131
|
+
do {
|
|
132
|
+
const u1 = rng() || 1e-12;
|
|
133
|
+
const u2 = rng() || 1e-12;
|
|
134
|
+
x = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
|
|
135
|
+
v = 1 + c * x;
|
|
136
|
+
} while (v <= 0);
|
|
137
|
+
v = v * v * v;
|
|
138
|
+
const u = rng();
|
|
139
|
+
if (u < 1 - 0.0331 * x ** 4) return d * v;
|
|
140
|
+
if (Math.log(u) < 0.5 * x * x + d * (1 - v + Math.log(v))) return d * v;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export {
|
|
145
|
+
varianceBasedCurriculum,
|
|
146
|
+
thompsonCurriculum,
|
|
147
|
+
observationsFromRunRecords
|
|
148
|
+
};
|
|
149
|
+
//# sourceMappingURL=chunk-VZSRQ272.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/rl/active-curriculum.ts"],"sourcesContent":["/**\n * Adaptive curriculum / active scenario selection.\n *\n * Fixed scenario sets waste sample budget on cells the policy already\n * passes (no information left) and cells the policy never passes (no\n * gradient available either). Active learning over scenarios fixes this\n * by allocating the next sample budget to cells where the policy's\n * outcome is *uncertain* — those carry the most decision-relevant signal.\n *\n * This module ships two complementary strategies:\n *\n * 1. **Variance-based** — score each (variant, scenario) cell by the\n * empirical variance of past observations. Allocate next-round budget\n * proportional to variance. Standard active-learning-by-uncertainty\n * heuristic; works well when the policy is non-deterministic and\n * cells differ in observation noise.\n *\n * 2. **Bandit-based (Thompson sampling)** — model each (variant,\n * scenario) cell as a Beta-Bernoulli arm; sample a posterior; pick\n * cells whose posterior mean is closest to the per-scenario decision\n * threshold. The right primitive when scenarios are\n * \"pass/fail\" rather than continuous, and when promotion gates fire\n * at a known threshold (e.g., 0.5).\n *\n * The output is a *next-round budget allocation* — a list of (variant,\n * scenario, count) triples. The consumer's matrix runner consumes the\n * allocation, runs those cells, feeds the new observations back. Loop.\n *\n * Out of scope (deliberate): scenario *generation* — that's the\n * adversarial primitive's job. This module allocates over an existing\n * scenario pool.\n */\n\nimport type { RunRecord } from '../run-record'\n\nexport interface CellObservation {\n variantId: string\n scenarioId: string\n /** Observed score in [0, 1]. */\n score: number\n /** For Bernoulli arms — derive from the score with a threshold if needed. */\n pass?: boolean\n}\n\nexport interface CurriculumAllocation {\n variantId: string\n scenarioId: string\n /** How many additional reps to run on this cell. */\n count: number\n /** Strategy-specific reason for the allocation. */\n reason: string\n}\n\nexport interface VarianceCurriculumOptions {\n /** Total reps to allocate across all cells. */\n budget: number\n /**\n * Smoothing prior on variance — keeps the allocator from concentrating\n * on a cell with one observation just because its 1-sample variance is\n * 0. Default 0.05.\n */\n variancePrior?: number\n /**\n * Minimum reps per cell — even when the variance estimate is low, give\n * every cell at least this many. Default 1.\n */\n floorPerCell?: number\n}\n\n/**\n * Variance-proportional allocation. For each cell, estimate variance from\n * past observations + a prior, then allocate the budget proportional to\n * (sqrt(variance) + 1/sqrt(n)) — a classical optimal-allocation rule\n * (Neyman 1934) that balances \"explore noisy cells\" with \"explore\n * under-sampled cells.\"\n */\nexport function varianceBasedCurriculum(\n observations: CellObservation[],\n candidateCells: Array<{ variantId: string; scenarioId: string }>,\n opts: VarianceCurriculumOptions,\n): CurriculumAllocation[] {\n const variancePrior = opts.variancePrior ?? 0.05\n const floor = opts.floorPerCell ?? 1\n const budget = opts.budget\n\n const grouped = new Map<string, number[]>()\n for (const o of observations) {\n const k = `${o.variantId}::${o.scenarioId}`\n const arr = grouped.get(k) ?? []\n arr.push(o.score)\n grouped.set(k, arr)\n }\n\n const cellStats = candidateCells.map((c) => {\n const k = `${c.variantId}::${c.scenarioId}`\n const samples = grouped.get(k) ?? []\n const n = samples.length\n const mean = n === 0 ? 0.5 : samples.reduce((s, v) => s + v, 0) / n\n const variance =\n n < 2\n ? variancePrior\n : samples.reduce((s, v) => s + (v - mean) ** 2, 0) / (n - 1) + variancePrior\n // Neyman optimal allocation: weight ∝ √variance; add √(1/n) to break\n // ties toward under-sampled cells.\n const weight = Math.sqrt(variance) + 1 / Math.sqrt(Math.max(1, n))\n return { variantId: c.variantId, scenarioId: c.scenarioId, n, mean, variance, weight }\n })\n\n // Reserve floor*N for the floor; allocate the rest proportional to weight.\n const floorTotal = floor * cellStats.length\n if (floorTotal >= budget) {\n const each = Math.max(1, Math.floor(budget / Math.max(1, cellStats.length)))\n return cellStats.map((c) => ({\n variantId: c.variantId,\n scenarioId: c.scenarioId,\n count: each,\n reason: `floor allocation (budget tight; n=${c.n})`,\n }))\n }\n const remaining = budget - floorTotal\n const totalWeight = cellStats.reduce((s, c) => s + c.weight, 0)\n return cellStats.map((c) => {\n const proportional = totalWeight === 0 ? 0 : Math.round((c.weight / totalWeight) * remaining)\n return {\n variantId: c.variantId,\n scenarioId: c.scenarioId,\n count: floor + proportional,\n reason: `variance ${c.variance.toFixed(3)} (n=${c.n}, mean=${c.mean.toFixed(3)})`,\n }\n })\n}\n\nexport interface ThompsonCurriculumOptions {\n budget: number\n /**\n * The per-scenario decision threshold. Cells whose posterior mean is\n * closest to this get the most budget — that's where the next observation\n * has the highest information value for the gate decision. Default 0.5.\n */\n decisionThreshold?: number\n /** Beta prior parameters. Default α=β=1 (uniform). */\n priorAlpha?: number\n priorBeta?: number\n /** Seed the Thompson sampler. Default unset (Math.random). */\n seed?: number\n}\n\n/**\n * Thompson-sampling-style allocation for pass/fail cells. For each cell:\n *\n * - Maintain Beta(α + passes, β + failures) posterior on pass-rate\n * - Allocation weight ∝ exp(-((sampledMean - threshold) / σ)^2):\n * cells whose sampled posterior straddles the decision boundary get\n * the most weight; cells already clearly above or below get less.\n *\n * This is the right primitive when promotion gates fire at a known\n * threshold and you want to sharpen the posterior near the boundary.\n */\nexport function thompsonCurriculum(\n observations: CellObservation[],\n candidateCells: Array<{ variantId: string; scenarioId: string }>,\n opts: ThompsonCurriculumOptions,\n): CurriculumAllocation[] {\n const threshold = opts.decisionThreshold ?? 0.5\n const alpha0 = opts.priorAlpha ?? 1\n const beta0 = opts.priorBeta ?? 1\n const rng = makeRng(opts.seed)\n\n const grouped = new Map<string, { passes: number; failures: number }>()\n for (const o of observations) {\n const k = `${o.variantId}::${o.scenarioId}`\n const cur = grouped.get(k) ?? { passes: 0, failures: 0 }\n const pass = o.pass ?? o.score >= threshold\n if (pass) cur.passes += 1\n else cur.failures += 1\n grouped.set(k, cur)\n }\n\n const stats = candidateCells.map((c) => {\n const k = `${c.variantId}::${c.scenarioId}`\n const cur = grouped.get(k) ?? { passes: 0, failures: 0 }\n const a = alpha0 + cur.passes\n const b = beta0 + cur.failures\n // Sample a single Beta draw — the Thompson signal.\n const sampled = sampleBeta(a, b, rng)\n const distance = Math.abs(sampled - threshold)\n // Information-near-threshold weight: closer = higher.\n // Use Gaussian-shaped kernel with σ tuned to posterior std.\n const variance = (a * b) / ((a + b) ** 2 * (a + b + 1))\n const sigma = Math.max(0.05, Math.sqrt(variance))\n const weight = Math.exp(-((distance / sigma) ** 2))\n return {\n variantId: c.variantId,\n scenarioId: c.scenarioId,\n n: cur.passes + cur.failures,\n sampled,\n sigma,\n weight,\n a,\n b,\n }\n })\n\n const totalWeight = stats.reduce((s, c) => s + c.weight, 0)\n return stats.map((c) => {\n const proportional = totalWeight === 0 ? 0 : Math.round((c.weight / totalWeight) * opts.budget)\n return {\n variantId: c.variantId,\n scenarioId: c.scenarioId,\n count: Math.max(0, proportional),\n reason: `Beta(${c.a.toFixed(1)},${c.b.toFixed(1)}) sample=${c.sampled.toFixed(3)} (target ${threshold})`,\n }\n })\n}\n\n/** Convenience: extract `CellObservation[]` directly from `RunRecord[]`. */\nexport function observationsFromRunRecords(\n runs: RunRecord[],\n opts: { passThreshold?: number; useHoldout?: boolean } = {},\n): CellObservation[] {\n const threshold = opts.passThreshold ?? 0.5\n const useHoldout = opts.useHoldout ?? true\n const out: CellObservation[] = []\n for (const r of runs) {\n if (!r.scenarioId) continue\n const score = useHoldout\n ? (r.outcome.holdoutScore ?? r.outcome.searchScore)\n : (r.outcome.searchScore ?? r.outcome.holdoutScore)\n if (typeof score !== 'number' || !Number.isFinite(score)) continue\n out.push({\n variantId: r.candidateId,\n scenarioId: r.scenarioId,\n score,\n pass: score >= threshold,\n })\n }\n return out\n}\n\n// ── Helpers ──────────────────────────────────────────────────────────────\n\nfunction makeRng(seed?: number): () => number {\n if (seed === undefined) return Math.random\n let s = seed >>> 0\n return () => {\n s = (s + 0x6d2b79f5) >>> 0\n let t = s\n t = Math.imul(t ^ (t >>> 15), t | 1)\n t ^= t + Math.imul(t ^ (t >>> 7), t | 61)\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296\n }\n}\n\n/**\n * Sample from Beta(α, β) via the Marsaglia–Tsang method using two Gamma\n * variates. Accuracy is good for α, β > 1; we floor the parameters at 1\n * to avoid degenerate cases.\n */\nfunction sampleBeta(alpha: number, beta: number, rng: () => number): number {\n const a = Math.max(1, alpha)\n const b = Math.max(1, beta)\n const x = sampleGamma(a, rng)\n const y = sampleGamma(b, rng)\n return x / (x + y)\n}\n\nfunction sampleGamma(shape: number, rng: () => number): number {\n // Marsaglia–Tsang for shape ≥ 1.\n const d = shape - 1 / 3\n const c = 1 / Math.sqrt(9 * d)\n while (true) {\n let x: number\n let v: number\n do {\n const u1 = rng() || 1e-12\n const u2 = rng() || 1e-12\n // Box-Muller for a normal sample.\n x = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2)\n v = 1 + c * x\n } while (v <= 0)\n v = v * v * v\n const u = rng()\n if (u < 1 - 0.0331 * x ** 4) return d * v\n if (Math.log(u) < 0.5 * x * x + d * (1 - v + Math.log(v))) return d * v\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
AnalystRegistry,
|
|
3
|
-
DEFAULT_TRACE_ANALYST_KINDS,
|
|
4
|
-
computeFindingId,
|
|
5
|
-
createTraceAnalystKind,
|
|
6
|
-
makeFinding
|
|
7
|
-
} from "./chunk-VIDQF3F5.js";
|
|
8
1
|
import {
|
|
9
2
|
LlmClient,
|
|
10
3
|
callLlmJson
|
|
11
4
|
} from "./chunk-CVVHBFGN.js";
|
|
5
|
+
import {
|
|
6
|
+
computeFindingId
|
|
7
|
+
} from "./chunk-45EEMHTC.js";
|
|
12
8
|
import {
|
|
13
9
|
NotFoundError
|
|
14
10
|
} from "./chunk-3BFEG2F6.js";
|
|
@@ -111,180 +107,6 @@ function resolveModel(req, defaultModel) {
|
|
|
111
107
|
return { ...req, model: defaultModel };
|
|
112
108
|
}
|
|
113
109
|
|
|
114
|
-
// src/trace-analyst/behavioral-metrics.ts
|
|
115
|
-
var INPUT_GROWTH_FACTOR = 3;
|
|
116
|
-
var MIN_TOOL_CALLS = 3;
|
|
117
|
-
var VERIFY_RE = /verif|eval|inspect|check|assert|validat|review|confirm/i;
|
|
118
|
-
function num(v) {
|
|
119
|
-
return typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
120
|
-
}
|
|
121
|
-
function inputTokensOf(s) {
|
|
122
|
-
return num(s.attributes["llm.input_tokens"]) ?? num(s.attributes["llm.usage.input_tokens"]);
|
|
123
|
-
}
|
|
124
|
-
function outputTokensOf(s) {
|
|
125
|
-
return num(s.attributes["llm.output_tokens"]) ?? num(s.attributes["llm.usage.output_tokens"]);
|
|
126
|
-
}
|
|
127
|
-
function stepOf(s) {
|
|
128
|
-
return num(s.attributes.step);
|
|
129
|
-
}
|
|
130
|
-
function toolNameOf(s) {
|
|
131
|
-
if (s.tool_name) return s.tool_name;
|
|
132
|
-
const t = s.attributes["tool.name"];
|
|
133
|
-
return typeof t === "string" && t.length > 0 ? t : null;
|
|
134
|
-
}
|
|
135
|
-
function computeTraceMetrics(spans) {
|
|
136
|
-
const ordered = [...spans].sort((a, b) => {
|
|
137
|
-
const sa = stepOf(a);
|
|
138
|
-
const sb = stepOf(b);
|
|
139
|
-
if (sa !== null && sb !== null && sa !== sb) return sa - sb;
|
|
140
|
-
return a.start_time.localeCompare(b.start_time);
|
|
141
|
-
});
|
|
142
|
-
const inputTokenTrajectory = [];
|
|
143
|
-
const outputTokenTrajectory = [];
|
|
144
|
-
const toolHistogram = {};
|
|
145
|
-
let hasSelfVerification = false;
|
|
146
|
-
for (const s of ordered) {
|
|
147
|
-
const inT = inputTokensOf(s);
|
|
148
|
-
if (inT !== null) inputTokenTrajectory.push(inT);
|
|
149
|
-
const outT = outputTokensOf(s);
|
|
150
|
-
if (outT !== null) outputTokenTrajectory.push(outT);
|
|
151
|
-
const tool = toolNameOf(s);
|
|
152
|
-
if (tool) {
|
|
153
|
-
toolHistogram[tool] = (toolHistogram[tool] ?? 0) + 1;
|
|
154
|
-
if (VERIFY_RE.test(tool)) hasSelfVerification = true;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
const totalToolCalls = Object.values(toolHistogram).reduce((a, b) => a + b, 0);
|
|
158
|
-
const distinctTools = Object.keys(toolHistogram).length;
|
|
159
|
-
const toolDiversityRatio = totalToolCalls === 0 ? 1 : distinctTools / totalToolCalls;
|
|
160
|
-
const signals = [];
|
|
161
|
-
if (inputTokenTrajectory.length >= 3) {
|
|
162
|
-
const first = inputTokenTrajectory[0];
|
|
163
|
-
const last = inputTokenTrajectory[inputTokenTrajectory.length - 1];
|
|
164
|
-
const growth = first > 0 ? last / first : 0;
|
|
165
|
-
if (last > first && growth >= INPUT_GROWTH_FACTOR) {
|
|
166
|
-
signals.push({
|
|
167
|
-
code: "monotonic-input-growth",
|
|
168
|
-
severity: "high",
|
|
169
|
-
detail: `LLM input tokens grew ${growth.toFixed(1)}x (${first}\u2192${last}) across ${inputTokenTrajectory.length} calls \u2014 full history re-sent each step with no compression.`,
|
|
170
|
-
evidence: {
|
|
171
|
-
first,
|
|
172
|
-
last,
|
|
173
|
-
growth_x: Number(growth.toFixed(2)),
|
|
174
|
-
calls: inputTokenTrajectory.length
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
if (outputTokenTrajectory.length >= 3) {
|
|
180
|
-
const first = outputTokenTrajectory[0];
|
|
181
|
-
const last = outputTokenTrajectory[outputTokenTrajectory.length - 1];
|
|
182
|
-
if (last < first) {
|
|
183
|
-
signals.push({
|
|
184
|
-
code: "output-length-decay",
|
|
185
|
-
severity: "medium",
|
|
186
|
-
detail: `LLM output tokens shrank ${first}\u2192${last} over ${outputTokenTrajectory.length} calls \u2014 less planning/reasoning per step as context grows.`,
|
|
187
|
-
evidence: { first, last, calls: outputTokenTrajectory.length }
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
if (totalToolCalls >= MIN_TOOL_CALLS && distinctTools === 1) {
|
|
192
|
-
const only = Object.keys(toolHistogram)[0];
|
|
193
|
-
signals.push({
|
|
194
|
-
code: "single-tool-dependency",
|
|
195
|
-
severity: "medium",
|
|
196
|
-
detail: `All ${totalToolCalls} tool calls are \`${only}\` \u2014 no tool diversity and no fallback path.`,
|
|
197
|
-
evidence: { tool: only, calls: totalToolCalls, distinct_tools: 1 }
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
if (totalToolCalls >= MIN_TOOL_CALLS && !hasSelfVerification) {
|
|
201
|
-
signals.push({
|
|
202
|
-
code: "no-self-verification",
|
|
203
|
-
severity: "medium",
|
|
204
|
-
detail: `${totalToolCalls} tool calls and none verify/inspect/check state \u2014 the agent never validates its own actions.`,
|
|
205
|
-
evidence: { tool_calls: totalToolCalls, verification_calls: 0 }
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
return {
|
|
209
|
-
llmCallCount: inputTokenTrajectory.length,
|
|
210
|
-
inputTokenTrajectory,
|
|
211
|
-
outputTokenTrajectory,
|
|
212
|
-
toolHistogram,
|
|
213
|
-
totalToolCalls,
|
|
214
|
-
distinctTools,
|
|
215
|
-
toolDiversityRatio,
|
|
216
|
-
hasSelfVerification,
|
|
217
|
-
signals
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// src/analyst/behavioral-analyst.ts
|
|
222
|
-
var RECOMMENDED_ACTION = {
|
|
223
|
-
"monotonic-input-growth": "Add a context-budget instruction: once prior context exceeds a threshold, summarize earlier steps into a short status line instead of re-sending full history.",
|
|
224
|
-
"output-length-decay": "Require a minimum planning/reasoning budget per step so late steps do not degrade into terse, error-prone commands.",
|
|
225
|
-
"single-tool-dependency": "Direct the agent to use the full toolset (verify / inspect / alternate actions), not a single execute call, and to plan a fallback when a call returns an unexpected result.",
|
|
226
|
-
"no-self-verification": "After every state-mutating action, verify the result (eval / inspect / assert) before proceeding."
|
|
227
|
-
};
|
|
228
|
-
var ANALYST_ID = "efficiency-behavioral";
|
|
229
|
-
function deriveEfficiencyFindings(metrics, opts = {}) {
|
|
230
|
-
const analystId = opts.analystId ?? ANALYST_ID;
|
|
231
|
-
return metrics.signals.map(
|
|
232
|
-
(sig) => makeFinding({
|
|
233
|
-
analyst_id: analystId,
|
|
234
|
-
area: "efficiency",
|
|
235
|
-
subject: sig.code,
|
|
236
|
-
// kebab — passes the cluster grammar; stable key for diffFindings
|
|
237
|
-
claim: sig.detail,
|
|
238
|
-
severity: sig.severity,
|
|
239
|
-
// Deterministic arithmetic over spans, not a model judgment → certain.
|
|
240
|
-
confidence: 1,
|
|
241
|
-
evidence_refs: [
|
|
242
|
-
{
|
|
243
|
-
kind: "metric",
|
|
244
|
-
uri: `metric://efficiency/${sig.code}`,
|
|
245
|
-
excerpt: JSON.stringify(sig.evidence)
|
|
246
|
-
}
|
|
247
|
-
],
|
|
248
|
-
recommended_action: RECOMMENDED_ACTION[sig.code],
|
|
249
|
-
metadata: { deterministic: true, evidence: sig.evidence },
|
|
250
|
-
...opts.producedAt ? { produced_at: opts.producedAt } : {}
|
|
251
|
-
})
|
|
252
|
-
);
|
|
253
|
-
}
|
|
254
|
-
function behavioralAnalyst() {
|
|
255
|
-
return {
|
|
256
|
-
id: ANALYST_ID,
|
|
257
|
-
description: "Deterministic behavioral/efficiency findings over OTLP spans \u2014 token-growth, output-decay, tool-monoculture, missing self-verification. Zero LLM; model-agnostic by construction.",
|
|
258
|
-
inputKind: "trace-store",
|
|
259
|
-
cost: { kind: "deterministic" },
|
|
260
|
-
version: "1.0.0",
|
|
261
|
-
async analyze(store) {
|
|
262
|
-
const overview = await store.getOverview();
|
|
263
|
-
const spans = [];
|
|
264
|
-
for (const traceId of overview.sample_trace_ids) {
|
|
265
|
-
const viewed = await store.viewTrace({ trace_id: traceId });
|
|
266
|
-
if (viewed.spans) spans.push(...viewed.spans);
|
|
267
|
-
}
|
|
268
|
-
return deriveEfficiencyFindings(computeTraceMetrics(spans));
|
|
269
|
-
}
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// src/analyst/default-registry.ts
|
|
274
|
-
function buildDefaultAnalystRegistry(opts = {}) {
|
|
275
|
-
const registry = new AnalystRegistry(opts.registry);
|
|
276
|
-
if (opts.includeBehavioral !== false) {
|
|
277
|
-
registry.register(behavioralAnalyst());
|
|
278
|
-
}
|
|
279
|
-
if (opts.ai) {
|
|
280
|
-
const kinds = opts.kinds ?? DEFAULT_TRACE_ANALYST_KINDS;
|
|
281
|
-
for (const spec of kinds) {
|
|
282
|
-
registry.register(createTraceAnalystKind(spec, { ai: opts.ai, model: opts.model }));
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
return registry;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
110
|
// src/concurrency.ts
|
|
289
111
|
var Mutex = class {
|
|
290
112
|
locked = false;
|
|
@@ -562,12 +384,12 @@ function buildSkillUsageReport(config) {
|
|
|
562
384
|
});
|
|
563
385
|
return { generatedFromTraces: transcripts, records };
|
|
564
386
|
}
|
|
565
|
-
var
|
|
387
|
+
var ANALYST_ID = "skill-usage";
|
|
566
388
|
function finding(area, subject, claim, severity, confidence, producedAt, recommended, evidenceUri, rationale) {
|
|
567
389
|
return {
|
|
568
390
|
schema_version: "1.0.0",
|
|
569
|
-
finding_id: computeFindingId({ analyst_id:
|
|
570
|
-
analyst_id:
|
|
391
|
+
finding_id: computeFindingId({ analyst_id: ANALYST_ID, area, subject, claim }),
|
|
392
|
+
analyst_id: ANALYST_ID,
|
|
571
393
|
produced_at: producedAt,
|
|
572
394
|
severity,
|
|
573
395
|
area,
|
|
@@ -687,7 +509,7 @@ function emitSkillUsageFindings(report, producedAt) {
|
|
|
687
509
|
return out;
|
|
688
510
|
}
|
|
689
511
|
var SkillUsageAnalyst = class {
|
|
690
|
-
id =
|
|
512
|
+
id = ANALYST_ID;
|
|
691
513
|
description = "Deterministic multi-signal skill-usage analysis: flags dead skills, measurement-invisible (orchestrated) usage, discovery gaps, public-repo leaks, bloat, missing evals, and missing run-logging.";
|
|
692
514
|
inputKind = "custom";
|
|
693
515
|
cost = { kind: "deterministic", est_usd_per_run: 0 };
|
|
@@ -1049,10 +871,6 @@ function createSemanticConceptJudge(options = {}) {
|
|
|
1049
871
|
export {
|
|
1050
872
|
createAnalystAi,
|
|
1051
873
|
createChatClient,
|
|
1052
|
-
computeTraceMetrics,
|
|
1053
|
-
deriveEfficiencyFindings,
|
|
1054
|
-
behavioralAnalyst,
|
|
1055
|
-
buildDefaultAnalystRegistry,
|
|
1056
874
|
Mutex,
|
|
1057
875
|
LockedJsonlAppender,
|
|
1058
876
|
resetLockedAppendersForTesting,
|
|
@@ -1072,4 +890,4 @@ export {
|
|
|
1072
890
|
runSemanticConceptJudge,
|
|
1073
891
|
createSemanticConceptJudge
|
|
1074
892
|
};
|
|
1075
|
-
//# sourceMappingURL=chunk-
|
|
893
|
+
//# sourceMappingURL=chunk-XY4DDNEG.js.map
|