@gamaze/hicortex 0.20.7 → 0.20.9
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 +10 -41
- package/dist/calibration.d.ts +174 -0
- package/dist/calibration.js +231 -0
- package/dist/capture.d.ts +15 -3
- package/dist/capture.js +10 -1
- package/dist/classify-domains.d.ts +6 -0
- package/dist/classify-domains.js +7 -1
- package/dist/cli.js +2 -3
- package/dist/config-read.d.ts +1 -1
- package/dist/config-read.js +96 -9
- package/dist/consolidate.d.ts +79 -68
- package/dist/consolidate.js +218 -174
- package/dist/dashboard.d.ts +4 -3
- package/dist/dedup.d.ts +34 -26
- package/dist/dedup.js +91 -57
- package/dist/distiller.js +1 -1
- package/dist/domain-classify.d.ts +7 -6
- package/dist/domain-classify.js +12 -10
- package/dist/eval/decay-eval.d.ts +3 -3
- package/dist/eval/decay-eval.js +4 -4
- package/dist/eval/planted-eval.d.ts +26 -0
- package/dist/eval/planted-eval.js +97 -0
- package/dist/eval/planted-fixtures.d.ts +107 -0
- package/dist/eval/planted-fixtures.js +283 -0
- package/dist/eval/planted-harness.d.ts +176 -0
- package/dist/eval/planted-harness.js +649 -0
- package/dist/index.js +4 -3
- package/dist/init.d.ts +9 -3
- package/dist/init.js +52 -9
- package/dist/llm.d.ts +43 -58
- package/dist/llm.js +87 -101
- package/dist/mcp-server.js +29 -29
- package/dist/nightly.js +105 -103
- package/dist/nofit.d.ts +4 -11
- package/dist/nofit.js +6 -23
- package/dist/recall-index.d.ts +30 -28
- package/dist/recall-index.js +21 -18
- package/dist/recall-registry.d.ts +2 -1
- package/dist/recall-registry.js +35 -1
- package/dist/reconsolidation.d.ts +124 -72
- package/dist/reconsolidation.js +359 -148
- package/dist/relink.js +3 -4
- package/dist/retrieval.d.ts +68 -35
- package/dist/retrieval.js +292 -104
- package/dist/run-deadline.d.ts +62 -0
- package/dist/run-deadline.js +73 -0
- package/dist/schema-prototypes.d.ts +3 -3
- package/dist/schema-prototypes.js +3 -3
- package/dist/state.d.ts +2 -3
- package/dist/storage.d.ts +16 -16
- package/dist/storage.js +62 -24
- package/dist/telemetry.d.ts +8 -7
- package/dist/token-budget.js +3 -4
- package/dist/type-classify.js +4 -4
- package/dist/types.d.ts +95 -155
- package/domains.example.json +4 -5
- package/hermes-plugin/hicortex/README.md +2 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -1
- package/pi-extension/hicortex/README.md +1 -1
- package/server.json +3 -3
package/dist/mcp-server.js
CHANGED
|
@@ -76,6 +76,7 @@ const viz_js_1 = require("./viz.js");
|
|
|
76
76
|
const dashboard_js_1 = require("./dashboard.js");
|
|
77
77
|
const identity_store_js_1 = require("./identity-store.js");
|
|
78
78
|
const retrieval = __importStar(require("./retrieval.js"));
|
|
79
|
+
const CALIBRATION = __importStar(require("./calibration.js"));
|
|
79
80
|
const recall_registry_js_1 = require("./recall-registry.js");
|
|
80
81
|
const memory_instructions_js_1 = require("./memory-instructions.js");
|
|
81
82
|
const recall_index_js_1 = require("./recall-index.js");
|
|
@@ -180,7 +181,7 @@ function createMcpServer() {
|
|
|
180
181
|
// -- hicortex_search --
|
|
181
182
|
server.tool("hicortex_search", "Search shared long-term memory (all agents, all sessions). CALL THIS BEFORE assuming, guessing, or asking the user about anything that may have come up before: prior decisions, preferences, project facts, people, hardware, past incidents. If you are about to write 'I don't have information about…', search first.", {
|
|
182
183
|
query: zod_1.z.string().describe("Search query text"),
|
|
183
|
-
limit: zod_1.z.coerce.number().optional().describe("Max results (default: server
|
|
184
|
+
limit: zod_1.z.coerce.number().optional().describe("Max results (default: the server default)"),
|
|
184
185
|
project: zod_1.z.string().optional().describe("Filter by project name"),
|
|
185
186
|
}, async ({ query, limit, project }) => {
|
|
186
187
|
if (!db)
|
|
@@ -214,7 +215,7 @@ function createMcpServer() {
|
|
|
214
215
|
// -- hicortex_recent --
|
|
215
216
|
server.tool("hicortex_recent", "Get recent memories, optionally filtered by project. CALL THIS AT THE START of substantive work on a project to catch up on its latest state — cheaper than asking the user what happened.", {
|
|
216
217
|
project: zod_1.z.string().optional().describe("Filter by project name"),
|
|
217
|
-
limit: zod_1.z.coerce.number().optional().describe("Max results (default: server
|
|
218
|
+
limit: zod_1.z.coerce.number().optional().describe("Max results (default: the server default)"),
|
|
218
219
|
}, async ({ project, limit }) => {
|
|
219
220
|
if (!db)
|
|
220
221
|
return { content: [{ type: "text", text: "Hicortex not initialized" }], isError: true };
|
|
@@ -674,9 +675,10 @@ async function startServer(options = {}) {
|
|
|
674
675
|
});
|
|
675
676
|
}
|
|
676
677
|
if (llmConfig) {
|
|
677
|
-
// Tuning (#220: maxTokens + enableThinking
|
|
678
|
-
//
|
|
679
|
-
// the nightly
|
|
678
|
+
// Tuning (#220: maxTokens + enableThinking; #408: the diagnostic env tier
|
|
679
|
+
// for numCtx + ollama flush), validated + resolved via the shared overlay
|
|
680
|
+
// (also applied in resolveSavedLlmConfig for the nightly — the ONE
|
|
681
|
+
// resolution point per process). Wrong-typed values warn + drop.
|
|
680
682
|
(0, llm_js_1.applyTierTuningOverlay)(llmConfig, savedConfig);
|
|
681
683
|
llm = new llm_js_1.LlmClient(llmConfig);
|
|
682
684
|
console.log(`[hicortex] LLM (one model, all phases): ${llmConfig.provider}/${llmConfig.model}`);
|
|
@@ -749,34 +751,29 @@ async function startServer(options = {}) {
|
|
|
749
751
|
console.warn("[hicortex] Config uses the legacy 'contextAgents' key — renamed to 'identityAgents' in 0.18 (#264). " +
|
|
750
752
|
"The legacy key still works; update your config to silence this warning.");
|
|
751
753
|
}
|
|
752
|
-
// #
|
|
753
|
-
//
|
|
754
|
-
//
|
|
755
|
-
retrieval.
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
const
|
|
759
|
-
|
|
754
|
+
// #408: recall/decay calibration is RELEASE-MANAGED (calibration.ts) — no
|
|
755
|
+
// config keys are read here anymore. The plain configure*() calls pin this
|
|
756
|
+
// process to the shipped constants (they remain the eval/test seam; see
|
|
757
|
+
// retrieval.ts). The boot log still prints the active values — now sourced
|
|
758
|
+
// from the calibration constants via the configure*() returns.
|
|
759
|
+
retrieval.configureDecay();
|
|
760
|
+
const recallCfg = retrieval.configureRecall();
|
|
761
|
+
const scoringCfg = retrieval.configureScoring();
|
|
762
|
+
const sessionIntentCfg = retrieval.configureSessionIntent();
|
|
763
|
+
console.log(`[hicortex] Recall (release-managed calibration): k=${recallCfg.searchLimit}/recent=${recallCfg.recentLimit}` +
|
|
760
764
|
`/window=${recallCfg.recentWindowDays}d/cold=${recallCfg.coldExposureSlots}` +
|
|
761
|
-
`/novelty=${
|
|
765
|
+
`/novelty=${CALIBRATION.NOVELTY_FLOOR_SLOTS}` +
|
|
762
766
|
` · ` +
|
|
763
767
|
`score sim=${scoringCfg.similarity}/str=${scoringCfg.strength}/conn=${scoringCfg.connections}` +
|
|
764
768
|
`/rec=${scoringCfg.recency}, fresh=${scoringCfg.freshnessBoostWeight}@${scoringCfg.freshnessBoostDays}d, ` +
|
|
765
769
|
`superseded×${scoringCfg.supersededDemotion}` +
|
|
766
770
|
`, intent w=${sessionIntentCfg.weight}` +
|
|
767
771
|
(sessionIntentCfg.weight === 0 ? " (disabled)" : ""));
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
maxItems: savedConfig?.recallMaxItems,
|
|
774
|
-
minPromptLength: savedConfig?.recallMinPromptChars,
|
|
775
|
-
titleChars: savedConfig?.recallTitleChars,
|
|
776
|
-
// #324 novelty floor: slots of recallMaxItems guaranteed to the
|
|
777
|
-
// pure-prompt (unblended) search's top passing hit(s). 0 disables.
|
|
778
|
-
noveltyFloorSlots: savedConfig?.noveltyFloorSlots,
|
|
779
|
-
};
|
|
772
|
+
// Defaults resolve inside recall-index.ts / recall-registry.ts from the
|
|
773
|
+
// calibration constants (#408) — the options objects stay as the seams the
|
|
774
|
+
// eval injects through.
|
|
775
|
+
recallRegistry = new recall_registry_js_1.SessionRecallRegistry();
|
|
776
|
+
recallIndexOptions = {};
|
|
780
777
|
memoryInstructionsEnabled = savedConfig?.memoryInstructions !== false;
|
|
781
778
|
if (resolvedAgents.dropped.length > 0) {
|
|
782
779
|
console.warn(`[hicortex] Ignoring invalid identityAgents entries: ${resolvedAgents.dropped.join(", ")} ` +
|
|
@@ -1002,7 +999,8 @@ async function startServer(options = {}) {
|
|
|
1002
999
|
res.status(400).json({ error: "Missing 'query'" });
|
|
1003
1000
|
return;
|
|
1004
1001
|
}
|
|
1005
|
-
// No hardcoded default: absent limit →
|
|
1002
|
+
// No hardcoded default: absent limit → the release-managed calibration
|
|
1003
|
+
// default (#408 — was the searchLimit config key).
|
|
1006
1004
|
const limit = req.query.limit ? Number(req.query.limit) : undefined;
|
|
1007
1005
|
const project = typeof req.query.project === "string" && req.query.project ? req.query.project : undefined;
|
|
1008
1006
|
// 0.16.x: `privacy` query param is ACCEPTED for backward compat (old
|
|
@@ -1074,7 +1072,8 @@ async function startServer(options = {}) {
|
|
|
1074
1072
|
return;
|
|
1075
1073
|
}
|
|
1076
1074
|
const project = typeof req.query.project === "string" && req.query.project ? req.query.project : undefined;
|
|
1077
|
-
// No hardcoded default: absent limit →
|
|
1075
|
+
// No hardcoded default: absent limit → the release-managed calibration
|
|
1076
|
+
// default (#408 — was the recentLimit config key).
|
|
1078
1077
|
// 0.16.x: `privacy` query param accepted but ignored (vestigial column).
|
|
1079
1078
|
const limit = req.query.limit ? Number(req.query.limit) : undefined;
|
|
1080
1079
|
warnDeprecatedPrivacyParamIfPresent(req.query, "recent");
|
|
@@ -1789,7 +1788,8 @@ function readConfigFile(stateDir) {
|
|
|
1789
1788
|
}
|
|
1790
1789
|
catch (e) {
|
|
1791
1790
|
console.warn(`[hicortex] ${configPath} exists but could not be parsed — server booting degraded ` +
|
|
1792
|
-
`(config-driven LLM
|
|
1791
|
+
`(config-driven LLM knobs and agentId self-heal will not apply; decay/recall ` +
|
|
1792
|
+
`calibration is release-managed and unaffected). ` +
|
|
1793
1793
|
`Fix the JSON and restart. Cause: ${e instanceof Error ? e.message : String(e)}`);
|
|
1794
1794
|
return null;
|
|
1795
1795
|
}
|
package/dist/nightly.js
CHANGED
|
@@ -64,7 +64,6 @@ const embedder_js_1 = require("./embedder.js");
|
|
|
64
64
|
const storage = __importStar(require("./storage.js"));
|
|
65
65
|
const consolidate_js_1 = require("./consolidate.js");
|
|
66
66
|
const domain_classify_js_1 = require("./domain-classify.js");
|
|
67
|
-
const nofit_js_1 = require("./nofit.js");
|
|
68
67
|
const transcript_reader_js_1 = require("./transcript-reader.js");
|
|
69
68
|
const hermes_transcript_reader_js_1 = require("./hermes-transcript-reader.js");
|
|
70
69
|
const pi_transcript_reader_js_1 = require("./pi-transcript-reader.js");
|
|
@@ -76,6 +75,7 @@ const state_js_1 = require("./state.js");
|
|
|
76
75
|
const identity_store_js_1 = require("./identity-store.js");
|
|
77
76
|
const capture_cursors_js_1 = require("./capture-cursors.js");
|
|
78
77
|
const capture_js_1 = require("./capture.js");
|
|
78
|
+
const run_deadline_js_1 = require("./run-deadline.js");
|
|
79
79
|
const dashboard_js_1 = require("./dashboard.js");
|
|
80
80
|
const telemetry_js_1 = require("./telemetry.js");
|
|
81
81
|
const init_js_1 = require("./init.js");
|
|
@@ -167,7 +167,7 @@ function computeSince(stateDir, recaptureWindowDays, now = new Date()) {
|
|
|
167
167
|
}
|
|
168
168
|
/** POST /distill transport for server mode — localhost. Sends authToken so
|
|
169
169
|
* self-capture works regardless of the localhost-bypass marker (#271 root-cause fix). */
|
|
170
|
-
function makeLocalPost(port, authToken) {
|
|
170
|
+
function makeLocalPost(port, authToken, deadline) {
|
|
171
171
|
return async (body) => {
|
|
172
172
|
const resp = await fetch(`http://127.0.0.1:${port}/distill`, {
|
|
173
173
|
method: "POST",
|
|
@@ -177,13 +177,16 @@ function makeLocalPost(port, authToken) {
|
|
|
177
177
|
},
|
|
178
178
|
body: JSON.stringify(body),
|
|
179
179
|
// Synchronous 35B distillation of a large segment can take minutes.
|
|
180
|
-
|
|
180
|
+
// #405: never wait past the run deadline — the capture loop's
|
|
181
|
+
// between-segments check is the clean stop; this is the hard bound
|
|
182
|
+
// for the POST in flight when the deadline expires mid-wait.
|
|
183
|
+
signal: AbortSignal.timeout(postTimeoutMs(deadline)),
|
|
181
184
|
});
|
|
182
185
|
return normalizePostResult(resp);
|
|
183
186
|
};
|
|
184
187
|
}
|
|
185
188
|
/** POST /distill transport for client mode — remote URL + optional bearer token. */
|
|
186
|
-
function makeRemotePost(serverUrl, authToken) {
|
|
189
|
+
function makeRemotePost(serverUrl, authToken, deadline) {
|
|
187
190
|
return async (body) => {
|
|
188
191
|
const resp = await fetch(`${serverUrl}/distill`, {
|
|
189
192
|
method: "POST",
|
|
@@ -192,11 +195,20 @@ function makeRemotePost(serverUrl, authToken) {
|
|
|
192
195
|
...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),
|
|
193
196
|
},
|
|
194
197
|
body: JSON.stringify(body),
|
|
195
|
-
signal: AbortSignal.timeout(
|
|
198
|
+
signal: AbortSignal.timeout(postTimeoutMs(deadline)),
|
|
196
199
|
});
|
|
197
200
|
return normalizePostResult(resp);
|
|
198
201
|
};
|
|
199
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* Per-POST timeout: 20 min (synchronous distillation of a large segment can
|
|
205
|
+
* take minutes), clamped to the run deadline's remaining time when one is in
|
|
206
|
+
* force (#405) — a POST launched near expiry never outlives the deadline.
|
|
207
|
+
*/
|
|
208
|
+
function postTimeoutMs(deadline) {
|
|
209
|
+
const CAP_MS = 20 * 60 * 1000;
|
|
210
|
+
return Math.max(1, Math.min(CAP_MS, deadline ? deadline.remainingMs() : Infinity));
|
|
211
|
+
}
|
|
200
212
|
async function normalizePostResult(resp) {
|
|
201
213
|
if (resp.status === 201) {
|
|
202
214
|
const data = (await resp.json().catch(() => ({})));
|
|
@@ -272,9 +284,6 @@ function captureLockWaitMs() {
|
|
|
272
284
|
const env = Number(process.env.HICORTEX_CAPTURE_LOCK_WAIT_MS);
|
|
273
285
|
return Number.isFinite(env) && env >= 0 ? env : CAPTURE_LOCK_WAIT_MS;
|
|
274
286
|
}
|
|
275
|
-
// readPositiveConfig moved to ./config-read.ts (shared with the distill-tier
|
|
276
|
-
// overlay in llm.ts / mcp-server.ts). Validates positive-number config knobs
|
|
277
|
-
// at the disk→runtime boundary with a warn-on-rejected-value.
|
|
278
287
|
const NIGHTLY_LOG_MAX_BYTES = 1024 * 1024; // 1 MB — years of normal runs
|
|
279
288
|
/**
|
|
280
289
|
* Keep ~/.hicortex/nightly.log bounded. The launchd plist and systemd unit
|
|
@@ -320,10 +329,11 @@ function runEvictionOnly(options = {}) {
|
|
|
320
329
|
const stateDir = options.stateDir ?? HICORTEX_HOME;
|
|
321
330
|
const savedConfig = readNightlyConfig(stateDir);
|
|
322
331
|
// Same clock as the full nightly (see runNightly's identical trio) — the
|
|
323
|
-
// eviction ranker reads these module knobs.
|
|
324
|
-
|
|
325
|
-
(0, retrieval_js_1.
|
|
326
|
-
(0, retrieval_js_1.
|
|
332
|
+
// eviction ranker reads these module knobs. #408: calibration constants,
|
|
333
|
+
// never user config — the calls just pin the process to the shipped values.
|
|
334
|
+
(0, retrieval_js_1.configureDecay)();
|
|
335
|
+
(0, retrieval_js_1.configureRecall)();
|
|
336
|
+
(0, retrieval_js_1.configureScoring)();
|
|
327
337
|
const dbPath = (0, db_js_1.resolveDbPath)(options.dbPath);
|
|
328
338
|
console.log(`[hicortex] evict-only run${options.dryRun ? " (dry run)" : ""} — DB: ${dbPath}`);
|
|
329
339
|
const db = (0, db_js_1.initDb)(dbPath);
|
|
@@ -460,16 +470,31 @@ async function runNightly(options = {}) {
|
|
|
460
470
|
}
|
|
461
471
|
const dbPath = (0, db_js_1.resolveDbPath)(options.dbPath);
|
|
462
472
|
// #192: consolidation's decay/prune stage must score with the same clock as
|
|
463
|
-
// the server's retrieval path
|
|
464
|
-
(
|
|
465
|
-
(0, retrieval_js_1.
|
|
466
|
-
(0, retrieval_js_1.
|
|
473
|
+
// the server's retrieval path. #408: that clock is the calibration constant
|
|
474
|
+
// set (calibration.ts) — both processes resolve identically by construction.
|
|
475
|
+
(0, retrieval_js_1.configureDecay)();
|
|
476
|
+
(0, retrieval_js_1.configureRecall)();
|
|
477
|
+
(0, retrieval_js_1.configureScoring)();
|
|
467
478
|
const modeLabel = consolidateOnly ? " (consolidate-only)" : captureOnly ? " (capture-only)" : dryRun ? " (dry run)" : "";
|
|
468
479
|
console.log(`[hicortex] Nightly pipeline starting${modeLabel}`);
|
|
469
480
|
if (captureOnly) {
|
|
470
481
|
console.log(`[hicortex] capture-only run — consolidation skipped`);
|
|
471
482
|
}
|
|
472
483
|
console.log(`[hicortex] DB: ${dbPath}`);
|
|
484
|
+
// #405: the ONE pipeline deadline. Full and consolidate-only runs create
|
|
485
|
+
// it at start and share it across capture + every consolidation stage
|
|
486
|
+
// (checked at safe boundaries; a hit defers cleanly — see run-deadline.ts).
|
|
487
|
+
// Capture-only/watchdog runs do NOT: they keep their 30-min systemd
|
|
488
|
+
// backstop (capture is no-LLM and short). Dry-run is exempt (writes
|
|
489
|
+
// nothing, spends nothing). 0/invalid config → the 240 default — a
|
|
490
|
+
// deadline always exists (the old reconsolidationMaxMinutes 0=off is gone).
|
|
491
|
+
const deadline = !dryRun && !captureOnly
|
|
492
|
+
? (0, run_deadline_js_1.createRunDeadline)((0, run_deadline_js_1.resolveNightlyTimeBudgetMinutes)(savedConfig))
|
|
493
|
+
: undefined;
|
|
494
|
+
if (deadline) {
|
|
495
|
+
console.log(`[hicortex] Nightly time budget: ${Math.round((deadline.deadlineAt - Date.now()) / 60_000)} min ` +
|
|
496
|
+
`(deadline at ${new Date(deadline.deadlineAt).toISOString()})`);
|
|
497
|
+
}
|
|
473
498
|
// Init DB — consolidation reads the DB directly; capture goes via HTTP.
|
|
474
499
|
const db = (0, db_js_1.initDb)(dbPath);
|
|
475
500
|
try {
|
|
@@ -515,8 +540,11 @@ async function runNightly(options = {}) {
|
|
|
515
540
|
else {
|
|
516
541
|
// Full nightly waits out a transient --capture-only overlap (each segment
|
|
517
542
|
// POST can block up to 20 min); capture-only fails fast. dry-run writes
|
|
518
|
-
// nothing so it needs no lock.
|
|
519
|
-
|
|
543
|
+
// nothing so it needs no lock. #405: the wait never outlives the run
|
|
544
|
+
// deadline — waiting the full 30 min with 5 min left would burn the
|
|
545
|
+
// whole pipeline on a lock queue. captureLockWaitMs() keeps its env
|
|
546
|
+
// override (tests); min() clamps to whatever remains.
|
|
547
|
+
const lockWaitMs = Math.max(0, Math.min(captureLockWaitMs(), deadline ? deadline.remainingMs() : Infinity));
|
|
520
548
|
const releaseLock = dryRun
|
|
521
549
|
? (() => { })
|
|
522
550
|
: await (0, capture_js_1.acquireCaptureLock)(stateDir, captureOnly ? 0 : lockWaitMs);
|
|
@@ -581,11 +609,12 @@ async function runNightly(options = {}) {
|
|
|
581
609
|
// source_agent_id / source_domain are per-client provenance from
|
|
582
610
|
// config.json (agentId / sourceDomain) — attribution only, no filtering.
|
|
583
611
|
const result = await (0, capture_js_1.captureBatches)(batches, {
|
|
584
|
-
post: makeLocalPost(port, savedConfig?.authToken),
|
|
612
|
+
post: makeLocalPost(port, savedConfig?.authToken, deadline),
|
|
585
613
|
cursorStore,
|
|
586
614
|
dryRun,
|
|
587
615
|
sourceAgentId: savedConfig?.agentId,
|
|
588
616
|
sourceDomain: savedConfig?.sourceDomain,
|
|
617
|
+
deadline,
|
|
589
618
|
});
|
|
590
619
|
memoriesIngested = result.memoriesIngested;
|
|
591
620
|
distillUsage = result.distillUsage;
|
|
@@ -633,10 +662,12 @@ async function runNightly(options = {}) {
|
|
|
633
662
|
// nothing-to-do short-circuit (zero LLM calls), NOT a failure.
|
|
634
663
|
// "throttled" (#246) = the llmTokensPerMonth fair-use cap was projected to
|
|
635
664
|
// be exceeded, so consolidation was skipped before any LLM call.
|
|
636
|
-
// "endpoint_down" (#337) = the
|
|
637
|
-
//
|
|
638
|
-
//
|
|
639
|
-
//
|
|
665
|
+
// "endpoint_down" (#337) = the LLM circuit breaker was open after the run —
|
|
666
|
+
// transient (retried next run), and NEVER "completed": the stages fail
|
|
667
|
+
// soft, so without this override a dead-endpoint run would report clean.
|
|
668
|
+
// "deferred" (#405) = the run-wide wall-clock deadline fired — at least one
|
|
669
|
+
// stage stopped at a safe boundary and drains next run (lastConsolidated
|
|
670
|
+
// held, exactly like endpoint_down).
|
|
640
671
|
let consolidationStatus;
|
|
641
672
|
// #246: total consolidation tokens consumed this run (hoisted for telemetry
|
|
642
673
|
// + the dashboard snapshot). Undefined when consolidation didn't run at all
|
|
@@ -688,12 +719,11 @@ async function runNightly(options = {}) {
|
|
|
688
719
|
// estimate so the next month starts clean.
|
|
689
720
|
if (!dryRun) {
|
|
690
721
|
(0, state_js_1.updateState)((s) => {
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
if (
|
|
695
|
-
|
|
696
|
-
s.llmTokensThisPeriod = { prompt: 0, completion: 0, total: 0, periodStart: now.toISOString() };
|
|
722
|
+
// #405: the ONE monthly-reset staleness helper — shared by
|
|
723
|
+
// shouldThrottleTokens, this file's post-consolidation
|
|
724
|
+
// accrual write, and token-budget.ts recordDistillUsage.
|
|
725
|
+
if ((0, consolidate_js_1.isStaleTokenPeriod)(s.llmTokensThisPeriod?.periodStart)) {
|
|
726
|
+
s.llmTokensThisPeriod = { prompt: 0, completion: 0, total: 0, periodStart: new Date().toISOString() };
|
|
697
727
|
s.llmTokensLastRun = 0;
|
|
698
728
|
console.log("[hicortex] Token fair-use period reset (new month) — throttle cleared.");
|
|
699
729
|
}
|
|
@@ -702,59 +732,41 @@ async function runNightly(options = {}) {
|
|
|
702
732
|
}
|
|
703
733
|
}
|
|
704
734
|
if (consolidationStatus !== "throttled") {
|
|
705
|
-
// #
|
|
706
|
-
//
|
|
707
|
-
//
|
|
708
|
-
//
|
|
709
|
-
//
|
|
710
|
-
//
|
|
711
|
-
//
|
|
712
|
-
//
|
|
713
|
-
|
|
714
|
-
// nightly re-runs 2-4×/day, and capture is unaffected (the daemon's
|
|
715
|
-
// /distill path has its own probe). Zero LLM phases run when the
|
|
716
|
-
// probe fails — one fast failure is the whole cost.
|
|
717
|
-
const probeOk = await llm.probe();
|
|
718
|
-
if (!probeOk) {
|
|
719
|
-
console.error("[hicortex] LLM endpoint not generating — consolidation skipped " +
|
|
720
|
-
"(endpoint_down, will retry next run). See the ops runbook's " +
|
|
721
|
-
"known failure signatures; llmProbeTimeoutMs tunes the probe's patience.");
|
|
722
|
-
consolidationStatus = "endpoint_down";
|
|
723
|
-
}
|
|
724
|
-
else {
|
|
735
|
+
// #405: the pre-consolidation readiness probe gate is DELETED. The
|
|
736
|
+
// breaker's first-fast-failure semantics took over its job: a
|
|
737
|
+
// wedged endpoint costs at most 3 ladder-exhausted logical calls
|
|
738
|
+
// (~31 min at the 900 s ceiling, once per dead night, bounded by
|
|
739
|
+
// the run deadline) before the post-run breakerOpen override below
|
|
740
|
+
// reports endpoint_down. The probe SURVIVES in the daemon's
|
|
741
|
+
// /distill gate (mcp-server.ts resolveDistillProbeGate) — a
|
|
742
|
+
// different process with a different job.
|
|
743
|
+
{
|
|
725
744
|
const cfgDomains = (0, domain_classify_js_1.parseConfigDomains)(savedConfig);
|
|
726
745
|
console.log(`[hicortex] Running consolidation...`);
|
|
727
746
|
const report = await (0, consolidate_js_1.runConsolidation)(db, llm, embedder_js_1.embed, dryRun, false, undefined, {
|
|
728
747
|
domains: cfgDomains,
|
|
729
748
|
contentDomainsReady: true,
|
|
730
|
-
|
|
749
|
+
// #408: weakPrimaryFloor is a release-managed calibration
|
|
750
|
+
// constant now — no config threading; the Options field stays
|
|
751
|
+
// as the eval/test seam.
|
|
731
752
|
}, {
|
|
732
|
-
|
|
733
|
-
|
|
753
|
+
// #405: no supersessionMaxCalls — the ONE run budget is the
|
|
754
|
+
// only call cap. #408: minSimilarity defaults to the
|
|
755
|
+
// calibration constant (seam only).
|
|
734
756
|
},
|
|
735
|
-
// #
|
|
736
|
-
|
|
757
|
+
// #405: the ONE per-run LLM-call ceiling (default 5000;
|
|
758
|
+
// consolidateMaxLlmCalls honored as a deprecated alias).
|
|
759
|
+
(0, consolidate_js_1.resolveNightlyLlmCallBudget)(savedConfig),
|
|
737
760
|
// #245: soft cap on the corpus (default 10000; 0 disables eviction).
|
|
738
761
|
memorySoftCapResolved, {
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
// — the stage defaults to 0.92 / 250.
|
|
748
|
-
autoMergeThreshold: (savedConfig?.dedupAutoMergeThreshold ??
|
|
749
|
-
savedConfig?.dedupMergeThreshold),
|
|
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,
|
|
757
|
-
});
|
|
762
|
+
// #384/#392 reconsolidation knobs — eval/test seams since
|
|
763
|
+
// #408 (release-managed calibration constants; nothing is
|
|
764
|
+
// threaded from config). The stage validates and falls back
|
|
765
|
+
// to its calibration defaults on invalid/absent values.
|
|
766
|
+
},
|
|
767
|
+
// #405: the ONE run-wide deadline — capture and every
|
|
768
|
+
// consolidation stage check this same handle.
|
|
769
|
+
deadline);
|
|
758
770
|
console.log(`[hicortex] Consolidation ${report.status} in ${report.elapsed_seconds}s` +
|
|
759
771
|
(report.stages.reflection ? ` (${report.stages.reflection.lessons_generated} lessons)` : ""));
|
|
760
772
|
consolidationStatus = report.status;
|
|
@@ -812,27 +824,18 @@ async function runNightly(options = {}) {
|
|
|
812
824
|
(0, state_js_1.updateState)((s) => {
|
|
813
825
|
const now = new Date();
|
|
814
826
|
const cur = s.llmTokensThisPeriod;
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
//
|
|
820
|
-
|
|
821
|
-
const
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
periodStart = now.toISOString();
|
|
825
|
-
prompt = 0;
|
|
826
|
-
completion = 0;
|
|
827
|
-
total = 0;
|
|
828
|
-
}
|
|
829
|
-
if (tokensTotal) {
|
|
830
|
-
prompt += tokensTotal.prompt;
|
|
831
|
-
completion += tokensTotal.completion;
|
|
832
|
-
total += tokensTotal.total;
|
|
833
|
-
}
|
|
827
|
+
// Monthly reset — the ONE staleness helper (#405): zero the
|
|
828
|
+
// accrual before adding this run's contribution when
|
|
829
|
+
// periodStart sits in a previous UTC calendar month. Shared
|
|
830
|
+
// with shouldThrottleTokens, this file's throttle-branch
|
|
831
|
+
// reset, and token-budget.ts recordDistillUsage.
|
|
832
|
+
const stale = (0, consolidate_js_1.isStaleTokenPeriod)(cur?.periodStart, now);
|
|
833
|
+
const prompt = (stale ? 0 : cur?.prompt ?? 0) + (tokensTotal?.prompt ?? 0);
|
|
834
|
+
const completion = (stale ? 0 : cur?.completion ?? 0) + (tokensTotal?.completion ?? 0);
|
|
835
|
+
const total = (stale ? 0 : cur?.total ?? 0) + (tokensTotal?.total ?? 0);
|
|
834
836
|
s.llmTokensThisPeriod = {
|
|
835
|
-
prompt, completion, total,
|
|
837
|
+
prompt, completion, total,
|
|
838
|
+
periodStart: stale ? now.toISOString() : cur.periodStart,
|
|
836
839
|
};
|
|
837
840
|
s.llmTokensLastRun = tokensTotal?.total ?? 0;
|
|
838
841
|
}, stateDir);
|
|
@@ -1089,13 +1092,12 @@ async function runClientNightly(config, dryRun, stateDir = HICORTEX_HOME, recapt
|
|
|
1089
1092
|
// the whole run — the pre-flight only needs the link back, which can take
|
|
1090
1093
|
// ~1 min after wake.
|
|
1091
1094
|
//
|
|
1092
|
-
//
|
|
1093
|
-
//
|
|
1094
|
-
//
|
|
1095
|
-
//
|
|
1096
|
-
//
|
|
1097
|
-
//
|
|
1098
|
-
// timeout length helps — the capture watchdog's frequent retry handles that (#239).
|
|
1095
|
+
// #163/#405: constants (the preflightTimeoutMs/preflightAttempts/
|
|
1096
|
+
// preflightRetryGapMs config keys are removed — no incident in the #403
|
|
1097
|
+
// inventory ever required tuning them). 20s per attempt absorbs a slow link
|
|
1098
|
+
// re-establishing after the client wakes (bumped from 15s in 0.17); for a
|
|
1099
|
+
// genuinely DOWN link no timeout length helps — the capture watchdog's
|
|
1100
|
+
// frequent retry handles that (#239).
|
|
1099
1101
|
//
|
|
1100
1102
|
// WALL-CLOCK NOTE: setTimeout and AbortSignal.timeout do NOT advance while
|
|
1101
1103
|
// macOS is asleep, so the ~3m worst case (3×20s + 2×60s) is wall-clock-
|
|
@@ -1103,9 +1105,9 @@ async function runClientNightly(config, dryRun, stateDir = HICORTEX_HOME, recapt
|
|
|
1103
1105
|
// elapsed time can exceed it. Not a defect: the capture lock isn't held
|
|
1104
1106
|
// during the retry and the cursor design is dup-over-loss, so a late success
|
|
1105
1107
|
// is harmless. Just don't treat 3m as a hard wall-clock bound.
|
|
1106
|
-
const PREFLIGHT_TIMEOUT_MS =
|
|
1107
|
-
const PREFLIGHT_ATTEMPTS =
|
|
1108
|
-
const PREFLIGHT_RETRY_GAP_MS =
|
|
1108
|
+
const PREFLIGHT_TIMEOUT_MS = 20_000;
|
|
1109
|
+
const PREFLIGHT_ATTEMPTS = 3;
|
|
1110
|
+
const PREFLIGHT_RETRY_GAP_MS = 60_000;
|
|
1109
1111
|
let reachable = false;
|
|
1110
1112
|
for (let attempt = 1; attempt <= PREFLIGHT_ATTEMPTS; attempt++) {
|
|
1111
1113
|
try {
|
package/dist/nofit.d.ts
CHANGED
|
@@ -54,21 +54,14 @@ import type { DomainDef } from "./types.js";
|
|
|
54
54
|
* Default weak-primary floor: minimum cosine(memory embedding, best domain
|
|
55
55
|
* prototype) for a no-fit memory to earn a weak primary.
|
|
56
56
|
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* ~/.hicortex/config.json.
|
|
57
|
+
* #408: RELEASE-MANAGED — the constant (and its provenance: a starting point
|
|
58
|
+
* for bge-small-en-v1.5 embeddings) lives in calibration.ts. There is no
|
|
59
|
+
* config override anymore; the DomainStageOptions.weakPrimaryFloor field
|
|
60
|
+
* stays as the eval/test seam.
|
|
62
61
|
*/
|
|
63
62
|
export declare const DEFAULT_WEAK_PRIMARY_FLOOR = 0.45;
|
|
64
63
|
/** Halving never takes base_strength below this (survivable, not zeroed). */
|
|
65
64
|
export declare const NO_ASSOCIATION_MIN_STRENGTH = 0.05;
|
|
66
|
-
/**
|
|
67
|
-
* Resolve the weak-primary floor from a raw config object. Accepts a finite
|
|
68
|
-
* number in (0, 1); anything else (absent, wrong type, out of range) falls
|
|
69
|
-
* back to DEFAULT_WEAK_PRIMARY_FLOOR with a warning for invalid values.
|
|
70
|
-
*/
|
|
71
|
-
export declare function resolveWeakPrimaryFloor(config: Record<string, unknown> | null | undefined): number;
|
|
72
65
|
/** Outcome of resolving a no-fit memory against the domain prototypes. */
|
|
73
66
|
export type NoFitResolution = {
|
|
74
67
|
kind: "weak_primary";
|
package/dist/nofit.js
CHANGED
|
@@ -84,41 +84,24 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
84
84
|
})();
|
|
85
85
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
86
86
|
exports.NO_ASSOCIATION_MIN_STRENGTH = exports.DEFAULT_WEAK_PRIMARY_FLOOR = void 0;
|
|
87
|
-
exports.resolveWeakPrimaryFloor = resolveWeakPrimaryFloor;
|
|
88
87
|
exports.resolveNoFit = resolveNoFit;
|
|
89
88
|
exports.applyWeakPrimary = applyWeakPrimary;
|
|
90
89
|
exports.applyNoAssociationDecay = applyNoAssociationDecay;
|
|
91
90
|
const storage = __importStar(require("./storage.js"));
|
|
92
91
|
const schema_prototypes_js_1 = require("./schema-prototypes.js");
|
|
92
|
+
const CALIBRATION = __importStar(require("./calibration.js"));
|
|
93
93
|
/**
|
|
94
94
|
* Default weak-primary floor: minimum cosine(memory embedding, best domain
|
|
95
95
|
* prototype) for a no-fit memory to earn a weak primary.
|
|
96
96
|
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
* ~/.hicortex/config.json.
|
|
97
|
+
* #408: RELEASE-MANAGED — the constant (and its provenance: a starting point
|
|
98
|
+
* for bge-small-en-v1.5 embeddings) lives in calibration.ts. There is no
|
|
99
|
+
* config override anymore; the DomainStageOptions.weakPrimaryFloor field
|
|
100
|
+
* stays as the eval/test seam.
|
|
102
101
|
*/
|
|
103
|
-
exports.DEFAULT_WEAK_PRIMARY_FLOOR =
|
|
102
|
+
exports.DEFAULT_WEAK_PRIMARY_FLOOR = CALIBRATION.WEAK_PRIMARY_FLOOR;
|
|
104
103
|
/** Halving never takes base_strength below this (survivable, not zeroed). */
|
|
105
104
|
exports.NO_ASSOCIATION_MIN_STRENGTH = 0.05;
|
|
106
|
-
/**
|
|
107
|
-
* Resolve the weak-primary floor from a raw config object. Accepts a finite
|
|
108
|
-
* number in (0, 1); anything else (absent, wrong type, out of range) falls
|
|
109
|
-
* back to DEFAULT_WEAK_PRIMARY_FLOOR with a warning for invalid values.
|
|
110
|
-
*/
|
|
111
|
-
function resolveWeakPrimaryFloor(config) {
|
|
112
|
-
const raw = config?.weakPrimaryFloor;
|
|
113
|
-
if (raw === undefined || raw === null)
|
|
114
|
-
return exports.DEFAULT_WEAK_PRIMARY_FLOOR;
|
|
115
|
-
if (typeof raw === "number" && Number.isFinite(raw) && raw > 0 && raw < 1) {
|
|
116
|
-
return raw;
|
|
117
|
-
}
|
|
118
|
-
console.warn(`[hicortex] invalid weakPrimaryFloor in config (${JSON.stringify(raw)}) — ` +
|
|
119
|
-
`must be a number in (0, 1); using default ${exports.DEFAULT_WEAK_PRIMARY_FLOOR}`);
|
|
120
|
-
return exports.DEFAULT_WEAK_PRIMARY_FLOOR;
|
|
121
|
-
}
|
|
122
105
|
/**
|
|
123
106
|
* Decide what happens to a no-fit memory (READ-ONLY — no writes, so callers
|
|
124
107
|
* batching writes into a transaction can decide during the scan phase):
|