@dzhechkov/harness-core 0.7.10 → 0.7.12
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/.dz-manifest.json +97 -37
- package/README.md +27 -1
- package/dist/amendment-trace.d.ts +12 -1
- package/dist/amendment-trace.d.ts.map +1 -1
- package/dist/amendment-trace.js +22 -4
- package/dist/amendment-trace.js.map +1 -1
- package/dist/feature-adr-routing.d.ts +69 -12
- package/dist/feature-adr-routing.d.ts.map +1 -1
- package/dist/feature-adr-routing.js +117 -7
- package/dist/feature-adr-routing.js.map +1 -1
- package/dist/index.d.ts +7 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -3
- package/dist/index.js.map +1 -1
- package/dist/learning-backend.d.ts +39 -0
- package/dist/learning-backend.d.ts.map +1 -1
- package/dist/learning-backend.js +31 -11
- package/dist/learning-backend.js.map +1 -1
- package/dist/lesson-bandit.d.ts +116 -0
- package/dist/lesson-bandit.d.ts.map +1 -0
- package/dist/lesson-bandit.js +235 -0
- package/dist/lesson-bandit.js.map +1 -0
- package/dist/lesson-payoff.d.ts +260 -0
- package/dist/lesson-payoff.d.ts.map +1 -0
- package/dist/lesson-payoff.js +597 -0
- package/dist/lesson-payoff.js.map +1 -0
- package/dist/patterns.d.ts +21 -0
- package/dist/patterns.d.ts.map +1 -1
- package/dist/patterns.js +42 -3
- package/dist/patterns.js.map +1 -1
- package/dist/project-skills-root.d.ts +44 -0
- package/dist/project-skills-root.d.ts.map +1 -0
- package/dist/project-skills-root.js +62 -0
- package/dist/project-skills-root.js.map +1 -0
- package/dist/publish.d.ts.map +1 -1
- package/dist/publish.js +6 -0
- package/dist/publish.js.map +1 -1
- package/dist/vector-tier.d.ts +30 -0
- package/dist/vector-tier.d.ts.map +1 -1
- package/dist/vector-tier.js +130 -15
- package/dist/vector-tier.js.map +1 -1
- package/package.json +6 -6
- package/sbom.json +186 -36
- package/src/amendment-trace.ts +34 -4
- package/src/feature-adr-routing.ts +146 -7
- package/src/index.ts +16 -2
- package/src/learning-backend.ts +62 -11
- package/src/lesson-bandit.ts +279 -0
- package/src/lesson-payoff.ts +728 -0
- package/src/patterns.ts +66 -5
- package/src/project-skills-root.ts +63 -0
- package/src/publish.ts +6 -0
- package/src/vector-tier.ts +182 -16
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VENDORED — contextual Thompson-Sampling bandit (feature lesson-bandit-rerank, ADR-001 D-1).
|
|
3
|
+
*
|
|
4
|
+
* Upstream: agentdb@3.0.0-alpha.20 — dist/src/backends/rvf/SolverBandit.js
|
|
5
|
+
* Licence: MIT, Copyright (c) 2024-2025 ruv
|
|
6
|
+
* Vendored: 2026-08-26. 215 lines, ZERO imports (both re-verified at copy time:
|
|
7
|
+
* `wc -l` → 215, `grep -cE 'require|^import'` → 0).
|
|
8
|
+
* SHA-256: 0199299fb60ef67afa030182185894c950126038398032349fe3618d910f6d7d
|
|
9
|
+
* (of the upstream .js at copy time; pinned by lesson-bandit-vendor.test.ts)
|
|
10
|
+
* Reason: the path is NOT in agentdb's package.json "exports" map (verified 2026-08-26);
|
|
11
|
+
* a deep import of a private path in a 3.0.0-alpha prerelease can change silently,
|
|
12
|
+
* and a ranking feature that quietly stops ranking looks exactly like one that works.
|
|
13
|
+
*
|
|
14
|
+
* Do not edit logic. Re-vendor from upstream and re-diff instead.
|
|
15
|
+
*
|
|
16
|
+
* WHAT CHANGED relative to the upstream .js, and nothing else:
|
|
17
|
+
* · `class SolverBandit` → `class LessonBandit` (ADR-001 D-1: one name, no alias);
|
|
18
|
+
* · TYPES added (field declarations, method signatures, the three exported interfaces);
|
|
19
|
+
* · `armKeys[0]` → `armKeys[0]!` and `ctx.get(armKey)` → `!` — required by this package's
|
|
20
|
+
* `noUncheckedIndexedAccess`; the emitted arithmetic is unchanged.
|
|
21
|
+
* The Jöhnk / Marsaglia-Tsang samplers, the `a<=1 && b<=1 ⇒ Math.random()` short-circuit, the
|
|
22
|
+
* exploration bonus and the cost EMA are preserved verbatim — the MEASURED API behaviour (200
|
|
23
|
+
* pulls on a 0.85-vs-0.15 pair → 100/100 correct picks) is a property of exactly this arithmetic.
|
|
24
|
+
*
|
|
25
|
+
* THIS FILE MUST STAY IMPORT-FREE (NFR-2 / C-7 / INV-6) — asserted by a repo test.
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* Thompson Sampling bandit with contextual arms.
|
|
29
|
+
*
|
|
30
|
+
* Usage:
|
|
31
|
+
* const bandit = new LessonBandit();
|
|
32
|
+
* const arm = bandit.selectArm('code_review', ['skill-a', 'skill-b', 'skill-c']);
|
|
33
|
+
* // ... execute the selected arm ...
|
|
34
|
+
* bandit.recordReward('code_review', arm, 0.85);
|
|
35
|
+
*/
|
|
36
|
+
export class LessonBandit {
|
|
37
|
+
contexts = new Map();
|
|
38
|
+
config;
|
|
39
|
+
constructor(config) {
|
|
40
|
+
this.config = {
|
|
41
|
+
costWeight: config?.costWeight ?? 0.01,
|
|
42
|
+
costDecay: config?.costDecay ?? 0.1,
|
|
43
|
+
explorationBonus: config?.explorationBonus ?? 0.1,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Select the best arm for a given context using Thompson Sampling.
|
|
48
|
+
*
|
|
49
|
+
* For each candidate arm, samples from its Beta(alpha, beta) distribution
|
|
50
|
+
* and subtracts a cost penalty. Returns the arm with the highest score.
|
|
51
|
+
* Unknown arms get an exploration bonus.
|
|
52
|
+
*/
|
|
53
|
+
selectArm(contextKey, armKeys) {
|
|
54
|
+
if (armKeys.length === 0)
|
|
55
|
+
throw new Error('No arms provided');
|
|
56
|
+
if (armKeys.length === 1)
|
|
57
|
+
return armKeys[0];
|
|
58
|
+
const ctx = this.contexts.get(contextKey);
|
|
59
|
+
let bestArm = armKeys[0];
|
|
60
|
+
let bestScore = -Infinity;
|
|
61
|
+
for (const arm of armKeys) {
|
|
62
|
+
const stats = ctx?.get(arm);
|
|
63
|
+
let score;
|
|
64
|
+
if (!stats || stats.pulls === 0) {
|
|
65
|
+
// Unknown arm: sample from uniform + exploration bonus
|
|
66
|
+
score = Math.random() + this.config.explorationBonus;
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
// Thompson sample from Beta(alpha, beta)
|
|
70
|
+
const sample = this.sampleBeta(stats.alpha, stats.beta);
|
|
71
|
+
score = sample - stats.costEma * this.config.costWeight;
|
|
72
|
+
}
|
|
73
|
+
if (score > bestScore) {
|
|
74
|
+
bestScore = score;
|
|
75
|
+
bestArm = arm;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return bestArm;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Record the outcome of pulling an arm.
|
|
82
|
+
*
|
|
83
|
+
* @param contextKey - The context bucket (e.g., task type)
|
|
84
|
+
* @param armKey - The arm that was pulled (e.g., skill name)
|
|
85
|
+
* @param reward - Success signal in [0, 1]
|
|
86
|
+
* @param cost - Optional cost signal (latency, tokens, etc.)
|
|
87
|
+
*/
|
|
88
|
+
recordReward(contextKey, armKey, reward, cost) {
|
|
89
|
+
if (!this.contexts.has(contextKey)) {
|
|
90
|
+
this.contexts.set(contextKey, new Map());
|
|
91
|
+
}
|
|
92
|
+
const ctx = this.contexts.get(contextKey);
|
|
93
|
+
if (!ctx.has(armKey)) {
|
|
94
|
+
ctx.set(armKey, { alpha: 1, beta: 1, pulls: 0, totalReward: 0, costEma: 0 });
|
|
95
|
+
}
|
|
96
|
+
const arm = ctx.get(armKey);
|
|
97
|
+
// Update Beta distribution
|
|
98
|
+
const r = Math.max(0, Math.min(1, reward));
|
|
99
|
+
arm.alpha += r;
|
|
100
|
+
arm.beta += (1 - r);
|
|
101
|
+
arm.pulls++;
|
|
102
|
+
arm.totalReward += r;
|
|
103
|
+
// Update cost EMA
|
|
104
|
+
if (cost !== undefined) {
|
|
105
|
+
arm.costEma = arm.costEma * (1 - this.config.costDecay) + cost * this.config.costDecay;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Rerank a list of candidates using bandit scores.
|
|
110
|
+
* Returns indices sorted by Thompson-sampled score (best first).
|
|
111
|
+
*/
|
|
112
|
+
rerank(contextKey, armKeys) {
|
|
113
|
+
if (armKeys.length <= 1)
|
|
114
|
+
return [...armKeys];
|
|
115
|
+
const ctx = this.contexts.get(contextKey);
|
|
116
|
+
const scored = armKeys.map((arm) => {
|
|
117
|
+
const stats = ctx?.get(arm);
|
|
118
|
+
let score;
|
|
119
|
+
if (!stats || stats.pulls === 0) {
|
|
120
|
+
score = Math.random() + this.config.explorationBonus;
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
score = this.sampleBeta(stats.alpha, stats.beta) - stats.costEma * this.config.costWeight;
|
|
124
|
+
}
|
|
125
|
+
return { arm, score };
|
|
126
|
+
});
|
|
127
|
+
scored.sort((a, b) => b.score - a.score);
|
|
128
|
+
return scored.map((s) => s.arm);
|
|
129
|
+
}
|
|
130
|
+
/** Get arm stats for a specific context */
|
|
131
|
+
getArmStats(contextKey, armKey) {
|
|
132
|
+
return this.contexts.get(contextKey)?.get(armKey) ?? null;
|
|
133
|
+
}
|
|
134
|
+
/** Get aggregate statistics */
|
|
135
|
+
getStats() {
|
|
136
|
+
let totalArms = 0, totalPulls = 0, totalReward = 0;
|
|
137
|
+
for (const ctx of this.contexts.values()) {
|
|
138
|
+
totalArms += ctx.size;
|
|
139
|
+
for (const arm of ctx.values()) {
|
|
140
|
+
totalPulls += arm.pulls;
|
|
141
|
+
totalReward += arm.totalReward;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return { contexts: this.contexts.size, totalArms, totalPulls, totalReward };
|
|
145
|
+
}
|
|
146
|
+
/** Serialize to JSON-safe state */
|
|
147
|
+
serialize() {
|
|
148
|
+
const contexts = {};
|
|
149
|
+
for (const [ctxKey, arms] of this.contexts) {
|
|
150
|
+
contexts[ctxKey] = {};
|
|
151
|
+
for (const [armKey, stats] of arms) {
|
|
152
|
+
contexts[ctxKey][armKey] = { ...stats };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return { version: 1, config: { ...this.config }, contexts };
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Restore from serialized state.
|
|
159
|
+
*
|
|
160
|
+
* STATIC (C-2). A first draft that called it as an instance method threw `TypeError`; the
|
|
161
|
+
* signature here makes the static call the only one that compiles.
|
|
162
|
+
*/
|
|
163
|
+
static deserialize(state) {
|
|
164
|
+
const bandit = new LessonBandit(state.config);
|
|
165
|
+
for (const [ctxKey, arms] of Object.entries(state.contexts)) {
|
|
166
|
+
const ctx = new Map();
|
|
167
|
+
for (const [armKey, stats] of Object.entries(arms)) {
|
|
168
|
+
ctx.set(armKey, { ...stats });
|
|
169
|
+
}
|
|
170
|
+
bandit.contexts.set(ctxKey, ctx);
|
|
171
|
+
}
|
|
172
|
+
return bandit;
|
|
173
|
+
}
|
|
174
|
+
/** Reset all learned state */
|
|
175
|
+
reset() {
|
|
176
|
+
this.contexts.clear();
|
|
177
|
+
}
|
|
178
|
+
// ─── Private ───
|
|
179
|
+
/**
|
|
180
|
+
* Sample from Beta(a, b) using the Jöhnk algorithm.
|
|
181
|
+
* Fast approximation for typical bandit parameters.
|
|
182
|
+
*/
|
|
183
|
+
sampleBeta(a, b) {
|
|
184
|
+
// For a=1, b=1 (uniform): just return Math.random()
|
|
185
|
+
if (a <= 1 && b <= 1)
|
|
186
|
+
return Math.random();
|
|
187
|
+
// Jöhnk's algorithm for general Beta
|
|
188
|
+
if (a < 1 && b < 1) {
|
|
189
|
+
for (let iter = 0; iter < 1000; iter++) {
|
|
190
|
+
const u = Math.random();
|
|
191
|
+
const v = Math.random();
|
|
192
|
+
const x = Math.pow(u, 1 / a);
|
|
193
|
+
const y = Math.pow(v, 1 / b);
|
|
194
|
+
if (x + y <= 1)
|
|
195
|
+
return x / (x + y);
|
|
196
|
+
}
|
|
197
|
+
return Math.random(); // fallback (extremely unlikely)
|
|
198
|
+
}
|
|
199
|
+
// For larger parameters, use Gamma ratio
|
|
200
|
+
const ga = this.sampleGamma(a);
|
|
201
|
+
const gb = this.sampleGamma(b);
|
|
202
|
+
return ga / (ga + gb);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Sample from Gamma(shape, 1) using Marsaglia & Tsang's method.
|
|
206
|
+
*/
|
|
207
|
+
sampleGamma(shape) {
|
|
208
|
+
if (shape < 1) {
|
|
209
|
+
return this.sampleGamma(shape + 1) * Math.pow(Math.random(), 1 / shape);
|
|
210
|
+
}
|
|
211
|
+
const d = shape - 1 / 3;
|
|
212
|
+
const c = 1 / Math.sqrt(9 * d);
|
|
213
|
+
for (let iter = 0; iter < 1000; iter++) {
|
|
214
|
+
let x, v;
|
|
215
|
+
do {
|
|
216
|
+
x = this.sampleNormal();
|
|
217
|
+
v = 1 + c * x;
|
|
218
|
+
} while (v <= 0);
|
|
219
|
+
v = v * v * v;
|
|
220
|
+
const u = Math.random();
|
|
221
|
+
if (u < 1 - 0.0331 * (x * x) * (x * x))
|
|
222
|
+
return d * v;
|
|
223
|
+
if (Math.log(u) < 0.5 * x * x + d * (1 - v + Math.log(v)))
|
|
224
|
+
return d * v;
|
|
225
|
+
}
|
|
226
|
+
return d; // fallback (extremely unlikely)
|
|
227
|
+
}
|
|
228
|
+
/** Box-Muller normal sample */
|
|
229
|
+
sampleNormal() {
|
|
230
|
+
const u1 = Math.random();
|
|
231
|
+
const u2 = Math.random();
|
|
232
|
+
return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
//# sourceMappingURL=lesson-bandit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lesson-bandit.js","sourceRoot":"","sources":["../src/lesson-bandit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAiCH;;;;;;;;GAQG;AACH,MAAM,OAAO,YAAY;IACf,QAAQ,GAAG,IAAI,GAAG,EAAiC,CAAC;IAC3C,MAAM,CAAe;IAEtC,YAAY,MAA8B;QACxC,IAAI,CAAC,MAAM,GAAG;YACZ,UAAU,EAAE,MAAM,EAAE,UAAU,IAAI,IAAI;YACtC,SAAS,EAAE,MAAM,EAAE,SAAS,IAAI,GAAG;YACnC,gBAAgB,EAAE,MAAM,EAAE,gBAAgB,IAAI,GAAG;SAClD,CAAC;IACJ,CAAC;IAED;;;;;;OAMG;IACH,SAAS,CAAC,UAAkB,EAAE,OAA0B;QACtD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC;QACtC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YACtB,OAAO,OAAO,CAAC,CAAC,CAAE,CAAC;QACrB,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC1C,IAAI,OAAO,GAAG,OAAO,CAAC,CAAC,CAAE,CAAC;QAC1B,IAAI,SAAS,GAAG,CAAC,QAAQ,CAAC;QAC1B,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;YAC1B,MAAM,KAAK,GAAG,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;YAC5B,IAAI,KAAa,CAAC;YAClB,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK,KAAK,CAAC,EAAE,CAAC;gBAChC,uDAAuD;gBACvD,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC;YACvD,CAAC;iBACI,CAAC;gBACJ,yCAAyC;gBACzC,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;gBACxD,KAAK,GAAG,MAAM,GAAG,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC;YAC1D,CAAC;YACD,IAAI,KAAK,GAAG,SAAS,EAAE,CAAC;gBACtB,SAAS,GAAG,KAAK,CAAC;gBAClB,OAAO,GAAG,GAAG,CAAC;YAChB,CAAC;QACH,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;;;;;;OAOG;IACH,YAAY,CAAC,UAAkB,EAAE,MAAc,EAAE,MAAc,EAAE,IAAa;QAC5E,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;YACnC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,GAAG,EAAE,CAAC,CAAC;QAC3C,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAE,CAAC;QAC3C,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YACrB,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;QAC/E,CAAC;QACD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,CAAE,CAAC;QAC7B,2BAA2B;QAC3B,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QAC3C,GAAG,CAAC,KAAK,IAAI,CAAC,CAAC;QACf,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACpB,GAAG,CAAC,KAAK,EAAE,CAAC;QACZ,GAAG,CAAC,WAAW,IAAI,CAAC,CAAC;QACrB,kBAAkB;QAClB,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,GAAG,CAAC,OAAO,GAAG,GAAG,CAAC,OAAO,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC;QACzF,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,UAAkB,EAAE,OAA0B;QACnD,IAAI,OAAO,CAAC,MAAM,IAAI,CAAC;YACrB,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC;QACtB,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC1C,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;YACjC,MAAM,KAAK,GAAG,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;YAC5B,IAAI,KAAa,CAAC;YAClB,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK,KAAK,CAAC,EAAE,CAAC;gBAChC,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC;YACvD,CAAC;iBACI,CAAC;gBACJ,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC;YAC5F,CAAC;YACD,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;QACxB,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;QACzC,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAClC,CAAC;IAED,2CAA2C;IAC3C,WAAW,CAAC,UAAkB,EAAE,MAAc;QAC5C,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC;IAC5D,CAAC;IAED,+BAA+B;IAC/B,QAAQ;QACN,IAAI,SAAS,GAAG,CAAC,EAAE,UAAU,GAAG,CAAC,EAAE,WAAW,GAAG,CAAC,CAAC;QACnD,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;YACzC,SAAS,IAAI,GAAG,CAAC,IAAI,CAAC;YACtB,KAAK,MAAM,GAAG,IAAI,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC;gBAC/B,UAAU,IAAI,GAAG,CAAC,KAAK,CAAC;gBACxB,WAAW,IAAI,GAAG,CAAC,WAAW,CAAC;YACjC,CAAC;QACH,CAAC;QACD,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC;IAC9E,CAAC;IAED,mCAAmC;IACnC,SAAS;QACP,MAAM,QAAQ,GAA6C,EAAE,CAAC;QAC9D,KAAK,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC3C,QAAQ,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC;YACtB,KAAK,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC;gBACnC,QAAQ,CAAC,MAAM,CAAE,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC;YAC3C,CAAC;QACH,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,CAAC;IAC9D,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,WAAW,CAAC,KAA4B;QAC7C,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC9C,KAAK,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC5D,MAAM,GAAG,GAAG,IAAI,GAAG,EAAoB,CAAC;YACxC,KAAK,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;gBACnD,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;YAChC,CAAC;YACD,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QACnC,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,8BAA8B;IAC9B,KAAK;QACH,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;IAED,kBAAkB;IAElB;;;OAGG;IACK,UAAU,CAAC,CAAS,EAAE,CAAS;QACrC,oDAAoD;QACpD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;YAClB,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC;QACvB,qCAAqC;QACrC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACnB,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC;gBACvC,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;gBACxB,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;gBACxB,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;gBAC7B,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;gBAC7B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;oBACZ,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YACvB,CAAC;YACD,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,gCAAgC;QACxD,CAAC;QACD,yCAAyC;QACzC,MAAM,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;QAC/B,MAAM,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;QAC/B,OAAO,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;IACxB,CAAC;IAED;;OAEG;IACK,WAAW,CAAC,KAAa;QAC/B,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACd,OAAO,IAAI,CAAC,WAAW,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,KAAK,CAAC,CAAC;QAC1E,CAAC;QACD,MAAM,CAAC,GAAG,KAAK,GAAG,CAAC,GAAG,CAAC,CAAC;QACxB,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC/B,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC;YACvC,IAAI,CAAS,EAAE,CAAS,CAAC;YACzB,GAAG,CAAC;gBACF,CAAC,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;gBACxB,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAChB,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE;YACjB,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACd,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YACxB,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;gBACpC,OAAO,CAAC,GAAG,CAAC,CAAC;YACf,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBACvD,OAAO,CAAC,GAAG,CAAC,CAAC;QACjB,CAAC;QACD,OAAO,CAAC,CAAC,CAAC,gCAAgC;IAC5C,CAAC;IAED,+BAA+B;IACvB,YAAY;QAClB,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QACzB,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QACzB,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;IACnE,CAAC;CACF"}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lesson Payoff — the anti-corruption layer around the vendored bandit engine
|
|
3
|
+
* (feature lesson-bandit-rerank; ADR-001 / ADR-002 / ADR-003, domain model §6).
|
|
4
|
+
*
|
|
5
|
+
* WHAT THIS OWNS. One question: *how often did THIS lesson, in THIS kind of situation, actually
|
|
6
|
+
* resolve a problem?* It knows a lesson only as an opaque `ArmKey` (its `dzId`). It never reads
|
|
7
|
+
* lesson text, never writes to the pattern store, never decides candidate membership, and never
|
|
8
|
+
* touches quarantine state (INV-9).
|
|
9
|
+
*
|
|
10
|
+
* WHY AN ACL WHEN WE OWN THE COPY. Vendoring gives us the FILE, not the LANGUAGE. The engine's
|
|
11
|
+
* only counter-advancing method (`recordReward`) also moves the Beta parameters, so there is no way
|
|
12
|
+
* to tell it "a pull happened, no evidence either way". Our domain MUST be able to say that: an
|
|
13
|
+
* EXPOSURE is not a REWARD (INV-2 — the defect cross-model QE already removed once from
|
|
14
|
+
* `patterns.ts`, where recall-hit telemetry was silently promoting every viewed lesson). So the
|
|
15
|
+
* exposure counter lives in OUR envelope, beside the engine payload, and INV-2 is true by DATA
|
|
16
|
+
* LAYOUT rather than by call-site discipline: there is no field in `bandit.contexts` an exposure is
|
|
17
|
+
* allowed to touch.
|
|
18
|
+
*
|
|
19
|
+
* DETERMINISM (ADR-001 D-2 / D-7, reaffirmed by ADR-003's P5). The default payoff term is the
|
|
20
|
+
* POSTERIOR MEAN `alpha/(alpha+beta)`, mapped to `[-1,+1]`, using ONLY `getArmStats` — never
|
|
21
|
+
* `selectArm`, never `rerank`. Both of those Thompson-SAMPLE (`Math.random()` on every zero-pull
|
|
22
|
+
* arm), which would make two identical recalls disagree, defeat the byte-identity proof, and hand
|
|
23
|
+
* quarantined lessons a random lift. `05_architecture.md` §3 and `04_domain_model.md` §6.2 sketch an
|
|
24
|
+
* order-projection over `rerank()` instead; that draft is refuted by ADR-003's P5 ("with
|
|
25
|
+
* `banditExploration` absent, no `Math.random()` is consumed on the recall path"), which `rerank`
|
|
26
|
+
* cannot satisfy. The ADRs are the decision record and they win; this file implements them, and the
|
|
27
|
+
* bound the order-projection was chosen for is preserved exactly — the term is in `[-1,+1]` by
|
|
28
|
+
* construction, so the caller's cap is an EXACT bound.
|
|
29
|
+
*
|
|
30
|
+
* FAILURE POSTURE (NFR-5 / INV-7). Every read here degrades to "no term applied, honest reason
|
|
31
|
+
* recorded"; every write degrades to "this reward was dropped, counted, and logged". Nothing in
|
|
32
|
+
* this module may throw into `hybridRecall` or `reinforcePattern`.
|
|
33
|
+
*/
|
|
34
|
+
import { type SerializedBanditState } from './lesson-bandit.js';
|
|
35
|
+
import { type MemoryLearningConfig } from './patterns.js';
|
|
36
|
+
/** OUR envelope version — distinct from the engine's own `version` field (architecture §7.2). */
|
|
37
|
+
export declare const BANDIT_STATE_SCHEMA = 1;
|
|
38
|
+
/**
|
|
39
|
+
* The named lock guarding the state file. It resolves to `<projectRoot>/.dz/locks/lesson-bandit.lock`,
|
|
40
|
+
* i.e. the lock sits in the SAME `.dz` that holds the store — two worktrees pointing at one brain
|
|
41
|
+
* therefore share one lock, which is the only placement that serializes the right writers.
|
|
42
|
+
*/
|
|
43
|
+
export declare const BANDIT_LOCK_NAME = "lesson-bandit";
|
|
44
|
+
export declare function banditStateDir(projectRoot: string): string;
|
|
45
|
+
/**
|
|
46
|
+
* `<projectRoot>/.dz/lesson-bandit/state.json` (plan §0 D0-1 — the ADRs' worked examples say
|
|
47
|
+
* `.dz/memory/bandit-state.json`; ADR-002's FR-2 already hedges "or equivalent path under `.dz/`",
|
|
48
|
+
* and requirements/domain-model/architecture all key on this one).
|
|
49
|
+
*/
|
|
50
|
+
export declare function banditStatePath(projectRoot: string): string;
|
|
51
|
+
export interface ResolvedBanditConfig {
|
|
52
|
+
/** `memory.learning.banditRerank === true`. Absent/invalid ⇒ false ⇒ this context is never built. */
|
|
53
|
+
readonly enabled: boolean;
|
|
54
|
+
/** What the config ASKED for, before the `enabled` gate. */
|
|
55
|
+
readonly explorationRequested: boolean;
|
|
56
|
+
/** Effective exploration: requested AND armed. `explore` without `rerank` is a no-op (ADR-003). */
|
|
57
|
+
readonly exploration: boolean;
|
|
58
|
+
}
|
|
59
|
+
export declare function resolveBanditConfig(projectRoot: string, cfg?: MemoryLearningConfig): ResolvedBanditConfig;
|
|
60
|
+
/**
|
|
61
|
+
* ContextKey (domain model §3.3) — the coarse situation bucket a recall happened in, derived from
|
|
62
|
+
* the resolved recall domain (the axis `dz recall --domain` already boosts on). COARSE on purpose:
|
|
63
|
+
* with a per-query key every arm would sit at `pulls === 0` forever and the term would be noise.
|
|
64
|
+
* Normalising constructor, so two spellings of one domain cannot fork a posterior.
|
|
65
|
+
*/
|
|
66
|
+
export declare function contextKeyFor(domain?: string | null | undefined): string;
|
|
67
|
+
/** A confirmation happened: this lesson demonstrably helped. */
|
|
68
|
+
export interface RewardEvent {
|
|
69
|
+
readonly dzId: string;
|
|
70
|
+
readonly contextKey: string;
|
|
71
|
+
readonly reward: number;
|
|
72
|
+
readonly ts: string;
|
|
73
|
+
}
|
|
74
|
+
/** A lesson was merely SEEN in a recall result. Carries no reward mass, by construction. */
|
|
75
|
+
export interface ExposureEvent {
|
|
76
|
+
readonly dzId: string;
|
|
77
|
+
readonly contextKey: string;
|
|
78
|
+
readonly ts: string;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Build a {@link RewardEvent}, clamping reward into `[0,1]` at the domain boundary (INV-8/AC-7).
|
|
82
|
+
*
|
|
83
|
+
* A NON-FINITE reward (`NaN`, `Infinity`) is REJECTED (`null`), not coerced: `clamp01(NaN)` is
|
|
84
|
+
* `NaN`, which would poison `alpha`; and coercing it to `0` would be worse still — `reward: 0` moves
|
|
85
|
+
* `beta`, i.e. it records evidence the lesson FAILED. Dropping is the only fail-safe direction.
|
|
86
|
+
*/
|
|
87
|
+
export declare function makeRewardEvent(dzId: string, contextKey: string, reward: number, ts: string): RewardEvent | null;
|
|
88
|
+
/**
|
|
89
|
+
* Translate one upstream learning sample into exactly one domain event (domain model §5).
|
|
90
|
+
*
|
|
91
|
+
* `kind:'merge'` and every UNRECOGNISED kind are DROPPED — a sample kind added upstream tomorrow
|
|
92
|
+
* defaults to *ignored*, never to *rewarded*. That asymmetry is the whole point of translating
|
|
93
|
+
* rather than subscribing.
|
|
94
|
+
*/
|
|
95
|
+
export declare function classifySignal(sample: {
|
|
96
|
+
readonly kind: string;
|
|
97
|
+
readonly dzId: string;
|
|
98
|
+
readonly reward?: number | undefined;
|
|
99
|
+
readonly ts: string;
|
|
100
|
+
}, contextKey: string): {
|
|
101
|
+
readonly type: 'reward';
|
|
102
|
+
readonly event: RewardEvent;
|
|
103
|
+
} | {
|
|
104
|
+
readonly type: 'exposure';
|
|
105
|
+
readonly event: ExposureEvent;
|
|
106
|
+
} | null;
|
|
107
|
+
/** The single bounded number the ranker gets for one candidate (domain model §3.5). */
|
|
108
|
+
export interface PayoffTerm {
|
|
109
|
+
readonly dzId: string;
|
|
110
|
+
/** In `[-1,+1]` BEFORE the caller's cap. */
|
|
111
|
+
readonly term: number;
|
|
112
|
+
readonly basis: 'posterior' | 'unknown-arm' | 'explored';
|
|
113
|
+
}
|
|
114
|
+
export interface BanditStateEnvelope {
|
|
115
|
+
readonly schemaVersion: number;
|
|
116
|
+
/** Verbatim `LessonBandit.serialize()` output — nested untouched so a re-vendor stays a diff. */
|
|
117
|
+
readonly bandit: SerializedBanditState;
|
|
118
|
+
/** OUR projection. INV-2 lives here: an exposure moves this map and nothing in `bandit`. */
|
|
119
|
+
readonly exposures: Record<string, Record<string, number>>;
|
|
120
|
+
/** OUR projection (ADR-003 §4) — trial impressions, kept separate from confirmed reward. */
|
|
121
|
+
readonly explorePulls: Record<string, Record<string, number>>;
|
|
122
|
+
readonly updatedAt: string;
|
|
123
|
+
}
|
|
124
|
+
export type BanditLoadReason = 'absent' | 'future-schema' | 'malformed' | `corrupt: ${string}`;
|
|
125
|
+
export declare function freshBanditEnvelope(now?: string): BanditStateEnvelope;
|
|
126
|
+
/**
|
|
127
|
+
* Read the state envelope. **Never throws** (INV-7): every failure yields a FRESH empty envelope
|
|
128
|
+
* plus an honest `reason`, and recall proceeds with no payoff axis.
|
|
129
|
+
*
|
|
130
|
+
* This function is READ-ONLY — it does not delete, repair, or rename anything. `dz recall` is not
|
|
131
|
+
* the right process to destroy state, and the read path is lock-free (§7.4), so a rename here would
|
|
132
|
+
* race every concurrent recall. Quarantining a corrupt file happens once, inside the WRITE
|
|
133
|
+
* transaction, where a write is expected and the lock is held (see {@link mutateBanditState}).
|
|
134
|
+
*/
|
|
135
|
+
export declare function loadBanditState(projectRoot: string): {
|
|
136
|
+
readonly state: BanditStateEnvelope;
|
|
137
|
+
readonly reason?: BanditLoadReason;
|
|
138
|
+
};
|
|
139
|
+
export interface BanditWriteOutcome {
|
|
140
|
+
readonly ok: boolean;
|
|
141
|
+
readonly reason?: string;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Record a genuine confirmation (ADR-001 D-5). This is the ONLY operation that moves reward mass.
|
|
145
|
+
* Called from `reinforcePattern`'s non-exposure branch, AFTER the store lock is released.
|
|
146
|
+
*/
|
|
147
|
+
export declare function recordReward(projectRoot: string, event: RewardEvent): BanditWriteOutcome;
|
|
148
|
+
/**
|
|
149
|
+
* Record that lessons were merely SEEN (INV-2 / AC-2).
|
|
150
|
+
*
|
|
151
|
+
* ONE locked transaction per RECALL, not per hit (architecture §4) — `limit` transactions per recall
|
|
152
|
+
* would hold the lock far more often than the work justifies.
|
|
153
|
+
*
|
|
154
|
+
* **No engine call happens here, at any reward value, including `0`.** `recordReward(ctx, arm, 0)`
|
|
155
|
+
* adds `beta += 1`: it is evidence the lesson FAILED. Penalising a lesson for being read is the
|
|
156
|
+
* mirror image of the promote-by-view defect this invariant exists to prevent.
|
|
157
|
+
*
|
|
158
|
+
* `exploredDzIds` are the arms that received a trial lift on this recall (ADR-003 §4) — counted in
|
|
159
|
+
* their own projection, never in `alpha`/`beta`, and never touching `qStatus`.
|
|
160
|
+
*/
|
|
161
|
+
export declare function recordExposures(projectRoot: string, events: readonly ExposureEvent[], exploredDzIds?: readonly string[], meta?: {
|
|
162
|
+
readonly moved?: number;
|
|
163
|
+
readonly arms?: number;
|
|
164
|
+
}): BanditWriteOutcome;
|
|
165
|
+
export interface PayoffTerms {
|
|
166
|
+
/** Keyed by `dzId`. An arm absent from `armKeys` is NEVER present here (INV-4 / AC-4). */
|
|
167
|
+
readonly terms: ReadonlyMap<string, PayoffTerm>;
|
|
168
|
+
/** `null` when the state loaded cleanly; otherwise the honest degradation reason (§10). */
|
|
169
|
+
readonly reason: BanditLoadReason | null;
|
|
170
|
+
/** Arms with `pulls === 0` — "no measured payoff yet". */
|
|
171
|
+
readonly unknownArms: number;
|
|
172
|
+
/** Arms that received a trial lift on this call (0 unless exploration is armed). */
|
|
173
|
+
readonly explored: readonly string[];
|
|
174
|
+
/** The ids behind `unknownArms`, so a downstream cut can narrow that counter too. */
|
|
175
|
+
readonly unknownDzIds: readonly string[];
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Project the posterior for each arm into a bounded term (ADR-001 D-2). **Lock-free** (§7.4): the
|
|
179
|
+
* writer's final step is a rename, so a reader sees the whole old document or the whole new one,
|
|
180
|
+
* never a partial; a reader that loses the race by microseconds ranks with a state one reward stale,
|
|
181
|
+
* which is a ranking hint, not a fact.
|
|
182
|
+
*
|
|
183
|
+
* ```
|
|
184
|
+
* term = (alpha/(alpha+beta) - 0.5) * 2 ∈ [-1,+1] when pulls > 0 ('posterior')
|
|
185
|
+
* term = 0 when pulls === 0 ('unknown-arm')
|
|
186
|
+
* ```
|
|
187
|
+
* `pulls === 0 ⇒ 0` is the cold start ADR-001 accepts on purpose: no evidence ⇒ no nudge, and
|
|
188
|
+
* crucially no RANDOM lift. `Math.random()` is reached only when `opts.exploration` is true, and
|
|
189
|
+
* then only for a NON-QUARANTINED zero-pull arm (ADR-003 P5/P6) — quarantined lessons are excluded
|
|
190
|
+
* here as a SET RELATION, in addition to the ranker's own arm-list filter upstream.
|
|
191
|
+
*
|
|
192
|
+
* Never throws (NFR-5): any failure yields an empty map plus a reason.
|
|
193
|
+
*/
|
|
194
|
+
export declare function payoffTermsFor(projectRoot: string, contextKey: string, armKeys: readonly string[], opts?: {
|
|
195
|
+
readonly exploration?: boolean;
|
|
196
|
+
readonly quarantined?: ReadonlySet<string>;
|
|
197
|
+
readonly rng?: () => number;
|
|
198
|
+
}): PayoffTerms;
|
|
199
|
+
/**
|
|
200
|
+
* The per-query explanation carried on `HybridRecall` when the feature is armed. When disarmed the
|
|
201
|
+
* whole field is ABSENT (not `null`, not `{}`) — its mere presence tells a reader the feature ran.
|
|
202
|
+
*/
|
|
203
|
+
export interface BanditRecallReport {
|
|
204
|
+
readonly contextKey: string;
|
|
205
|
+
/** POST-cut count: exactly the arms that were reordered and survived to the caller (FR-8/AC-11). */
|
|
206
|
+
readonly armsConsidered: number;
|
|
207
|
+
/** Arms filtered out before the engine ever saw them (INV-3). */
|
|
208
|
+
readonly quarantinedExcluded: number;
|
|
209
|
+
/** `pulls === 0` — no measured payoff yet. */
|
|
210
|
+
readonly unknownArms: number;
|
|
211
|
+
/** Candidates whose position changed against the same ranking WITHOUT the bandit term. */
|
|
212
|
+
readonly moved: number;
|
|
213
|
+
/** Exploration state and how many arms received a trial lift (ADR-003 D5 — a stochastic ranking
|
|
214
|
+
* is never presented as a deterministic one). */
|
|
215
|
+
readonly exploration: boolean;
|
|
216
|
+
readonly explored: number;
|
|
217
|
+
readonly reason: BanditLoadReason | null;
|
|
218
|
+
/** The POST-cut arm ids, so a further truncation downstream can narrow this report honestly. */
|
|
219
|
+
readonly armDzIds: readonly string[];
|
|
220
|
+
readonly movedDzIds: readonly string[];
|
|
221
|
+
/** Ids behind `unknownArms` and `explored`, so a downstream cut narrows EVERY list-dependent
|
|
222
|
+
* counter, not just two of four. */
|
|
223
|
+
readonly unknownDzIds: readonly string[];
|
|
224
|
+
readonly exploredDzIds: readonly string[];
|
|
225
|
+
/** The two rankings `moved` was derived from. Kept because `moved` cannot be narrowed by
|
|
226
|
+
* intersection: whether a SURVIVING candidate still changed position is a fact about the order
|
|
227
|
+
* AFTER the dropped candidates are removed, and only a recomputation can answer it. */
|
|
228
|
+
readonly beforeOrder: readonly string[];
|
|
229
|
+
readonly afterOrder: readonly string[];
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Narrow a report to the hits a caller actually PRINTED. `dz recall` over-fetches under `--domain`
|
|
233
|
+
* and truncates again, so the un-narrowed count would describe a PRE-cut list the reader never saw —
|
|
234
|
+
* the exact dishonesty FR-8/AC-11 names.
|
|
235
|
+
*/
|
|
236
|
+
export declare function narrowBanditReport(report: BanditRecallReport, shownDzIds: readonly string[]): BanditRecallReport;
|
|
237
|
+
export interface BanditHealth {
|
|
238
|
+
readonly present: boolean;
|
|
239
|
+
readonly verdict: 'INSUFFICIENT_DATA' | 'OK';
|
|
240
|
+
readonly reason: BanditLoadReason | null;
|
|
241
|
+
readonly updatedAt: string | null;
|
|
242
|
+
readonly contexts: number;
|
|
243
|
+
readonly armsTotal: number;
|
|
244
|
+
/** Arms with ANY measured payoff (`alpha > 1`). Zero means the payoff axis is empty. */
|
|
245
|
+
readonly armsWithReward: number;
|
|
246
|
+
readonly totalPulls: number;
|
|
247
|
+
readonly totalReward: number;
|
|
248
|
+
readonly exposureTotal: number;
|
|
249
|
+
readonly explorePullTotal: number;
|
|
250
|
+
readonly rewardEvents: number;
|
|
251
|
+
readonly exposureEvents: number;
|
|
252
|
+
readonly banditWriteErrors: number;
|
|
253
|
+
/** Mean `moved / armsConsidered` over logged recalls; `null` when nothing was logged. */
|
|
254
|
+
readonly movedRate: number | null;
|
|
255
|
+
}
|
|
256
|
+
/** Read-only health snapshot from the state file + `sessions.jsonl`. Never throws. */
|
|
257
|
+
export declare function banditStats(projectRoot: string): BanditHealth;
|
|
258
|
+
/** Text rendering of {@link banditStats} for `dz compounding`. Pure. */
|
|
259
|
+
export declare function renderBanditHealth(h: BanditHealth): string;
|
|
260
|
+
//# sourceMappingURL=lesson-payoff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lesson-payoff.d.ts","sourceRoot":"","sources":["../src/lesson-payoff.ts"],"names":[],"mappings":"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"}
|