@gamaze/hicortex 0.20.4 → 0.20.6
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 -1
- package/dist/backup.d.ts +12 -8
- package/dist/backup.js +13 -9
- package/dist/claude-desktop.d.ts +138 -0
- package/dist/claude-desktop.js +251 -0
- package/dist/cli.d.ts +6 -2
- package/dist/cli.js +62 -3
- package/dist/consolidate.d.ts +8 -1
- package/dist/consolidate.js +84 -2
- package/dist/db.js +36 -0
- package/dist/dedup.d.ts +157 -25
- package/dist/dedup.js +376 -83
- package/dist/domain-classify.js +4 -2
- package/dist/index.js +7 -7
- package/dist/init.d.ts +4 -1
- package/dist/init.js +103 -1
- package/dist/llm.d.ts +19 -13
- package/dist/llm.js +25 -14
- package/dist/mcp-server.d.ts +6 -0
- package/dist/mcp-server.js +84 -13
- package/dist/mcp-stdio.js +6 -1
- package/dist/memory-instructions.d.ts +18 -0
- package/dist/memory-instructions.js +39 -2
- package/dist/nightly.js +20 -1
- package/dist/reconsolidation.d.ts +362 -0
- package/dist/reconsolidation.js +1349 -0
- package/dist/retrieval.d.ts +14 -0
- package/dist/retrieval.js +41 -3
- package/dist/state.d.ts +23 -1
- package/dist/storage.d.ts +25 -0
- package/dist/storage.js +49 -7
- package/dist/type-classify.js +4 -2
- package/dist/types.d.ts +198 -0
- package/hermes-plugin/hicortex/provider.py +29 -17
- package/opencode-plugin/hicortex/index.ts +7 -7
- package/package.json +1 -1
- package/pi-extension/hicortex/index.ts +7 -7
- package/server.json +2 -2
package/dist/retrieval.d.ts
CHANGED
|
@@ -147,6 +147,20 @@ export declare function recallQueryVector(registry: CentroidStore, sessionId: st
|
|
|
147
147
|
* old → new). One query, not per-candidate.
|
|
148
148
|
*/
|
|
149
149
|
export declare function findSupersededIds(db: Database.Database, candidateIds: string[]): Set<string>;
|
|
150
|
+
/**
|
|
151
|
+
* The full ranking-demotion set among `candidateIds` (#384): the UNION of
|
|
152
|
+
* (a) sources of a `superseded_by` link (legacy + stageSupersession — link
|
|
153
|
+
* driven, works on pre-v14 rows with NULL status) and (b) rows whose
|
|
154
|
+
* `memories.status` is 'superseded' or 'retracted' (reconsolidation marks +
|
|
155
|
+
* explicit ingest marks). `corrected` is deliberately NOT demoting — a
|
|
156
|
+
* rewritten memory carries the CORRECTION, and demoting it would bury the
|
|
157
|
+
* fix (the exact failure reconsolidation exists to repair). `absorbed` needs
|
|
158
|
+
* no entry here: absorbed rows have no vector/FTS row and are filtered at
|
|
159
|
+
* candidacy. One batched query, both call sites (retrieve + searchRecent).
|
|
160
|
+
* Byte-identical behavior for memories with no correction relationship
|
|
161
|
+
* (NULL status, no link) — they never match either arm.
|
|
162
|
+
*/
|
|
163
|
+
export declare function findDemotedIds(db: Database.Database, candidateIds: string[]): Set<string>;
|
|
150
164
|
/**
|
|
151
165
|
* Convert an L2 distance (as returned by sqlite-vec's vec0 `distance`) to
|
|
152
166
|
* cosine similarity. Valid because our embeddings are L2-normalized
|
package/dist/retrieval.js
CHANGED
|
@@ -63,6 +63,7 @@ exports.getSessionIntent = getSessionIntent;
|
|
|
63
63
|
exports.blendQueryVector = blendQueryVector;
|
|
64
64
|
exports.recallQueryVector = recallQueryVector;
|
|
65
65
|
exports.findSupersededIds = findSupersededIds;
|
|
66
|
+
exports.findDemotedIds = findDemotedIds;
|
|
66
67
|
exports.l2ToCosine = l2ToCosine;
|
|
67
68
|
exports.effectiveStrength = effectiveStrength;
|
|
68
69
|
exports.computeScore = computeScore;
|
|
@@ -276,6 +277,34 @@ function findSupersededIds(db, candidateIds) {
|
|
|
276
277
|
.all(...candidateIds);
|
|
277
278
|
return new Set(rows.map((r) => r.source_id));
|
|
278
279
|
}
|
|
280
|
+
/**
|
|
281
|
+
* The full ranking-demotion set among `candidateIds` (#384): the UNION of
|
|
282
|
+
* (a) sources of a `superseded_by` link (legacy + stageSupersession — link
|
|
283
|
+
* driven, works on pre-v14 rows with NULL status) and (b) rows whose
|
|
284
|
+
* `memories.status` is 'superseded' or 'retracted' (reconsolidation marks +
|
|
285
|
+
* explicit ingest marks). `corrected` is deliberately NOT demoting — a
|
|
286
|
+
* rewritten memory carries the CORRECTION, and demoting it would bury the
|
|
287
|
+
* fix (the exact failure reconsolidation exists to repair). `absorbed` needs
|
|
288
|
+
* no entry here: absorbed rows have no vector/FTS row and are filtered at
|
|
289
|
+
* candidacy. One batched query, both call sites (retrieve + searchRecent).
|
|
290
|
+
* Byte-identical behavior for memories with no correction relationship
|
|
291
|
+
* (NULL status, no link) — they never match either arm.
|
|
292
|
+
*/
|
|
293
|
+
function findDemotedIds(db, candidateIds) {
|
|
294
|
+
if (candidateIds.length === 0)
|
|
295
|
+
return new Set();
|
|
296
|
+
const placeholders = candidateIds.map(() => "?").join(",");
|
|
297
|
+
const rows = db
|
|
298
|
+
.prepare(`SELECT DISTINCT id FROM (
|
|
299
|
+
SELECT source_id AS id FROM memory_links
|
|
300
|
+
WHERE relationship = 'superseded_by' AND source_id IN (${placeholders})
|
|
301
|
+
UNION
|
|
302
|
+
SELECT id FROM memories
|
|
303
|
+
WHERE status IN ('superseded', 'retracted') AND id IN (${placeholders})
|
|
304
|
+
)`)
|
|
305
|
+
.all(...candidateIds, ...candidateIds);
|
|
306
|
+
return new Set(rows.map((r) => r.id));
|
|
307
|
+
}
|
|
279
308
|
/**
|
|
280
309
|
* Placeholder L2 distance for candidates that have no measured vector
|
|
281
310
|
* distance (FTS-only hits and graph-discovered neighbors). Chosen so that
|
|
@@ -608,6 +637,11 @@ async function retrieve(db, embedFn, query, options) {
|
|
|
608
637
|
const mem = storage.getMemory(db, gid);
|
|
609
638
|
if (!mem)
|
|
610
639
|
continue;
|
|
640
|
+
// #384: absorbed memories never enter via the graph either — they keep
|
|
641
|
+
// their link rows (evidence + rollback reference), so graph traversal can
|
|
642
|
+
// reach them, but they are invisible to recall by contract.
|
|
643
|
+
if (mem.status === "absorbed")
|
|
644
|
+
continue;
|
|
611
645
|
// #203: project check removed — project is a soft affinity in computeScore,
|
|
612
646
|
// not a filter. 0.16.x: privacy check removed — the column is vestigial,
|
|
613
647
|
// never filtered. sourceAgent stays a hard filter.
|
|
@@ -622,8 +656,10 @@ async function retrieve(db, embedFn, query, options) {
|
|
|
622
656
|
const scored = [];
|
|
623
657
|
const maxRrf = Math.max(...([...rrfScores.values()].length > 0 ? [...rrfScores.values()] : [1]));
|
|
624
658
|
// One query for the whole candidate set (#191 Phase B): superseded memories
|
|
625
|
-
// are demoted in computeScore rather than strength-penalized.
|
|
626
|
-
|
|
659
|
+
// are demoted in computeScore rather than strength-penalized. #384: the set
|
|
660
|
+
// is the full demotion set — link-driven supersessions UNION status-marked
|
|
661
|
+
// superseded/retracted rows (see findDemotedIds).
|
|
662
|
+
const supersededIds = findDemotedIds(db, [...candidateMap.keys()]);
|
|
627
663
|
// #203: ONE batched load of every candidate's graded domain tags — fed to
|
|
628
664
|
// computeScore for domain affinity. Only needed when the scope carries
|
|
629
665
|
// missionDomains; absent otherwise (skips the query entirely on /search and
|
|
@@ -714,7 +750,9 @@ function searchRecent(db, options) {
|
|
|
714
750
|
? [...connectionCounts.values()]
|
|
715
751
|
: [0]));
|
|
716
752
|
const scored = [];
|
|
717
|
-
|
|
753
|
+
// #384: same full demotion set as retrieve() — link-driven supersessions
|
|
754
|
+
// UNION status-marked superseded/retracted rows (findDemotedIds).
|
|
755
|
+
const supersededRecent = findDemotedIds(db, candidates.map((c) => c.id));
|
|
718
756
|
for (const mem of candidates) {
|
|
719
757
|
const connCount = connectionCounts.get(mem.id) ?? 0;
|
|
720
758
|
const score = computeScore(mem, DEFAULT_GRAPH_DISTANCE, connCount, maxConnections, now, {
|
package/dist/state.d.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* Note: ~/.hicortex/config.json is intentionally NOT merged here. Config is
|
|
17
17
|
* user-edited and tracked separately from machine state.
|
|
18
18
|
*/
|
|
19
|
-
import type { LicenseInfo, ModuleIndex } from "./types.js";
|
|
19
|
+
import type { LicenseInfo, ModuleIndex, ResolutionBandStat } from "./types.js";
|
|
20
20
|
/** Persisted tier information — reflects the last successful validation. */
|
|
21
21
|
export interface PersistedTier {
|
|
22
22
|
/** Tier name from the validation API response. */
|
|
@@ -60,6 +60,17 @@ export interface HicortexState {
|
|
|
60
60
|
* gradually over many nights.
|
|
61
61
|
*/
|
|
62
62
|
supersessionCursor?: number;
|
|
63
|
+
/**
|
|
64
|
+
* Resume cursor for the nightly's reconsolidation stage (#384) — highest
|
|
65
|
+
* memories.rowid whose candidates have been evaluated (or infra-skipped)
|
|
66
|
+
* this run. Absent/0 = never run. Same advance-past-considered-candidates
|
|
67
|
+
* discipline as supersessionCursor, with one addition: when a rewrite group
|
|
68
|
+
* could not be applied (budget exhausted / rewrite-call infra error), the
|
|
69
|
+
* cursor holds BELOW the earliest candidate contributing to an un-applied
|
|
70
|
+
* group so those pairs are re-detected next run — a confirmed correction is
|
|
71
|
+
* never silently dropped by the cursor passing it.
|
|
72
|
+
*/
|
|
73
|
+
reconsolidationCursor?: number;
|
|
63
74
|
/**
|
|
64
75
|
* Resume cursor for `hicortex classify-types` (#216) — highest memories.rowid
|
|
65
76
|
* whose batch has been fully committed. Absent/0 = never run (or reset).
|
|
@@ -89,6 +100,17 @@ export interface HicortexState {
|
|
|
89
100
|
* which means the first run is never throttled (correct: no baseline yet).
|
|
90
101
|
*/
|
|
91
102
|
llmTokensLastRun?: number;
|
|
103
|
+
/**
|
|
104
|
+
* Cumulative per-band verdict statistics for the unified resolution pass
|
|
105
|
+
* (#392), keyed by cosine band label ("0.75-0.8", …, ">=0.92" — labels
|
|
106
|
+
* derive from the live floor/ceiling at write time). Accumulated across
|
|
107
|
+
* runs, never reset: labeled calibration evidence for moving the
|
|
108
|
+
* floor/ceiling boundaries later. The deterministic zone persists its own
|
|
109
|
+
* band; the reconsolidation stage persists the judged bands. Never written
|
|
110
|
+
* on dry runs. The per-run snapshot lives in the stage report
|
|
111
|
+
* (`stages.reconsolidation.band_stats`).
|
|
112
|
+
*/
|
|
113
|
+
resolutionBandStats?: Record<string, ResolutionBandStat>;
|
|
92
114
|
}
|
|
93
115
|
/**
|
|
94
116
|
* Load the state file. Returns an empty state if the file is missing
|
package/dist/storage.d.ts
CHANGED
|
@@ -46,6 +46,27 @@ export declare function touchMemoriesShown(db: Database.Database, memoryIds: str
|
|
|
46
46
|
* Delete a memory, its vector, its tags, and all its links.
|
|
47
47
|
*/
|
|
48
48
|
export declare function deleteMemory(db: Database.Database, memoryId: string): void;
|
|
49
|
+
/**
|
|
50
|
+
* A memory's rowid in `memories` (the FTS table's rowid), or null when the
|
|
51
|
+
* row does not exist. Shared by the absorb primitive below and the
|
|
52
|
+
* reconsolidation stage's FTS bookkeeping (#384/#392).
|
|
53
|
+
*/
|
|
54
|
+
export declare function memoryRowid(db: Database.Database, memoryId: string): number | null;
|
|
55
|
+
/**
|
|
56
|
+
* Drop a memory's retrieval candidacy: status `absorbed`, vector row deleted,
|
|
57
|
+
* FTS row deleted (direct DELETE — the AFTER UPDATE trigger's `UPDATE … WHERE
|
|
58
|
+
* rowid` is a silent no-op on the missing row, so later column edits cannot
|
|
59
|
+
* resurrect it). The plain row + links are KEPT (evidence, session lineage,
|
|
60
|
+
* rollback reference). Must run inside a transaction.
|
|
61
|
+
*
|
|
62
|
+
* The shared absorb primitive (#392): the reconsolidation stage's rewrite
|
|
63
|
+
* path (via the `absorbTrigger` re-export) AND dedup merge losers both fold a
|
|
64
|
+
* row into invisible-evidence state through this ONE function, so the
|
|
65
|
+
* "absorbed" vocabulary can never drift between them. Tags/domain are the
|
|
66
|
+
* CALLER's concern (the rewrite path clears the target's; a dedup merge
|
|
67
|
+
* clears the loser's before absorbing).
|
|
68
|
+
*/
|
|
69
|
+
export declare function absorbMemory(db: Database.Database, memoryId: string): void;
|
|
49
70
|
/** Options for setMemoryTags (graded-schema spec 2026-07-07). */
|
|
50
71
|
export interface SetMemoryTagsOptions {
|
|
51
72
|
/**
|
|
@@ -223,6 +244,8 @@ export declare function insertMemoriesBatch(db: Database.Database, memories: Arr
|
|
|
223
244
|
export declare function countMemories(db: Database.Database): number;
|
|
224
245
|
/**
|
|
225
246
|
* Get memories created in the last N days, newest first.
|
|
247
|
+
* Absorbed memories are excluded (#384): they are invisible to recall — the
|
|
248
|
+
* plain row is evidence only, never a recent-recall candidate.
|
|
226
249
|
*/
|
|
227
250
|
export declare function getRecentMemories(db: Database.Database, days?: number, limit?: number): Memory[];
|
|
228
251
|
/**
|
|
@@ -245,5 +268,7 @@ export declare function getPruneCandidates(db: Database.Database, cutoffIso: str
|
|
|
245
268
|
export declare function getAllLinkCounts(db: Database.Database): Map<string, number>;
|
|
246
269
|
/**
|
|
247
270
|
* Get all memories with default base_strength (never scored).
|
|
271
|
+
* Absorbed memories are excluded (#384): they are invisible to recall, so
|
|
272
|
+
* importance-scoring one would spend an LLM call on dead evidence.
|
|
248
273
|
*/
|
|
249
274
|
export declare function getUnscoredMemories(db: Database.Database): Memory[];
|
package/dist/storage.js
CHANGED
|
@@ -13,6 +13,8 @@ exports.updateMemory = updateMemory;
|
|
|
13
13
|
exports.strengthenMemory = strengthenMemory;
|
|
14
14
|
exports.touchMemoriesShown = touchMemoriesShown;
|
|
15
15
|
exports.deleteMemory = deleteMemory;
|
|
16
|
+
exports.memoryRowid = memoryRowid;
|
|
17
|
+
exports.absorbMemory = absorbMemory;
|
|
16
18
|
exports.setMemoryTags = setMemoryTags;
|
|
17
19
|
exports.getMemoryTags = getMemoryTags;
|
|
18
20
|
exports.getMemoryTagsWeighted = getMemoryTagsWeighted;
|
|
@@ -132,6 +134,10 @@ const ALLOWED_UPDATE_FIELDS = new Set([
|
|
|
132
134
|
"privacy",
|
|
133
135
|
"memory_type",
|
|
134
136
|
"updated_at",
|
|
137
|
+
// Reconsolidation state (#384, migration v14): written by the
|
|
138
|
+
// reconsolidation stage, explicit ingest marks, and history rollback.
|
|
139
|
+
// Code-defined vocabulary — see Memory.status.
|
|
140
|
+
"status",
|
|
135
141
|
]);
|
|
136
142
|
/**
|
|
137
143
|
* Update specific fields on a memory.
|
|
@@ -185,6 +191,36 @@ function deleteMemory(db, memoryId) {
|
|
|
185
191
|
db.prepare("DELETE FROM memory_vectors WHERE id = ?").run(memoryId);
|
|
186
192
|
db.prepare("DELETE FROM memories WHERE id = ?").run(memoryId);
|
|
187
193
|
}
|
|
194
|
+
/**
|
|
195
|
+
* A memory's rowid in `memories` (the FTS table's rowid), or null when the
|
|
196
|
+
* row does not exist. Shared by the absorb primitive below and the
|
|
197
|
+
* reconsolidation stage's FTS bookkeeping (#384/#392).
|
|
198
|
+
*/
|
|
199
|
+
function memoryRowid(db, memoryId) {
|
|
200
|
+
const row = db.prepare("SELECT rowid AS rid FROM memories WHERE id = ?").get(memoryId);
|
|
201
|
+
return row?.rid ?? null;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Drop a memory's retrieval candidacy: status `absorbed`, vector row deleted,
|
|
205
|
+
* FTS row deleted (direct DELETE — the AFTER UPDATE trigger's `UPDATE … WHERE
|
|
206
|
+
* rowid` is a silent no-op on the missing row, so later column edits cannot
|
|
207
|
+
* resurrect it). The plain row + links are KEPT (evidence, session lineage,
|
|
208
|
+
* rollback reference). Must run inside a transaction.
|
|
209
|
+
*
|
|
210
|
+
* The shared absorb primitive (#392): the reconsolidation stage's rewrite
|
|
211
|
+
* path (via the `absorbTrigger` re-export) AND dedup merge losers both fold a
|
|
212
|
+
* row into invisible-evidence state through this ONE function, so the
|
|
213
|
+
* "absorbed" vocabulary can never drift between them. Tags/domain are the
|
|
214
|
+
* CALLER's concern (the rewrite path clears the target's; a dedup merge
|
|
215
|
+
* clears the loser's before absorbing).
|
|
216
|
+
*/
|
|
217
|
+
function absorbMemory(db, memoryId) {
|
|
218
|
+
const rid = memoryRowid(db, memoryId);
|
|
219
|
+
updateMemory(db, memoryId, { status: "absorbed" });
|
|
220
|
+
db.prepare("DELETE FROM memory_vectors WHERE id = ?").run(memoryId);
|
|
221
|
+
if (rid !== null)
|
|
222
|
+
db.prepare("DELETE FROM memories_fts WHERE rowid = ?").run(rid);
|
|
223
|
+
}
|
|
188
224
|
/**
|
|
189
225
|
* Set a memory's classification tags (graded schema model).
|
|
190
226
|
*
|
|
@@ -451,12 +487,14 @@ function searchFts(db, query, limit = 10, sourceAgent) {
|
|
|
451
487
|
* Create a link between two memories.
|
|
452
488
|
*/
|
|
453
489
|
function addLink(db, sourceId, targetId, relationship, strength = 0.5) {
|
|
454
|
-
// Guard: superseded_by
|
|
455
|
-
//
|
|
456
|
-
// pair — INSERT OR REPLACE would
|
|
457
|
-
|
|
490
|
+
// Guard: superseded_by and corrected_by are the ranking-demotion /
|
|
491
|
+
// correction-resolution signals, so never let a different relationship
|
|
492
|
+
// clobber an existing one for the same pair — INSERT OR REPLACE would
|
|
493
|
+
// otherwise silently remove the resolution (corrected_by is protected
|
|
494
|
+
// exactly like superseded_by, #384 AC9).
|
|
495
|
+
if (relationship !== "superseded_by" && relationship !== "corrected_by") {
|
|
458
496
|
const protectedLink = db
|
|
459
|
-
.prepare("SELECT 1 FROM memory_links WHERE source_id = ? AND target_id = ? AND relationship
|
|
497
|
+
.prepare("SELECT 1 FROM memory_links WHERE source_id = ? AND target_id = ? AND relationship IN ('superseded_by', 'corrected_by') LIMIT 1")
|
|
460
498
|
.get(sourceId, targetId);
|
|
461
499
|
if (protectedLink)
|
|
462
500
|
return;
|
|
@@ -530,11 +568,13 @@ function countMemories(db) {
|
|
|
530
568
|
}
|
|
531
569
|
/**
|
|
532
570
|
* Get memories created in the last N days, newest first.
|
|
571
|
+
* Absorbed memories are excluded (#384): they are invisible to recall — the
|
|
572
|
+
* plain row is evidence only, never a recent-recall candidate.
|
|
533
573
|
*/
|
|
534
574
|
function getRecentMemories(db, days = 7, limit = 50) {
|
|
535
575
|
const rows = db
|
|
536
576
|
.prepare(`SELECT * FROM memories
|
|
537
|
-
WHERE created_at >= datetime('now', ?)
|
|
577
|
+
WHERE created_at >= datetime('now', ?) AND COALESCE(status, '') != 'absorbed'
|
|
538
578
|
ORDER BY created_at DESC LIMIT ?`)
|
|
539
579
|
.all(`-${days} days`, limit);
|
|
540
580
|
return rows.map(rowToMemory);
|
|
@@ -602,11 +642,13 @@ function getAllLinkCounts(db) {
|
|
|
602
642
|
}
|
|
603
643
|
/**
|
|
604
644
|
* Get all memories with default base_strength (never scored).
|
|
645
|
+
* Absorbed memories are excluded (#384): they are invisible to recall, so
|
|
646
|
+
* importance-scoring one would spend an LLM call on dead evidence.
|
|
605
647
|
*/
|
|
606
648
|
function getUnscoredMemories(db) {
|
|
607
649
|
const rows = db
|
|
608
650
|
.prepare(`SELECT * FROM memories
|
|
609
|
-
WHERE base_strength = 0.5
|
|
651
|
+
WHERE base_strength = 0.5 AND COALESCE(status, '') != 'absorbed'
|
|
610
652
|
ORDER BY ingested_at ASC`)
|
|
611
653
|
.all();
|
|
612
654
|
return rows.map(rowToMemory);
|
package/dist/type-classify.js
CHANGED
|
@@ -151,8 +151,10 @@ async function classifyMemoryType(content, llm) {
|
|
|
151
151
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
152
152
|
let raw;
|
|
153
153
|
try {
|
|
154
|
-
//
|
|
155
|
-
|
|
154
|
+
// No per-call cap (#391): the classify-tier ceiling (classifyMaxTokens,
|
|
155
|
+
// default 1024) resolves inside completeClassify — a hardcoded 20
|
|
156
|
+
// starved reasoning models whose thinking ate the whole output budget.
|
|
157
|
+
const r = await llm.completeClassify(prompt);
|
|
156
158
|
raw = r.text;
|
|
157
159
|
}
|
|
158
160
|
catch (err) {
|
package/dist/types.d.ts
CHANGED
|
@@ -32,6 +32,16 @@ export interface Memory {
|
|
|
32
32
|
privacy: ("PUBLIC" | "WORK" | "PERSONAL" | "SENSITIVE") | null;
|
|
33
33
|
memory_type: "experience" | "learnings" | "knowledge" | "decisions";
|
|
34
34
|
updated_at: string | null;
|
|
35
|
+
/**
|
|
36
|
+
* Reconsolidation state (#384, migration v14). Code-defined vocabulary,
|
|
37
|
+
* never config: NULL/absent = active (the default, and every pre-v14 row);
|
|
38
|
+
* 'superseded'/'retracted' = marked stale or wrong (demoted in ranking);
|
|
39
|
+
* 'corrected' = rewritten in place (does NOT demote — demoting it would
|
|
40
|
+
* bury the correction); 'absorbed' = invisible to recall (trigger memory
|
|
41
|
+
* folded into a corrected target — no vector/FTS row, plain row + link
|
|
42
|
+
* kept as evidence and rollback reference).
|
|
43
|
+
*/
|
|
44
|
+
status?: string | null;
|
|
35
45
|
}
|
|
36
46
|
/** A link between two memories. */
|
|
37
47
|
export interface MemoryLink {
|
|
@@ -67,6 +77,68 @@ export interface MemorySearchResult {
|
|
|
67
77
|
* both, or graph traversal). Used by the /recall-index relevance gate. */
|
|
68
78
|
source?: "vector" | "fts" | "both" | "graph";
|
|
69
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Per-cosine-band verdict statistics for the unified resolution pass (#392).
|
|
82
|
+
* Bands are labeled from the live floor/ceiling ("0.75-0.8", …, ">=0.92").
|
|
83
|
+
* The stage report carries the per-run snapshot; state.json
|
|
84
|
+
* `resolutionBandStats` carries the cumulative series — calibration evidence
|
|
85
|
+
* for moving the floor/ceiling boundaries later, with data.
|
|
86
|
+
*/
|
|
87
|
+
export interface ResolutionBandStat {
|
|
88
|
+
/** Candidate pairs judged (or deterministically merged) in this band. */
|
|
89
|
+
pairs: number;
|
|
90
|
+
/** Verdict/action counts. `merge` counts gated merges (applied or applicable). */
|
|
91
|
+
merge: number;
|
|
92
|
+
corrects: number;
|
|
93
|
+
supersedes: number;
|
|
94
|
+
none: number;
|
|
95
|
+
/** Merge verdicts below the confidence gate — both memories kept. */
|
|
96
|
+
merge_below_gate: number;
|
|
97
|
+
/** Sum of verdict confidences (divide by `pairs` for the mean). Deterministic merges count 1.0 each. */
|
|
98
|
+
conf_sum: number;
|
|
99
|
+
/** Deterministic band only: clusters refused by the metadata rails. */
|
|
100
|
+
metadata_skipped?: number;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Report of the deterministic merge zone (#392) — the >= dedupAutoMergeThreshold
|
|
104
|
+
* band, merged by the dedup core's union-find clustering with ZERO LLM calls.
|
|
105
|
+
* Computed in dedup.ts (runDeterministicMergeZone); surfaced verbatim as
|
|
106
|
+
* `stages.reconsolidation.merges`.
|
|
107
|
+
*/
|
|
108
|
+
export interface DeterministicMergeZoneReport {
|
|
109
|
+
/** The cosine ceiling in force (config dedupAutoMergeThreshold; default 0.92). */
|
|
110
|
+
threshold: number;
|
|
111
|
+
/** The pacing cap in force (dedupNightlyMaxMerges; 0 = machinery disabled). */
|
|
112
|
+
max_merges: number;
|
|
113
|
+
/** Every cluster found at the threshold (mergeable + mismatch-skipped). */
|
|
114
|
+
clusters_found: number;
|
|
115
|
+
/** Clusters that passed the metadata rails (would merge). */
|
|
116
|
+
mergeable_clusters: number;
|
|
117
|
+
/** Clusters actually merged this run (apply only; 0 on dry-run). */
|
|
118
|
+
merged_clusters: number;
|
|
119
|
+
/** Loser rows absorbed (hidden from recall, kept as evidence) this run. */
|
|
120
|
+
losers_merged: number;
|
|
121
|
+
/** Loser links re-pointed onto canonicals this run. */
|
|
122
|
+
links_repointed: number;
|
|
123
|
+
/** Clusters skipped — members disagree on project / source_agent. */
|
|
124
|
+
skipped_metadata_mismatch: number;
|
|
125
|
+
/** Mergeable clusters NOT attempted because the pacing cap was exhausted. */
|
|
126
|
+
capped: number;
|
|
127
|
+
/** Clusters whose merge transaction failed (rolled back; retried next run). */
|
|
128
|
+
failed: number;
|
|
129
|
+
/** Apply only: the capture lock was busy — zero merges, fail-soft. */
|
|
130
|
+
lock_busy?: boolean;
|
|
131
|
+
/** Apply only: the pre-merge backup failed — zero merges, fail-soft. */
|
|
132
|
+
backup_failed?: boolean;
|
|
133
|
+
/** Apply only: path of the pre-merge DB backup. */
|
|
134
|
+
backup_path?: string;
|
|
135
|
+
/** Dry-run only: bounded preview of the first 10 mergeable clusters. */
|
|
136
|
+
preview?: Array<{
|
|
137
|
+
size: number;
|
|
138
|
+
canonical_id: string;
|
|
139
|
+
loser_ids: string[];
|
|
140
|
+
}>;
|
|
141
|
+
}
|
|
70
142
|
/** Report returned by the consolidation pipeline. */
|
|
71
143
|
export interface ConsolidationReport {
|
|
72
144
|
started_at: string;
|
|
@@ -141,6 +213,81 @@ export interface ConsolidationReport {
|
|
|
141
213
|
/** supersessionCursor after this run (unchanged in dry-run). */
|
|
142
214
|
cursor: number;
|
|
143
215
|
};
|
|
216
|
+
/**
|
|
217
|
+
* Reconsolidation (#384) — runs after supersession, before decay/prune.
|
|
218
|
+
* Since #392 this is THE unified resolution stage: its verdict also carries
|
|
219
|
+
* a `merge` disposition, and the deterministic merge zone (pairs at/above
|
|
220
|
+
* `dedupAutoMergeThreshold`) runs inside it, LLM-free, before the scan.
|
|
221
|
+
*/
|
|
222
|
+
reconsolidation?: {
|
|
223
|
+
/** Candidates examined this run (rowid > cursor; no shape filter). */
|
|
224
|
+
scanned: number;
|
|
225
|
+
/** Pairs actually sent to the verdict LLM (detection + explicit-mark verification). */
|
|
226
|
+
pairs_evaluated: number;
|
|
227
|
+
/**
|
|
228
|
+
* #394: pairs the similarity floor discovered this run (KNN neighbors
|
|
229
|
+
* at/above correctionMinSimilarity), counted before any skip or
|
|
230
|
+
* judgment — the only sizing number a dry-run can show, where
|
|
231
|
+
* pairs_evaluated is always 0.
|
|
232
|
+
*/
|
|
233
|
+
pairs_discovered: number;
|
|
234
|
+
/** #394: discovered pairs with no resolution link yet — the actionable
|
|
235
|
+
* candidates (deterministic-zone work + would-be verdict calls). */
|
|
236
|
+
pairs_discovered_unlinked: number;
|
|
237
|
+
/** Targets rewritten in place this run (one history row each). */
|
|
238
|
+
rewritten: number;
|
|
239
|
+
/** Triggers absorbed (invisible to recall: vector + FTS dropped). */
|
|
240
|
+
absorbed: number;
|
|
241
|
+
/** Triggers kept live by their disposition (standalone substance). */
|
|
242
|
+
kept_linked: number;
|
|
243
|
+
/** Memories marked status 'superseded' (mark-only path). */
|
|
244
|
+
marked_superseded: number;
|
|
245
|
+
/** Memories marked status 'retracted' (mark-only: below gate / non-fact / failed contract). */
|
|
246
|
+
marked_retracted: number;
|
|
247
|
+
/** Verdicts that were `corrects` but below correctionRewriteMinConfidence. */
|
|
248
|
+
below_gate: number;
|
|
249
|
+
/** Rewrite groups degraded to mark-only on a failed rewrite contract. */
|
|
250
|
+
contract_failed: number;
|
|
251
|
+
/** Verdict/rewrite calls skipped on a parse/infra error (retried naturally). */
|
|
252
|
+
skipped_infra: number;
|
|
253
|
+
/** Pairs skipped because a resolution link already existed (either direction). */
|
|
254
|
+
skipped_idempotent: number;
|
|
255
|
+
/** Explicit ingest marks verified and upgraded into a rewrite group. */
|
|
256
|
+
explicit_verified: number;
|
|
257
|
+
/** Explicit marks whose verification diverged (mark retained untouched). */
|
|
258
|
+
explicit_divergent: number;
|
|
259
|
+
/** reconsolidationCursor after this run (unchanged in dry-run). */
|
|
260
|
+
cursor: number;
|
|
261
|
+
/**
|
|
262
|
+
* #392: the deterministic merge zone's own report (pairs >= the
|
|
263
|
+
* ceiling, union-find merged, zero LLM). Present on every run —
|
|
264
|
+
* including quiet-night skips (a stock install with a pre-upgrade
|
|
265
|
+
* backlog still drains it, LLM-free).
|
|
266
|
+
*/
|
|
267
|
+
merges: DeterministicMergeZoneReport;
|
|
268
|
+
/** #392: judged-zone pair merges applied this run (merge verdicts at/above the confidence gate). */
|
|
269
|
+
merge_pairs_applied: number;
|
|
270
|
+
/** #392: merge verdicts below correctionRewriteMinConfidence — both memories kept. */
|
|
271
|
+
merge_below_gate: number;
|
|
272
|
+
/**
|
|
273
|
+
* #392: pairs the scan saw at/above the ceiling — owned by the
|
|
274
|
+
* deterministic zone (or waiting for its cap), never LLM-judged.
|
|
275
|
+
*/
|
|
276
|
+
skipped_above_ceiling: number;
|
|
277
|
+
/**
|
|
278
|
+
* #392: judged merge pairs refused by the metadata rails (project /
|
|
279
|
+
* source_agent disagreement). Both memories kept; the cursor advances —
|
|
280
|
+
* the verdict was rendered, this is not an infra failure.
|
|
281
|
+
*/
|
|
282
|
+
skipped_metadata_mismatch: number;
|
|
283
|
+
/**
|
|
284
|
+
* #392: per-run verdict statistics by cosine band ("0.75-0.8" …
|
|
285
|
+
* ">=0.92"; labels derive from the live floor/ceiling). Calibration
|
|
286
|
+
* evidence for moving the boundaries later; the cumulative series lives
|
|
287
|
+
* in state.json `resolutionBandStats`.
|
|
288
|
+
*/
|
|
289
|
+
band_stats: Record<string, ResolutionBandStat>;
|
|
290
|
+
};
|
|
144
291
|
decay_prune?: {
|
|
145
292
|
candidates: number;
|
|
146
293
|
pruned: number;
|
|
@@ -345,6 +492,18 @@ export interface HicortexConfig {
|
|
|
345
492
|
* when it finishes early. Read in llm.ts; see #220.
|
|
346
493
|
*/
|
|
347
494
|
maxTokens?: number;
|
|
495
|
+
/**
|
|
496
|
+
* Max output tokens for the classify tier ONLY — the short JSON-verdict
|
|
497
|
+
* calls: correction/supersession verdicts, rewrite contracts, and type +
|
|
498
|
+
* domain tag classification. Default 1024. A ceiling, not a target
|
|
499
|
+
* (generation stops at the model's natural end) — raise it when a
|
|
500
|
+
* reasoning-style model spends the budget on internal reasoning and returns
|
|
501
|
+
* empty verdicts (the pre-#391 hardcoded per-call caps starved exactly that
|
|
502
|
+
* shape; a local non-reasoning model is unaffected by the raise).
|
|
503
|
+
* `maxTokens` continues to govern the heavy phases (distill/reflect).
|
|
504
|
+
* Read in llm.ts; see #391.
|
|
505
|
+
*/
|
|
506
|
+
classifyMaxTokens?: number;
|
|
348
507
|
/**
|
|
349
508
|
* Toggle the model's internal reasoning ("thinking") stream on the openai-compat
|
|
350
509
|
* path — applies to ALL phases (distill / reflect / classify / scoring) since one
|
|
@@ -547,6 +706,45 @@ export interface HicortexConfig {
|
|
|
547
706
|
orgName?: string;
|
|
548
707
|
/** Plan/tier label rendered as a small badge (e.g. "Cloud · Early bird"). */
|
|
549
708
|
planLabel?: string;
|
|
709
|
+
/**
|
|
710
|
+
* Minimum cosine similarity for a reconsolidation candidate pair (#384):
|
|
711
|
+
* each new-since-cursor memory is paired with up to 5 older KNN neighbors
|
|
712
|
+
* at/above this bar before the verdict call. Default 0.75 — a touch wider
|
|
713
|
+
* than the supersession stage's 0.80 because a retraction often rides inside
|
|
714
|
+
* an otherwise unrelated memory; the verdict + confidence gate carry the
|
|
715
|
+
* precision. Number in (0, 1]; invalid/absent keeps the default.
|
|
716
|
+
*/
|
|
717
|
+
correctionMinSimilarity?: number;
|
|
718
|
+
/**
|
|
719
|
+
* Minimum verdict confidence for the REWRITE fork of reconsolidation (#384):
|
|
720
|
+
* a `corrects` verdict at/above this bar on a fact-shaped target is rewritten
|
|
721
|
+
* in place; below it the pair degrades to mark-only (a weak mark is
|
|
722
|
+
* recoverable, a weak rewrite is corruption). Default 0.80. Number in
|
|
723
|
+
* (0, 1]; invalid/absent keeps the default. Since #392 this same gate also
|
|
724
|
+
* decides whether a `merge` verdict is applied (analogous reasoning: a weak
|
|
725
|
+
* merge keeps both memories, a confirmed merge hides one).
|
|
726
|
+
*/
|
|
727
|
+
correctionRewriteMinConfidence?: number;
|
|
728
|
+
/**
|
|
729
|
+
* Deterministic merge ceiling for the unified resolution pass (#392): memory
|
|
730
|
+
* pairs at/above this cosine are merged by the dedup core's union-find
|
|
731
|
+
* clustering with ZERO LLM calls; pairs in [correctionMinSimilarity, this
|
|
732
|
+
* value) get the one unified verdict call (merge/corrects/supersedes/none).
|
|
733
|
+
* Default 0.92 (the #100/#191 calibration). The legacy `dedupMergeThreshold`
|
|
734
|
+
* key is honored as a fallback when this key is absent. Number in (0, 1];
|
|
735
|
+
* invalid/absent keeps the default. Also read by the manual `hicortex dedup`
|
|
736
|
+
* CLI (same precedence: --threshold > this key > legacy key > 0.92).
|
|
737
|
+
*/
|
|
738
|
+
dedupAutoMergeThreshold?: number;
|
|
739
|
+
/**
|
|
740
|
+
* Pacing cap on merge OPERATIONS per nightly run (#392): deterministic-zone
|
|
741
|
+
* clusters plus judged pair merges count against ONE cap, so a
|
|
742
|
+
* misbehaving-distiller burst is bounded and a large pre-existing backlog
|
|
743
|
+
* drains over a few nights rather than in one run. Default 250. `0` disables
|
|
744
|
+
* the merge machinery entirely (the deterministic zone is skipped; a
|
|
745
|
+
* confirmed merge verdict keeps both memories). Non-negative integer.
|
|
746
|
+
*/
|
|
747
|
+
dedupNightlyMaxMerges?: number;
|
|
550
748
|
}
|
|
551
749
|
/** A config-owned life-sphere domain (see HicortexConfig.domains). */
|
|
552
750
|
export interface DomainDef {
|
|
@@ -484,8 +484,12 @@ class HicortexProvider(MemoryProvider):
|
|
|
484
484
|
{
|
|
485
485
|
"name": "hicortex_search",
|
|
486
486
|
"description": (
|
|
487
|
-
"Search long-term memory
|
|
488
|
-
"
|
|
487
|
+
"Search shared long-term memory (all agents, all sessions). "
|
|
488
|
+
"CALL THIS BEFORE assuming, guessing, or asking the user about "
|
|
489
|
+
"anything that may have come up before: prior decisions, "
|
|
490
|
+
"preferences, project facts, people, hardware, past incidents. "
|
|
491
|
+
"If you are about to write 'I don't have information about…', "
|
|
492
|
+
"search first."
|
|
489
493
|
),
|
|
490
494
|
"parameters": {
|
|
491
495
|
"type": "object",
|
|
@@ -504,13 +508,13 @@ class HicortexProvider(MemoryProvider):
|
|
|
504
508
|
"name": "hicortex_get",
|
|
505
509
|
"description": (
|
|
506
510
|
"Fetch ONE memory's full content by id — use this to lazy-load "
|
|
507
|
-
"entries from the recall index or from search
|
|
508
|
-
"snippet was not enough. Fetching a memory marks
|
|
509
|
-
"(strengthens it), so fetch entries that could
|
|
510
|
-
"action — not every shown one. When the memory
|
|
511
|
-
"answer, cite it
|
|
512
|
-
"memory FETCHED and a one-line entry
|
|
513
|
-
"don't pass SNIPPET off as established."
|
|
511
|
+
"entries from the '## Memory recall (auto)' index or from search "
|
|
512
|
+
"results whose snippet was not enough. Fetching a memory marks "
|
|
513
|
+
"it as used (strengthens it), so fetch entries that could "
|
|
514
|
+
"change your action — not every shown one. When the memory "
|
|
515
|
+
"shapes your answer, cite it to the user (id + date + origin "
|
|
516
|
+
"agent) — mark a fetched memory `FETCHED` and a one-line entry "
|
|
517
|
+
"cited unread `SNIPPET`; don't pass SNIPPET off as established."
|
|
514
518
|
),
|
|
515
519
|
"parameters": {
|
|
516
520
|
"type": "object",
|
|
@@ -526,9 +530,9 @@ class HicortexProvider(MemoryProvider):
|
|
|
526
530
|
{
|
|
527
531
|
"name": "hicortex_recent",
|
|
528
532
|
"description": (
|
|
529
|
-
"Get recent memories, optionally filtered by project.
|
|
530
|
-
"of
|
|
531
|
-
"
|
|
533
|
+
"Get recent memories, optionally filtered by project. CALL THIS "
|
|
534
|
+
"AT THE START of substantive work on a project to catch up on "
|
|
535
|
+
"its latest state — cheaper than asking the user what happened."
|
|
532
536
|
),
|
|
533
537
|
"parameters": {
|
|
534
538
|
"type": "object",
|
|
@@ -542,7 +546,9 @@ class HicortexProvider(MemoryProvider):
|
|
|
542
546
|
"name": "hicortex_ingest",
|
|
543
547
|
"description": (
|
|
544
548
|
"Store a new memory in long-term storage. "
|
|
545
|
-
"Use for Knowledge, Decisions, or Learnings."
|
|
549
|
+
"Use for Knowledge, Decisions, or Learnings. Capture is "
|
|
550
|
+
"automatic (nightly) — use this ONLY for explicitly requested "
|
|
551
|
+
"learnings, never routine content."
|
|
546
552
|
),
|
|
547
553
|
"parameters": {
|
|
548
554
|
"type": "object",
|
|
@@ -561,8 +567,10 @@ class HicortexProvider(MemoryProvider):
|
|
|
561
567
|
{
|
|
562
568
|
"name": "hicortex_lessons",
|
|
563
569
|
"description": (
|
|
564
|
-
"Get actionable Learnings
|
|
565
|
-
"
|
|
570
|
+
"Get actionable Learnings — auto-generated insights about "
|
|
571
|
+
"mistakes to avoid. CALL THIS before retrying an approach that "
|
|
572
|
+
"failed before, or when picking up work where past problems may "
|
|
573
|
+
"have been recorded."
|
|
566
574
|
),
|
|
567
575
|
"parameters": {
|
|
568
576
|
"type": "object",
|
|
@@ -575,7 +583,9 @@ class HicortexProvider(MemoryProvider):
|
|
|
575
583
|
"name": "hicortex_index",
|
|
576
584
|
"description": (
|
|
577
585
|
"Get the knowledge domain index — shows what topics and projects "
|
|
578
|
-
"are stored in memory, grouped by domain."
|
|
586
|
+
"are stored in memory, grouped by domain. Call before a broad "
|
|
587
|
+
"search to see which knowledge domains exist, or when unsure "
|
|
588
|
+
"what the memory covers."
|
|
579
589
|
),
|
|
580
590
|
"parameters": {
|
|
581
591
|
"type": "object",
|
|
@@ -586,7 +596,9 @@ class HicortexProvider(MemoryProvider):
|
|
|
586
596
|
"name": "hicortex_graph",
|
|
587
597
|
"description": (
|
|
588
598
|
"Query the memory knowledge graph — find connected memories, "
|
|
589
|
-
"hub nodes, or paths between memories."
|
|
599
|
+
"hub nodes, or paths between memories. Use it to explore "
|
|
600
|
+
"memories connected to one you just fetched, or to find hub "
|
|
601
|
+
"memories in a domain."
|
|
590
602
|
),
|
|
591
603
|
"parameters": {
|
|
592
604
|
"type": "object",
|