@gamaze/hicortex 0.16.8 → 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.
package/README.md CHANGED
@@ -211,6 +211,7 @@ Config at `~/.hicortex/config.json`. Created by `init`. Key options:
211
211
  | `domains` | Your memory domain list (`[{name, description}]`). Scaffolded by `init`; edit freely — see [Memory Domains & Tags](#memory-domains--tags) |
212
212
  | `weakPrimaryFloor` | Minimum similarity for a no-fit memory to keep a weak domain association (default: 0.45) |
213
213
  | `moduleIndexTokenBudget` | Max tokens for domain index in lessons context (default: 500) |
214
+ | `lessonsLimit` | Max lessons injected into an agent's session-start context (default: 10). Lessons are ranked per session by project/domain affinity + recency + strength + access, so each session sees its most-relevant slice. Lower = leaner system prompts. |
214
215
  | `contextClients` | Which harnesses inject the [context layer](#context-layer) at session start (default `["cc"]`; `"all"` or any subset of `cc`/`hermes`/`oc`) |
215
216
  | `contextAgents` | Per-agent context modes (0.13): `{ "<id>": "override" \| "global" \| "off" }`. Absent + no `agents/<id>/` dir → every agent gets the global set. Boot-time (restart to apply) — see [Per-agent context](#per-agent-context-013) |
216
217
  | `agentName` | This install's per-agent context id sent as `?agent=`. **Unset by default** (CC shares the global context — no `?agent=` sent). Explicit opt-in via `init --agent-name <name>`; `init --agent-name ""` clears it. An empty/whitespace value equals unset |
@@ -25,8 +25,18 @@ export declare function isPro(): boolean;
25
25
  export declare function maxMemoriesAllowed(): number;
26
26
  /** Always false — no cap is ever reached. */
27
27
  export declare function memoryCapReached(_currentCount: number): boolean;
28
- /** Always 20. */
29
- export declare function lessonsLimit(): number;
28
+ /**
29
+ * Max lessons injected into an agent's session-start context. Configurable via
30
+ * `config.lessonsLimit` (a positive integer); defaults to 10. Reuses
31
+ * `readPositiveConfig` so a present-but-invalid value (0, negative, NaN, wrong
32
+ * type) is rejected AT THE BOUNDARY with a warn — never silently. 0 is NOT
33
+ * honoured as "off" (use a real config switch if that's ever needed); it is
34
+ * invalid and falls back to 10 with a warning, so an operator can't
35
+ * accidentally silence lesson injection by typing 0.
36
+ */
37
+ export declare function lessonsLimit(config?: {
38
+ lessonsLimit?: unknown;
39
+ } | null): number;
30
40
  /** Always true — remote ingest is always allowed. */
31
41
  export declare function remoteIngestAllowed(): boolean;
32
42
  /** Direct read of the underlying features record. */
package/dist/features.js CHANGED
@@ -24,6 +24,7 @@ exports.getCurrentFeatures = getCurrentFeatures;
24
24
  const paths_js_1 = require("./paths.js");
25
25
  const license_js_1 = require("./license.js");
26
26
  const state_js_1 = require("./state.js");
27
+ const config_read_js_1 = require("./config-read.js");
27
28
  const DEFAULT_STATE_DIR = (0, paths_js_1.hicortexHome)();
28
29
  // A single canonical "full" feature set — no tiers.
29
30
  const FULL_FEATURES = {
@@ -90,9 +91,19 @@ function maxMemoriesAllowed() {
90
91
  function memoryCapReached(_currentCount) {
91
92
  return false;
92
93
  }
93
- /** Always 20. */
94
- function lessonsLimit() {
95
- return 20;
94
+ /**
95
+ * Max lessons injected into an agent's session-start context. Configurable via
96
+ * `config.lessonsLimit` (a positive integer); defaults to 10. Reuses
97
+ * `readPositiveConfig` so a present-but-invalid value (0, negative, NaN, wrong
98
+ * type) is rejected AT THE BOUNDARY with a warn — never silently. 0 is NOT
99
+ * honoured as "off" (use a real config switch if that's ever needed); it is
100
+ * invalid and falls back to 10 with a warning, so an operator can't
101
+ * accidentally silence lesson injection by typing 0.
102
+ */
103
+ function lessonsLimit(config) {
104
+ if (!config)
105
+ return 10;
106
+ return Math.floor((0, config_read_js_1.readPositiveConfig)({ lessonsLimit: config.lessonsLimit }, "lessonsLimit", 10));
96
107
  }
97
108
  /** Always true — remote ingest is always allowed. */
98
109
  function remoteIngestAllowed() {
package/dist/index.js CHANGED
@@ -50,6 +50,9 @@ const THIS_HARNESS = "oc";
50
50
  let serverUrl = DEFAULT_SERVER_URL;
51
51
  let authToken;
52
52
  let hicortexHome = HICORTEX_HOME;
53
+ /** Resolved plugin config captured at service start — used for tunable knobs
54
+ * (e.g. lessonsLimit) that injected context blocks read at hook time. */
55
+ let pluginConfig = null;
53
56
  /** Old-server guard (F2): 0 = not latched; otherwise the Date.now() epoch-ms
54
57
  * until which /recall-index is skipped after a 404 (pre-0.14 server). The
55
58
  * latch EXPIRES so a client-first rollout heals itself once the server is
@@ -152,7 +155,7 @@ async function buildLessonsBlock(project) {
152
155
  const { data } = await serverGet("/lessons", LESSONS_TIMEOUT_MS);
153
156
  if (!data || !data.lessons || data.lessons.length === 0)
154
157
  return null;
155
- const maxLessons = (0, features_js_1.lessonsLimit)();
158
+ const maxLessons = (0, features_js_1.lessonsLimit)(pluginConfig);
156
159
  const state = (0, state_js_1.loadState)(hicortexHome);
157
160
  const moduleIndex = data.moduleIndex ?? state.moduleIndex;
158
161
  const selected = await (0, extensions_js_1.getLessonSelector)().select(data.lessons, {
@@ -264,6 +267,7 @@ exports.default = {
264
267
  id: "hicortex-service",
265
268
  async start(ctx) {
266
269
  const config = (ctx.config ?? {});
270
+ pluginConfig = config;
267
271
  const log = ctx.logger
268
272
  ? (msg) => ctx.logger.info(msg)
269
273
  : console.log;
@@ -39,6 +39,8 @@ export interface ResolvedConfig {
39
39
  home: string;
40
40
  /** Per-agent context id sent as ?agent= (0.13); null → global (no param). */
41
41
  agentName: string | null;
42
+ /** Max lessons to inject (config.lessonsLimit, default 10). */
43
+ lessonsLimit?: number;
42
44
  }
43
45
  /**
44
46
  * Read ~/.hicortex/config.json and resolve the server URL + auth token, or
@@ -64,7 +64,13 @@ function resolveConfig() {
64
64
  // context. A configured agentName that sanitizes to null → agentId null too
65
65
  // (NO ?agent=), never a 400 that the fail-soft hook would silently swallow.
66
66
  const agentName = (0, context_store_js_1.resolveAgentIdentity)(config).agentId;
67
- return { serverUrl, authToken: config.authToken, home, agentName };
67
+ return {
68
+ serverUrl,
69
+ authToken: config.authToken,
70
+ home,
71
+ agentName,
72
+ lessonsLimit: typeof config.lessonsLimit === "number" ? config.lessonsLimit : undefined,
73
+ };
68
74
  }
69
75
  function authHeaders(authToken) {
70
76
  return authToken ? { "Authorization": `Bearer ${authToken}` } : {};
@@ -81,7 +87,7 @@ async function fetchLessonsBlock(cfg) {
81
87
  if (!resp.ok)
82
88
  return null;
83
89
  const data = await resp.json();
84
- const maxLessons = (0, features_js_1.lessonsLimit)();
90
+ const maxLessons = (0, features_js_1.lessonsLimit)(cfg);
85
91
  const moduleIndex = data.moduleIndex ?? (0, state_js_1.loadState)(cfg.home).moduleIndex;
86
92
  // The SessionStart hook runs in the session's working directory, whose last
87
93
  // path component matches the capture-side CC project name convention
package/dist/nightly.js CHANGED
@@ -366,6 +366,7 @@ 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");
@@ -390,6 +391,14 @@ async function runNightly(options = {}) {
390
391
  });
391
392
  console.log(`[hicortex] Consolidation ${report.status} in ${report.elapsed_seconds}s` +
392
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;
393
402
  }
394
403
  }
395
404
  // Step 4: Update last-run timestamp.
@@ -434,6 +443,7 @@ async function runNightly(options = {}) {
434
443
  agent: agentType,
435
444
  mem: storage.countMemories(db),
436
445
  lessons: storage.getLessons(db, 365).length,
446
+ lessonsGenerated,
437
447
  sessions: batches.length,
438
448
  ok: !hadTransientFailure,
439
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
@@ -300,6 +300,13 @@ export interface HicortexConfig {
300
300
  * doubled for margin). Only relevant when `ollamaFlushEvery` > 0.
301
301
  */
302
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;
303
310
  }
304
311
  /** A config-owned life-sphere domain (see HicortexConfig.domains). */
305
312
  export interface DomainDef {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gamaze/hicortex",
3
- "version": "0.16.8",
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": {