@gamaze/hicortex 0.16.5 → 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 +15 -0
- package/dist/llm.js +48 -1
- package/dist/types.d.ts +21 -0
- 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
|
@@ -31,6 +31,10 @@ export interface LlmConfig {
|
|
|
31
31
|
enableThinking?: boolean;
|
|
32
32
|
/** Context window for the ollama fast tier (completeOllama). Falls back to 2048. */
|
|
33
33
|
numCtx?: number;
|
|
34
|
+
/** Flush ollama memory every N ollama calls (0 = off). See HicortexConfig.ollamaFlushEvery. */
|
|
35
|
+
ollamaFlushEvery?: number;
|
|
36
|
+
/** Ms to wait after an ollama flush for the runner to release. */
|
|
37
|
+
ollamaFlushWaitMs?: number;
|
|
34
38
|
/** Optional separate endpoint for reflect-tier LLM (e.g. remote Ollama with larger model). */
|
|
35
39
|
reflectBaseUrl?: string;
|
|
36
40
|
reflectApiKey?: string;
|
|
@@ -212,6 +216,7 @@ export declare class RateLimitError extends Error {
|
|
|
212
216
|
}
|
|
213
217
|
export declare class LlmClient {
|
|
214
218
|
private config;
|
|
219
|
+
private ollamaCallCount;
|
|
215
220
|
constructor(config: LlmConfig);
|
|
216
221
|
/** Endpoint identity for shared rate-limit state (provider + base URL). */
|
|
217
222
|
private get endpointKey();
|
|
@@ -261,6 +266,16 @@ export declare class LlmClient {
|
|
|
261
266
|
* Ollama: use /api/generate with think:false (important for qwen3.5 models).
|
|
262
267
|
*/
|
|
263
268
|
private completeOllama;
|
|
269
|
+
/**
|
|
270
|
+
* Flush ollama's accumulated memory: unload the model (keep_alive:0) so the
|
|
271
|
+
* runner exits + releases its per-request RSS growth, then wait for the release
|
|
272
|
+
* before the next call reloads fresh. The runner takes >90 s to exit after
|
|
273
|
+
* keep_alive:0 (measured), so the wait is generous (ollamaFlushWaitMs, default
|
|
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.
|
|
277
|
+
*/
|
|
278
|
+
private flushOllama;
|
|
264
279
|
/**
|
|
265
280
|
* Anthropic Messages API (/v1/messages).
|
|
266
281
|
* Auth via x-api-key header.
|
package/dist/llm.js
CHANGED
|
@@ -211,6 +211,12 @@ function applyTierTuningOverlay(llmConfig, savedConfig) {
|
|
|
211
211
|
if (savedConfig.numCtx !== undefined) {
|
|
212
212
|
llmConfig.numCtx = (0, config_read_js_1.readPositiveConfig)(savedConfig, "numCtx", 2048);
|
|
213
213
|
}
|
|
214
|
+
if (savedConfig.ollamaFlushEvery !== undefined) {
|
|
215
|
+
llmConfig.ollamaFlushEvery = (0, config_read_js_1.readNonNegativeConfig)(savedConfig, "ollamaFlushEvery", 0);
|
|
216
|
+
}
|
|
217
|
+
if (savedConfig.ollamaFlushWaitMs !== undefined) {
|
|
218
|
+
llmConfig.ollamaFlushWaitMs = (0, config_read_js_1.readPositiveConfig)(savedConfig, "ollamaFlushWaitMs", 180000);
|
|
219
|
+
}
|
|
214
220
|
}
|
|
215
221
|
/**
|
|
216
222
|
* Resolve an LlmConfig from a saved ~/.hicortex/config.json object.
|
|
@@ -511,6 +517,7 @@ exports.RateLimitError = RateLimitError;
|
|
|
511
517
|
const rateLimitedUntilByEndpoint = new Map();
|
|
512
518
|
class LlmClient {
|
|
513
519
|
config;
|
|
520
|
+
ollamaCallCount = 0;
|
|
514
521
|
constructor(config) {
|
|
515
522
|
this.config = config;
|
|
516
523
|
}
|
|
@@ -546,7 +553,20 @@ class LlmClient {
|
|
|
546
553
|
// so ONLY this tier gets the smaller context window — the heavy tiers keep 32768
|
|
547
554
|
// (preserves detectChunkSize's chunk-sizing). thinking is not threaded (scoring is
|
|
548
555
|
// excluded from the thinking toggle; ollama is think:false regardless).
|
|
549
|
-
|
|
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;
|
|
550
570
|
}
|
|
551
571
|
/**
|
|
552
572
|
* Reflect-tier completion (nightly reflection, needs reasoning).
|
|
@@ -741,6 +761,33 @@ class LlmClient {
|
|
|
741
761
|
}
|
|
742
762
|
return result.trim();
|
|
743
763
|
}
|
|
764
|
+
/**
|
|
765
|
+
* Flush ollama's accumulated memory: unload the model (keep_alive:0) so the
|
|
766
|
+
* runner exits + releases its per-request RSS growth, then wait for the release
|
|
767
|
+
* before the next call reloads fresh. The runner takes >90 s to exit after
|
|
768
|
+
* keep_alive:0 (measured), so the wait is generous (ollamaFlushWaitMs, default
|
|
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.
|
|
772
|
+
*/
|
|
773
|
+
async flushOllama(model) {
|
|
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…`);
|
|
777
|
+
try {
|
|
778
|
+
await fetch(url, {
|
|
779
|
+
method: "POST",
|
|
780
|
+
headers: { "Content-Type": "application/json" },
|
|
781
|
+
body: JSON.stringify({ model, keep_alive: 0 }),
|
|
782
|
+
signal: AbortSignal.timeout(60_000),
|
|
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`);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
744
791
|
/**
|
|
745
792
|
* Anthropic Messages API (/v1/messages).
|
|
746
793
|
* Auth via x-api-key header.
|
package/dist/types.d.ts
CHANGED
|
@@ -313,6 +313,27 @@ export interface HicortexConfig {
|
|
|
313
313
|
* scoring prompt actually needs more.
|
|
314
314
|
*/
|
|
315
315
|
numCtx?: number;
|
|
316
|
+
/**
|
|
317
|
+
* Flush ollama's accumulated memory every N scoring calls — workaround for
|
|
318
|
+
* ollama's per-request memory growth (the runner's RSS climbs ~171 MB/call and
|
|
319
|
+
* isn't freed between requests), which swap-thrashes RAM-constrained boxes
|
|
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.
|
|
328
|
+
*/
|
|
329
|
+
ollamaFlushEvery?: number;
|
|
330
|
+
/**
|
|
331
|
+
* Milliseconds to wait after an ollama flush (`keep_alive:0`) for the runner
|
|
332
|
+
* to exit + release its accumulated memory before the next call reloads.
|
|
333
|
+
* Default 180000 (3 min — the runner takes >90 s to exit after keep_alive:0;
|
|
334
|
+
* doubled for margin). Only relevant when `ollamaFlushEvery` > 0.
|
|
335
|
+
*/
|
|
336
|
+
ollamaFlushWaitMs?: number;
|
|
316
337
|
}
|
|
317
338
|
/** A config-owned life-sphere domain (see HicortexConfig.domains). */
|
|
318
339
|
export interface DomainDef {
|
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": {
|