@vellumai/assistant 0.10.4-dev.202607021837.b0213d4 → 0.10.4-dev.202607021852.8dcc2d9
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/package.json
CHANGED
|
@@ -144,6 +144,7 @@ const BASELINE: Record<string, readonly string[]> = {
|
|
|
144
144
|
"../../../../skills/catalog-cache.js",
|
|
145
145
|
"../../../../skills/install-meta.js",
|
|
146
146
|
"../../../../skills/skill-memory.js",
|
|
147
|
+
"../../../../telemetry/watchdog-events-store.js",
|
|
147
148
|
"../../../../tools/skills/delete-managed.js",
|
|
148
149
|
"../../../../util/abort-reasons.js",
|
|
149
150
|
"../../../../util/errors.js",
|
|
@@ -359,8 +359,8 @@
|
|
|
359
359
|
"scope": "assistant",
|
|
360
360
|
"key": "memory-v3-injection-gate",
|
|
361
361
|
"label": "Memory v3 injection gate",
|
|
362
|
-
"description": "Per-turn injection gate for memory v3: after the BM25F needle + dense cosine finder lanes run, skip the selectPool LLM call (inject nothing) when finder-lane scores fall below the configured thresholds (memory.v3.gate). Default
|
|
363
|
-
"defaultEnabled":
|
|
362
|
+
"description": "Per-turn injection gate for memory v3: after the BM25F needle + dense cosine finder lanes run, skip the selectPool LLM call (inject nothing) when finder-lane scores fall below the configured thresholds (memory.v3.gate). Default on (flipped remotely July 2026); this registry value is the fallback when no platform snapshot is available.",
|
|
363
|
+
"defaultEnabled": true
|
|
364
364
|
},
|
|
365
365
|
{
|
|
366
366
|
"id": "self-intro-greeting",
|
|
@@ -79,8 +79,37 @@ mock.module("../dense.js", () => ({
|
|
|
79
79
|
},
|
|
80
80
|
}));
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
// The watchdog telemetry store is stubbed with the same active-flag delegation
|
|
83
|
+
// as the dense lane: gate tests capture the per-run gate telemetry; any other
|
|
84
|
+
// test file sharing the process falls through to the real store. Spread the
|
|
85
|
+
// real module so the query/type exports stay present.
|
|
86
|
+
const realWatchdogStore = {
|
|
87
|
+
...(await import("../../../../../telemetry/watchdog-events-store.js")),
|
|
88
|
+
};
|
|
89
|
+
let watchdogMockActive = false;
|
|
90
|
+
let recordedGateEvents: Array<{
|
|
91
|
+
checkName: string;
|
|
92
|
+
value?: number | null;
|
|
93
|
+
detail?: Record<string, unknown> | null;
|
|
94
|
+
}> = [];
|
|
95
|
+
mock.module("../../../../../telemetry/watchdog-events-store.js", () => ({
|
|
96
|
+
...realWatchdogStore,
|
|
97
|
+
recordWatchdogEvent: (
|
|
98
|
+
record: Parameters<typeof realWatchdogStore.recordWatchdogEvent>[0],
|
|
99
|
+
) => {
|
|
100
|
+
if (!watchdogMockActive) {
|
|
101
|
+
return realWatchdogStore.recordWatchdogEvent(record);
|
|
102
|
+
}
|
|
103
|
+
recordedGateEvents.push(record);
|
|
104
|
+
},
|
|
105
|
+
}));
|
|
106
|
+
|
|
107
|
+
const {
|
|
108
|
+
orchestrate,
|
|
109
|
+
DEFAULT_NEEDLE_K,
|
|
110
|
+
DEFAULT_DENSE_K,
|
|
111
|
+
MEMORY_V3_INJECTION_GATE_CHECK_NAME,
|
|
112
|
+
} = await import("../orchestrate.js");
|
|
84
113
|
|
|
85
114
|
// ---------------------------------------------------------------------------
|
|
86
115
|
// Fixtures: a tiny corpus of pages with bodies + `links:` frontmatter.
|
|
@@ -261,9 +290,11 @@ function selectProvider(keep: Slug[], pin: Slug[] = []): Provider {
|
|
|
261
290
|
|
|
262
291
|
beforeEach(() => {
|
|
263
292
|
denseMockActive = true;
|
|
293
|
+
watchdogMockActive = true;
|
|
264
294
|
providerStub = null;
|
|
265
295
|
denseHits = [];
|
|
266
296
|
denseCalls = [];
|
|
297
|
+
recordedGateEvents = [];
|
|
267
298
|
lastPool = [];
|
|
268
299
|
lastPoolLines = [];
|
|
269
300
|
lastPrefixBlock = null;
|
|
@@ -272,6 +303,7 @@ beforeEach(() => {
|
|
|
272
303
|
|
|
273
304
|
afterAll(() => {
|
|
274
305
|
denseMockActive = false;
|
|
306
|
+
watchdogMockActive = false;
|
|
275
307
|
});
|
|
276
308
|
|
|
277
309
|
// ---------------------------------------------------------------------------
|
|
@@ -1199,4 +1231,84 @@ describe("orchestrate — injection gate", () => {
|
|
|
1199
1231
|
expect(selectCalls).toBe(1);
|
|
1200
1232
|
expect(result.selections.map((s) => s.slug)).toContain("topic-a");
|
|
1201
1233
|
});
|
|
1234
|
+
|
|
1235
|
+
test("a scored gate run records ONE telemetry event carrying the decision", async () => {
|
|
1236
|
+
const lanes = await buildLanes();
|
|
1237
|
+
// Dense top-1 (0.9) clears denseThreshold (0.52) → dense_pass.
|
|
1238
|
+
denseHits = [{ article: "topic-b", section: 0, score: 0.9 }];
|
|
1239
|
+
providerStub = selectProvider(["topic-a"]);
|
|
1240
|
+
|
|
1241
|
+
await orchestrate(
|
|
1242
|
+
makeTurn(1, "apple"),
|
|
1243
|
+
depsOf(lanes, { denseK: 100, gateConfig: gateConfigOf() }),
|
|
1244
|
+
);
|
|
1245
|
+
|
|
1246
|
+
expect(recordedGateEvents).toHaveLength(1);
|
|
1247
|
+
const event = recordedGateEvents[0]!;
|
|
1248
|
+
expect(event.checkName).toBe(MEMORY_V3_INJECTION_GATE_CHECK_NAME);
|
|
1249
|
+
expect(event.value).toBe(1);
|
|
1250
|
+
expect(event.detail).toMatchObject({
|
|
1251
|
+
pass: true,
|
|
1252
|
+
reason: "dense_pass",
|
|
1253
|
+
top_dense_score: 0.9,
|
|
1254
|
+
});
|
|
1255
|
+
});
|
|
1256
|
+
|
|
1257
|
+
test("a closed gate records pass:false with the failure reason", async () => {
|
|
1258
|
+
const lanes = await buildLanes();
|
|
1259
|
+
// No needle-term overlap and sub-threshold dense → fail_no_signal.
|
|
1260
|
+
denseHits = [{ article: "topic-b", section: 0, score: 0.1 }];
|
|
1261
|
+
providerStub = selectProvider([]);
|
|
1262
|
+
|
|
1263
|
+
await orchestrate(
|
|
1264
|
+
makeTurn(1, "zzzz nomatch"),
|
|
1265
|
+
depsOf(lanes, { denseK: 100, gateConfig: gateConfigOf() }),
|
|
1266
|
+
);
|
|
1267
|
+
|
|
1268
|
+
expect(recordedGateEvents).toHaveLength(1);
|
|
1269
|
+
expect(recordedGateEvents[0]!.detail).toMatchObject({
|
|
1270
|
+
pass: false,
|
|
1271
|
+
reason: "fail_no_signal",
|
|
1272
|
+
});
|
|
1273
|
+
});
|
|
1274
|
+
|
|
1275
|
+
test("the dense-unavailable pass-open records reason dense_unavailable", async () => {
|
|
1276
|
+
const lanes = await buildLanes();
|
|
1277
|
+
// denseK: 0 → no live dense hits → the gate passes open without scoring.
|
|
1278
|
+
const needle = {
|
|
1279
|
+
query: () => [],
|
|
1280
|
+
queryScored: () => [{ article: "topic-a", section: 0, score: 0.1 }],
|
|
1281
|
+
bestSection: () => -1,
|
|
1282
|
+
idf: () => 0,
|
|
1283
|
+
};
|
|
1284
|
+
providerStub = selectProvider(["topic-a"]);
|
|
1285
|
+
|
|
1286
|
+
await orchestrate(
|
|
1287
|
+
makeTurn(1, "apple"),
|
|
1288
|
+
depsOf(lanes, { needle, denseK: 0, gateConfig: gateConfigOf() }),
|
|
1289
|
+
);
|
|
1290
|
+
|
|
1291
|
+
expect(recordedGateEvents).toHaveLength(1);
|
|
1292
|
+
expect(recordedGateEvents[0]!.detail).toMatchObject({
|
|
1293
|
+
pass: true,
|
|
1294
|
+
reason: "dense_unavailable",
|
|
1295
|
+
});
|
|
1296
|
+
});
|
|
1297
|
+
|
|
1298
|
+
test("no gate telemetry when the gate is disabled or omitted", async () => {
|
|
1299
|
+
const lanes = await buildLanes();
|
|
1300
|
+
denseHits = [{ article: "topic-b", section: 0, score: 0.1 }];
|
|
1301
|
+
providerStub = selectProvider(["topic-a"]);
|
|
1302
|
+
|
|
1303
|
+
await orchestrate(
|
|
1304
|
+
makeTurn(1, "apple"),
|
|
1305
|
+
depsOf(lanes, {
|
|
1306
|
+
denseK: 100,
|
|
1307
|
+
gateConfig: gateConfigOf({ enabled: false }),
|
|
1308
|
+
}),
|
|
1309
|
+
);
|
|
1310
|
+
await orchestrate(makeTurn(2, "apple"), depsOf(lanes));
|
|
1311
|
+
|
|
1312
|
+
expect(recordedGateEvents).toEqual([]);
|
|
1313
|
+
});
|
|
1202
1314
|
});
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
*/
|
|
56
56
|
|
|
57
57
|
import type { AssistantConfig } from "../../../../config/schema.js";
|
|
58
|
+
import { recordWatchdogEvent } from "../../../../telemetry/watchdog-events-store.js";
|
|
58
59
|
import { getLogger } from "../../../../util/logger.js";
|
|
59
60
|
import { denseLaneScored } from "./dense.js";
|
|
60
61
|
import type { EdgeGraph } from "./edge.js";
|
|
@@ -82,6 +83,28 @@ import type {
|
|
|
82
83
|
// logger names the per-turn INJECTION gate wired in below.
|
|
83
84
|
const log = getLogger("memory-v3-injection-gate");
|
|
84
85
|
|
|
86
|
+
/** `check_name` of the `watchdog` telemetry event recorded once per
|
|
87
|
+
* injection-gate run. The platform's memory admin analytics count and filter
|
|
88
|
+
* on this exact string — keep it stable. */
|
|
89
|
+
export const MEMORY_V3_INJECTION_GATE_CHECK_NAME = "memory_v3_injection_gate";
|
|
90
|
+
|
|
91
|
+
/** Record one injection-gate run to usage telemetry. `detail` keys are the
|
|
92
|
+
* platform-side contract (snake_case, scores and reason codes only — never
|
|
93
|
+
* conversation content). Never throws: the gate is pass-open by design, and a
|
|
94
|
+
* telemetry failure must not cost the turn its memory either. */
|
|
95
|
+
function recordGateRun(detail: Record<string, unknown>): void {
|
|
96
|
+
try {
|
|
97
|
+
recordWatchdogEvent({
|
|
98
|
+
checkName: MEMORY_V3_INJECTION_GATE_CHECK_NAME,
|
|
99
|
+
value: 1,
|
|
100
|
+
detail,
|
|
101
|
+
});
|
|
102
|
+
} catch {
|
|
103
|
+
// recordWatchdogEvent already no-ops on opt-out and a missing telemetry
|
|
104
|
+
// DB; anything past that is not worth failing the turn over.
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
85
108
|
/** Default number of BM25 needle articles to fold into the pool. */
|
|
86
109
|
export const DEFAULT_NEEDLE_K = 12;
|
|
87
110
|
/** Default number of dense-lane articles to fold into the pool. */
|
|
@@ -429,6 +452,7 @@ export async function orchestrate(
|
|
|
429
452
|
},
|
|
430
453
|
"memory-v3 injection gate: dense lane unavailable, passing open",
|
|
431
454
|
);
|
|
455
|
+
recordGateRun({ pass: true, reason: "dense_unavailable" });
|
|
432
456
|
} else {
|
|
433
457
|
let gate: V3GateResult | null = null;
|
|
434
458
|
try {
|
|
@@ -445,6 +469,7 @@ export async function orchestrate(
|
|
|
445
469
|
},
|
|
446
470
|
"memory-v3 injection gate threw; passing open (proceeding with selection)",
|
|
447
471
|
);
|
|
472
|
+
recordGateRun({ pass: true, reason: "gate_error" });
|
|
448
473
|
}
|
|
449
474
|
if (gate) {
|
|
450
475
|
log.info(
|
|
@@ -455,6 +480,13 @@ export async function orchestrate(
|
|
|
455
480
|
},
|
|
456
481
|
"memory-v3 injection gate decision",
|
|
457
482
|
);
|
|
483
|
+
recordGateRun({
|
|
484
|
+
pass: gate.pass,
|
|
485
|
+
reason: gate.reason,
|
|
486
|
+
top_dense_score: gate.topDenseScore,
|
|
487
|
+
top_norm_sparse_score: gate.topNormSparseScore,
|
|
488
|
+
checked_articles: gate.checkedArticles,
|
|
489
|
+
});
|
|
458
490
|
if (!gate.pass) {
|
|
459
491
|
// A closed gate produces no finder lane and no matched sections; only
|
|
460
492
|
// the `selections` differ between bypass (stable prefix) and hard-skip.
|