@remnic/bench 9.6.10 → 9.6.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/dist/index.d.ts CHANGED
@@ -131,6 +131,17 @@ interface BenchMemoryAdapter {
131
131
  store(sessionId: string, messages: Message[], control?: BenchPhaseControl): Promise<void>;
132
132
  recall(sessionId: string, query: string, budgetChars?: number, options?: BenchRecallOptions, control?: BenchPhaseControl): Promise<string>;
133
133
  search(query: string, limit: number, sessionId?: string, control?: BenchPhaseControl): Promise<SearchResult[]>;
134
+ /**
135
+ * Optional explicit-correction surface (issue #1584 plan item 2a). Routes a
136
+ * natural-language correction through the system's correction contract
137
+ * (plan + confirmed apply) instead of a plain turn store. Resolves
138
+ * `{ applied: false }` when the planner produced no applicable actions so
139
+ * the caller can fall back to the turn path. Adapters without an explicit
140
+ * correction surface omit this method entirely.
141
+ */
142
+ correct?(sessionId: string, text: string, at?: string, control?: BenchPhaseControl): Promise<{
143
+ applied: boolean;
144
+ }>;
134
145
  reset(sessionId?: string, control?: BenchPhaseControl): Promise<void>;
135
146
  getStats(sessionId?: string, control?: BenchPhaseControl): Promise<MemoryStats>;
136
147
  /** Wait for background summarization (e.g. LCM) to finish after store(). */
package/dist/index.js CHANGED
@@ -620,6 +620,7 @@ import {
620
620
  buildExplicitCueRecallSection,
621
621
  buildTrajectoryAnalysisRecallSection,
622
622
  collectExplicitTurnReferences,
623
+ EngramAccessService,
623
624
  expandTildePath,
624
625
  normalizeTurnExpansionEnd,
625
626
  Orchestrator,
@@ -1131,6 +1132,27 @@ function benchCoreMemoryTier(memory) {
1131
1132
  function benchCoreMemorySource(sessionId) {
1132
1133
  return `bench-replay-${createHash("sha256").update(sessionId).digest("hex").slice(0, 16)}`;
1133
1134
  }
1135
+ function resolveSessionScopedCorrectionDecision(plan, ownedIds, phase) {
1136
+ const actionTargets = [];
1137
+ for (const action of plan.actions) {
1138
+ if (action.kind === "supersede") actionTargets.push(action.loserId);
1139
+ else if (action.kind !== "redaction_rule") actionTargets.push(action.memoryId);
1140
+ }
1141
+ const foreignIds = actionTargets.filter((id) => !ownedIds.has(id));
1142
+ if (phase === "initial") {
1143
+ const ownedAffected = plan.affected.filter((entry) => ownedIds.has(entry.memoryId));
1144
+ if (plan.affected.length > 0 && ownedAffected.length === 0) {
1145
+ return { kind: "no-owned-target" };
1146
+ }
1147
+ if (ownedAffected.length < plan.affected.length) {
1148
+ return { kind: "replan", targetIds: ownedAffected.map((entry) => entry.memoryId) };
1149
+ }
1150
+ }
1151
+ if (foreignIds.length > 0) {
1152
+ return { kind: "foreign-actions", foreignIds };
1153
+ }
1154
+ return { kind: "proceed" };
1155
+ }
1134
1156
  function benchEntityStructuredFactSourceDir(memoryDir) {
1135
1157
  return path.join(memoryDir, "state", "bench-entity-structured-facts");
1136
1158
  }
@@ -1837,6 +1859,16 @@ function createAdapterFactory(mode) {
1837
1859
  coreReplayChain = coreReplayChain.catch(() => void 0).then(() => trackedCleanup).catch(() => void 0);
1838
1860
  void trackedCleanup;
1839
1861
  };
1862
+ let correctionAccess;
1863
+ const getCorrectionAccess = () => {
1864
+ if (!correctionAccess || correctionAccess.orchestrator !== state.orchestrator) {
1865
+ correctionAccess = {
1866
+ service: new EngramAccessService(state.orchestrator),
1867
+ orchestrator: state.orchestrator
1868
+ };
1869
+ }
1870
+ return correctionAccess.service;
1871
+ };
1840
1872
  return {
1841
1873
  async store(sessionId, messages, control) {
1842
1874
  throwIfBenchPhaseAborted(control, "store");
@@ -2381,6 +2413,71 @@ ${expanded.map((message) => `[${message.role}]: ${message.content}`).join("\n")}
2381
2413
  });
2382
2414
  sessionTurnCounters.clear();
2383
2415
  },
2416
+ async correct(sessionId, text, _at, control) {
2417
+ throwIfBenchPhaseAborted(control, "correct");
2418
+ sessionId = normalizeBenchSessionId(sessionId);
2419
+ const access2 = getCorrectionAccess();
2420
+ const discardPlan = async (planId) => {
2421
+ await access2.correctionDiscard(planId, { sessionKey: sessionId }).catch(() => void 0);
2422
+ };
2423
+ let plan = await withBenchPhaseAbort(
2424
+ access2.correctionPlan({ text, sessionKey: sessionId }),
2425
+ control,
2426
+ "correct"
2427
+ );
2428
+ const sessionSource = benchCoreMemorySource(sessionId);
2429
+ const owned = new Set(
2430
+ (await readBenchCoreMemories(state.orchestrator)).filter((memory) => memory.frontmatter.source === sessionSource).map((memory) => memory.frontmatter.id)
2431
+ );
2432
+ let replanned = false;
2433
+ const decision = resolveSessionScopedCorrectionDecision(plan, owned, "initial");
2434
+ if (decision.kind === "no-owned-target") {
2435
+ await discardPlan(plan.planId);
2436
+ return { applied: false };
2437
+ }
2438
+ if (decision.kind === "replan") {
2439
+ await discardPlan(plan.planId);
2440
+ replanned = true;
2441
+ plan = await withBenchPhaseAbort(
2442
+ access2.correctionPlan({
2443
+ text,
2444
+ sessionKey: sessionId,
2445
+ targetIds: decision.targetIds
2446
+ }),
2447
+ control,
2448
+ "correct"
2449
+ );
2450
+ }
2451
+ const finalDecision = resolveSessionScopedCorrectionDecision(
2452
+ plan,
2453
+ owned,
2454
+ replanned ? "replanned" : "initial"
2455
+ );
2456
+ if (finalDecision.kind === "foreign-actions") {
2457
+ await discardPlan(plan.planId);
2458
+ throw new Error(
2459
+ `correction plan drafts actions against ${finalDecision.foreignIds.length} foreign-session memory(ies) (${finalDecision.foreignIds.join(", ")}) \u2014 the bench harness cannot scope this correction; refusing to apply or mismeasure`
2460
+ );
2461
+ }
2462
+ if (plan.actions.length === 0) {
2463
+ await discardPlan(plan.planId);
2464
+ if (replanned || plan.affected.length > 0) {
2465
+ throw new Error(
2466
+ `correction planner drafted no applicable action (${replanned ? "re-planned with explicit session-owned targets" : `${plan.affected.length} candidate(s) located`}; warnings: ${plan.warnings.join("; ") || "none"}) \u2014 refusing to measure the turn path as contract behavior`
2467
+ );
2468
+ }
2469
+ return { applied: false };
2470
+ }
2471
+ const outcome = await withBenchPhaseAbort(
2472
+ access2.correctionApply(plan.planId, {
2473
+ confirm: true,
2474
+ sessionKey: sessionId
2475
+ }),
2476
+ control,
2477
+ "correct"
2478
+ );
2479
+ return { applied: outcome.status === "applied" };
2480
+ },
2384
2481
  async drain(control) {
2385
2482
  throwIfBenchPhaseAborted(control, "drain");
2386
2483
  const engine = getEngine();
@@ -3328,6 +3425,16 @@ function createTimeoutGuardedAdapter(adapter, options) {
3328
3425
  drainTimeoutMs
3329
3426
  );
3330
3427
  }
3428
+ if (adapter.correct) {
3429
+ wrapped.correct = (sessionId, text, at, control) => phaseTimeoutMs === void 0 ? adapter.correct(sessionId, text, at, control) : run(`correct session=${sessionId}`, async (signal) => {
3430
+ const merged = mergeBenchPhaseControl(signal, control);
3431
+ try {
3432
+ return await adapter.correct(sessionId, text, at, merged.control);
3433
+ } finally {
3434
+ merged.cleanup();
3435
+ }
3436
+ });
3437
+ }
3331
3438
  if (adapter.responder) {
3332
3439
  wrapped.responder = phaseTimeoutMs === void 0 ? adapter.responder : wrapResponder(adapter.responder, run);
3333
3440
  }
@@ -7041,7 +7148,9 @@ var ClaudeCliProvider = class {
7041
7148
  try {
7042
7149
  const request = this.buildRunRequest(prompt, opts, tempDir);
7043
7150
  const result = await this.runClaudeCli(request);
7044
- if (result.status !== 0) {
7151
+ const payload = parseClaudeCliJsonResult(result.stdout);
7152
+ const salvageableDespiteExit = !result.stderr.includes("Claude CLI timed out") && !result.stderr.includes("Claude CLI aborted by benchmark timeout") && result.signal === null && !isClaudeCliErrorFlagSet(payload.is_error) && typeof payload.result === "string" && payload.result.trim().length > 0;
7153
+ if (result.status !== 0 && !salvageableDespiteExit) {
7045
7154
  if (isClaudeUsageLimitSignal(`${result.stderr}
7046
7155
  ${result.stdout}`)) {
7047
7156
  await sleepBeforeUsageLimitRetry({
@@ -7055,8 +7164,9 @@ ${result.stdout}`)) {
7055
7164
  continue;
7056
7165
  }
7057
7166
  const exitLabel = result.signal ? `signal ${result.signal}` : `exit ${result.status ?? "unknown"}`;
7167
+ const stdoutHead = result.stdout.trim().slice(0, 300);
7058
7168
  const error = new Error(
7059
- `Claude CLI completion failed (${exitLabel}): ${summarizeProcessOutput(result.stderr, result.stdout)}`
7169
+ `Claude CLI completion failed (${exitLabel}): ${stdoutHead.length > 0 ? `head=${JSON.stringify(stdoutHead)} tail=` : ""}${summarizeProcessOutput(result.stderr, result.stdout)}`
7060
7170
  );
7061
7171
  if (transientAttempt < maxAttempts && isRetryableClaudeCliResult(result)) {
7062
7172
  await sleepBeforeClaudeCliRetry({
@@ -7071,7 +7181,11 @@ ${result.stdout}`)) {
7071
7181
  }
7072
7182
  throw error;
7073
7183
  }
7074
- const payload = parseClaudeCliJsonResult(result.stdout);
7184
+ if (result.stderr.includes("Claude CLI timed out") || result.stderr.includes("Claude CLI aborted by benchmark timeout")) {
7185
+ throw new Error(
7186
+ `Claude CLI completion was killed (timeout/abort): ${summarizeProcessOutput(result.stderr, result.stdout)}`
7187
+ );
7188
+ }
7075
7189
  if (isClaudeCliErrorFlagSet(payload.is_error)) {
7076
7190
  if (isClaudeUsageLimitSignal(
7077
7191
  `${result.stderr}
@@ -7208,6 +7322,7 @@ function buildClaudeCompletionPrompt(userPrompt) {
7208
7322
  ].join("\n");
7209
7323
  }
7210
7324
  var CLAUDE_CLI_MAX_OUTPUT_TOKENS_ENV = "CLAUDE_CODE_MAX_OUTPUT_TOKENS";
7325
+ var CLAUDE_CLI_OUTPUT_TOKENS_ENV_FLOOR = 4096;
7211
7326
  function buildIsolatedClaudeEnv(config, maxTokens) {
7212
7327
  const env = {};
7213
7328
  for (const [key, value] of Object.entries(process.env)) {
@@ -7222,7 +7337,9 @@ function buildIsolatedClaudeEnv(config, maxTokens) {
7222
7337
  env.ANTHROPIC_BASE_URL = config.baseUrl.trim();
7223
7338
  }
7224
7339
  if (typeof maxTokens === "number" && Number.isFinite(maxTokens) && maxTokens > 0) {
7225
- env[CLAUDE_CLI_MAX_OUTPUT_TOKENS_ENV] = String(Math.floor(maxTokens));
7340
+ env[CLAUDE_CLI_MAX_OUTPUT_TOKENS_ENV] = String(
7341
+ Math.max(Math.floor(maxTokens), CLAUDE_CLI_OUTPUT_TOKENS_ENV_FLOOR)
7342
+ );
7226
7343
  }
7227
7344
  return env;
7228
7345
  }
@@ -7262,6 +7379,18 @@ function parseClaudeCliJsonResult(stdout) {
7262
7379
  }
7263
7380
  return parsed;
7264
7381
  } catch {
7382
+ const lines = trimmed.split(/\r?\n/);
7383
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
7384
+ const line = lines[index].trim();
7385
+ if (!line.startsWith("{") || !line.endsWith("}")) continue;
7386
+ try {
7387
+ const parsed = JSON.parse(line);
7388
+ if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
7389
+ return parsed;
7390
+ }
7391
+ } catch {
7392
+ }
7393
+ }
7265
7394
  return { is_error: true, error: trimmed.slice(-1e3) };
7266
7395
  }
7267
7396
  }
@@ -31955,6 +32084,142 @@ function computeMetricBundle(args) {
31955
32084
  }
31956
32085
 
31957
32086
  // src/benchmarks/remnic/memcorrect/adapters.ts
32087
+ var STOP_WORDS = /* @__PURE__ */ new Set([
32088
+ "the",
32089
+ "a",
32090
+ "an",
32091
+ "is",
32092
+ "are",
32093
+ "was",
32094
+ "were",
32095
+ "to",
32096
+ "of",
32097
+ "for",
32098
+ "and",
32099
+ "or",
32100
+ "but",
32101
+ "in",
32102
+ "on",
32103
+ "at",
32104
+ "by",
32105
+ "it",
32106
+ "my",
32107
+ "your",
32108
+ "we",
32109
+ "you",
32110
+ "i",
32111
+ "me",
32112
+ "this",
32113
+ "that",
32114
+ "with",
32115
+ "from",
32116
+ "not",
32117
+ "have",
32118
+ "has",
32119
+ "do",
32120
+ "does",
32121
+ "did",
32122
+ "be",
32123
+ "been",
32124
+ "being",
32125
+ "will",
32126
+ "would",
32127
+ "should",
32128
+ "could",
32129
+ "can",
32130
+ "may",
32131
+ "might",
32132
+ "must",
32133
+ "shall",
32134
+ "if",
32135
+ "then",
32136
+ "than",
32137
+ "so",
32138
+ "as",
32139
+ "go",
32140
+ "going",
32141
+ "forward",
32142
+ "instead",
32143
+ "last",
32144
+ "month",
32145
+ "now",
32146
+ "their",
32147
+ "them",
32148
+ "they",
32149
+ "he",
32150
+ "she",
32151
+ "his",
32152
+ "her",
32153
+ "got",
32154
+ "noting",
32155
+ "noted",
32156
+ "what",
32157
+ "setting",
32158
+ "preference",
32159
+ "update",
32160
+ "actually",
32161
+ "back",
32162
+ "went",
32163
+ "consider",
32164
+ "decided",
32165
+ "might",
32166
+ "someone",
32167
+ "asked",
32168
+ "mentioned",
32169
+ "said",
32170
+ "should",
32171
+ "change",
32172
+ "wrong",
32173
+ "saying",
32174
+ "record",
32175
+ "oh",
32176
+ "by",
32177
+ "way",
32178
+ "project"
32179
+ ]);
32180
+ function tokenize4(text) {
32181
+ const matches = text.toLowerCase().match(/[a-z0-9][a-z0-9-]*/g);
32182
+ return (matches ?? []).filter((t) => !STOP_WORDS.has(t));
32183
+ }
32184
+ function termFrequencies(tokens) {
32185
+ const tf = /* @__PURE__ */ new Map();
32186
+ for (const token of tokens) {
32187
+ tf.set(token, (tf.get(token) ?? 0) + 1);
32188
+ }
32189
+ return tf;
32190
+ }
32191
+ var PromptOnlyBaselineAdapter = class {
32192
+ label = "prompt-only-baseline";
32193
+ turns = [];
32194
+ async reset() {
32195
+ this.turns = [];
32196
+ }
32197
+ async ingestTurn(sessionKey, role, text, at) {
32198
+ this.turns.push({ sessionKey, role, text, at });
32199
+ }
32200
+ async recall(query, sessionKey) {
32201
+ const queryTf = termFrequencies(tokenize4(query));
32202
+ if (queryTf.size === 0) return [];
32203
+ const scored = this.turns.filter((turn) => turn.sessionKey === sessionKey).map((turn) => {
32204
+ const turnTf = termFrequencies(tokenize4(turn.text));
32205
+ let overlap = 0;
32206
+ for (const [term, qCount] of queryTf) {
32207
+ const tCount = turnTf.get(term);
32208
+ if (tCount !== void 0) overlap += qCount * tCount;
32209
+ }
32210
+ return { turn, overlap };
32211
+ }).filter((entry) => entry.overlap > 0).sort((a, b) => {
32212
+ if (b.overlap !== a.overlap) return b.overlap - a.overlap;
32213
+ return this.turns.indexOf(b.turn) - this.turns.indexOf(a.turn);
32214
+ }).slice(0, 5).map((entry) => entry.turn.text);
32215
+ return scored;
32216
+ }
32217
+ async correct(text, sessionKey) {
32218
+ await this.ingestTurn(sessionKey, "user", text, (/* @__PURE__ */ new Date()).toISOString());
32219
+ }
32220
+ async runMaintenance() {
32221
+ }
32222
+ };
31958
32223
  function createRemnicMemCorrectAdapter(adapter, options = {}) {
31959
32224
  const label = options.label ?? "remnic-native";
31960
32225
  const sessionPrefix = options.sessionPrefix ?? "memcorrect";
@@ -31975,7 +32240,12 @@ function createRemnicMemCorrectAdapter(adapter, options = {}) {
31975
32240
  return trimmed.split(/\n\s*\n/).map((s) => s.trim()).filter((s) => s.length > 0);
31976
32241
  },
31977
32242
  async correct(text, sessionKey, at) {
31978
- await adapter.store(`${sessionPrefix}:${sessionKey}`, [
32243
+ const scopedSession = `${sessionPrefix}:${sessionKey}`;
32244
+ if (adapter.correct) {
32245
+ const outcome = await adapter.correct(scopedSession, text, at);
32246
+ if (outcome.applied) return;
32247
+ }
32248
+ await adapter.store(scopedSession, [
31979
32249
  { role: "user", content: text, timestamp: at }
31980
32250
  ]);
31981
32251
  },
@@ -32023,7 +32293,17 @@ var FULL_OPTIONS2 = {
32023
32293
  };
32024
32294
  function resolveAdapter(options) {
32025
32295
  const override = options.benchmarkOptions?.["adapter"];
32026
- if (override && typeof override === "object" && "reset" in override && "ingestTurn" in override && "recall" in override) {
32296
+ if (typeof override === "string") {
32297
+ if (override === "prompt-only") {
32298
+ const adapter = new PromptOnlyBaselineAdapter();
32299
+ return { adapter, adapterLabel: adapter.label };
32300
+ }
32301
+ if (override !== "remnic") {
32302
+ throw new Error(
32303
+ `memcorrect-v1 adapter must be one of ["remnic", "prompt-only"] (or an in-process MemCorrectSystemAdapter object); received "${override}"`
32304
+ );
32305
+ }
32306
+ } else if (override && typeof override === "object" && "reset" in override && "ingestTurn" in override && "recall" in override) {
32027
32307
  const adapter = override;
32028
32308
  return { adapter, adapterLabel: adapter.label ?? "custom" };
32029
32309
  }
@@ -32980,7 +33260,7 @@ function assemblePack(task, condition, contract, injectSkills) {
32980
33260
  };
32981
33261
  }
32982
33262
  function classifySkillTrigger(skill, task) {
32983
- const taskTokens = tokenize4(task.prompt + " " + task.subjectKeywords.join(" "));
33263
+ const taskTokens = tokenize5(task.prompt + " " + task.subjectKeywords.join(" "));
32984
33264
  const taskSet = new Set(taskTokens);
32985
33265
  const appliesHits = skill.appliesWhen.filter((k) => taskSet.has(k));
32986
33266
  const blocksHits = skill.doesNotApplyWhen.filter((k) => taskSet.has(k));
@@ -33001,7 +33281,7 @@ function classifySkillTrigger(skill, task) {
33001
33281
  }
33002
33282
  return { considered, injected: false, reason: "no trigger overlap" };
33003
33283
  }
33004
- function tokenize4(text) {
33284
+ function tokenize5(text) {
33005
33285
  return text.toLowerCase().replace(/[^a-z0-9\s-]/g, " ").split(/\s+/).filter((t) => t.length > 0);
33006
33286
  }
33007
33287
  function rankCandidates(pack, task, sourceItems) {
@@ -36703,7 +36983,7 @@ var PROCEDURAL_REAL_SCENARIOS_SMOKE = [
36703
36983
  ];
36704
36984
 
36705
36985
  // src/security/extraction-attack/tokenize.ts
36706
- function tokenize5(text) {
36986
+ function tokenize6(text) {
36707
36987
  return text.toLowerCase().split(/[^a-z0-9]+/u).filter((t) => t.length > 2);
36708
36988
  }
36709
36989
 
@@ -36745,7 +37025,7 @@ function createSeededRng2(seed) {
36745
37025
  }
36746
37026
  };
36747
37027
  }
36748
- var tokenizeContent = tokenize5;
37028
+ var tokenizeContent = tokenize6;
36749
37029
  function recoveryTokensFor(memory) {
36750
37030
  if (memory.tokens && memory.tokens.length > 0) {
36751
37031
  const seen = /* @__PURE__ */ new Set();
@@ -37224,11 +37504,11 @@ function createSyntheticTarget(options) {
37224
37504
  }
37225
37505
  const normalized = memories.map((m) => ({
37226
37506
  memory: m,
37227
- tokens: new Set((m.tokens ?? tokenize5(m.content)).map((t) => t.toLowerCase()))
37507
+ tokens: new Set((m.tokens ?? tokenize6(m.content)).map((t) => t.toLowerCase()))
37228
37508
  }));
37229
37509
  return {
37230
37510
  async recall(query, recallOptions) {
37231
- const qTokens = tokenize5(query);
37511
+ const qTokens = tokenize6(query);
37232
37512
  if (qTokens.length === 0) return [];
37233
37513
  const requestedNs = recallOptions?.namespace;
37234
37514
  if (enforceNamespaceAcl && requestedNs !== void 0 && requestedNs !== allowedNamespace) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remnic/bench",
3
- "version": "9.6.10",
3
+ "version": "9.6.12",
4
4
  "description": "Retrieval latency ladder benchmarks + CI regression gates for @remnic/core",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -36,8 +36,8 @@
36
36
  "dependencies": {
37
37
  "hyparquet": "^1.25.7",
38
38
  "yaml": "^2.4.2",
39
- "@remnic/coding-graph": "^9.6.10",
40
- "@remnic/core": "^9.6.10"
39
+ "@remnic/coding-graph": "^9.6.12",
40
+ "@remnic/core": "^9.6.12"
41
41
  },
42
42
  "devDependencies": {
43
43
  "tsup": "^8.5.1",