@gamaze/hicortex 0.22.3 → 0.23.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/README.md +4 -2
- package/dist/calibration.d.ts +26 -11
- package/dist/calibration.js +33 -13
- package/dist/capture.js +4 -1
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +26 -0
- package/dist/cluster.d.ts +8 -5
- package/dist/cluster.js +3 -7
- package/dist/consolidate.d.ts +4 -64
- package/dist/consolidate.js +15 -285
- package/dist/db.js +21 -0
- package/dist/dedup.d.ts +14 -9
- package/dist/dedup.js +33 -25
- package/dist/distiller.d.ts +18 -0
- package/dist/distiller.js +94 -9
- package/dist/eval/planted-harness.js +1 -1
- package/dist/eval/run-eval.js +2 -4
- package/dist/nightly.js +2 -4
- package/dist/recall-hook-cli.js +10 -0
- package/dist/recall-index.js +12 -0
- package/dist/reconsolidation.d.ts +17 -11
- package/dist/reconsolidation.js +38 -28
- package/dist/relink.d.ts +2 -2
- package/dist/relink.js +2 -2
- package/dist/retrieval.d.ts +5 -4
- package/dist/retrieval.js +5 -4
- package/dist/state.d.ts +8 -8
- package/dist/storage.d.ts +2 -2
- package/dist/storage.js +2 -2
- package/dist/sweep-volatile.d.ts +55 -0
- package/dist/sweep-volatile.js +184 -0
- package/dist/types.d.ts +26 -28
- package/dist/wrapper-prompt.d.ts +43 -0
- package/dist/wrapper-prompt.js +122 -0
- package/package.json +1 -1
- package/server.json +2 -2
package/dist/distiller.js
CHANGED
|
@@ -10,10 +10,12 @@ exports.extractConversationText = extractConversationText;
|
|
|
10
10
|
exports.distillSession = distillSession;
|
|
11
11
|
exports.isNoExtractResponse = isNoExtractResponse;
|
|
12
12
|
exports.hasMinimalSubstance = hasMinimalSubstance;
|
|
13
|
+
exports.isVolatileStatusEntry = isVolatileStatusEntry;
|
|
13
14
|
exports.typeFromTag = typeFromTag;
|
|
14
15
|
exports.parseDistilledEntries = parseDistilledEntries;
|
|
15
16
|
const prompts_js_1 = require("./prompts.js");
|
|
16
17
|
const redact_js_1 = require("./redact.js");
|
|
18
|
+
const calibration_js_1 = require("./calibration.js");
|
|
17
19
|
const MAX_TRANSCRIPT_CHARS = 80_000;
|
|
18
20
|
const MIN_CONVERSATION_CHARS = 200;
|
|
19
21
|
// #339 (2026-08-24 postmortem): NO_EXTRACT over-firing visibility threshold.
|
|
@@ -403,21 +405,36 @@ async function distillChunk(llm, transcript, projectName, date, onUsage) {
|
|
|
403
405
|
console.log(`[hicortex] topic-first check: ${offTopic}/${parsed.length} entries look actor/bracket-led (prompt may be ignored)`);
|
|
404
406
|
}
|
|
405
407
|
const entries = [];
|
|
406
|
-
const
|
|
408
|
+
const substanceDropped = [];
|
|
409
|
+
const volatileDropped = [];
|
|
407
410
|
for (const entry of parsed) {
|
|
408
|
-
if (hasMinimalSubstance(entry.content)) {
|
|
409
|
-
|
|
411
|
+
if (!hasMinimalSubstance(entry.content)) {
|
|
412
|
+
substanceDropped.push(entry.content);
|
|
413
|
+
continue;
|
|
410
414
|
}
|
|
411
|
-
|
|
412
|
-
|
|
415
|
+
// #489 volatility gate (owner decision 2: entry-level, deterministic,
|
|
416
|
+
// beside the substance gate — cleanMessageContent untouched): GH-ticket /
|
|
417
|
+
// version-bump / commit-state status shapes drop into the SAME #156
|
|
418
|
+
// trail (owner decision 1: mark-and-skip, never silent). Kill-switch =
|
|
419
|
+
// the release-managed calibration constant (owner decision 5).
|
|
420
|
+
if (calibration_js_1.VOLATILE_STATUS_FILTER && isVolatileStatusEntry(entry.content)) {
|
|
421
|
+
volatileDropped.push(entry.content);
|
|
422
|
+
continue;
|
|
413
423
|
}
|
|
424
|
+
entries.push(entry);
|
|
414
425
|
}
|
|
415
|
-
|
|
416
|
-
|
|
426
|
+
const dropped = [...substanceDropped, ...volatileDropped];
|
|
427
|
+
for (const [label, list] of [
|
|
428
|
+
["Substance gate", substanceDropped],
|
|
429
|
+
["Volatility gate", volatileDropped],
|
|
430
|
+
]) {
|
|
431
|
+
if (list.length === 0)
|
|
432
|
+
continue;
|
|
433
|
+
for (const d of list) {
|
|
417
434
|
const preview = d.length > 120 ? `${d.slice(0, 120)}…` : d;
|
|
418
|
-
console.log(`[hicortex]
|
|
435
|
+
console.log(`[hicortex] ${label}: dropped "${preview}"`);
|
|
419
436
|
}
|
|
420
|
-
console.log(`[hicortex]
|
|
437
|
+
console.log(`[hicortex] ${label}: dropped ${list.length}/${parsed.length} entr(ies)`);
|
|
421
438
|
}
|
|
422
439
|
// Parsed-zero bypass (#339 CR finding 2): a non-empty response with no
|
|
423
440
|
// NO_EXTRACT token that still parses to zero bullets is the silent twin of
|
|
@@ -508,6 +525,74 @@ function hasMinimalSubstance(entry) {
|
|
|
508
525
|
return false;
|
|
509
526
|
return true;
|
|
510
527
|
}
|
|
528
|
+
// ---------------------------------------------------------------------------
|
|
529
|
+
// Volatility gate (#489) — deterministic, entry-level, beside the substance
|
|
530
|
+
// gate. Owner directive (gold-set A adjudication): GitHub ticket/workflow
|
|
531
|
+
// states and version numbers churn faster than nightly consolidation retires
|
|
532
|
+
// them — they arrive as supersession noise and inflate the store with
|
|
533
|
+
// same-subject rows. The ephemera PROMPT already names these categories and
|
|
534
|
+
// demonstrably under-fires (a month of it running, gold A still full of
|
|
535
|
+
// GH-status/version rows) — so the filter is deterministic, catching entries
|
|
536
|
+
// synthesized from ANY source prose.
|
|
537
|
+
// ---------------------------------------------------------------------------
|
|
538
|
+
/**
|
|
539
|
+
* Durability escapes — decision/policy predicates that OVERRIDE every
|
|
540
|
+
* volatility trigger (#489 spec): "switched from X to Y" is a durable model
|
|
541
|
+
* choice even though it names two versions; a version boundary is a policy
|
|
542
|
+
* even though it cites one. Whole-word, case-insensitive. Version numbers
|
|
543
|
+
* never trigger alone — version + STATUS-verb triggers; version +
|
|
544
|
+
* DECISION-verb escapes.
|
|
545
|
+
*/
|
|
546
|
+
const VOLATILE_ESCAPES = /\b(?:switch(?:ed)?\s+from|adopt(?:ed)?|standardi[sz]ed|deprecated|polic(?:y|ies)|rule|boundary|convention|must|never|always|only\s+when)\b/i;
|
|
547
|
+
/** T1 — GH ticket/workflow status: a #number ticket + a status predicate, or
|
|
548
|
+
* a workflow-run shape (checks/CI outcome, test-count status). */
|
|
549
|
+
const TICKET_REF = /(?:^|[\s(#])(?:pr|issue|epic)?\s*#\d+/i;
|
|
550
|
+
const TICKET_STATUS = /\b(?:merged?|closed?|reopened?|opened?|approved?|blocked?|failing|pass(?:ed|ing)?|green|red|ready|draft)\b/i;
|
|
551
|
+
const WORKFLOW_STATUS = /\b(?:checks?\s+(?:passed|failed|green|red)|ci\s+(?:green|red)|\d+\s+tests?\s+(?:pass(?:ed|ing)?|fail(?:ed|ing)?))\b/i;
|
|
552
|
+
/** T2 — version-bump status: a semver-ish number + a release verb, or a
|
|
553
|
+
* version→version transition. The `\b` boundary keeps "Qwen3.6" (no
|
|
554
|
+
* boundary between n and 3) from matching — model names are not versions. */
|
|
555
|
+
const SEMVERISH = /\bv?\d+\.\d+(?:\.\d+)?\b/;
|
|
556
|
+
const RELEASE_VERB = /\b(?:released?|deployed?|promoted?|published?|tagged?|bumped?|cut|shipped?|rolled?\s+out|dist-tags?)\b/i;
|
|
557
|
+
const VERSION_TRANSITION = /\bv?\d+\.\d+(?:\.\d+)?\b[^.\d]{0,20}(?:→|->|to)\s*v?\d+\.\d+(?:\.\d+)?\b/i;
|
|
558
|
+
/** T3 — branch/commit state: a short sha (7-40 hex, word-bounded) + a
|
|
559
|
+
* vcs-motion verb. The SHA is the discriminator, so the verb list is bare
|
|
560
|
+
* ("rebased feature branch onto 4f9c1ab" must fire); a durable narrative
|
|
561
|
+
* carrying a sha + verb rides the escape list or the length cap. */
|
|
562
|
+
const SHORT_SHA = /\b[0-9a-f]{7,40}\b/;
|
|
563
|
+
const VCS_STATE = /\b(?:pushed?|merged?|rebased?|force-?pushed?|updated?)\b/i;
|
|
564
|
+
/**
|
|
565
|
+
* True when the entry is a VOLATILE STATUS SHAPE — GH-ticket/workflow status,
|
|
566
|
+
* version-bump status, or branch/commit state — and carries no durability
|
|
567
|
+
* escape (#489, owner decisions 1+2). Runs in distillChunk's gate zone beside
|
|
568
|
+
* hasMinimalSubstance; drops ride the SAME #156 dropped[] trail. Also reused
|
|
569
|
+
* verbatim by `hicortex sweep-volatile` over the stored corpus — ONE gate,
|
|
570
|
+
* one meaning.
|
|
571
|
+
*
|
|
572
|
+
* PRECISION OVER RECALL (the substance gate's law, inherited): escapes are
|
|
573
|
+
* checked FIRST and override every trigger; entries longer than
|
|
574
|
+
* VOLATILE_GATE_MAX_CHARS are treated as mixed prose whose status clause is
|
|
575
|
+
* not the DOMINANT content (kept). Accepted false-positive mode, stated
|
|
576
|
+
* plainly: an entry pairing a status clause with a distinct durable clause
|
|
577
|
+
* and no escape word drops, losing the durable half — unless the distiller
|
|
578
|
+
* emitted that half as its own entry, which is exactly what the ephemera
|
|
579
|
+
* prompt tells it to do. Every drop is auditable via the trail.
|
|
580
|
+
*/
|
|
581
|
+
function isVolatileStatusEntry(entry) {
|
|
582
|
+
const raw = entry.trim();
|
|
583
|
+
if (!raw || raw.length > calibration_js_1.VOLATILE_GATE_MAX_CHARS)
|
|
584
|
+
return false;
|
|
585
|
+
if (VOLATILE_ESCAPES.test(raw))
|
|
586
|
+
return false;
|
|
587
|
+
const ticketStatus = TICKET_REF.test(raw) && TICKET_STATUS.test(raw);
|
|
588
|
+
const workflowStatus = WORKFLOW_STATUS.test(raw);
|
|
589
|
+
// A version number + release verb, OR a bare version→version transition
|
|
590
|
+
// (the transition fires without a verb — "0.22.2 → 0.22.3 on rc" is a
|
|
591
|
+
// version-bump status; the escape list carries the durable flips).
|
|
592
|
+
const versionBump = (SEMVERISH.test(raw) && RELEASE_VERB.test(raw)) || VERSION_TRANSITION.test(raw);
|
|
593
|
+
const commitState = SHORT_SHA.test(raw) && VCS_STATE.test(raw);
|
|
594
|
+
return ticketStatus || workflowStatus || versionBump || commitState;
|
|
595
|
+
}
|
|
511
596
|
/**
|
|
512
597
|
* Map a single-letter type tag to the stored memory_type. Unknown/absent →
|
|
513
598
|
* experience (the pre-#216 default). `[L]` is explicitly rejected →
|
|
@@ -644,7 +644,7 @@ function renderPlantedReport(args) {
|
|
|
644
644
|
`- conflicts (guard-C): flagged ${s.conflict_flagged}, skipped ${s.conflict_skipped} ` +
|
|
645
645
|
`(zone clusters + judged merges refused on a conflicts link)\n` +
|
|
646
646
|
`- zone: clusters_found ${s.merges.clusters_found}, merged ${s.merges.merged_clusters}, ` +
|
|
647
|
-
`losers_merged ${s.merges.losers_merged},
|
|
647
|
+
`losers_merged ${s.merges.losers_merged}, project_skipped ${s.merges.skipped_project_mismatch}, ` +
|
|
648
648
|
`conflict_skipped ${s.merges.skipped_conflict}\n` +
|
|
649
649
|
`- skipped: infra ${s.skipped_infra}, idempotent ${s.skipped_idempotent}, above_ceiling ${s.skipped_above_ceiling}\n`);
|
|
650
650
|
return L.join("\n");
|
package/dist/eval/run-eval.js
CHANGED
|
@@ -66,11 +66,9 @@ function renderDups(d) {
|
|
|
66
66
|
`${d.pairAttribution.recoveryReingest} recovery/re-ingest-suspect (${pct(d.pairAttribution.totalPairs > 0 ? d.pairAttribution.recoveryReingest / d.pairAttribution.totalPairs : 0)}), ${d.pairAttribution.organic} organic (${pct(d.pairAttribution.totalPairs > 0 ? d.pairAttribution.organic / d.pairAttribution.totalPairs : 0)}).\n`);
|
|
67
67
|
lines.push(`### Top ${d.topClusters.length} clusters (0.90 threshold, largest first)\n`);
|
|
68
68
|
d.topClusters.forEach((cluster, i) => {
|
|
69
|
+
// #206 decision 2: project is the only merge-safety rail (agent rail removed).
|
|
69
70
|
const mismatch = cluster.metadataMismatch;
|
|
70
|
-
const mismatchFlags = [
|
|
71
|
-
mismatch.projectMismatch ? "project" : null,
|
|
72
|
-
mismatch.sourceAgentMismatch ? "source_agent" : null,
|
|
73
|
-
].filter(Boolean);
|
|
71
|
+
const mismatchFlags = [mismatch.projectMismatch ? "project" : null].filter(Boolean);
|
|
74
72
|
lines.push(`**Cluster ${i + 1}** — size ${cluster.size}, attribution: ${cluster.attribution.recoveryReingest} recovery-pair(s) / ${cluster.attribution.organic} organic-pair(s)` +
|
|
75
73
|
(mismatchFlags.length > 0 ? `, metadata mismatch: ${mismatchFlags.join(", ")}` : ", metadata consistent"));
|
|
76
74
|
for (const m of cluster.members) {
|
package/dist/nightly.js
CHANGED
|
@@ -782,13 +782,11 @@ async function runNightly(options = {}) {
|
|
|
782
782
|
// #408: weakPrimaryFloor is a release-managed calibration
|
|
783
783
|
// constant now — no config threading; the Options field stays
|
|
784
784
|
// as the eval/test seam.
|
|
785
|
-
}, {
|
|
786
|
-
// #405: no supersessionMaxCalls — the ONE run budget is the
|
|
787
|
-
// only call cap. #408: minSimilarity defaults to the
|
|
788
|
-
// calibration constant (seam only).
|
|
789
785
|
},
|
|
790
786
|
// #405: the ONE per-run LLM-call ceiling (default 5000;
|
|
791
787
|
// consolidateMaxLlmCalls honored as a deprecated alias).
|
|
788
|
+
// (#206-B: the retired supersession stage's options slot sat
|
|
789
|
+
// here — removed with the stage; true-update detection is 3.8's.)
|
|
792
790
|
(0, consolidate_js_1.resolveNightlyLlmCallBudget)(savedConfig),
|
|
793
791
|
// #245: soft cap on the corpus (default 10000; 0 disables eviction).
|
|
794
792
|
memorySoftCapResolved, {
|
package/dist/recall-hook-cli.js
CHANGED
|
@@ -17,6 +17,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
exports.buildHookRequest = buildHookRequest;
|
|
18
18
|
exports.runRecallHook = runRecallHook;
|
|
19
19
|
const learnings_identity_js_1 = require("./learnings-identity.js");
|
|
20
|
+
const wrapper_prompt_js_1 = require("./wrapper-prompt.js");
|
|
20
21
|
const node_path_1 = require("node:path");
|
|
21
22
|
const FETCH_TIMEOUT_MS = 1000;
|
|
22
23
|
/** Read all of stdin (CC pipes the hook payload JSON). */
|
|
@@ -44,6 +45,15 @@ function buildHookRequest(payload, cwd = process.cwd()) {
|
|
|
44
45
|
const prompt = typeof payload.prompt === "string" ? payload.prompt : "";
|
|
45
46
|
if (!prompt)
|
|
46
47
|
return null;
|
|
48
|
+
// #489: a pure plumbing payload (task-notification without a report result,
|
|
49
|
+
// command envelope, caveat, stdout echo, bare system-reminder) produces NO
|
|
50
|
+
// request at all — no HTTP call, so the 1000 ms hook budget is spent on
|
|
51
|
+
// real prompts only. A wrapper CARRYING prose (agent-message hand-back,
|
|
52
|
+
// report <result>, user text around the wrapper) recalls on the full text
|
|
53
|
+
// (owner decision 4) — the shared classifier, same function the server
|
|
54
|
+
// guards with (wrapper-prompt.ts).
|
|
55
|
+
if ((0, wrapper_prompt_js_1.isPureWrapperPrompt)(prompt))
|
|
56
|
+
return null;
|
|
47
57
|
// #203 scope: derive project from the session cwd so retrieval can apply a
|
|
48
58
|
// soft project-affinity boost. basename(cwd) matches capture's
|
|
49
59
|
// decodeProjectDirName for non-hyphenated dirs (the common case); a hyphen
|
package/dist/recall-index.js
CHANGED
|
@@ -93,6 +93,7 @@ exports.handleRecallIndex = handleRecallIndex;
|
|
|
93
93
|
exports.handleMemoryGet = handleMemoryGet;
|
|
94
94
|
exports.formatMemoryGetText = formatMemoryGetText;
|
|
95
95
|
const storage = __importStar(require("./storage.js"));
|
|
96
|
+
const wrapper_prompt_js_1 = require("./wrapper-prompt.js");
|
|
96
97
|
const type_labels_js_1 = require("./type-labels.js");
|
|
97
98
|
const retrieval_js_1 = require("./retrieval.js");
|
|
98
99
|
const CALIBRATION = __importStar(require("./calibration.js"));
|
|
@@ -321,6 +322,17 @@ async function handleRecallIndex(deps, body) {
|
|
|
321
322
|
return { status: 200, body: { ok: true, reset: true } };
|
|
322
323
|
}
|
|
323
324
|
const prompt = typeof req.prompt === "string" ? req.prompt.trim() : "";
|
|
325
|
+
// #489 wrapper guard — the short-prompt gate's slot, BEFORE beginTurn and
|
|
326
|
+
// before any precision recording, so a pure plumbing payload (CC delivers
|
|
327
|
+
// task-notifications/command envelopes as user-role messages; any client
|
|
328
|
+
// could POST one) gets {block: null} with NO recall_pushes row, NO turn
|
|
329
|
+
// burn, NO shown_count bump, NO last_accessed refresh. The SAME shared
|
|
330
|
+
// classifier the hook client guards with (wrapper-prompt.ts) — defense in
|
|
331
|
+
// depth. A wrapper carrying real payload prose falls through and recalls on
|
|
332
|
+
// the full text, exactly today's behavior (owner decision 4).
|
|
333
|
+
if ((0, wrapper_prompt_js_1.isPureWrapperPrompt)(prompt)) {
|
|
334
|
+
return { status: 200, body: { block: null, skipped: "wrapper-prompt" } };
|
|
335
|
+
}
|
|
324
336
|
const minPromptLength = deps.options?.minPromptLength ?? DEFAULT_MIN_PROMPT_LENGTH;
|
|
325
337
|
if (prompt.length < minPromptLength) {
|
|
326
338
|
return { status: 200, body: { block: null, skipped: "short-prompt" } };
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Reconsolidation (#384, #392) — the store resolves its own corrections, and
|
|
3
3
|
* THE unified resolution stage.
|
|
4
4
|
*
|
|
5
|
-
* Nightly consolidation stage (runs as Stage 3.8, after
|
|
5
|
+
* Nightly consolidation stage (runs as Stage 3.8, after links, before
|
|
6
6
|
* decay/prune) that detects memories which correct, retract, supersede, or
|
|
7
7
|
* DUPLICATE older ones; REWRITES corrected facts in place (absorbing
|
|
8
8
|
* transition-only trigger memories), MERGES confirmed duplicates via the
|
|
@@ -44,8 +44,9 @@
|
|
|
44
44
|
* planDedup and the judged mergeMemoryIds) refuse to blend a conflicts-linked
|
|
45
45
|
* pair, counted as conflict_skipped. The zone therefore runs AFTER the scan —
|
|
46
46
|
* with the zone first, a >=0.92 conflict pair was blended
|
|
47
|
-
* before the judge ever saw it (the planted-eval harm:
|
|
48
|
-
*
|
|
47
|
+
* before the judge ever saw it (the planted-eval harm: an unjudged blend —
|
|
48
|
+
* whichever row lost the canonical pick, its wording was simply erased);
|
|
49
|
+
* running it last means verdicts/marks/binds land first
|
|
49
50
|
* and the zone merges only what no verdict claimed — a conflicts bind set by
|
|
50
51
|
* this run's scan guards the SAME run's zone.
|
|
51
52
|
*
|
|
@@ -94,10 +95,14 @@ export declare const RECONSOLIDATION_STAGE_LABEL = "reconsolidation";
|
|
|
94
95
|
/**
|
|
95
96
|
* Default minimum COSINE similarity for a correction candidate pair —
|
|
96
97
|
* RELEASE-MANAGED since #408 (calibration.ts CORRECTION_MIN_SIMILARITY;
|
|
97
|
-
* provenance there).
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
98
|
+
* provenance there). Deliberately wide: a retraction often rides inside an
|
|
99
|
+
* otherwise unrelated memory (the field failure that opened this issue), so
|
|
100
|
+
* the neighborhood gate must be a touch wider while the LLM verdict +
|
|
101
|
+
* confidence gate carry the precision load. Since #206-B (owner decision 6)
|
|
102
|
+
* this floor also subsumes the retired Stage 3.7 supersession scan's 0.80:
|
|
103
|
+
* 3.8 is the ONLY true-update detector, scanning every new memory — no
|
|
104
|
+
* shape gate, wider floor — with `corrects`/`supersedes` partitioning what
|
|
105
|
+
* 3.7's binary prompt called a supersession.
|
|
101
106
|
*/
|
|
102
107
|
export declare const DEFAULT_CORRECTION_MIN_SIMILARITY = 0.75;
|
|
103
108
|
/**
|
|
@@ -193,7 +198,7 @@ export interface ScoutShape {
|
|
|
193
198
|
}
|
|
194
199
|
/**
|
|
195
200
|
* Build the constrained correction-shape prompt (classify-tier cost profile:
|
|
196
|
-
*
|
|
201
|
+
* 1500-char truncation, verdict-prompt precedent). The wording asks for
|
|
197
202
|
* the OLD claim's distinctive terms — the field-failure mechanism is that a
|
|
198
203
|
* correction CONTAINS the words of what it corrects, even when the surrounding
|
|
199
204
|
* topics (and therefore the embedding cosine) are unrelated. Guard-C extends
|
|
@@ -204,13 +209,14 @@ export declare function buildScoutShapePrompt(content: string): string;
|
|
|
204
209
|
/**
|
|
205
210
|
* Parse the scout shape reply. Null on unparseable JSON, a missing/non-boolean
|
|
206
211
|
* `correction`, or a missing/out-of-range `confidence` — the caller counts
|
|
207
|
-
* skipped_infra and moves on (
|
|
212
|
+
* skipped_infra and moves on (the retired supersession stage's parse
|
|
213
|
+
* discipline, kept: never
|
|
208
214
|
* mis-detect on ambiguity). `references` is lenient (missing/non-string → "")
|
|
209
215
|
* because an empty string simply yields no FTS hits — a harmless miss, not a
|
|
210
216
|
* mis-judgment.
|
|
211
217
|
*/
|
|
212
218
|
export declare function parseScoutShape(reply: string): ScoutShape | null;
|
|
213
|
-
/** Build the constrained pair-verdict prompt (1500-char truncation
|
|
219
|
+
/** Build the constrained pair-verdict prompt (1500-char truncation). */
|
|
214
220
|
export declare function buildCorrectionVerdictPrompt(oldContent: string, newContent: string): string;
|
|
215
221
|
export interface CorrectionVerdict {
|
|
216
222
|
action: ResolutionAction;
|
|
@@ -219,7 +225,7 @@ export interface CorrectionVerdict {
|
|
|
219
225
|
/**
|
|
220
226
|
* Parse the pair verdict. Null on anything unparseable, unknown action, or an
|
|
221
227
|
* out-of-range/missing confidence — the caller counts skipped_infra and moves
|
|
222
|
-
* on (same
|
|
228
|
+
* on (the same never-mis-judge-on-ambiguity discipline).
|
|
223
229
|
*/
|
|
224
230
|
export declare function parseCorrectionVerdict(reply: string): CorrectionVerdict | null;
|
|
225
231
|
export interface RewriteTriggerDisposition {
|
package/dist/reconsolidation.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Reconsolidation (#384, #392) — the store resolves its own corrections, and
|
|
4
4
|
* THE unified resolution stage.
|
|
5
5
|
*
|
|
6
|
-
* Nightly consolidation stage (runs as Stage 3.8, after
|
|
6
|
+
* Nightly consolidation stage (runs as Stage 3.8, after links, before
|
|
7
7
|
* decay/prune) that detects memories which correct, retract, supersede, or
|
|
8
8
|
* DUPLICATE older ones; REWRITES corrected facts in place (absorbing
|
|
9
9
|
* transition-only trigger memories), MERGES confirmed duplicates via the
|
|
@@ -45,8 +45,9 @@
|
|
|
45
45
|
* planDedup and the judged mergeMemoryIds) refuse to blend a conflicts-linked
|
|
46
46
|
* pair, counted as conflict_skipped. The zone therefore runs AFTER the scan —
|
|
47
47
|
* with the zone first, a >=0.92 conflict pair was blended
|
|
48
|
-
* before the judge ever saw it (the planted-eval harm:
|
|
49
|
-
*
|
|
48
|
+
* before the judge ever saw it (the planted-eval harm: an unjudged blend —
|
|
49
|
+
* whichever row lost the canonical pick, its wording was simply erased);
|
|
50
|
+
* running it last means verdicts/marks/binds land first
|
|
50
51
|
* and the zone merges only what no verdict claimed — a conflicts bind set by
|
|
51
52
|
* this run's scan guards the SAME run's zone.
|
|
52
53
|
*
|
|
@@ -154,10 +155,14 @@ exports.RECONSOLIDATION_STAGE_LABEL = "reconsolidation";
|
|
|
154
155
|
/**
|
|
155
156
|
* Default minimum COSINE similarity for a correction candidate pair —
|
|
156
157
|
* RELEASE-MANAGED since #408 (calibration.ts CORRECTION_MIN_SIMILARITY;
|
|
157
|
-
* provenance there).
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
158
|
+
* provenance there). Deliberately wide: a retraction often rides inside an
|
|
159
|
+
* otherwise unrelated memory (the field failure that opened this issue), so
|
|
160
|
+
* the neighborhood gate must be a touch wider while the LLM verdict +
|
|
161
|
+
* confidence gate carry the precision load. Since #206-B (owner decision 6)
|
|
162
|
+
* this floor also subsumes the retired Stage 3.7 supersession scan's 0.80:
|
|
163
|
+
* 3.8 is the ONLY true-update detector, scanning every new memory — no
|
|
164
|
+
* shape gate, wider floor — with `corrects`/`supersedes` partitioning what
|
|
165
|
+
* 3.7's binary prompt called a supersession.
|
|
161
166
|
*/
|
|
162
167
|
exports.DEFAULT_CORRECTION_MIN_SIMILARITY = CALIBRATION.CORRECTION_MIN_SIMILARITY;
|
|
163
168
|
/**
|
|
@@ -167,11 +172,12 @@ exports.DEFAULT_CORRECTION_MIN_SIMILARITY = CALIBRATION.CORRECTION_MIN_SIMILARIT
|
|
|
167
172
|
* weak rewrite is corruption.
|
|
168
173
|
*/
|
|
169
174
|
exports.DEFAULT_CORRECTION_REWRITE_MIN_CONFIDENCE = CALIBRATION.CORRECTION_REWRITE_MIN_CONFIDENCE;
|
|
170
|
-
/** Neighbor pool size before older/similarity filtering narrows to top 5
|
|
175
|
+
/** Neighbor pool size before older/similarity filtering narrows to top 5
|
|
176
|
+
* (formerly the supersession stage's constants — 3.8 inherited the shape). */
|
|
171
177
|
const CORRECTION_NEIGHBOR_POOL = 15;
|
|
172
|
-
/** Older-neighbor pairs kept per candidate after filtering
|
|
178
|
+
/** Older-neighbor pairs kept per candidate after filtering. */
|
|
173
179
|
const CORRECTION_NEIGHBOR_TOP_K = 5;
|
|
174
|
-
/** Content truncation for prompts (classify-tier cost profile
|
|
180
|
+
/** Content truncation for prompts (classify-tier cost profile). */
|
|
175
181
|
const PROMPT_TRUNCATE_CHARS = 1500;
|
|
176
182
|
/** Head of the old content quoted in the provenance footer. */
|
|
177
183
|
exports.FOOTER_HEAD_MAX_CHARS = 160;
|
|
@@ -214,7 +220,7 @@ function nowIso() {
|
|
|
214
220
|
}
|
|
215
221
|
/**
|
|
216
222
|
* Build the constrained correction-shape prompt (classify-tier cost profile:
|
|
217
|
-
*
|
|
223
|
+
* 1500-char truncation, verdict-prompt precedent). The wording asks for
|
|
218
224
|
* the OLD claim's distinctive terms — the field-failure mechanism is that a
|
|
219
225
|
* correction CONTAINS the words of what it corrects, even when the surrounding
|
|
220
226
|
* topics (and therefore the embedding cosine) are unrelated. Guard-C extends
|
|
@@ -237,7 +243,8 @@ function buildScoutShapePrompt(content) {
|
|
|
237
243
|
/**
|
|
238
244
|
* Parse the scout shape reply. Null on unparseable JSON, a missing/non-boolean
|
|
239
245
|
* `correction`, or a missing/out-of-range `confidence` — the caller counts
|
|
240
|
-
* skipped_infra and moves on (
|
|
246
|
+
* skipped_infra and moves on (the retired supersession stage's parse
|
|
247
|
+
* discipline, kept: never
|
|
241
248
|
* mis-detect on ambiguity). `references` is lenient (missing/non-string → "")
|
|
242
249
|
* because an empty string simply yields no FTS hits — a harmless miss, not a
|
|
243
250
|
* mis-judgment.
|
|
@@ -264,7 +271,7 @@ function parseScoutShape(reply) {
|
|
|
264
271
|
const references = typeof obj.references === "string" ? obj.references : "";
|
|
265
272
|
return { correction: obj.correction, references: references.trim(), confidence };
|
|
266
273
|
}
|
|
267
|
-
/** Build the constrained pair-verdict prompt (1500-char truncation
|
|
274
|
+
/** Build the constrained pair-verdict prompt (1500-char truncation). */
|
|
268
275
|
function buildCorrectionVerdictPrompt(oldContent, newContent) {
|
|
269
276
|
const trunc = (s) => (s.length > PROMPT_TRUNCATE_CHARS ? `${s.slice(0, PROMPT_TRUNCATE_CHARS)}…` : s);
|
|
270
277
|
return (`You are checking how a NEWER memory relates to an OLDER one in an AI agent's long-term memory.\n\n` +
|
|
@@ -287,7 +294,7 @@ function buildCorrectionVerdictPrompt(oldContent, newContent) {
|
|
|
287
294
|
/**
|
|
288
295
|
* Parse the pair verdict. Null on anything unparseable, unknown action, or an
|
|
289
296
|
* out-of-range/missing confidence — the caller counts skipped_infra and moves
|
|
290
|
-
* on (same
|
|
297
|
+
* on (the same never-mis-judge-on-ambiguity discipline).
|
|
291
298
|
*/
|
|
292
299
|
function parseCorrectionVerdict(reply) {
|
|
293
300
|
if (!reply)
|
|
@@ -558,8 +565,8 @@ function accumulateBandStat(cumulative, run) {
|
|
|
558
565
|
cumulative.none += run.none;
|
|
559
566
|
cumulative.merge_below_gate += run.merge_below_gate;
|
|
560
567
|
cumulative.conf_sum += run.conf_sum;
|
|
561
|
-
if (run.
|
|
562
|
-
cumulative.
|
|
568
|
+
if (run.project_skipped !== undefined) {
|
|
569
|
+
cumulative.project_skipped = (cumulative.project_skipped ?? 0) + run.project_skipped;
|
|
563
570
|
}
|
|
564
571
|
}
|
|
565
572
|
/**
|
|
@@ -715,7 +722,9 @@ async function stageReconsolidation(db, llm, budget, embedFn, dryRun, stateDir,
|
|
|
715
722
|
// re-detect). Once the backlog drains, pairs_reevaluated reads 0.
|
|
716
723
|
const prevScannedRowid = (0, state_js_1.loadState)(stateDir).reconsolidationScannedRowid ?? startCursor;
|
|
717
724
|
let scannedRowidHighwater = startCursor;
|
|
718
|
-
// NO shape filter (AC2) —
|
|
725
|
+
// NO shape filter (AC2) — also why 3.8 subsumes the retired Stage 3.7
|
|
726
|
+
// supersession scan (its shape gate would miss exactly these pairs).
|
|
727
|
+
// Absorbed rows are
|
|
719
728
|
// excluded: they are invisible to recall and must not re-enter judgment.
|
|
720
729
|
const rows = db
|
|
721
730
|
.prepare(`SELECT rowid AS __rowid, * FROM memories
|
|
@@ -739,7 +748,7 @@ async function stageReconsolidation(db, llm, budget, embedFn, dryRun, stateDir,
|
|
|
739
748
|
let explicitDivergent = 0;
|
|
740
749
|
let mergeBelowGate = 0;
|
|
741
750
|
let skippedAboveCeiling = 0;
|
|
742
|
-
let
|
|
751
|
+
let skippedProjectMismatch = 0;
|
|
743
752
|
let mergePairsApplied = 0;
|
|
744
753
|
// #393 guard-C: conflicts verdicts rendered (link written, both live) and
|
|
745
754
|
// judged-path merge refusals on a conflicts-linked pair.
|
|
@@ -1228,10 +1237,10 @@ async function stageReconsolidation(db, llm, budget, embedFn, dryRun, stateDir,
|
|
|
1228
1237
|
console.log(`[hicortex] Reconsolidation: merged ${pair.oldId.slice(0, 8)} + ${pair.newId.slice(0, 8)} ` +
|
|
1229
1238
|
`into canonical ${result.canonicalId.slice(0, 8)} (${result.linksRepointed} link(s) re-pointed)`);
|
|
1230
1239
|
}
|
|
1231
|
-
else if (result.reason === "
|
|
1232
|
-
|
|
1240
|
+
else if (result.reason === "project_mismatch") {
|
|
1241
|
+
skippedProjectMismatch++;
|
|
1233
1242
|
console.log(`[hicortex] Reconsolidation: merge of ${pair.oldId.slice(0, 8)} + ${pair.newId.slice(0, 8)} ` +
|
|
1234
|
-
`skipped (
|
|
1243
|
+
`skipped (project mismatch) — both kept`);
|
|
1235
1244
|
}
|
|
1236
1245
|
else if (result.reason === "conflict_linked") {
|
|
1237
1246
|
// #393 guard-C: the pair is conflicts-linked (operator-planted
|
|
@@ -1446,10 +1455,10 @@ async function stageReconsolidation(db, llm, budget, embedFn, dryRun, stateDir,
|
|
|
1446
1455
|
console.log(`[hicortex] Reconsolidation: merged ${pair.oldId.slice(0, 8)} + ${pair.newId.slice(0, 8)} ` +
|
|
1447
1456
|
`into canonical ${result.canonicalId.slice(0, 8)} (${result.linksRepointed} link(s) re-pointed)`);
|
|
1448
1457
|
}
|
|
1449
|
-
else if (result.reason === "
|
|
1450
|
-
|
|
1458
|
+
else if (result.reason === "project_mismatch") {
|
|
1459
|
+
skippedProjectMismatch++;
|
|
1451
1460
|
console.log(`[hicortex] Reconsolidation: merge of ${pair.oldId.slice(0, 8)} + ${pair.newId.slice(0, 8)} ` +
|
|
1452
|
-
`skipped (
|
|
1461
|
+
`skipped (project mismatch) — both kept`);
|
|
1453
1462
|
}
|
|
1454
1463
|
else if (result.reason === "conflict_linked") {
|
|
1455
1464
|
conflictSkippedJudged++;
|
|
@@ -1473,7 +1482,8 @@ async function stageReconsolidation(db, llm, budget, embedFn, dryRun, stateDir,
|
|
|
1473
1482
|
// deterministic sweep: verdicts, marks, and binds land first, and the zone
|
|
1474
1483
|
// merges only what no verdict claimed. With the zone first, a >=0.92
|
|
1475
1484
|
// genuine-conflict pair was blended before the judge ever saw it
|
|
1476
|
-
// (
|
|
1485
|
+
// (an unjudged blend — the canonical-pick loser's wording erased —
|
|
1486
|
+
// the planted-eval harm);
|
|
1477
1487
|
// running it last means a `conflicts` bind set by THIS run's scan guards
|
|
1478
1488
|
// the SAME run's zone. LLM-free and budget-free — an LLM-less night still
|
|
1479
1489
|
// drains duplicates (a deadline-deferred cluster re-detects next run at
|
|
@@ -1499,8 +1509,8 @@ async function stageReconsolidation(db, llm, budget, embedFn, dryRun, stateDir,
|
|
|
1499
1509
|
det.pairs = merges.losers_merged;
|
|
1500
1510
|
det.merge = merges.losers_merged;
|
|
1501
1511
|
det.conf_sum = merges.losers_merged;
|
|
1502
|
-
if (merges.
|
|
1503
|
-
det.
|
|
1512
|
+
if (merges.skipped_project_mismatch > 0) {
|
|
1513
|
+
det.project_skipped = merges.skipped_project_mismatch;
|
|
1504
1514
|
}
|
|
1505
1515
|
bandStats[`>=${autoMergeThreshold}`] = det;
|
|
1506
1516
|
}
|
|
@@ -1569,7 +1579,7 @@ async function stageReconsolidation(db, llm, budget, embedFn, dryRun, stateDir,
|
|
|
1569
1579
|
merge_pairs_applied: mergePairsApplied,
|
|
1570
1580
|
merge_below_gate: mergeBelowGate,
|
|
1571
1581
|
skipped_above_ceiling: skippedAboveCeiling,
|
|
1572
|
-
|
|
1582
|
+
skipped_project_mismatch: skippedProjectMismatch,
|
|
1573
1583
|
conflict_flagged: conflictFlagged,
|
|
1574
1584
|
conflict_skipped: conflictSkippedJudged + merges.skipped_conflict,
|
|
1575
1585
|
scout_scanned: scoutScanned,
|
package/dist/relink.d.ts
CHANGED
|
@@ -86,8 +86,8 @@ export interface RelinkReport {
|
|
|
86
86
|
/**
|
|
87
87
|
* Read the stored embedding for a memory from memory_vectors.
|
|
88
88
|
* Returns null when the row is missing (caller falls back to re-embedding).
|
|
89
|
-
* @deprecated moved to storage.ts (shared with
|
|
90
|
-
*
|
|
89
|
+
* @deprecated moved to storage.ts (shared with the resolution stages);
|
|
90
|
+
* re-exported here so existing importers of relink.ts keep working.
|
|
91
91
|
*/
|
|
92
92
|
export { getStoredEmbedding } from "./storage.js";
|
|
93
93
|
/**
|
package/dist/relink.js
CHANGED
|
@@ -107,8 +107,8 @@ function loadExistingPairs(db) {
|
|
|
107
107
|
/**
|
|
108
108
|
* Read the stored embedding for a memory from memory_vectors.
|
|
109
109
|
* Returns null when the row is missing (caller falls back to re-embedding).
|
|
110
|
-
* @deprecated moved to storage.ts (shared with
|
|
111
|
-
*
|
|
110
|
+
* @deprecated moved to storage.ts (shared with the resolution stages);
|
|
111
|
+
* re-exported here so existing importers of relink.ts keep working.
|
|
112
112
|
*/
|
|
113
113
|
var storage_js_1 = require("./storage.js");
|
|
114
114
|
Object.defineProperty(exports, "getStoredEmbedding", { enumerable: true, get: function () { return storage_js_1.getStoredEmbedding; } });
|
package/dist/retrieval.d.ts
CHANGED
|
@@ -155,14 +155,15 @@ export declare function recallQueryVector(registry: CentroidStore, sessionId: st
|
|
|
155
155
|
}): Float32Array;
|
|
156
156
|
/**
|
|
157
157
|
* Ids among `candidateIds` that have been superseded by a later memory — i.e.
|
|
158
|
-
* they are the SOURCE of a `superseded_by` link (
|
|
159
|
-
* old → new
|
|
158
|
+
* they are the SOURCE of a `superseded_by` link (the resolution pass links
|
|
159
|
+
* old → new; formerly also the retired Stage 3.7 supersession scan). One
|
|
160
|
+
* query, not per-candidate.
|
|
160
161
|
*/
|
|
161
162
|
export declare function findSupersededIds(db: Database.Database, candidateIds: string[]): Set<string>;
|
|
162
163
|
/**
|
|
163
164
|
* The full ranking-demotion set among `candidateIds` (#384): the UNION of
|
|
164
|
-
* (a) sources of a `superseded_by` link (legacy +
|
|
165
|
-
* driven, works on pre-v14 rows with NULL status) and (b) rows whose
|
|
165
|
+
* (a) sources of a `superseded_by` link (legacy + retired-3.7 + resolution
|
|
166
|
+
* verdicts — link driven, works on pre-v14 rows with NULL status) and (b) rows whose
|
|
166
167
|
* `memories.status` is 'superseded' or 'retracted' (reconsolidation marks +
|
|
167
168
|
* explicit ingest marks). `corrected` is deliberately NOT demoting — a
|
|
168
169
|
* rewritten memory carries the CORRECTION, and demoting it would bury the
|
package/dist/retrieval.js
CHANGED
|
@@ -275,8 +275,9 @@ function recallQueryVector(registry, sessionId, promptEmb, opts) {
|
|
|
275
275
|
}
|
|
276
276
|
/**
|
|
277
277
|
* Ids among `candidateIds` that have been superseded by a later memory — i.e.
|
|
278
|
-
* they are the SOURCE of a `superseded_by` link (
|
|
279
|
-
* old → new
|
|
278
|
+
* they are the SOURCE of a `superseded_by` link (the resolution pass links
|
|
279
|
+
* old → new; formerly also the retired Stage 3.7 supersession scan). One
|
|
280
|
+
* query, not per-candidate.
|
|
280
281
|
*/
|
|
281
282
|
function findSupersededIds(db, candidateIds) {
|
|
282
283
|
if (candidateIds.length === 0)
|
|
@@ -290,8 +291,8 @@ function findSupersededIds(db, candidateIds) {
|
|
|
290
291
|
}
|
|
291
292
|
/**
|
|
292
293
|
* The full ranking-demotion set among `candidateIds` (#384): the UNION of
|
|
293
|
-
* (a) sources of a `superseded_by` link (legacy +
|
|
294
|
-
* driven, works on pre-v14 rows with NULL status) and (b) rows whose
|
|
294
|
+
* (a) sources of a `superseded_by` link (legacy + retired-3.7 + resolution
|
|
295
|
+
* verdicts — link driven, works on pre-v14 rows with NULL status) and (b) rows whose
|
|
295
296
|
* `memories.status` is 'superseded' or 'retracted' (reconsolidation marks +
|
|
296
297
|
* explicit ingest marks). `corrected` is deliberately NOT demoting — a
|
|
297
298
|
* rewritten memory carries the CORRECTION, and demoting it would bury the
|
package/dist/state.d.ts
CHANGED
|
@@ -51,19 +51,19 @@ export interface HicortexState {
|
|
|
51
51
|
*/
|
|
52
52
|
domainCursor?: number;
|
|
53
53
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
54
|
+
* #206-B: the retired supersession stage's cursor key
|
|
55
|
+
* (`supersessionCursor`) is deliberately NOT modeled here anymore. The
|
|
56
|
+
* stage (3.7, #191 Phase B) is retired into the reconsolidation pass, the
|
|
57
|
+
* whole-corpus backfill was complete before retirement, and nothing reads
|
|
58
|
+
* the key — pre-#206-B installs keep their stale value on disk, left to
|
|
59
|
+
* rot unread (no migration, no deletion). Do not repurpose the name.
|
|
60
60
|
*/
|
|
61
|
-
supersessionCursor?: number;
|
|
62
61
|
/**
|
|
63
62
|
* Resume cursor for the nightly's reconsolidation stage (#384) — highest
|
|
64
63
|
* memories.rowid whose candidates have been evaluated (or infra-skipped)
|
|
65
64
|
* with all their CONFIRMED work applied. Absent/0 = never run. Same
|
|
66
|
-
* advance-past-considered-candidates discipline
|
|
65
|
+
* advance-past-considered-candidates discipline (formerly the retired
|
|
66
|
+
* supersessionCursor's),
|
|
67
67
|
* with one addition (#439): confirmed merges and rewrite groups apply at
|
|
68
68
|
* the candidate boundary — the END of the iteration that confirmed them —
|
|
69
69
|
* and the cursor advances past a candidate only when that apply landed.
|
package/dist/storage.d.ts
CHANGED
|
@@ -149,8 +149,8 @@ export declare function getMemoryTagsWeightedBatched(db: Database.Database, memo
|
|
|
149
149
|
* Read the stored embedding for a memory from memory_vectors.
|
|
150
150
|
* Returns null when the row is missing (caller falls back to re-embedding).
|
|
151
151
|
*
|
|
152
|
-
* Shared by `hicortex relink` and the nightly's
|
|
153
|
-
* (consolidate.ts) — lives here (not in relink.ts) so
|
|
152
|
+
* Shared by `hicortex relink` and the nightly's resolution stages
|
|
153
|
+
* (reconsolidation.ts, consolidate.ts) — lives here (not in relink.ts) so they can use
|
|
154
154
|
* it without importing from relink.ts, which itself imports from
|
|
155
155
|
* consolidate.ts (BudgetTracker, discoverLinkCandidates).
|
|
156
156
|
*/
|
package/dist/storage.js
CHANGED
|
@@ -408,8 +408,8 @@ function getMemoryTagsWeightedBatched(db, memoryIds) {
|
|
|
408
408
|
* Read the stored embedding for a memory from memory_vectors.
|
|
409
409
|
* Returns null when the row is missing (caller falls back to re-embedding).
|
|
410
410
|
*
|
|
411
|
-
* Shared by `hicortex relink` and the nightly's
|
|
412
|
-
* (consolidate.ts) — lives here (not in relink.ts) so
|
|
411
|
+
* Shared by `hicortex relink` and the nightly's resolution stages
|
|
412
|
+
* (reconsolidation.ts, consolidate.ts) — lives here (not in relink.ts) so they can use
|
|
413
413
|
* it without importing from relink.ts, which itself imports from
|
|
414
414
|
* consolidate.ts (BudgetTracker, discoverLinkCandidates).
|
|
415
415
|
*/
|