@gamaze/hicortex 0.16.4 → 0.16.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/llm.d.ts +15 -0
- package/dist/llm.js +68 -10
- package/dist/types.d.ts +27 -0
- package/package.json +1 -1
package/dist/llm.d.ts
CHANGED
|
@@ -29,6 +29,12 @@ export interface LlmConfig {
|
|
|
29
29
|
maxTokens?: number;
|
|
30
30
|
/** Toggle thinking on the openai-compat path for the heavy phases. Absent = no kwarg sent. */
|
|
31
31
|
enableThinking?: boolean;
|
|
32
|
+
/** Context window for the ollama fast tier (completeOllama). Falls back to 2048. */
|
|
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;
|
|
32
38
|
/** Optional separate endpoint for reflect-tier LLM (e.g. remote Ollama with larger model). */
|
|
33
39
|
reflectBaseUrl?: string;
|
|
34
40
|
reflectApiKey?: string;
|
|
@@ -210,6 +216,7 @@ export declare class RateLimitError extends Error {
|
|
|
210
216
|
}
|
|
211
217
|
export declare class LlmClient {
|
|
212
218
|
private config;
|
|
219
|
+
private ollamaCallCount;
|
|
213
220
|
constructor(config: LlmConfig);
|
|
214
221
|
/** Endpoint identity for shared rate-limit state (provider + base URL). */
|
|
215
222
|
private get endpointKey();
|
|
@@ -259,6 +266,14 @@ export declare class LlmClient {
|
|
|
259
266
|
* Ollama: use /api/generate with think:false (important for qwen3.5 models).
|
|
260
267
|
*/
|
|
261
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). Best-effort — a flush failure just means no release this cycle.
|
|
275
|
+
*/
|
|
276
|
+
private flushOllama;
|
|
262
277
|
/**
|
|
263
278
|
* Anthropic Messages API (/v1/messages).
|
|
264
279
|
* Auth via x-api-key header.
|
package/dist/llm.js
CHANGED
|
@@ -208,6 +208,15 @@ function applyTierTuningOverlay(llmConfig, savedConfig) {
|
|
|
208
208
|
if (thinking !== undefined) {
|
|
209
209
|
llmConfig.enableThinking = thinking;
|
|
210
210
|
}
|
|
211
|
+
if (savedConfig.numCtx !== undefined) {
|
|
212
|
+
llmConfig.numCtx = (0, config_read_js_1.readPositiveConfig)(savedConfig, "numCtx", 2048);
|
|
213
|
+
}
|
|
214
|
+
if (savedConfig.ollamaFlushEvery !== undefined) {
|
|
215
|
+
llmConfig.ollamaFlushEvery = (0, config_read_js_1.readPositiveConfig)(savedConfig, "ollamaFlushEvery", 0);
|
|
216
|
+
}
|
|
217
|
+
if (savedConfig.ollamaFlushWaitMs !== undefined) {
|
|
218
|
+
llmConfig.ollamaFlushWaitMs = (0, config_read_js_1.readPositiveConfig)(savedConfig, "ollamaFlushWaitMs", 180000);
|
|
219
|
+
}
|
|
211
220
|
}
|
|
212
221
|
/**
|
|
213
222
|
* Resolve an LlmConfig from a saved ~/.hicortex/config.json object.
|
|
@@ -508,6 +517,7 @@ exports.RateLimitError = RateLimitError;
|
|
|
508
517
|
const rateLimitedUntilByEndpoint = new Map();
|
|
509
518
|
class LlmClient {
|
|
510
519
|
config;
|
|
520
|
+
ollamaCallCount = 0;
|
|
511
521
|
constructor(config) {
|
|
512
522
|
this.config = config;
|
|
513
523
|
}
|
|
@@ -539,10 +549,11 @@ class LlmClient {
|
|
|
539
549
|
*/
|
|
540
550
|
async completeFast(prompt, maxTokens) {
|
|
541
551
|
const tokens = maxTokens ?? this.config.maxTokens ?? 2048;
|
|
542
|
-
//
|
|
543
|
-
//
|
|
544
|
-
//
|
|
545
|
-
|
|
552
|
+
// Scoring is the fast tier: numCtx (default 2048, ~850-token prompts) is threaded
|
|
553
|
+
// so ONLY this tier gets the smaller context window — the heavy tiers keep 32768
|
|
554
|
+
// (preserves detectChunkSize's chunk-sizing). thinking is not threaded (scoring is
|
|
555
|
+
// excluded from the thinking toggle; ollama is think:false regardless).
|
|
556
|
+
return this.complete(this.config.model, prompt, tokens, 600_000, undefined, this.config.numCtx ?? 2048);
|
|
546
557
|
}
|
|
547
558
|
/**
|
|
548
559
|
* Reflect-tier completion (nightly reflection, needs reasoning).
|
|
@@ -608,9 +619,14 @@ class LlmClient {
|
|
|
608
619
|
apiKey,
|
|
609
620
|
provider,
|
|
610
621
|
});
|
|
622
|
+
// numCtx intentionally NOT forwarded here: only the heavy tiers route through
|
|
623
|
+
// completeWithOverride (there is no fastBaseUrl/fast-tier override), and they
|
|
624
|
+
// must land on completeOllama's 32768 default to preserve detectChunkSize. If a
|
|
625
|
+
// fast-tier override is ever added, thread numCtx here too — else the fast tier
|
|
626
|
+
// would silently revert to 32768 (the original bug, reintroduced).
|
|
611
627
|
return tempClient.complete(model, prompt, maxTokens, timeoutMs, thinking);
|
|
612
628
|
}
|
|
613
|
-
async complete(model, prompt, maxTokens, timeoutMs, thinking) {
|
|
629
|
+
async complete(model, prompt, maxTokens, timeoutMs, thinking, numCtx) {
|
|
614
630
|
if (this.isRateLimited) {
|
|
615
631
|
throw new RateLimitError(this.rateLimitedUntil - Date.now());
|
|
616
632
|
}
|
|
@@ -618,7 +634,7 @@ class LlmClient {
|
|
|
618
634
|
let lastErr;
|
|
619
635
|
for (let attempt = 0; attempt <= retryDelays.length; attempt++) {
|
|
620
636
|
try {
|
|
621
|
-
return await this.completeOnce(model, prompt, maxTokens, timeoutMs, thinking);
|
|
637
|
+
return await this.completeOnce(model, prompt, maxTokens, timeoutMs, thinking, numCtx);
|
|
622
638
|
}
|
|
623
639
|
catch (err) {
|
|
624
640
|
lastErr = err instanceof Error ? err : new Error(String(err));
|
|
@@ -635,12 +651,12 @@ class LlmClient {
|
|
|
635
651
|
}
|
|
636
652
|
throw lastErr;
|
|
637
653
|
}
|
|
638
|
-
async completeOnce(model, prompt, maxTokens, timeoutMs, thinking) {
|
|
654
|
+
async completeOnce(model, prompt, maxTokens, timeoutMs, thinking, numCtx) {
|
|
639
655
|
if (this.config.provider === "claude-cli") {
|
|
640
656
|
return this.completeClaude(model, prompt, timeoutMs);
|
|
641
657
|
}
|
|
642
658
|
if (this.config.provider === "ollama") {
|
|
643
|
-
return this.completeOllama(model, prompt, maxTokens, timeoutMs);
|
|
659
|
+
return this.completeOllama(model, prompt, maxTokens, timeoutMs, numCtx);
|
|
644
660
|
}
|
|
645
661
|
if (this.config.provider === "anthropic") {
|
|
646
662
|
return this.completeAnthropic(model, prompt, maxTokens, timeoutMs);
|
|
@@ -676,7 +692,7 @@ class LlmClient {
|
|
|
676
692
|
/**
|
|
677
693
|
* Ollama: use /api/generate with think:false (important for qwen3.5 models).
|
|
678
694
|
*/
|
|
679
|
-
async completeOllama(model, prompt, maxTokens, timeoutMs) {
|
|
695
|
+
async completeOllama(model, prompt, maxTokens, timeoutMs, numCtx) {
|
|
680
696
|
const url = `${this.config.baseUrl.replace(/\/$/, "")}/api/generate`;
|
|
681
697
|
// Ollama can take minutes to process large contexts — use streaming to avoid
|
|
682
698
|
// Node.js fetch headers timeout (default ~300s kills long Ollama inferences)
|
|
@@ -688,7 +704,14 @@ class LlmClient {
|
|
|
688
704
|
prompt,
|
|
689
705
|
stream: true,
|
|
690
706
|
think: false,
|
|
691
|
-
|
|
707
|
+
// num_ctx: threaded from completeFast (the fast/scoring tier) via the numCtx
|
|
708
|
+
// param. The fast tier passes the numCtx config key (default 2048; scoring
|
|
709
|
+
// prompts are ~850 tokens) to minimize the KV/prompt-cache footprint that, at
|
|
710
|
+
// 32768, accumulated past available RAM on memory-constrained boxes during long
|
|
711
|
+
// consolidations and swap-thrashed the nightly. The heavy tiers
|
|
712
|
+
// (completeDistill/Reflect/Classify) do NOT pass numCtx → default 32768, which
|
|
713
|
+
// preserves detectChunkSize's chunk-sizing (it packs ~60% of context per chunk).
|
|
714
|
+
options: { num_predict: maxTokens, num_ctx: numCtx ?? 32768 },
|
|
692
715
|
}),
|
|
693
716
|
signal: AbortSignal.timeout(timeoutMs),
|
|
694
717
|
});
|
|
@@ -723,8 +746,43 @@ class LlmClient {
|
|
|
723
746
|
catch { /* skip malformed lines */ }
|
|
724
747
|
}
|
|
725
748
|
}
|
|
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
|
+
}
|
|
726
763
|
return result.trim();
|
|
727
764
|
}
|
|
765
|
+
/**
|
|
766
|
+
* Flush ollama's accumulated memory: unload the model (keep_alive:0) so the
|
|
767
|
+
* runner exits + releases its per-request RSS growth, then wait for the release
|
|
768
|
+
* before the next call reloads fresh. The runner takes >90 s to exit after
|
|
769
|
+
* keep_alive:0 (measured), so the wait is generous (ollamaFlushWaitMs, default
|
|
770
|
+
* 180 s). Best-effort — a flush failure just means no release this cycle.
|
|
771
|
+
*/
|
|
772
|
+
async flushOllama(model) {
|
|
773
|
+
const url = `${this.config.baseUrl.replace(/\/$/, "")}/api/generate`;
|
|
774
|
+
try {
|
|
775
|
+
await fetch(url, {
|
|
776
|
+
method: "POST",
|
|
777
|
+
headers: { "Content-Type": "application/json" },
|
|
778
|
+
body: JSON.stringify({ model, keep_alive: 0 }),
|
|
779
|
+
signal: AbortSignal.timeout(60_000),
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
catch { /* best-effort flush */ }
|
|
783
|
+
const waitMs = this.config.ollamaFlushWaitMs ?? 180_000;
|
|
784
|
+
await new Promise((r) => setTimeout(r, waitMs));
|
|
785
|
+
}
|
|
728
786
|
/**
|
|
729
787
|
* Anthropic Messages API (/v1/messages).
|
|
730
788
|
* Auth via x-api-key header.
|
package/dist/types.d.ts
CHANGED
|
@@ -303,6 +303,33 @@ export interface HicortexConfig {
|
|
|
303
303
|
* See #220.
|
|
304
304
|
*/
|
|
305
305
|
enableThinking?: boolean;
|
|
306
|
+
/**
|
|
307
|
+
* Context window for the ollama FAST tier (importance scoring via `completeFast`).
|
|
308
|
+
* Default 2048. Scoring prompts are ~850 tokens, so 2048 fits with headroom. The
|
|
309
|
+
* prior hardcoded 32768 — still the default for the heavy distill/reflect/classify
|
|
310
|
+
* tiers (which need it for `detectChunkSize`'s chunk-sizing) — made the KV-cache +
|
|
311
|
+
* prompt-cache accumulate past available RAM on memory-constrained boxes during long
|
|
312
|
+
* consolidations and swap-thrash the nightly. Applies to scoring only; raise if a
|
|
313
|
+
* scoring prompt actually needs more.
|
|
314
|
+
*/
|
|
315
|
+
numCtx?: number;
|
|
316
|
+
/**
|
|
317
|
+
* Flush ollama's accumulated memory every N ollama 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 ollama call
|
|
321
|
+
* triggers a `keep_alive:0` unload + an `ollamaFlushWaitMs` pause for the
|
|
322
|
+
* runner to exit + release, then the next call reloads fresh. N=15 caps a
|
|
323
|
+
* cycle at ~2.5 GB. Opt-in — set e.g. 15 on constrained boxes.
|
|
324
|
+
*/
|
|
325
|
+
ollamaFlushEvery?: number;
|
|
326
|
+
/**
|
|
327
|
+
* Milliseconds to wait after an ollama flush (`keep_alive:0`) for the runner
|
|
328
|
+
* to exit + release its accumulated memory before the next call reloads.
|
|
329
|
+
* Default 180000 (3 min — the runner takes >90 s to exit after keep_alive:0;
|
|
330
|
+
* doubled for margin). Only relevant when `ollamaFlushEvery` > 0.
|
|
331
|
+
*/
|
|
332
|
+
ollamaFlushWaitMs?: number;
|
|
306
333
|
}
|
|
307
334
|
/** A config-owned life-sphere domain (see HicortexConfig.domains). */
|
|
308
335
|
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.6",
|
|
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": {
|