@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/assistant",
3
- "version": "0.10.4-dev.202607021837.b0213d4",
3
+ "version": "0.10.4-dev.202607021852.8dcc2d9",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -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 off.",
363
- "defaultEnabled": false
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
- const { orchestrate, DEFAULT_NEEDLE_K, DEFAULT_DENSE_K } =
83
- await import("../orchestrate.js");
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.