@gamaze/hicortex 0.16.6 → 0.16.7
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/config-read.d.ts +6 -0
- package/dist/config-read.js +15 -0
- package/dist/llm.d.ts +3 -1
- package/dist/llm.js +25 -20
- package/dist/types.d.ts +9 -5
- package/package.json +1 -1
package/dist/config-read.d.ts
CHANGED
|
@@ -23,3 +23,9 @@ export declare function readPositiveConfig(config: Record<string, unknown>, key:
|
|
|
23
23
|
* treated as truthy.
|
|
24
24
|
*/
|
|
25
25
|
export declare function readStrictBoolean(config: Record<string, unknown>, key: string): boolean | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Read a non-negative finite number (allows 0, unlike readPositiveConfig).
|
|
28
|
+
* Returns `def` when absent OR invalid. Used for keys where 0 is a valid "off"
|
|
29
|
+
* value (e.g. ollamaFlushEvery).
|
|
30
|
+
*/
|
|
31
|
+
export declare function readNonNegativeConfig(config: Record<string, unknown>, key: string, def: number): number;
|
package/dist/config-read.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
16
|
exports.readPositiveConfig = readPositiveConfig;
|
|
17
17
|
exports.readStrictBoolean = readStrictBoolean;
|
|
18
|
+
exports.readNonNegativeConfig = readNonNegativeConfig;
|
|
18
19
|
/**
|
|
19
20
|
* Read a positive finite number from a config object. Returns `def` when the
|
|
20
21
|
* key is absent OR present-but-invalid (with a warn in the latter case).
|
|
@@ -43,3 +44,17 @@ function readStrictBoolean(config, key) {
|
|
|
43
44
|
console.warn(`[hicortex] config "${key}" = ${String(v)} is not a boolean — ignored.`);
|
|
44
45
|
return undefined;
|
|
45
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Read a non-negative finite number (allows 0, unlike readPositiveConfig).
|
|
49
|
+
* Returns `def` when absent OR invalid. Used for keys where 0 is a valid "off"
|
|
50
|
+
* value (e.g. ollamaFlushEvery).
|
|
51
|
+
*/
|
|
52
|
+
function readNonNegativeConfig(config, key, def) {
|
|
53
|
+
const v = config[key];
|
|
54
|
+
if (v === undefined)
|
|
55
|
+
return def;
|
|
56
|
+
if (typeof v === "number" && Number.isFinite(v) && v >= 0)
|
|
57
|
+
return v;
|
|
58
|
+
console.warn(`[hicortex] config "${key}" = ${String(v)} is not a non-negative finite number — using default ${def}.`);
|
|
59
|
+
return def;
|
|
60
|
+
}
|
package/dist/llm.d.ts
CHANGED
|
@@ -271,7 +271,9 @@ export declare class LlmClient {
|
|
|
271
271
|
* runner exits + releases its per-request RSS growth, then wait for the release
|
|
272
272
|
* before the next call reloads fresh. The runner takes >90 s to exit after
|
|
273
273
|
* keep_alive:0 (measured), so the wait is generous (ollamaFlushWaitMs, default
|
|
274
|
-
* 180 s).
|
|
274
|
+
* 180 s). Logs the flush so the wait is distinguishable from a hang. If the
|
|
275
|
+
* unload request fails (ollama down), the wait is skipped — no dead time for a
|
|
276
|
+
* release that can't have happened. See #229 review.
|
|
275
277
|
*/
|
|
276
278
|
private flushOllama;
|
|
277
279
|
/**
|
package/dist/llm.js
CHANGED
|
@@ -212,7 +212,7 @@ function applyTierTuningOverlay(llmConfig, savedConfig) {
|
|
|
212
212
|
llmConfig.numCtx = (0, config_read_js_1.readPositiveConfig)(savedConfig, "numCtx", 2048);
|
|
213
213
|
}
|
|
214
214
|
if (savedConfig.ollamaFlushEvery !== undefined) {
|
|
215
|
-
llmConfig.ollamaFlushEvery = (0, config_read_js_1.
|
|
215
|
+
llmConfig.ollamaFlushEvery = (0, config_read_js_1.readNonNegativeConfig)(savedConfig, "ollamaFlushEvery", 0);
|
|
216
216
|
}
|
|
217
217
|
if (savedConfig.ollamaFlushWaitMs !== undefined) {
|
|
218
218
|
llmConfig.ollamaFlushWaitMs = (0, config_read_js_1.readPositiveConfig)(savedConfig, "ollamaFlushWaitMs", 180000);
|
|
@@ -553,7 +553,20 @@ class LlmClient {
|
|
|
553
553
|
// so ONLY this tier gets the smaller context window — the heavy tiers keep 32768
|
|
554
554
|
// (preserves detectChunkSize's chunk-sizing). thinking is not threaded (scoring is
|
|
555
555
|
// excluded from the thinking toggle; ollama is think:false regardless).
|
|
556
|
-
|
|
556
|
+
const result = await this.complete(this.config.model, prompt, tokens, 600_000, undefined, this.config.numCtx ?? 2048);
|
|
557
|
+
// Periodic flush: scoped to scoring (the fast tier) only — heavy tiers
|
|
558
|
+
// (distill/reflect/classify) don't flush, avoiding a multi-minute pause
|
|
559
|
+
// mid-distillation + unloading the wrong model. Override-routed tiers never
|
|
560
|
+
// reach here (completeWithOverride builds a fresh client). See #229 review.
|
|
561
|
+
const flushEvery = this.config.ollamaFlushEvery ?? 0;
|
|
562
|
+
if (this.config.provider === "ollama" && flushEvery > 0) {
|
|
563
|
+
this.ollamaCallCount++;
|
|
564
|
+
if (this.ollamaCallCount >= flushEvery) {
|
|
565
|
+
await this.flushOllama(this.config.model);
|
|
566
|
+
this.ollamaCallCount = 0;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return result;
|
|
557
570
|
}
|
|
558
571
|
/**
|
|
559
572
|
* Reflect-tier completion (nightly reflection, needs reasoning).
|
|
@@ -746,20 +759,6 @@ class LlmClient {
|
|
|
746
759
|
catch { /* skip malformed lines */ }
|
|
747
760
|
}
|
|
748
761
|
}
|
|
749
|
-
// Flush ollama's accumulated memory every N calls. ollama's runner RSS grows
|
|
750
|
-
// ~171 MB/call and isn't freed between requests — on RAM-constrained boxes
|
|
751
|
-
// this swap-thrashes long consolidations. After the Nth call, unload the
|
|
752
|
-
// model (keep_alive:0) + wait for the runner to exit + release, so the next
|
|
753
|
-
// call reloads fresh (low RSS) instead of accumulating to thrash. Opt-in via
|
|
754
|
-
// ollamaFlushEvery (0 = off).
|
|
755
|
-
const flushEvery = this.config.ollamaFlushEvery ?? 0;
|
|
756
|
-
if (flushEvery > 0) {
|
|
757
|
-
this.ollamaCallCount++;
|
|
758
|
-
if (this.ollamaCallCount >= flushEvery) {
|
|
759
|
-
await this.flushOllama(model);
|
|
760
|
-
this.ollamaCallCount = 0;
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
762
|
return result.trim();
|
|
764
763
|
}
|
|
765
764
|
/**
|
|
@@ -767,10 +766,14 @@ class LlmClient {
|
|
|
767
766
|
* runner exits + releases its per-request RSS growth, then wait for the release
|
|
768
767
|
* before the next call reloads fresh. The runner takes >90 s to exit after
|
|
769
768
|
* keep_alive:0 (measured), so the wait is generous (ollamaFlushWaitMs, default
|
|
770
|
-
* 180 s).
|
|
769
|
+
* 180 s). Logs the flush so the wait is distinguishable from a hang. If the
|
|
770
|
+
* unload request fails (ollama down), the wait is skipped — no dead time for a
|
|
771
|
+
* release that can't have happened. See #229 review.
|
|
771
772
|
*/
|
|
772
773
|
async flushOllama(model) {
|
|
773
774
|
const url = `${this.config.baseUrl.replace(/\/$/, "")}/api/generate`;
|
|
775
|
+
const waitMs = this.config.ollamaFlushWaitMs ?? 180_000;
|
|
776
|
+
console.log(`[hicortex] ollama flush: unloading ${model} after ${this.ollamaCallCount} scoring calls, waiting ${waitMs / 1000}s for memory release…`);
|
|
774
777
|
try {
|
|
775
778
|
await fetch(url, {
|
|
776
779
|
method: "POST",
|
|
@@ -778,10 +781,12 @@ class LlmClient {
|
|
|
778
781
|
body: JSON.stringify({ model, keep_alive: 0 }),
|
|
779
782
|
signal: AbortSignal.timeout(60_000),
|
|
780
783
|
});
|
|
784
|
+
await new Promise((r) => setTimeout(r, waitMs));
|
|
785
|
+
console.log(`[hicortex] ollama flush: complete`);
|
|
786
|
+
}
|
|
787
|
+
catch {
|
|
788
|
+
console.warn(`[hicortex] ollama flush: unload request failed (ollama unreachable?) — skipping wait`);
|
|
781
789
|
}
|
|
782
|
-
catch { /* best-effort flush */ }
|
|
783
|
-
const waitMs = this.config.ollamaFlushWaitMs ?? 180_000;
|
|
784
|
-
await new Promise((r) => setTimeout(r, waitMs));
|
|
785
790
|
}
|
|
786
791
|
/**
|
|
787
792
|
* Anthropic Messages API (/v1/messages).
|
package/dist/types.d.ts
CHANGED
|
@@ -314,13 +314,17 @@ export interface HicortexConfig {
|
|
|
314
314
|
*/
|
|
315
315
|
numCtx?: number;
|
|
316
316
|
/**
|
|
317
|
-
* Flush ollama's accumulated memory every N
|
|
317
|
+
* Flush ollama's accumulated memory every N scoring calls — workaround for
|
|
318
318
|
* ollama's per-request memory growth (the runner's RSS climbs ~171 MB/call and
|
|
319
319
|
* isn't freed between requests), which swap-thrashes RAM-constrained boxes
|
|
320
|
-
* during long consolidations. Default 0 (off). When >0, every Nth
|
|
321
|
-
* triggers a `keep_alive:0` unload + an `ollamaFlushWaitMs`
|
|
322
|
-
* runner to exit + release, then the next call reloads fresh.
|
|
323
|
-
* cycle at ~2.5 GB.
|
|
320
|
+
* during long consolidations. Default 0 (off). When >0, every Nth scoring call
|
|
321
|
+
* (`completeFast`) triggers a `keep_alive:0` unload + an `ollamaFlushWaitMs`
|
|
322
|
+
* pause for the runner to exit + release, then the next call reloads fresh.
|
|
323
|
+
* N=15 caps a cycle at ~2.5 GB. Scoped to the fast tier (scoring) only. Note:
|
|
324
|
+
* N counts **logical** scoring calls, not raw HTTP requests — `complete()`
|
|
325
|
+
* retries up to 4× on timeout, so under retry pressure the actual accumulation
|
|
326
|
+
* may be up to 4×N calls' worth. In practice the flush prevents the thrash that
|
|
327
|
+
* causes retries, keeping the count accurate.
|
|
324
328
|
*/
|
|
325
329
|
ollamaFlushEvery?: number;
|
|
326
330
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gamaze/hicortex",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.7",
|
|
4
4
|
"description": "Self-learning memory for AI agents — experience captured automatically, distilled into lessons overnight, shared across your whole fleet. Works with Hermes, OpenClaw, Claude Code, and Pi.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|