@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/README.md +102 -78
- package/dist/cli.js +10 -4
- package/dist/consolidate.js +33 -2
- package/dist/db.js +5 -1
- package/dist/init.d.ts +3 -1
- package/dist/init.js +375 -55
- package/dist/license.js +1 -0
- package/dist/llm.d.ts +1 -0
- package/dist/llm.js +49 -4
- package/dist/mcp-server.js +62 -2
- package/dist/nightly.js +172 -3
- package/dist/prompts.d.ts +1 -1
- package/dist/prompts.js +41 -13
- package/dist/storage.js +5 -4
- package/dist/types.d.ts +2 -0
- package/package.json +2 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
- "
|
|
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
|
-
|
|
56
|
-
- "
|
|
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:
|
|
61
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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) =>
|
|
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
|
+
"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",
|