@gamaze/hicortex 0.16.7 → 0.16.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.
@@ -76,8 +76,8 @@ const init_js_1 = require("./init.js");
76
76
  // ---------------------------------------------------------------------------
77
77
  let db = null;
78
78
  let llm = null;
79
- // llmConfig is module-level so the /distill handler can call resolveDistillFallback
80
- // without having to read config on every request. null when no LLM is configured.
79
+ // llmConfig is module-level so the /distill handler can read numCtx without
80
+ // re-resolving on every request. null when no LLM is configured.
81
81
  let llmConfig = null;
82
82
  // One-time-per-process deprecation warning for the `?privacy=` query param
83
83
  // (0.16.x: the column is vestigial, never filtered). Old clients/plugins still
@@ -94,9 +94,6 @@ function warnDeprecatedPrivacyParamIfPresent(query, route) {
94
94
  `privacy is no longer filtered server-side (the column is vestigial). ` +
95
95
  `Use a separate Hicortex server for isolation. (This warning fires once per process.)`);
96
96
  }
97
- // distillFallbackMode controls whether a failed remote distill endpoint causes an
98
- // immediate abort ("strict", default) or a fallback to the base model ("local").
99
- let distillFallbackMode = "strict";
100
97
  let stateDir = "";
101
98
  // Resolved contextClients list (spec §2) — the harness names allowed to inject
102
99
  // the standing context layer. Echoed by GET /context so each hook self-gates.
@@ -374,7 +371,8 @@ async function startServer(options = {}) {
374
371
  // Named backends (claude-cli, ollama) → immediate config; everything else
375
372
  // goes through resolveExplicitLlmConfig which requires a user-chosen provider.
376
373
  // If nothing is configured: start recall-only with an unmissable warning.
377
- const savedConfig = (0, llm_js_1.applyModelsBlock)(readConfigFile(stateDir));
374
+ // One model serves all phases (#231) — no per-tier overlay here.
375
+ const savedConfig = readConfigFile(stateDir);
378
376
  // 0.16.2 activation gap: self-heal the agentId provenance field for
379
377
  // pre-0.16.2 server installs on first boot after upgrade. The server's own
380
378
  // nightly captures its sessions to localhost:8787/distill and needs this id;
@@ -402,7 +400,6 @@ async function startServer(options = {}) {
402
400
  baseUrl: savedConfig.llmBaseUrl ?? "http://localhost:11434",
403
401
  apiKey: "",
404
402
  model: savedConfig.llmModel ?? "qwen3.5:4b",
405
- reflectModel: savedConfig.reflectModel ?? savedConfig.llmModel ?? "qwen3.5:4b",
406
403
  provider: "ollama",
407
404
  };
408
405
  }
@@ -411,41 +408,15 @@ async function startServer(options = {}) {
411
408
  llmBaseUrl: savedConfig?.llmBaseUrl,
412
409
  llmApiKey: savedConfig?.llmApiKey,
413
410
  llmModel: savedConfig?.llmModel,
414
- reflectModel: savedConfig?.reflectModel,
415
411
  });
416
412
  }
417
413
  if (llmConfig) {
418
- // Apply optional distill endpoint (e.g. remote Ollama with faster model)
419
- if (savedConfig?.distillModel) {
420
- llmConfig.distillModel = savedConfig.distillModel;
421
- }
422
- if (savedConfig?.distillBaseUrl) {
423
- llmConfig.distillBaseUrl = savedConfig.distillBaseUrl;
424
- llmConfig.distillApiKey = savedConfig.distillApiKey ?? llmConfig.apiKey;
425
- llmConfig.distillProvider = savedConfig.distillProvider ?? llmConfig.provider;
426
- }
427
- // Heavy-phase tuning (#220): maxTokens + enableThinking, validated + copied
428
- // via the shared overlay (also applied in resolveSavedLlmConfig for the
429
- // nightly's reflect + classify). Wrong-typed values warn + drop.
414
+ // Tuning (#220: maxTokens + enableThinking + numCtx + flush), validated +
415
+ // copied via the shared overlay (also applied in resolveSavedLlmConfig for
416
+ // the nightly). Wrong-typed values warn + drop.
430
417
  (0, llm_js_1.applyTierTuningOverlay)(llmConfig, savedConfig);
431
- // Apply separate reflect endpoint if configured (e.g. remote Ollama with larger model)
432
- if (savedConfig?.reflectBaseUrl) {
433
- llmConfig.reflectBaseUrl = savedConfig.reflectBaseUrl;
434
- llmConfig.reflectApiKey = savedConfig.reflectApiKey ?? llmConfig.apiKey;
435
- llmConfig.reflectProvider = savedConfig.reflectProvider ?? llmConfig.provider;
436
- }
437
- // distillFallback: "strict" (default) aborts on remote failure so the session
438
- // is retried next run. "local" restores 0.9.0 fallback-to-base-model behavior.
439
- const df = savedConfig?.distillFallback;
440
- distillFallbackMode = df === "local" ? "local" : "strict";
441
418
  llm = new llm_js_1.LlmClient(llmConfig);
442
- const distillInfo = llmConfig.distillBaseUrl
443
- ? `${llmConfig.distillProvider}/${llmConfig.distillModel}@${llmConfig.distillBaseUrl}`
444
- : llmConfig.distillModel ? llmConfig.distillModel : "";
445
- const reflectInfo = llmConfig.reflectBaseUrl
446
- ? `${llmConfig.reflectProvider}/${llmConfig.reflectModel}@${llmConfig.reflectBaseUrl}`
447
- : llmConfig.reflectModel;
448
- console.log(`[hicortex] LLM fast: ${llmConfig.provider}/${llmConfig.model}${distillInfo ? `, distill: ${distillInfo}` : ""}, reflect: ${reflectInfo}`);
419
+ console.log(`[hicortex] LLM (one model, all phases): ${llmConfig.provider}/${llmConfig.model}`);
449
420
  }
450
421
  else {
451
422
  llm = null;
@@ -884,23 +855,12 @@ async function startServer(options = {}) {
884
855
  return;
885
856
  }
886
857
  }
887
- // Pre-flight the distill endpoint. In strict mode (default) a failed remote
888
- // probe returns "abort" immediately without mutating llmConfig — the nightly
889
- // watermark stays put so the session is re-shipped next run. In "local" mode
890
- // the config is mutated to fall back to the base model.
891
- const cfg = llmConfig;
892
- const distillFallbackStatus = await (0, llm_js_1.resolveDistillFallback)(cfg, distillFallbackMode);
893
- if (distillFallbackStatus === "abort") {
894
- res.status(503).json({ error: "Distill endpoint unavailable — session will be retried next run" });
895
- return;
896
- }
897
858
  // Cache detectChunkSize per endpoint so we probe at most once per server boot.
898
- const effectiveProvider = cfg.distillProvider ?? cfg.provider;
899
- const effectiveModel = cfg.distillModel ?? cfg.model;
900
- const effectiveBaseUrl = cfg.distillBaseUrl ?? cfg.baseUrl;
901
- const cacheKey = `${effectiveProvider}/${effectiveModel}@${effectiveBaseUrl}`;
859
+ // numCtx is passed so chunk size derives from the request's ACTUAL context
860
+ // window (#231, #228) the chunker and the request agree by construction.
861
+ const cacheKey = `${llmConfig.provider}/${llmConfig.model}@${llmConfig.baseUrl}`;
902
862
  if (!chunkSizeCache.has(cacheKey)) {
903
- chunkSizeCache.set(cacheKey, await (0, distiller_js_1.detectChunkSize)(effectiveProvider, effectiveModel, effectiveBaseUrl));
863
+ chunkSizeCache.set(cacheKey, await (0, distiller_js_1.detectChunkSize)(llmConfig.provider, llmConfig.model, llmConfig.baseUrl, llmConfig.numCtx));
904
864
  }
905
865
  const chunkSize = chunkSizeCache.get(cacheKey);
906
866
  const date = typeof session_date === "string" && session_date ? session_date : new Date().toISOString().slice(0, 10);
@@ -18,7 +18,6 @@ const node_os_1 = require("node:os");
18
18
  const node_child_process_1 = require("node:child_process");
19
19
  const db_js_1 = require("./db.js");
20
20
  const state_js_1 = require("./state.js");
21
- const llm_js_1 = require("./llm.js");
22
21
  const HICORTEX_HOME = (0, paths_js_1.hicortexHome)();
23
22
  const CONFIG_PATH = (0, node_path_1.join)(HICORTEX_HOME, "config.json");
24
23
  async function showNightlyStatus() {
@@ -39,7 +38,7 @@ async function showNightlyStatus() {
39
38
  try {
40
39
  // No `?? {}` coercion: a null/invalid parse must fall through to the catch
41
40
  // below (as it did pre-0.13.1) rather than print a fabricated-healthy status.
42
- const config = (0, llm_js_1.applyModelsBlock)(JSON.parse((0, node_fs_1.readFileSync)(CONFIG_PATH, "utf-8")));
41
+ const config = JSON.parse((0, node_fs_1.readFileSync)(CONFIG_PATH, "utf-8"));
43
42
  const backend = config.llmBackend ?? "auto-detect";
44
43
  const model = config.llmModel ?? "default";
45
44
  const mode = config.mode === "client" ? "client → " + (config.serverUrl ?? "?") : "server (local)";
package/dist/nightly.js CHANGED
@@ -366,62 +366,24 @@ async function runNightly(options = {}) {
366
366
  // Runs even if capture had transient failures (opens DB directly, independent
367
367
  // of the HTTP capture path). Full nightly only — capture-only runs are
368
368
  // intended to run more frequently than once daily.
369
+ let lessonsGenerated; // hoisted for telemetry; undefined when reflection didn't run (skipped) — bucketed apart from a real 0
369
370
  if (!dryRun && !captureOnly) {
370
371
  if (!llm || !llmConfig) {
371
372
  console.error("[hicortex] consolidation skipped: no LLM configured — run npx @gamaze/hicortex init");
372
373
  }
373
374
  else {
374
- // Pre-flight health check for the reflect endpoint.
375
- // If reflectBaseUrl points to a remote Ollama and it's down (MBP offline),
376
- // skip reflection entirely instead of waiting through 3 retries (~3.5 min).
377
- // Scoring + linking + decay still run.
378
- let skipReflection = false;
379
- if (llmConfig.reflectBaseUrl && (llmConfig.reflectProvider ?? llmConfig.provider) === "ollama") {
380
- const reflectModel = llmConfig.reflectModel ?? llmConfig.model;
381
- const health = await (0, llm_js_1.probeOllamaModel)(llmConfig.reflectBaseUrl, reflectModel);
382
- if (!health.ok) {
383
- const reason = health.reason === "unreachable"
384
- ? `reflect endpoint unreachable (${llmConfig.reflectBaseUrl})`
385
- : `reflect model not loaded (${reflectModel} missing on ${llmConfig.reflectBaseUrl})`;
386
- console.warn(`[hicortex] ${reason} — skipping reflection, scoring + linking will still run`);
387
- skipReflection = true;
388
- }
389
- }
390
- // Content-based domain classification (config-owned `domains`) uses
391
- // the classify tier (classifyModel/classifyBaseUrl) when configured,
392
- // else the reflect tier. Pre-flight the endpoint classification will
393
- // ACTUALLY use (resolveClassifyProbeTarget is the shared source of
394
- // truth with `hicortex classify-domains`). If it is down, content
395
- // classification is NOT ready this run (strict — skip, don't fall
396
- // back). When no `domains` list is configured, this is inert and the
397
- // legacy project-grouping path runs.
375
+ // One model serves all phases (#231) — there is no separate endpoint to
376
+ // pre-flight. If the model doesn't answer, `complete()` already retries at
377
+ // 30s/60s/120s (~3.5 min); anything still failing after that is an outage,
378
+ // not a blip. A failed phase costs latency, not data: capture cursors hold
379
+ // on failure (dup-over-loss), and consolidation has resumable cursors
380
+ // (domainCursor, supersessionCursor). The nightly runs 2-4×/day, so the
381
+ // wait is hours — no polling, no new config. (Issue #231.)
398
382
  const cfgDomains = (0, domain_classify_js_1.parseConfigDomains)(savedConfig);
399
- let contentDomainsReady = true;
400
- if (cfgDomains) {
401
- const classifyTarget = (0, llm_js_1.resolveClassifyProbeTarget)(llmConfig);
402
- if (classifyTarget?.tier === "reflect") {
403
- // Classification rides the reflect endpoint — reuse the probe above.
404
- contentDomainsReady = !skipReflection;
405
- }
406
- else if (classifyTarget) {
407
- const health = await (0, llm_js_1.probeOllamaModel)(classifyTarget.baseUrl, classifyTarget.model);
408
- if (!health.ok) {
409
- const reason = health.reason === "unreachable"
410
- ? `classify endpoint unreachable (${classifyTarget.baseUrl})`
411
- : `classify model not loaded (${classifyTarget.model} missing on ${classifyTarget.baseUrl})`;
412
- console.warn(`[hicortex] ${reason}`);
413
- contentDomainsReady = false;
414
- }
415
- }
416
- // classifyTarget === null → base endpoint or API provider, no probe.
417
- if (!contentDomainsReady) {
418
- console.warn("[hicortex] content-domain classification skipped — classification endpoint offline (strict)");
419
- }
420
- }
421
383
  console.log(`[hicortex] Running consolidation...`);
422
- const report = await (0, consolidate_js_1.runConsolidation)(db, llm, embedder_js_1.embed, dryRun, skipReflection, undefined, {
384
+ const report = await (0, consolidate_js_1.runConsolidation)(db, llm, embedder_js_1.embed, dryRun, false, undefined, {
423
385
  domains: cfgDomains,
424
- contentDomainsReady,
386
+ contentDomainsReady: true,
425
387
  weakPrimaryFloor: (0, nofit_js_1.resolveWeakPrimaryFloor)(savedConfig),
426
388
  }, {
427
389
  minSimilarity: savedConfig?.supersessionMinSimilarity,
@@ -429,6 +391,14 @@ async function runNightly(options = {}) {
429
391
  });
430
392
  console.log(`[hicortex] Consolidation ${report.status} in ${report.elapsed_seconds}s` +
431
393
  (report.stages.reflection ? ` (${report.stages.reflection.lessons_generated} lessons)` : ""));
394
+ // Only set when reflection actually RAN (not skipped). A skipped stage
395
+ // (e.g. endpoint offline, #232 fail-soft) must NOT collapse to 0 — that
396
+ // would make "endpoint down" indistinguishable from "prompt too tight"
397
+ // in the fleet aggregate. Leave undefined so the optional field is
398
+ // omitted and the aggregate buckets skipped runs separately.
399
+ const refl = report.stages.reflection;
400
+ if (refl && !refl.skipped)
401
+ lessonsGenerated = refl.lessons_generated;
432
402
  }
433
403
  }
434
404
  // Step 4: Update last-run timestamp.
@@ -473,6 +443,7 @@ async function runNightly(options = {}) {
473
443
  agent: agentType,
474
444
  mem: storage.countMemories(db),
475
445
  lessons: storage.getLessons(db, 365).length,
446
+ lessonsGenerated,
476
447
  sessions: batches.length,
477
448
  ok: !hadTransientFailure,
478
449
  shown: adoption.shown,
package/dist/prompts.d.ts CHANGED
@@ -1,6 +1,12 @@
1
1
  /**
2
2
  * LLM prompt templates for memory consolidation and distillation.
3
3
  * Copied EXACTLY from the Python codebase (proven working prompts).
4
+ *
5
+ * SHIPPED SOURCE: this file is compiled into dist/ and published to npm + the
6
+ * public mirror (it is NOT in .publicignore). Any example, name, or detail in
7
+ * these prompts is therefore public. Use generic, anonymous examples — never
8
+ * real internal project/tool/host names or real incident specifics. The
9
+ * infra-name denylist cannot catch conceptual leaks, only known names.
4
10
  */
5
11
  /**
6
12
  * Importance scoring prompt. Takes a {memories_block} with indexed memories.
package/dist/prompts.js CHANGED
@@ -2,6 +2,12 @@
2
2
  /**
3
3
  * LLM prompt templates for memory consolidation and distillation.
4
4
  * Copied EXACTLY from the Python codebase (proven working prompts).
5
+ *
6
+ * SHIPPED SOURCE: this file is compiled into dist/ and published to npm + the
7
+ * public mirror (it is NOT in .publicignore). Any example, name, or detail in
8
+ * these prompts is therefore public. Use generic, anonymous examples — never
9
+ * real internal project/tool/host names or real incident specifics. The
10
+ * infra-name denylist cannot catch conceptual leaks, only known names.
5
11
  */
6
12
  Object.defineProperty(exports, "__esModule", { value: true });
7
13
  exports.importanceScoring = importanceScoring;
@@ -41,6 +47,9 @@ function reflection(memoriesBlock, recentLessons) {
41
47
 
42
48
  Like human learning: we grow fastest when we reinforce what works AND correct what doesn't. A system that only learns from mistakes becomes overly cautious. A system that only learns from successes never improves. The combination multiplies.
43
49
 
50
+ GENERALITY BAR (read carefully — the most important rule):
51
+ Every lesson MUST be a generalizable operating principle that transfers across contexts, agents, and projects. It is NOT: an incident report, a changelog entry, a one-event fact, a tool-specific recipe, or a note about a named entity. If a memory is only interesting as "what happened today", it is an EPISODE — do not emit a lesson for it. Abstract away specific tool names, hostnames, and incident details from the lesson text; state the transferable rule.
52
+
44
53
  Quality over quantity. 1-3 lessons is typical. An empty array [] is the CORRECT response when memories show routine competent work without noteworthy patterns, surprises, or friction. Do not manufacture lessons from nothing.
45
54
 
46
55
  LESSON TYPES:
@@ -56,15 +65,19 @@ Good reinforce: "Bundling related changes into a single PR with clear narrative
56
65
  Good reinforce: "When presenting multi-scenario analysis, show assumptions side-by-side so stakeholders evaluate trade-offs rather than reacting to isolated worst-cases"
57
66
  Good correct: "Always verify ALL substitution targets by diffing output — partial fixes cause silent failures"
58
67
  Good principle: "Gather evidence from logs before forming hypotheses — evidence-first debugging resolved issues 3x faster today"
68
+ Good principle: "When a long-running background agent survives a system migration as an orphan, explicitly unregister it before declaring the migration clean — orphans cause silent crash-loops"
59
69
  Bad lesson: "The deploy script had a bug" (restatement, not actionable)
70
+ Bad (incident note / changelog entry): "The graph resolver was fixed by adding a computed-at marker column" — that is a changelog entry, not a lesson
71
+ Bad (tool-specific decision): "Tool X is banned because it broke Tool Y's settings" — a one-tool decision, not transferable
72
+ Bad (single-event): "The chat client crashed after a system restore because a background agent was left orphaned" — an incident report, not a principle
60
73
 
61
74
  For each lesson, output a JSON object:
62
- - "lesson": Concise, actionable rule in imperative voice
75
+ - "lesson": Concise, actionable rule in imperative voice. State the transferable rule — abstract away specific tool names, hostnames, and incident details.
63
76
  - "type": "reinforce" | "correct" | "principle"
64
77
  - "project": "global" unless genuinely project-specific (project-specific lessons are still valuable)
65
78
  - "severity": "critical" | "important" | "minor"
66
79
  - "confidence": "high" | "medium" | "low"
67
- - "source_pattern": What triggered this (1 sentence, no personal data)
80
+ - "source_pattern": What triggered this (1 sentence, no personal data; may name the specific incident/tool here — but the \`lesson\` field itself stays generic)
68
81
 
69
82
  Severity guide:
70
83
  - "critical": Near-misses that could have caused data loss or security breach, even if caught in time. Also: recurring patterns that keep appearing despite prior corrections.
@@ -87,7 +100,7 @@ Focus on:
87
100
 
88
101
  Privacy: Never include personal data (names, health, finances, credentials) in lesson text. Abstract to the process level.
89
102
 
90
- Skip: isolated trivial actions, already-documented rules. However, if multiple small successes form a consistent pattern of quality, extract that pattern as a reinforcement.
103
+ Skip: isolated trivial actions, already-documented rules, incident notes, changelog entries, and tool-specific recipes. However, if multiple small successes form a consistent pattern of quality, extract that pattern as a reinforcement.
91
104
 
92
105
  Respond with a JSON array. Empty array [] is a valid response.`;
93
106
  }
@@ -13,6 +13,9 @@
13
13
  * client is never miscounted as "cc".
14
14
  * mem — total memory count
15
15
  * lessons — total lesson count
16
+ * lessonsGenerated — lessons created THIS run (server mode only; the per-run
17
+ * delta, distinct from `lessons` which is the corpus total). Lets a
18
+ * silent decline in reflection output be seen in the fleet aggregate.
16
19
  * sessions — sessions distilled this run
17
20
  * ok — nightly succeeded (true/false)
18
21
  * shown — sum of shown_count (server mode only)
@@ -76,6 +79,12 @@ export interface TelemetryPayload {
76
79
  shown?: number;
77
80
  uses?: number;
78
81
  cold?: number;
82
+ /**
83
+ * Lessons generated THIS run (server mode only). The per-run delta —
84
+ * `lessons` above is the corpus total, which drifts slowly via decay/prune
85
+ * and can't reveal a sudden drop in reflection output. 0.16.9+.
86
+ */
87
+ lessonsGenerated?: number;
79
88
  }
80
89
  /**
81
90
  * Check if telemetry is enabled. Disabled by:
package/dist/telemetry.js CHANGED
@@ -14,6 +14,9 @@
14
14
  * client is never miscounted as "cc".
15
15
  * mem — total memory count
16
16
  * lessons — total lesson count
17
+ * lessonsGenerated — lessons created THIS run (server mode only; the per-run
18
+ * delta, distinct from `lessons` which is the corpus total). Lets a
19
+ * silent decline in reflection output be seen in the fleet aggregate.
17
20
  * sessions — sessions distilled this run
18
21
  * ok — nightly succeeded (true/false)
19
22
  * shown — sum of shown_count (server mode only)
package/dist/types.d.ts CHANGED
@@ -154,16 +154,6 @@ export interface ConsolidationReport {
154
154
  calls_by_stage: Record<string, number>;
155
155
  };
156
156
  }
157
- /**
158
- * A single per-stage override inside the nested `models` server-config block.
159
- * Consumed by `applyModelsBlock` (llm.ts), which re-exports this type.
160
- */
161
- export interface ModelTierOverride {
162
- model?: string;
163
- baseUrl?: string;
164
- apiKey?: string;
165
- provider?: string;
166
- }
167
157
  /** Plugin configuration from openclaw.plugin.json configSchema. */
168
158
  export interface HicortexConfig {
169
159
  licenseKey?: string;
@@ -186,33 +176,6 @@ export interface HicortexConfig {
186
176
  llmApiKey?: string;
187
177
  /** @deprecated Use the Hicortex server for distillation and consolidation. */
188
178
  llmModel?: string;
189
- /** @deprecated Use the Hicortex server for distillation and consolidation. */
190
- reflectModel?: string;
191
- /**
192
- * Optional dedicated model for memory tag classification (server-side
193
- * nightly + `hicortex classify-domains`). When unset, classification uses
194
- * the reflect tier exactly as before.
195
- */
196
- classifyModel?: string;
197
- /**
198
- * Optional dedicated endpoint for classification. When only classifyModel
199
- * is set, it runs on the reflect endpoint (else the base endpoint).
200
- */
201
- classifyBaseUrl?: string;
202
- /** Optional API key for the classify endpoint (defaults to the base apiKey). */
203
- classifyApiKey?: string;
204
- /** Optional provider for the classify endpoint (defaults to the base provider). */
205
- classifyProvider?: string;
206
- /**
207
- * Server config (NOT an OC-plugin key): nested per-stage model overrides.
208
- * `{ score|distill|reflect|classify: { model?, baseUrl?, apiKey?, provider? } }`.
209
- * Normalized onto the flat `llm*` / `distill*` / `reflect*` / `classify*`
210
- * keys at read time (see applyModelsBlock in llm.ts); nested wins, and the
211
- * flat keys remain supported at lower precedence. Happy path is a single model via
212
- * `llmModel`; use this block only for per-stage routing. `score.provider` is
213
- * ignored (base provider comes from llmBackend).
214
- */
215
- models?: Record<string, ModelTierOverride>;
216
179
  /** @deprecated Consolidation is owned by the server nightly. */
217
180
  consolidateHour?: number;
218
181
  /** @deprecated The OC plugin no longer opens its own database. */
@@ -283,34 +246,37 @@ export interface HicortexConfig {
283
246
  */
284
247
  preflightRetryGapMs?: number;
285
248
  /**
286
- * Max output tokens for the LLM phases — distillation, reflection, classification,
287
- * and scoring (completeDistill / completeReflect / completeClassify / completeFast).
288
- * Default 8192 for the heavy phases, 2048 for scoring (the local fast tier); an
289
- * explicit value overrides all of them. A ceiling, not a target: generation stops
290
- * at the model's natural end (finish_reason stop), so a higher cap costs no latency
249
+ * Max output tokens for the ONE LLM model used by all phases — distillation,
250
+ * reflection, classification, and scoring. Default 8192. An explicit value
251
+ * overrides the default. A ceiling, not a target: generation stops at the
252
+ * model's natural end (finish_reason stop), so a higher cap costs no latency
291
253
  * when it finishes early. Read in llm.ts; see #220.
292
254
  */
293
255
  maxTokens?: number;
294
256
  /**
295
257
  * Toggle the model's internal reasoning ("thinking") stream on the openai-compat
296
- * path — applies to the heavy phases (distill / reflect / classify) that share an
297
- * OpenAI-compatible endpoint (e.g. a reasoning model served via a local gateway).
298
- * Default false. A thinking model with thinking ON can burn the entire token
299
- * budget on an unclosed <think> block and emit nothing (probed 2026-08-04). When
300
- * set (true or false), completeOpenAiCompat sends
301
- * chat_template_kwargs:{enable_thinking}. Threaded structurally from each heavy
302
- * phase, so scoring (ollama) never sends it. No effect on the anthropic path.
303
- * See #220.
258
+ * path — applies to ALL phases (distill / reflect / classify / scoring) since one
259
+ * model serves all of them. Default false. A thinking model with thinking ON can
260
+ * burn the entire token budget on an unclosed <think> block and emit nothing
261
+ * (probed 2026-08-04). When set (true or false), completeOpenAiCompat sends
262
+ * chat_template_kwargs:{enable_thinking}. LOCAL-ENDPOINT ONLY: this is meaningful
263
+ * only for a chat-template-aware server (ollama, mlx-lm). If the one model is a
264
+ * cloud OpenAI-compatible endpoint (OpenAI / OpenRouter / Groq / z.ai), LEAVE THIS
265
+ * UNSET — the non-standard chat_template_kwargs field rides every call and can 400
266
+ * the whole pipeline (provider cannot distinguish MLX-gateway-as-openai from real
267
+ * cloud openai, so the gate must be operator-set, not detected). No effect on the
268
+ * anthropic or claude-cli paths. See #220, #231.
304
269
  */
305
270
  enableThinking?: boolean;
306
271
  /**
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.
272
+ * Context window for ollama (the one model, all phases). Default 8192 — the point
273
+ * where context stops being the binding constraint for a sub-8B model on ollama
274
+ * (above it the SMALL_MODEL_MAX_CHUNK_CHARS speed cap binds instead, so extra
275
+ * context buys nothing). Also drives `detectChunkSize`'s chunk sizing
276
+ * (chunkChars numCtx × 0.6 × 4 chars), so numCtx is the single dial and the
277
+ * chunker/request agreement is enforced by construction (#228). For an ≥8B model
278
+ * on ollama the speed cap is 60,000 chars, needing numCtx ≈ 25000 to reach — raise
279
+ * it if running 8B+ locally. No effect for non-ollama providers.
314
280
  */
315
281
  numCtx?: number;
316
282
  /**
@@ -334,6 +300,13 @@ export interface HicortexConfig {
334
300
  * doubled for margin). Only relevant when `ollamaFlushEvery` > 0.
335
301
  */
336
302
  ollamaFlushWaitMs?: number;
303
+ /**
304
+ * Max lessons injected into an agent's session-start context (default 10).
305
+ * Lessons are ranked per-session by project/domain affinity + recency +
306
+ * strength + access, so each session sees its most-relevant slice. Lower =
307
+ * leaner system prompts.
308
+ */
309
+ lessonsLimit?: number;
337
310
  }
338
311
  /** A config-owned life-sphere domain (see HicortexConfig.domains). */
339
312
  export interface DomainDef {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gamaze/hicortex",
3
- "version": "0.16.7",
3
+ "version": "0.16.9",
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": {