@gamaze/hicortex 0.20.5 → 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/dist/consolidate.js +2 -0
- package/dist/nightly.js +6 -0
- package/dist/reconsolidation.d.ts +41 -2
- package/dist/reconsolidation.js +129 -6
- package/dist/types.d.ts +10 -0
- package/package.json +1 -1
package/dist/consolidate.js
CHANGED
|
@@ -1402,6 +1402,8 @@ async function skippedRunResolutionReport(db, dryRun, stateDir, options = {}) {
|
|
|
1402
1402
|
return {
|
|
1403
1403
|
scanned: 0,
|
|
1404
1404
|
pairs_evaluated: 0,
|
|
1405
|
+
pairs_discovered: 0, // #394: the scan doesn't run on a quiet night — nothing discovered
|
|
1406
|
+
pairs_discovered_unlinked: 0,
|
|
1405
1407
|
rewritten: 0,
|
|
1406
1408
|
absorbed: 0,
|
|
1407
1409
|
kept_linked: 0,
|
package/dist/nightly.js
CHANGED
|
@@ -748,6 +748,12 @@ async function runNightly(options = {}) {
|
|
|
748
748
|
autoMergeThreshold: (savedConfig?.dedupAutoMergeThreshold ??
|
|
749
749
|
savedConfig?.dedupMergeThreshold),
|
|
750
750
|
maxMerges: savedConfig?.dedupNightlyMaxMerges,
|
|
751
|
+
// #401 runtime bounds: the wall-clock deadline (default 120
|
|
752
|
+
// min, 0 disables) and the per-run classify-call ceiling
|
|
753
|
+
// (default 600, 0 disables). Same posture — the stage
|
|
754
|
+
// validates and falls back on invalid/absent values.
|
|
755
|
+
maxMinutes: savedConfig?.reconsolidationMaxMinutes,
|
|
756
|
+
maxCalls: savedConfig?.reconsolidationMaxCalls,
|
|
751
757
|
});
|
|
752
758
|
console.log(`[hicortex] Consolidation ${report.status} in ${report.elapsed_seconds}s` +
|
|
753
759
|
(report.stages.reflection ? ` (${report.stages.reflection.lessons_generated} lessons)` : ""));
|
|
@@ -57,6 +57,24 @@ export declare const DEFAULT_CORRECTION_MIN_SIMILARITY = 0.75;
|
|
|
57
57
|
* weak rewrite is corruption.
|
|
58
58
|
*/
|
|
59
59
|
export declare const DEFAULT_CORRECTION_REWRITE_MIN_CONFIDENCE = 0.8;
|
|
60
|
+
/**
|
|
61
|
+
* Default wall-clock bound for the stage, in minutes (#401). Checked at the
|
|
62
|
+
* top of the candidate scan loop (and before each rewrite contract call); on
|
|
63
|
+
* expiry the scan breaks cleanly at the last fully-considered candidate and
|
|
64
|
+
* the next run resumes from the persisted cursor. 120 sits safely under any
|
|
65
|
+
* sane process-level nightly timeout. 0 disables the bound. Invalid →
|
|
66
|
+
* default.
|
|
67
|
+
*/
|
|
68
|
+
export declare const DEFAULT_RECONSOLIDATION_MAX_MINUTES = 120;
|
|
69
|
+
/**
|
|
70
|
+
* Default per-run classify-call ceiling for the stage (#401) — the
|
|
71
|
+
* supersessionMaxCalls pattern with a NON-ZERO default ON PURPOSE: that
|
|
72
|
+
* knob's 0=unlimited default is what let the first full-corpus pass grow
|
|
73
|
+
* unbounded. Counts EVERY classify-tier call the stage makes (mark
|
|
74
|
+
* verifications, pair verdicts, rewrite contracts). 0 disables the cap.
|
|
75
|
+
* Invalid → default.
|
|
76
|
+
*/
|
|
77
|
+
export declare const DEFAULT_RECONSOLIDATION_MAX_CALLS = 600;
|
|
60
78
|
/** Head of the old content quoted in the provenance footer. */
|
|
61
79
|
export declare const FOOTER_HEAD_MAX_CHARS = 160;
|
|
62
80
|
/** The code-defined status vocabulary (see module doc). Not user-configurable. */
|
|
@@ -91,6 +109,24 @@ export interface ReconsolidationOptions {
|
|
|
91
109
|
* pair merges against ONE cap. Invalid → default.
|
|
92
110
|
*/
|
|
93
111
|
maxMerges?: number;
|
|
112
|
+
/**
|
|
113
|
+
* reconsolidationMaxMinutes (config; default 120; 0 disables) — wall-clock
|
|
114
|
+
* deadline for the stage (#401), measured from stage start. Checked at the
|
|
115
|
+
* top of the candidate scan loop and before each rewrite contract call; on
|
|
116
|
+
* expiry the scan breaks cleanly — the cursor already points at the last
|
|
117
|
+
* fully-considered candidate, so the run ends consistent and the next
|
|
118
|
+
* nightly resumes from it. Invalid → default.
|
|
119
|
+
*/
|
|
120
|
+
maxMinutes?: number;
|
|
121
|
+
/**
|
|
122
|
+
* reconsolidationMaxCalls (config; default 600; 0 disables) — per-run
|
|
123
|
+
* ceiling on classify-tier calls for the stage (#401), the
|
|
124
|
+
* supersessionMaxCalls pattern with a NON-ZERO default (the 0=unlimited
|
|
125
|
+
* default there is what removed the last per-stage bound). Exhaustion
|
|
126
|
+
* mid-neighbor-loop or mid-rewrite-phase stops/defers cleanly at the
|
|
127
|
+
* current candidate boundary. Invalid → default.
|
|
128
|
+
*/
|
|
129
|
+
maxCalls?: number;
|
|
94
130
|
/**
|
|
95
131
|
* Capture-lock acquirer override (tests) — the deterministic zone and the
|
|
96
132
|
* judged-merge phase each hold a short lock window. Defaults to the real
|
|
@@ -98,7 +134,7 @@ export interface ReconsolidationOptions {
|
|
|
98
134
|
*/
|
|
99
135
|
acquireLock?: typeof acquireCaptureLock;
|
|
100
136
|
}
|
|
101
|
-
/** The
|
|
137
|
+
/** The stage report (typed once, in ConsolidationReport — field list there). */
|
|
102
138
|
export type ReconsolidationStageResult = NonNullable<ConsolidationReport["stages"]["reconsolidation"]>;
|
|
103
139
|
/**
|
|
104
140
|
* True when a memory is REWRITE-ELIGIBLE — a fact-shaped target. The fork is
|
|
@@ -282,7 +318,10 @@ export declare function bandForCosine(bands: ResolutionBand[], cosine: number):
|
|
|
282
318
|
* silently dropped by the cursor passing it).
|
|
283
319
|
*
|
|
284
320
|
* Dry-run: the zone's discovery + the free idempotency check only — zero LLM
|
|
285
|
-
* calls, zero writes, no cursor or band-stats persistence.
|
|
321
|
+
* calls, zero writes, no cursor or band-stats persistence. Gate discovery is
|
|
322
|
+
* reported on every run (pairs_discovered / pairs_discovered_unlinked, #394) —
|
|
323
|
+
* on a dry-run they are the sizing numbers (pairs_evaluated stays 0: no calls
|
|
324
|
+
* are ever made).
|
|
286
325
|
*/
|
|
287
326
|
export declare function stageReconsolidation(db: Database.Database, llm: LlmClient, budget: StageBudget, embedFn: EmbedFn, dryRun: boolean, stateDir: string | undefined, options?: ReconsolidationOptions): Promise<ReconsolidationStageResult>;
|
|
288
327
|
export interface RollbackResult {
|
package/dist/reconsolidation.js
CHANGED
|
@@ -71,7 +71,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
71
71
|
};
|
|
72
72
|
})();
|
|
73
73
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
74
|
-
exports.absorbTrigger = exports.DEMOTED_STATUSES = exports.FOOTER_HEAD_MAX_CHARS = exports.DEFAULT_CORRECTION_REWRITE_MIN_CONFIDENCE = exports.DEFAULT_CORRECTION_MIN_SIMILARITY = exports.RECONSOLIDATION_STAGE_LABEL = void 0;
|
|
74
|
+
exports.absorbTrigger = exports.DEMOTED_STATUSES = exports.FOOTER_HEAD_MAX_CHARS = exports.DEFAULT_RECONSOLIDATION_MAX_CALLS = exports.DEFAULT_RECONSOLIDATION_MAX_MINUTES = exports.DEFAULT_CORRECTION_REWRITE_MIN_CONFIDENCE = exports.DEFAULT_CORRECTION_MIN_SIMILARITY = exports.RECONSOLIDATION_STAGE_LABEL = void 0;
|
|
75
75
|
exports.isFactShapedTarget = isFactShapedTarget;
|
|
76
76
|
exports.buildCorrectionVerdictPrompt = buildCorrectionVerdictPrompt;
|
|
77
77
|
exports.parseCorrectionVerdict = parseCorrectionVerdict;
|
|
@@ -115,6 +115,31 @@ exports.DEFAULT_CORRECTION_MIN_SIMILARITY = 0.75;
|
|
|
115
115
|
* weak rewrite is corruption.
|
|
116
116
|
*/
|
|
117
117
|
exports.DEFAULT_CORRECTION_REWRITE_MIN_CONFIDENCE = 0.8;
|
|
118
|
+
/**
|
|
119
|
+
* Default wall-clock bound for the stage, in minutes (#401). Checked at the
|
|
120
|
+
* top of the candidate scan loop (and before each rewrite contract call); on
|
|
121
|
+
* expiry the scan breaks cleanly at the last fully-considered candidate and
|
|
122
|
+
* the next run resumes from the persisted cursor. 120 sits safely under any
|
|
123
|
+
* sane process-level nightly timeout. 0 disables the bound. Invalid →
|
|
124
|
+
* default.
|
|
125
|
+
*/
|
|
126
|
+
exports.DEFAULT_RECONSOLIDATION_MAX_MINUTES = 120;
|
|
127
|
+
/**
|
|
128
|
+
* Default per-run classify-call ceiling for the stage (#401) — the
|
|
129
|
+
* supersessionMaxCalls pattern with a NON-ZERO default ON PURPOSE: that
|
|
130
|
+
* knob's 0=unlimited default is what let the first full-corpus pass grow
|
|
131
|
+
* unbounded. Counts EVERY classify-tier call the stage makes (mark
|
|
132
|
+
* verifications, pair verdicts, rewrite contracts). 0 disables the cap.
|
|
133
|
+
* Invalid → default.
|
|
134
|
+
*/
|
|
135
|
+
exports.DEFAULT_RECONSOLIDATION_MAX_CALLS = 600;
|
|
136
|
+
/**
|
|
137
|
+
* Candidates between mid-scan cursor persists (#401). The cursor also
|
|
138
|
+
* persists at EVERY scan-loop exit path (deadline, call/budget cap,
|
|
139
|
+
* discovery failure), so a killed run loses at most K-1 candidates of scan
|
|
140
|
+
* progress instead of the whole night.
|
|
141
|
+
*/
|
|
142
|
+
const RECONSOLIDATION_CURSOR_PERSIST_EVERY = 50;
|
|
118
143
|
/** Neighbor pool size before older/similarity filtering narrows to top 5 (supersession mirror). */
|
|
119
144
|
const CORRECTION_NEIGHBOR_POOL = 15;
|
|
120
145
|
/** Older-neighbor pairs kept per candidate after filtering (supersession mirror). */
|
|
@@ -501,7 +526,10 @@ async function classifyPair(llm, oldContent, newContent) {
|
|
|
501
526
|
* silently dropped by the cursor passing it).
|
|
502
527
|
*
|
|
503
528
|
* Dry-run: the zone's discovery + the free idempotency check only — zero LLM
|
|
504
|
-
* calls, zero writes, no cursor or band-stats persistence.
|
|
529
|
+
* calls, zero writes, no cursor or band-stats persistence. Gate discovery is
|
|
530
|
+
* reported on every run (pairs_discovered / pairs_discovered_unlinked, #394) —
|
|
531
|
+
* on a dry-run they are the sizing numbers (pairs_evaluated stays 0: no calls
|
|
532
|
+
* are ever made).
|
|
505
533
|
*/
|
|
506
534
|
async function stageReconsolidation(db, llm, budget, embedFn, dryRun, stateDir, options = {}) {
|
|
507
535
|
// Config values pass through `unknown`-typed JSON — validate, never trust.
|
|
@@ -513,6 +541,13 @@ async function stageReconsolidation(db, llm, budget, embedFn, dryRun, stateDir,
|
|
|
513
541
|
const rewriteMinConfidence = validNumber(options.rewriteMinConfidence, exports.DEFAULT_CORRECTION_REWRITE_MIN_CONFIDENCE, (n) => n > 0 && n <= 1);
|
|
514
542
|
const autoMergeThreshold = validNumber(options.autoMergeThreshold, dedup_js_1.DEFAULT_DEDUP_MERGE_THRESHOLD, (n) => n > 0 && n <= 1);
|
|
515
543
|
const maxMerges = validNumber(options.maxMerges, dedup_js_1.DEFAULT_DEDUP_NIGHTLY_MAX_MERGES, (n) => n >= 0);
|
|
544
|
+
const maxMinutes = validNumber(options.maxMinutes, exports.DEFAULT_RECONSOLIDATION_MAX_MINUTES, (n) => n >= 0);
|
|
545
|
+
const maxCalls = validNumber(options.maxCalls, exports.DEFAULT_RECONSOLIDATION_MAX_CALLS, (n) => n >= 0);
|
|
546
|
+
// ---- #401 runtime bounds. The wall-clock deadline is measured from stage
|
|
547
|
+
// start (the deterministic zone's runtime counts against it — the binding
|
|
548
|
+
// constraint must be THIS knob, never the process-level backstop).
|
|
549
|
+
const deadlineAt = maxMinutes > 0 ? Date.now() + Math.round(maxMinutes * 60_000) : Infinity;
|
|
550
|
+
const deadlineHit = () => Date.now() >= deadlineAt;
|
|
516
551
|
// ---- #392 phase 0: the deterministic merge zone (pairs >= the ceiling),
|
|
517
552
|
// LLM-free and budget-free — an LLM-less night still drains duplicates. Its
|
|
518
553
|
// own short lock window, pre-merge backup, and pacing cap; fail-soft, never
|
|
@@ -548,6 +583,8 @@ async function stageReconsolidation(db, llm, budget, embedFn, dryRun, stateDir,
|
|
|
548
583
|
.all(startCursor);
|
|
549
584
|
let scanned = 0;
|
|
550
585
|
let pairsEvaluated = 0;
|
|
586
|
+
let pairsDiscovered = 0; // #394: gate discovery — counted before any skip/judgment
|
|
587
|
+
let pairsDiscoveredUnlinked = 0;
|
|
551
588
|
let rewritten = 0;
|
|
552
589
|
let absorbed = 0;
|
|
553
590
|
let keptLinked = 0;
|
|
@@ -576,6 +613,25 @@ async function stageReconsolidation(db, llm, budget, embedFn, dryRun, stateDir,
|
|
|
576
613
|
storage.addLink(db, oldId, newId, relationship, strength);
|
|
577
614
|
linksCreatedThisRun.add(`${oldId}|${newId}`);
|
|
578
615
|
};
|
|
616
|
+
// ---- #401: mid-scan cursor persistence. Called at EVERY scan-loop exit
|
|
617
|
+
// path (deadline, call/budget cap, mark-verify budget stop, discovery
|
|
618
|
+
// failure) plus a every-K batch tick, so a killed run loses at most K-1
|
|
619
|
+
// candidates of scan progress instead of the whole night. The end-of-stage
|
|
620
|
+
// updateState below stays the authoritative final write (it also applies
|
|
621
|
+
// the pendingMinRowid hold — pendingMinRowid is only ever set AFTER the
|
|
622
|
+
// scan loop, so the raw cursor is the effective cursor at every call site
|
|
623
|
+
// here). updateState is load→mutate→temp-rename atomic.
|
|
624
|
+
let scanBatch = 0;
|
|
625
|
+
let callsUsed = 0;
|
|
626
|
+
let deadlineStopped = false;
|
|
627
|
+
let callCapStopped = false;
|
|
628
|
+
const persistCursor = () => {
|
|
629
|
+
if (dryRun)
|
|
630
|
+
return;
|
|
631
|
+
(0, state_js_1.updateState)((s) => {
|
|
632
|
+
s.reconsolidationCursor = cursor;
|
|
633
|
+
}, stateDir);
|
|
634
|
+
};
|
|
579
635
|
const groups = new Map();
|
|
580
636
|
const addTrigger = (target, trigger, confidence, cosine, explicit) => {
|
|
581
637
|
let group = groups.get(target.id);
|
|
@@ -595,16 +651,39 @@ async function stageReconsolidation(db, llm, budget, embedFn, dryRun, stateDir,
|
|
|
595
651
|
}
|
|
596
652
|
};
|
|
597
653
|
for (const candidate of rows) {
|
|
598
|
-
|
|
654
|
+
// #401: runtime bounds first — exit cleanly at the last fully-considered
|
|
655
|
+
// candidate boundary (cursor = the previous candidate's rowid here).
|
|
656
|
+
if (!dryRun && deadlineHit()) {
|
|
657
|
+
deadlineStopped = true;
|
|
658
|
+
persistCursor();
|
|
659
|
+
break;
|
|
660
|
+
}
|
|
661
|
+
if (!dryRun && budget.exhausted) {
|
|
662
|
+
persistCursor();
|
|
663
|
+
break;
|
|
664
|
+
}
|
|
665
|
+
// #401: the per-stage call cap stops the scan at the candidate boundary
|
|
666
|
+
// (the in-loop check below is the mid-candidate backstop — supersession
|
|
667
|
+
// mirrors both).
|
|
668
|
+
if (!dryRun && maxCalls > 0 && callsUsed >= maxCalls) {
|
|
669
|
+
callCapStopped = true;
|
|
670
|
+
persistCursor();
|
|
599
671
|
break;
|
|
672
|
+
}
|
|
600
673
|
scanned++;
|
|
601
674
|
// ---- AC7: verify incoming explicit marks (corrected_by/superseded_by
|
|
602
675
|
// links targeting this candidate) before they can join a rewrite group.
|
|
676
|
+
// #401: only OPERATOR marks are verified — applyExplicitMark writes
|
|
677
|
+
// strength 1.0, while every stage-created link carries a measured cosine
|
|
678
|
+
// strength < 1 (markLink sites + supersession). Without the filter, every
|
|
679
|
+
// prior night's stage output re-entered verification: a self-sustaining
|
|
680
|
+
// backlog that re-litigated settled verdicts forever.
|
|
603
681
|
if (!dryRun) {
|
|
604
682
|
const incoming = db
|
|
605
683
|
.prepare(`SELECT source_id, relationship FROM memory_links
|
|
606
684
|
WHERE target_id = ? AND source_id != ?
|
|
607
|
-
AND relationship IN ('corrected_by', 'superseded_by')
|
|
685
|
+
AND relationship IN ('corrected_by', 'superseded_by')
|
|
686
|
+
AND strength >= 1.0`)
|
|
608
687
|
.all(candidate.id, candidate.id);
|
|
609
688
|
let markBudgetStop = false;
|
|
610
689
|
for (const mark of incoming) {
|
|
@@ -621,6 +700,7 @@ async function stageReconsolidation(db, llm, budget, embedFn, dryRun, stateDir,
|
|
|
621
700
|
}
|
|
622
701
|
const { verdict, usage } = await classifyPair(llm, target.content, candidate.content);
|
|
623
702
|
budget.recordUsage(exports.RECONSOLIDATION_STAGE_LABEL, usage);
|
|
703
|
+
callsUsed++; // #401
|
|
624
704
|
pairsEvaluated++;
|
|
625
705
|
if (!verdict) {
|
|
626
706
|
skippedInfra++; // mark retained; the neighborhood is revisited via newer candidacies
|
|
@@ -639,8 +719,10 @@ async function stageReconsolidation(db, llm, budget, embedFn, dryRun, stateDir,
|
|
|
639
719
|
`(verdict ${verdict.action}, confidence ${verdict.confidence.toFixed(2)}) — mark retained`);
|
|
640
720
|
}
|
|
641
721
|
}
|
|
642
|
-
if (markBudgetStop)
|
|
722
|
+
if (markBudgetStop) {
|
|
723
|
+
persistCursor(); // #401: this candidate's remaining marks re-verify next run
|
|
643
724
|
break;
|
|
725
|
+
}
|
|
644
726
|
}
|
|
645
727
|
// ---- AC2: detection pairs against older KNN neighbors.
|
|
646
728
|
let neighbors;
|
|
@@ -650,13 +732,16 @@ async function stageReconsolidation(db, llm, budget, embedFn, dryRun, stateDir,
|
|
|
650
732
|
catch (err) {
|
|
651
733
|
console.warn(`[hicortex] reconsolidation: discovery failed for ${candidate.id.slice(0, 8)} — ${err instanceof Error ? err.message : String(err)}`);
|
|
652
734
|
cursor = candidate.__rowid;
|
|
735
|
+
persistCursor(); // #401: every exit path persists
|
|
653
736
|
continue;
|
|
654
737
|
}
|
|
655
738
|
for (const neighbor of neighbors) {
|
|
739
|
+
pairsDiscovered++; // every neighbor passed the floor gate
|
|
656
740
|
if (alreadyResolutionLinked(db, neighbor.id, candidate.id)) {
|
|
657
741
|
skippedIdempotent++;
|
|
658
742
|
continue;
|
|
659
743
|
}
|
|
744
|
+
pairsDiscoveredUnlinked++; // still unlinked — the actionable candidate
|
|
660
745
|
// #392: pairs at/above the ceiling belong to the deterministic zone —
|
|
661
746
|
// counted here, never LLM-judged (the zone merges them or holds them
|
|
662
747
|
// for its cap; re-detection is structural, not cursor-based).
|
|
@@ -667,10 +752,16 @@ async function stageReconsolidation(db, llm, budget, embedFn, dryRun, stateDir,
|
|
|
667
752
|
}
|
|
668
753
|
if (dryRun)
|
|
669
754
|
continue; // preview only — no LLM call, no write
|
|
670
|
-
|
|
755
|
+
// #401: the per-stage call cap rides the same boundary as the budget —
|
|
756
|
+
// supersession-stage pattern (consolidate.ts stageSupersession).
|
|
757
|
+
if ((maxCalls > 0 && callsUsed >= maxCalls) || !budget.use(exports.RECONSOLIDATION_STAGE_LABEL)) {
|
|
758
|
+
callCapStopped = maxCalls > 0 && callsUsed >= maxCalls;
|
|
759
|
+
persistCursor(); // cursor still points at the last fully-considered candidate
|
|
671
760
|
break;
|
|
761
|
+
}
|
|
672
762
|
const { verdict, usage } = await classifyPair(llm, neighbor.content, candidate.content);
|
|
673
763
|
budget.recordUsage(exports.RECONSOLIDATION_STAGE_LABEL, usage);
|
|
764
|
+
callsUsed++; // #401
|
|
674
765
|
pairsEvaluated++;
|
|
675
766
|
if (!verdict) {
|
|
676
767
|
skippedInfra++;
|
|
@@ -726,6 +817,20 @@ async function stageReconsolidation(db, llm, budget, embedFn, dryRun, stateDir,
|
|
|
726
817
|
// verdict "none" → nothing to do
|
|
727
818
|
}
|
|
728
819
|
cursor = candidate.__rowid;
|
|
820
|
+
// #401: batch-tick persistence every K candidates — a kill between exit
|
|
821
|
+
// paths loses at most K-1 candidates of scan progress.
|
|
822
|
+
if (!dryRun && ++scanBatch >= RECONSOLIDATION_CURSOR_PERSIST_EVERY) {
|
|
823
|
+
persistCursor();
|
|
824
|
+
scanBatch = 0;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
if (deadlineStopped) {
|
|
828
|
+
console.log(`[hicortex] Reconsolidation: wall-clock deadline reached (reconsolidationMaxMinutes) — ` +
|
|
829
|
+
`scan stopped at cursor ${cursor}; the next run resumes from there`);
|
|
830
|
+
}
|
|
831
|
+
else if (callCapStopped) {
|
|
832
|
+
console.log(`[hicortex] Reconsolidation: per-run call cap reached (reconsolidationMaxCalls) — ` +
|
|
833
|
+
`scan stopped at cursor ${cursor}; the next run resumes from there`);
|
|
729
834
|
}
|
|
730
835
|
// ---- #392 judged-merge phase: apply the queued pair merges through the
|
|
731
836
|
// dedup core (mergeMemoryIds — same canonical pick, link re-points,
|
|
@@ -838,6 +943,19 @@ async function stageReconsolidation(db, llm, budget, embedFn, dryRun, stateDir,
|
|
|
838
943
|
};
|
|
839
944
|
if (!dryRun && groups.size > 0) {
|
|
840
945
|
for (const group of groups.values()) {
|
|
946
|
+
// #401: the bounds stop the rewrite phase too — a group whose rewrite
|
|
947
|
+
// call was never made is left untouched and holds the cursor (never
|
|
948
|
+
// partially applied), exactly like the budget-exhausted path below.
|
|
949
|
+
if (deadlineHit()) {
|
|
950
|
+
deadlineStopped = true;
|
|
951
|
+
deferFrom(group.targetId);
|
|
952
|
+
break;
|
|
953
|
+
}
|
|
954
|
+
if (maxCalls > 0 && callsUsed >= maxCalls) {
|
|
955
|
+
callCapStopped = true;
|
|
956
|
+
deferFrom(group.targetId);
|
|
957
|
+
break;
|
|
958
|
+
}
|
|
841
959
|
if (!budget.use(exports.RECONSOLIDATION_STAGE_LABEL)) {
|
|
842
960
|
deferFrom(group.targetId);
|
|
843
961
|
break;
|
|
@@ -849,6 +967,7 @@ async function stageReconsolidation(db, llm, budget, embedFn, dryRun, stateDir,
|
|
|
849
967
|
const r = await llm.completeClassify(buildRewritePrompt(group.target.content, triggersArg));
|
|
850
968
|
contract = parseRewriteReply(r.text, group.triggers.map((t) => t.id), group.target.content);
|
|
851
969
|
budget.recordUsage(exports.RECONSOLIDATION_STAGE_LABEL, r.usage);
|
|
970
|
+
callsUsed++; // #401: rewrite contracts count toward the stage call cap
|
|
852
971
|
}
|
|
853
972
|
catch {
|
|
854
973
|
infraError = true;
|
|
@@ -957,6 +1076,8 @@ async function stageReconsolidation(db, llm, budget, embedFn, dryRun, stateDir,
|
|
|
957
1076
|
for (const [label, stat] of runBands)
|
|
958
1077
|
bandStats[label] = stat;
|
|
959
1078
|
if (!dryRun) {
|
|
1079
|
+
// #401: the authoritative FINAL cursor write — the mid-scan persists
|
|
1080
|
+
// above are checkpoints; this one also applies the pendingMinRowid hold.
|
|
960
1081
|
(0, state_js_1.updateState)((s) => {
|
|
961
1082
|
s.reconsolidationCursor = cursor;
|
|
962
1083
|
// Cumulative judged-band accumulation (#392) — the zone already
|
|
@@ -984,6 +1105,8 @@ async function stageReconsolidation(db, llm, budget, embedFn, dryRun, stateDir,
|
|
|
984
1105
|
return {
|
|
985
1106
|
scanned,
|
|
986
1107
|
pairs_evaluated: pairsEvaluated,
|
|
1108
|
+
pairs_discovered: pairsDiscovered,
|
|
1109
|
+
pairs_discovered_unlinked: pairsDiscoveredUnlinked,
|
|
987
1110
|
rewritten,
|
|
988
1111
|
absorbed,
|
|
989
1112
|
kept_linked: keptLinked,
|
package/dist/types.d.ts
CHANGED
|
@@ -224,6 +224,16 @@ export interface ConsolidationReport {
|
|
|
224
224
|
scanned: number;
|
|
225
225
|
/** Pairs actually sent to the verdict LLM (detection + explicit-mark verification). */
|
|
226
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;
|
|
227
237
|
/** Targets rewritten in place this run (one history row each). */
|
|
228
238
|
rewritten: number;
|
|
229
239
|
/** Triggers absorbed (invisible to recall: vector + FTS dropped). */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gamaze/hicortex",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.6",
|
|
4
4
|
"description": "Persistent agent identity for AI agents \u2014 a hand-edited identity layer, nightly-distilled experience, and lessons injected every session, shared across your whole fleet. Works with Hermes, OpenClaw, Claude Code, Pi, and opencode.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|