@gamaze/hicortex 0.3.16 → 0.4.0

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/nightly.js CHANGED
@@ -96,8 +96,14 @@ function writeLastRun() {
96
96
  }
97
97
  async function runNightly(options = {}) {
98
98
  const dryRun = options.dryRun ?? false;
99
- const dbPath = (0, db_js_1.resolveDbPath)(options.dbPath);
100
99
  const stateDir = options.stateDir ?? HICORTEX_HOME;
100
+ // Check mode: client or server
101
+ const savedConfig = readNightlyConfig(stateDir);
102
+ if (savedConfig?.mode === "client") {
103
+ await runClientNightly(savedConfig, dryRun);
104
+ return;
105
+ }
106
+ const dbPath = (0, db_js_1.resolveDbPath)(options.dbPath);
101
107
  console.log(`[hicortex] Nightly pipeline starting${dryRun ? " (dry run)" : ""}`);
102
108
  console.log(`[hicortex] DB: ${dbPath}`);
103
109
  // Init DB
@@ -116,18 +122,43 @@ async function runNightly(options = {}) {
116
122
  }
117
123
  else {
118
124
  console.warn("[hicortex] claude-cli configured but binary not found, falling back");
119
- llmConfig = (0, llm_js_1.resolveLlmConfigForCC)();
125
+ llmConfig = (0, llm_js_1.resolveLlmConfigForCC)({
126
+ llmBaseUrl: savedConfig?.llmBaseUrl,
127
+ llmApiKey: savedConfig?.llmApiKey,
128
+ llmModel: savedConfig?.llmModel,
129
+ reflectModel: savedConfig?.reflectModel,
130
+ });
120
131
  }
121
132
  }
133
+ else if (savedConfig?.llmBackend === "ollama") {
134
+ llmConfig = {
135
+ baseUrl: savedConfig.llmBaseUrl ?? "http://localhost:11434",
136
+ apiKey: "",
137
+ model: savedConfig.llmModel ?? "qwen3.5:4b",
138
+ reflectModel: savedConfig.reflectModel ?? savedConfig.llmModel ?? "qwen3.5:4b",
139
+ provider: "ollama",
140
+ };
141
+ }
122
142
  else {
123
143
  llmConfig = (0, llm_js_1.resolveLlmConfigForCC)({
124
144
  llmBaseUrl: savedConfig?.llmBaseUrl,
125
145
  llmApiKey: savedConfig?.llmApiKey,
126
146
  llmModel: savedConfig?.llmModel,
147
+ reflectModel: savedConfig?.reflectModel,
127
148
  });
128
149
  }
150
+ // Apply distillModel and reflect overrides from config
151
+ if (savedConfig?.distillModel) {
152
+ llmConfig.distillModel = savedConfig.distillModel;
153
+ }
154
+ if (savedConfig?.reflectBaseUrl) {
155
+ llmConfig.reflectBaseUrl = savedConfig.reflectBaseUrl;
156
+ llmConfig.reflectApiKey = savedConfig.reflectApiKey ?? llmConfig.apiKey;
157
+ llmConfig.reflectProvider = savedConfig.reflectProvider ?? llmConfig.provider;
158
+ }
129
159
  const llm = new llm_js_1.LlmClient(llmConfig);
130
- console.log(`[hicortex] LLM: ${llmConfig.provider}/${llmConfig.model}`);
160
+ const distillInfo = llmConfig.distillModel ? `, distill: ${llmConfig.distillModel}` : "";
161
+ console.log(`[hicortex] LLM: ${llmConfig.provider}/${llmConfig.model}${distillInfo}`);
131
162
  // Step 1: Read new CC transcripts
132
163
  const since = readLastRun();
133
164
  console.log(`[hicortex] Reading CC transcripts since ${since.toISOString()}`);
@@ -206,3 +237,141 @@ async function runNightly(options = {}) {
206
237
  db.close();
207
238
  }
208
239
  }
240
+ // ---------------------------------------------------------------------------
241
+ // Client Mode Nightly — distill locally, POST to remote server
242
+ // ---------------------------------------------------------------------------
243
+ async function runClientNightly(config, dryRun) {
244
+ const serverUrl = config.serverUrl.replace(/\/+$/, "");
245
+ const authToken = config.authToken;
246
+ console.log(`[hicortex] Client nightly starting${dryRun ? " (dry run)" : ""}`);
247
+ console.log(`[hicortex] Server: ${serverUrl}`);
248
+ // Verify server is reachable
249
+ try {
250
+ const resp = await fetch(`${serverUrl}/health`, { signal: AbortSignal.timeout(5000) });
251
+ if (!resp.ok)
252
+ throw new Error(`HTTP ${resp.status}`);
253
+ const data = await resp.json();
254
+ console.log(`[hicortex] Server OK: v${data.version}, ${data.memories} memories`);
255
+ }
256
+ catch (err) {
257
+ console.error(`[hicortex] Server unreachable at ${serverUrl}: ${err instanceof Error ? err.message : String(err)}`);
258
+ console.error(`[hicortex] Aborting. Will retry next run.`);
259
+ return; // Don't update last-run so we retry
260
+ }
261
+ // Init LLM for local distillation
262
+ let llmConfig;
263
+ if (config.llmBackend === "claude-cli") {
264
+ const claudePath = (0, llm_js_1.findClaudeBinary)();
265
+ if (claudePath) {
266
+ llmConfig = (0, llm_js_1.claudeCliConfig)(claudePath);
267
+ }
268
+ else {
269
+ llmConfig = (0, llm_js_1.resolveLlmConfigForCC)();
270
+ }
271
+ }
272
+ else if (config.llmBackend === "ollama") {
273
+ llmConfig = {
274
+ baseUrl: config.llmBaseUrl ?? "http://localhost:11434",
275
+ apiKey: "",
276
+ model: config.llmModel ?? "qwen3.5:4b",
277
+ reflectModel: config.reflectModel ?? config.llmModel ?? "qwen3.5:4b",
278
+ provider: "ollama",
279
+ };
280
+ }
281
+ else {
282
+ llmConfig = (0, llm_js_1.resolveLlmConfigForCC)({
283
+ llmBaseUrl: config.llmBaseUrl,
284
+ llmApiKey: config.llmApiKey,
285
+ llmModel: config.llmModel,
286
+ });
287
+ }
288
+ if (config.distillModel) {
289
+ llmConfig.distillModel = config.distillModel;
290
+ }
291
+ const llm = new llm_js_1.LlmClient(llmConfig);
292
+ console.log(`[hicortex] LLM: ${llmConfig.provider}/${llmConfig.distillModel ?? llmConfig.model}`);
293
+ // Read new CC transcripts
294
+ const since = readLastRun();
295
+ console.log(`[hicortex] Reading CC transcripts since ${since.toISOString()}`);
296
+ const batches = (0, transcript_reader_js_1.readCcTranscripts)(since);
297
+ console.log(`[hicortex] Found ${batches.length} new session(s)`);
298
+ if (batches.length === 0) {
299
+ console.log(`[hicortex] Nothing to distill.`);
300
+ if (!dryRun)
301
+ writeLastRun();
302
+ return;
303
+ }
304
+ // Distill each session and POST to server
305
+ let memoriesIngested = 0;
306
+ let sessionsSent = 0;
307
+ for (const batch of batches) {
308
+ const transcript = (0, distiller_js_1.extractConversationText)(batch.entries);
309
+ if (transcript.length < 200) {
310
+ console.log(`[hicortex] Skip ${batch.sessionId.slice(0, 8)} (${batch.projectName}): too short`);
311
+ continue;
312
+ }
313
+ console.log(`[hicortex] Distilling ${batch.sessionId.slice(0, 8)} (${batch.projectName}, ${batch.date})`);
314
+ if (dryRun) {
315
+ console.log(`[hicortex] [dry-run] Would distill ${transcript.length} chars`);
316
+ continue;
317
+ }
318
+ try {
319
+ const entries = await (0, distiller_js_1.distillSession)(llm, transcript, batch.projectName, batch.date);
320
+ if (entries.length === 0) {
321
+ console.log(`[hicortex] → No memories extracted`);
322
+ continue;
323
+ }
324
+ // POST each extracted memory to the server
325
+ let sessionCount = 0;
326
+ for (const entry of entries) {
327
+ const resp = await fetch(`${serverUrl}/ingest`, {
328
+ method: "POST",
329
+ headers: {
330
+ "Content-Type": "application/json",
331
+ ...(authToken ? { "Authorization": `Bearer ${authToken}` } : {}),
332
+ },
333
+ body: JSON.stringify({
334
+ content: entry,
335
+ source_agent: `claude-code/${batch.projectName}`,
336
+ project: batch.projectName,
337
+ memory_type: "episode",
338
+ privacy: "WORK",
339
+ source_session: batch.sessionId,
340
+ session_date: batch.date,
341
+ }),
342
+ signal: AbortSignal.timeout(30_000),
343
+ });
344
+ const result = await resp.json();
345
+ if (resp.status === 201) {
346
+ sessionCount++;
347
+ memoriesIngested++;
348
+ }
349
+ else if (result.skipped) {
350
+ console.log(`[hicortex] → Already ingested (${result.existing_count} existing)`);
351
+ break;
352
+ }
353
+ else if (resp.status === 401) {
354
+ console.error(`[hicortex] Auth failed. Check authToken in ~/.hicortex/config.json`);
355
+ return;
356
+ }
357
+ else if (resp.status === 429) {
358
+ console.log(`[hicortex] Server memory limit reached.`);
359
+ return;
360
+ }
361
+ else {
362
+ console.error(`[hicortex] Ingest failed (${resp.status}): ${result.error}`);
363
+ }
364
+ }
365
+ if (sessionCount > 0) {
366
+ sessionsSent++;
367
+ console.log(`[hicortex] → ${sessionCount} memories sent to server`);
368
+ }
369
+ }
370
+ catch (err) {
371
+ console.error(`[hicortex] Failed: ${err instanceof Error ? err.message : String(err)}`);
372
+ }
373
+ }
374
+ if (!dryRun)
375
+ writeLastRun();
376
+ console.log(`[hicortex] Client nightly complete: ${memoriesIngested} memories from ${sessionsSent} sessions → ${serverUrl}`);
377
+ }
package/dist/prompts.d.ts CHANGED
@@ -9,7 +9,7 @@ export declare function importanceScoring(memoriesBlock: string): string;
9
9
  /**
10
10
  * Reflection prompt. Takes a {memories_block} with today's memories.
11
11
  */
12
- export declare function reflection(memoriesBlock: string): string;
12
+ export declare function reflection(memoriesBlock: string, recentLessons?: string): string;
13
13
  /**
14
14
  * Distillation prompt. Extracts knowledge from a session transcript.
15
15
  */
package/dist/prompts.js CHANGED
@@ -32,33 +32,61 @@ No explanations. Just the JSON array.`;
32
32
  /**
33
33
  * Reflection prompt. Takes a {memories_block} with today's memories.
34
34
  */
35
- function reflection(memoriesBlock) {
36
- return `You are a self-improvement analyst for a multi-agent AI system. Review today's memories and extract 1-3 actionable lessons.
35
+ function reflection(memoriesBlock, recentLessons) {
36
+ const recentSection = recentLessons
37
+ ? `\nRECENT LESSONS (already generated — do NOT duplicate, but DO escalate if patterns recur):\n${recentLessons}\n`
38
+ : "";
39
+ return `You are a learning analyst for a multi-agent AI system. Review today's memories and extract actionable lessons from BOTH successes and failures.
37
40
 
38
- Quality over quantity. One genuine insight that prevents a future mistake is worth more than five restatements of what happened. Most days, 1-2 lessons is ideal. An empty array is fine if nothing warrants a lesson.
41
+ 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.
39
42
 
40
- Prefer GLOBAL lessons (applicable across all projects and agents) over project-specific ones. Only mark a lesson as project-specific if it truly cannot generalize.
41
-
42
- Good lesson: "When modifying template files processed by sed, always verify ALL substitution targets by diffing the output — partial fixes cause silent failures on other deployments"
43
- Bad lesson: "The deploy script had a bug" (restatement, not actionable)
43
+ 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.
44
44
 
45
+ LESSON TYPES:
46
+ - "reinforce": An approach or strategy that worked well — repeat and spread it
47
+ - "correct": A mistake, gap, or near-miss that should be avoided
48
+ - "principle": A general insight derived from either success or failure
49
+ ${recentSection}
45
50
  TODAY'S MEMORIES:
46
51
  ${memoriesBlock}
47
52
 
53
+ EXAMPLES:
54
+ Good reinforce: "Bundling related changes into a single PR with clear narrative gets faster approval — apply for all refactors"
55
+ Good reinforce: "When presenting multi-scenario analysis, show assumptions side-by-side so stakeholders evaluate trade-offs rather than reacting to isolated worst-cases"
56
+ Good correct: "Always verify ALL substitution targets by diffing output — partial fixes cause silent failures"
57
+ Good principle: "Gather evidence from logs before forming hypotheses — evidence-first debugging resolved issues 3x faster today"
58
+ Bad lesson: "The deploy script had a bug" (restatement, not actionable)
59
+
48
60
  For each lesson, output a JSON object:
49
61
  - "lesson": Concise, actionable rule in imperative voice
50
- - "project": "global" unless genuinely project-specific
62
+ - "type": "reinforce" | "correct" | "principle"
63
+ - "project": "global" unless genuinely project-specific (project-specific lessons are still valuable)
51
64
  - "severity": "critical" | "important" | "minor"
52
65
  - "confidence": "high" | "medium" | "low"
53
- - "source_pattern": What triggered this (1 sentence)
66
+ - "source_pattern": What triggered this (1 sentence, no personal data)
54
67
 
55
- Confidence:
56
- - "high": Pattern across multiple events, or clear mistake with obvious fix. Safe to auto-inject into agent instructions.
68
+ Severity guide:
69
+ - "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.
70
+ - "important": Clear cause-effect, likely to recur. Worth sharing across agents.
71
+ - "minor": Useful optimization, single incident.
72
+
73
+ Confidence guide:
74
+ - "high": Pattern across multiple events, or clear cause-effect. Safe to auto-inject into agent instructions.
57
75
  - "medium": Single incident but likely to recur. Store but don't auto-propagate.
58
76
  - "low": Speculative. Store for retrieval only.
59
77
 
60
- Focus on: process gaps, repeated friction, silent failures, user corrections to agent behavior, cross-agent patterns.
61
- Skip: trivial actions, already-documented rules, one-off events.
78
+ Focus on:
79
+ - SUCCESSES: effective strategies, approaches the user validated, patterns that saved time, clean solutions
80
+ - FAILURES: process gaps, repeated friction, silent failures, user corrections
81
+ - OMISSIONS: things that should have been done but weren't (missing tests, unchecked code paths, forgotten follow-ups)
82
+ - NEAR-MISSES: problems caught before damage — these deserve critical severity
83
+ - CONTRADICTIONS: cases where something appeared to work but didn't, or agents reached opposite conclusions
84
+ - CROSS-AGENT PATTERNS: same issue or success across different agents — especially high-value
85
+ - PROCESS FEEDBACK: user feedback about the agent's approach/behavior, not just its output
86
+
87
+ Privacy: Never include personal data (names, health, finances, credentials) in lesson text. Abstract to the process level.
88
+
89
+ Skip: isolated trivial actions, already-documented rules. However, if multiple small successes form a consistent pattern of quality, extract that pattern as a reinforcement.
62
90
 
63
91
  Respond with a JSON array. Empty array [] is a valid response.`;
64
92
  }
package/dist/storage.js CHANGED
@@ -77,21 +77,22 @@ const ALLOWED_UPDATE_FIELDS = new Set([
77
77
  "project",
78
78
  "privacy",
79
79
  "memory_type",
80
+ "updated_at",
80
81
  ]);
81
82
  /**
82
83
  * Update specific fields on a memory.
83
84
  */
84
85
  function updateMemory(db, memoryId, fields) {
85
- const keys = Object.keys(fields);
86
- if (keys.length === 0)
87
- return;
86
+ // Auto-set updated_at timestamp
87
+ const fieldsWithTimestamp = { ...fields, updated_at: new Date().toISOString() };
88
+ const keys = Object.keys(fieldsWithTimestamp);
88
89
  for (const k of keys) {
89
90
  if (!ALLOWED_UPDATE_FIELDS.has(k)) {
90
91
  throw new Error(`Cannot update field: ${k}`);
91
92
  }
92
93
  }
93
94
  const setClause = keys.map((k) => `"${k}" = ?`).join(", ");
94
- const values = keys.map((k) => fields[k]);
95
+ const values = keys.map((k) => fieldsWithTimestamp[k]);
95
96
  values.push(memoryId);
96
97
  db.prepare(`UPDATE memories SET ${setClause} WHERE id = ?`).run(...values);
97
98
  }
package/dist/types.d.ts CHANGED
@@ -16,6 +16,7 @@ export interface Memory {
16
16
  project: string | null;
17
17
  privacy: "PUBLIC" | "WORK" | "PERSONAL" | "SENSITIVE";
18
18
  memory_type: "episode" | "lesson" | "fact" | "decision";
19
+ updated_at: string | null;
19
20
  }
20
21
  /** A link between two memories. */
21
22
  export interface MemoryLink {
@@ -98,6 +99,7 @@ export interface LicenseInfo {
98
99
  vectorSearch: boolean;
99
100
  maxMemories: number;
100
101
  crossAgent: boolean;
102
+ remoteIngest?: boolean;
101
103
  };
102
104
  email?: string;
103
105
  expires_at?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gamaze/hicortex",
3
- "version": "0.3.16",
3
+ "version": "0.4.0",
4
4
  "description": "Human-like memory for self-improving AI agents. Automatic capturing, nightly reflection, and cross-agent learning. Works with Claude Code and OpenClaw.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -35,6 +35,7 @@
35
35
  "node": ">=18"
36
36
  },
37
37
  "license": "UNLICENSED",
38
+ "homepage": "https://hicortex.gamaze.com",
38
39
  "repository": {
39
40
  "type": "git",
40
41
  "url": "https://github.com/mha33/hicortex.git",