@gamaze/hicortex 0.7.0 → 0.7.1
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/consolidate.js +76 -19
- package/dist/graph.d.ts +1 -1
- package/dist/graph.js +13 -7
- package/dist/mcp-server.js +3 -2
- package/dist/prompts.d.ts +5 -0
- package/dist/prompts.js +29 -0
- package/dist/types.d.ts +6 -0
- package/dist/types.js +7 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/dist/consolidate.js
CHANGED
|
@@ -43,6 +43,7 @@ exports.parseJsonLenient = parseJsonLenient;
|
|
|
43
43
|
exports.runConsolidation = runConsolidation;
|
|
44
44
|
exports.msUntilHour = msUntilHour;
|
|
45
45
|
exports.scheduleConsolidation = scheduleConsolidation;
|
|
46
|
+
const types_js_1 = require("./types.js");
|
|
46
47
|
const retrieval_js_1 = require("./retrieval.js");
|
|
47
48
|
const storage = __importStar(require("./storage.js"));
|
|
48
49
|
const prompts_js_1 = require("./prompts.js");
|
|
@@ -487,12 +488,17 @@ async function stageDomainCuration(db, llm, budget, dryRun, stateDir) {
|
|
|
487
488
|
console.log(`[hicortex] Domain curation: ${domains.length} domains from ${projectRows.length} projects`);
|
|
488
489
|
return { curated: true, domains: domains.length };
|
|
489
490
|
}
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
491
|
+
/** Batch size for LLM edge classification calls. */
|
|
492
|
+
const EDGE_CLASSIFICATION_BATCH_SIZE = 8;
|
|
493
|
+
/** Valid relationship type set for fast lookup. */
|
|
494
|
+
const VALID_REL_SET = new Set(types_js_1.VALID_RELATIONSHIP_TYPES);
|
|
495
|
+
async function stageLinks(db, memories, embedFn, dryRun, llm, budget) {
|
|
494
496
|
let autoLinked = 0;
|
|
497
|
+
let llmClassified = 0;
|
|
498
|
+
let heuristicFallback = 0;
|
|
495
499
|
let failed = 0;
|
|
500
|
+
// Phase A: Discovery — collect candidates via vector similarity
|
|
501
|
+
const candidates = [];
|
|
496
502
|
for (const mem of memories) {
|
|
497
503
|
try {
|
|
498
504
|
const embedding = await embedFn(mem.content);
|
|
@@ -500,27 +506,78 @@ async function stageLinks(db, memories, embedFn, dryRun) {
|
|
|
500
506
|
for (const neighbor of neighbors) {
|
|
501
507
|
const similarity = 1.0 - neighbor.distance;
|
|
502
508
|
if (similarity > CONSOLIDATE_LINK_THRESHOLD) {
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
509
|
+
const heuristicType = classifyRelationship(mem, neighbor, similarity);
|
|
510
|
+
candidates.push({ source: mem, target: neighbor, similarity, heuristicType });
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
catch {
|
|
515
|
+
failed++;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
if (candidates.length === 0) {
|
|
519
|
+
return { auto_linked: 0, llm_classified: 0, heuristic_fallback: 0, failed };
|
|
520
|
+
}
|
|
521
|
+
// Phase B: LLM batch classification
|
|
522
|
+
// Build batches and classify with LLM where budget allows
|
|
523
|
+
const classifiedTypes = new Array(candidates.length);
|
|
524
|
+
for (let i = 0; i < candidates.length; i += EDGE_CLASSIFICATION_BATCH_SIZE) {
|
|
525
|
+
const batch = candidates.slice(i, i + EDGE_CLASSIFICATION_BATCH_SIZE);
|
|
526
|
+
// Attempt LLM classification if budget allows
|
|
527
|
+
if (budget.use("edge_classification")) {
|
|
528
|
+
try {
|
|
529
|
+
const pairsBlock = batch.map((c, idx) => {
|
|
530
|
+
const srcContent = c.source.content.slice(0, 200);
|
|
531
|
+
const tgtContent = c.target.content.slice(0, 200);
|
|
532
|
+
return `[${idx}] SOURCE: ${c.source.memory_type} | ${c.source.project ?? "global"} | ${srcContent}\n TARGET: ${c.target.memory_type} | ${c.target.project ?? "global"} | ${tgtContent}\n similarity: ${c.similarity.toFixed(2)}`;
|
|
533
|
+
}).join("\n\n");
|
|
534
|
+
const prompt = (0, prompts_js_1.edgeClassification)(pairsBlock);
|
|
535
|
+
const raw = await llm.completeFast(prompt, 512);
|
|
536
|
+
const parsed = parseJsonLenient(raw, []);
|
|
537
|
+
if (Array.isArray(parsed) && parsed.length > 0) {
|
|
538
|
+
for (let j = 0; j < batch.length; j++) {
|
|
539
|
+
const llmType = parsed[j];
|
|
540
|
+
if (typeof llmType === "string" && VALID_REL_SET.has(llmType)) {
|
|
541
|
+
classifiedTypes[i + j] = llmType;
|
|
542
|
+
llmClassified++;
|
|
508
543
|
}
|
|
509
|
-
|
|
510
|
-
|
|
544
|
+
else {
|
|
545
|
+
// Invalid type from LLM — fall back to heuristic
|
|
546
|
+
classifiedTypes[i + j] = batch[j].heuristicType;
|
|
547
|
+
heuristicFallback++;
|
|
511
548
|
}
|
|
512
549
|
}
|
|
513
|
-
|
|
514
|
-
autoLinked++;
|
|
515
|
-
}
|
|
550
|
+
continue;
|
|
516
551
|
}
|
|
517
552
|
}
|
|
553
|
+
catch {
|
|
554
|
+
// LLM call failed — fall through to heuristic for this batch
|
|
555
|
+
}
|
|
518
556
|
}
|
|
519
|
-
|
|
520
|
-
|
|
557
|
+
// Budget exhausted or LLM failed — use heuristic for entire batch
|
|
558
|
+
for (let j = 0; j < batch.length; j++) {
|
|
559
|
+
classifiedTypes[i + j] = batch[j].heuristicType;
|
|
560
|
+
heuristicFallback++;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
// Phase C: Store all classified links
|
|
564
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
565
|
+
const c = candidates[i];
|
|
566
|
+
const relationship = classifiedTypes[i];
|
|
567
|
+
if (!dryRun) {
|
|
568
|
+
try {
|
|
569
|
+
storage.addLink(db, c.source.id, c.target.id, relationship, c.similarity);
|
|
570
|
+
autoLinked++;
|
|
571
|
+
}
|
|
572
|
+
catch {
|
|
573
|
+
failed++;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
else {
|
|
577
|
+
autoLinked++;
|
|
521
578
|
}
|
|
522
579
|
}
|
|
523
|
-
return { auto_linked: autoLinked, failed };
|
|
580
|
+
return { auto_linked: autoLinked, llm_classified: llmClassified, heuristic_fallback: heuristicFallback, failed };
|
|
524
581
|
}
|
|
525
582
|
/**
|
|
526
583
|
* Classify the relationship between two memories based on type, temporal ordering, and similarity.
|
|
@@ -660,8 +717,8 @@ async function runConsolidation(db, llm, embedFn, dryRun = false, skipReflection
|
|
|
660
717
|
}
|
|
661
718
|
// Stage 2.7: Domain Curation
|
|
662
719
|
report.stages.domain_curation = await stageDomainCuration(db, llm, budget, dryRun, stateDir);
|
|
663
|
-
// Stage 3: Link Discovery
|
|
664
|
-
report.stages.links = await stageLinks(db, precheck.newMemories, embedFn, dryRun);
|
|
720
|
+
// Stage 3: Link Discovery (with LLM-assisted edge classification)
|
|
721
|
+
report.stages.links = await stageLinks(db, precheck.newMemories, embedFn, dryRun, llm, budget);
|
|
665
722
|
// Stage 3.5: Hub Detection — boost highly-connected memories
|
|
666
723
|
report.stages.hub_boost = stageHubBoost(db, dryRun);
|
|
667
724
|
// Stage 4: Decay & Prune
|
package/dist/graph.d.ts
CHANGED
|
@@ -50,5 +50,5 @@ export interface GraphNeighbor {
|
|
|
50
50
|
content: string;
|
|
51
51
|
project: string | null;
|
|
52
52
|
}
|
|
53
|
-
export declare function getNeighbors(db: Database.Database, memoryId: string, limit?: number): GraphNeighbor[];
|
|
53
|
+
export declare function getNeighbors(db: Database.Database, memoryId: string, limit?: number, relationship?: string): GraphNeighbor[];
|
|
54
54
|
export declare function shortestPath(db: Database.Database, fromId: string, toId: string, maxDepth?: number): string[] | null;
|
package/dist/graph.js
CHANGED
|
@@ -175,14 +175,20 @@ function detectHubs(db, thresholdMultiplier = 2, minLinks = 3) {
|
|
|
175
175
|
}
|
|
176
176
|
return hubs;
|
|
177
177
|
}
|
|
178
|
-
function getNeighbors(db, memoryId, limit = 10) {
|
|
179
|
-
|
|
180
|
-
.prepare(`SELECT source_id, target_id, relationship, strength
|
|
178
|
+
function getNeighbors(db, memoryId, limit = 10, relationship) {
|
|
179
|
+
let sql = `SELECT source_id, target_id, relationship, strength
|
|
181
180
|
FROM memory_links
|
|
182
|
-
WHERE source_id = ? OR target_id = ?
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
181
|
+
WHERE (source_id = ? OR target_id = ?)`;
|
|
182
|
+
const params = [memoryId, memoryId];
|
|
183
|
+
if (relationship) {
|
|
184
|
+
sql += ` AND relationship = ?`;
|
|
185
|
+
params.push(relationship);
|
|
186
|
+
}
|
|
187
|
+
sql += ` ORDER BY strength DESC LIMIT ?`;
|
|
188
|
+
params.push(limit);
|
|
189
|
+
const rows = db
|
|
190
|
+
.prepare(sql)
|
|
191
|
+
.all(...params);
|
|
186
192
|
const results = [];
|
|
187
193
|
for (const row of rows) {
|
|
188
194
|
const isOutgoing = row.source_id === memoryId;
|
package/dist/mcp-server.js
CHANGED
|
@@ -251,7 +251,8 @@ function createMcpServer() {
|
|
|
251
251
|
target_id: zod_1.z.string().optional().describe("Target memory ID (required for path operation)"),
|
|
252
252
|
limit: zod_1.z.coerce.number().optional().describe("Max results (default 10)"),
|
|
253
253
|
domain: zod_1.z.string().optional().describe("Filter hubs by domain"),
|
|
254
|
-
|
|
254
|
+
relationship: zod_1.z.string().optional().describe("Filter neighbors by relationship type (e.g., CONTRADICTS, SUPERSEDES, derives)"),
|
|
255
|
+
}, async ({ operation, id, target_id, limit: resultLimit, domain: filterDomain, relationship: filterRelationship }) => {
|
|
255
256
|
if (!db)
|
|
256
257
|
return { content: [{ type: "text", text: "Hicortex not initialized" }], isError: true };
|
|
257
258
|
try {
|
|
@@ -261,7 +262,7 @@ function createMcpServer() {
|
|
|
261
262
|
const resolvedId = resolveMemoryId(db, id);
|
|
262
263
|
if (!resolvedId)
|
|
263
264
|
return { content: [{ type: "text", text: `Memory not found: ${id}` }], isError: true };
|
|
264
|
-
const neighbors = (0, graph_js_1.getNeighbors)(db, resolvedId, resultLimit ?? 10);
|
|
265
|
+
const neighbors = (0, graph_js_1.getNeighbors)(db, resolvedId, resultLimit ?? 10, filterRelationship);
|
|
265
266
|
if (neighbors.length === 0)
|
|
266
267
|
return { content: [{ type: "text", text: "No connected memories found." }] };
|
|
267
268
|
const text = neighbors.map((n) => `[${n.direction}] ${n.relationship} (${n.strength.toFixed(2)})\n ${n.id.slice(0, 8)} | ${n.project ?? "global"} | ${n.content}`).join("\n\n");
|
package/dist/prompts.d.ts
CHANGED
|
@@ -19,3 +19,8 @@ export declare function distillation(projectName: string, date: string, transcri
|
|
|
19
19
|
* Used during consolidation (Pro only, one call per nightly when projects change).
|
|
20
20
|
*/
|
|
21
21
|
export declare function domainCuration(projectLines: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Edge classification prompt. Presents memory pairs and asks the LLM to
|
|
24
|
+
* choose the most specific relationship type for each.
|
|
25
|
+
*/
|
|
26
|
+
export declare function edgeClassification(pairsBlock: string): string;
|
package/dist/prompts.js
CHANGED
|
@@ -8,6 +8,7 @@ exports.importanceScoring = importanceScoring;
|
|
|
8
8
|
exports.reflection = reflection;
|
|
9
9
|
exports.distillation = distillation;
|
|
10
10
|
exports.domainCuration = domainCuration;
|
|
11
|
+
exports.edgeClassification = edgeClassification;
|
|
11
12
|
/**
|
|
12
13
|
* Importance scoring prompt. Takes a {memories_block} with indexed memories.
|
|
13
14
|
*/
|
|
@@ -169,3 +170,31 @@ Rules:
|
|
|
169
170
|
|
|
170
171
|
Respond with ONLY a JSON array. No explanations.`;
|
|
171
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* Edge classification prompt. Presents memory pairs and asks the LLM to
|
|
175
|
+
* choose the most specific relationship type for each.
|
|
176
|
+
*/
|
|
177
|
+
function edgeClassification(pairsBlock) {
|
|
178
|
+
return `You are a memory graph analyst. Classify the relationship between each memory pair.
|
|
179
|
+
|
|
180
|
+
VALID RELATIONSHIP TYPES:
|
|
181
|
+
- derives: A lesson or fact was derived from episodes (lesson ← episode)
|
|
182
|
+
- updates: A newer memory updates/replaces an older one on the same topic
|
|
183
|
+
- extends: Memory adds detail to another within the same project
|
|
184
|
+
- relates_to: Generic association (use ONLY when no specific type fits)
|
|
185
|
+
- CONTRADICTS: Memories give opposite advice or conflicting information
|
|
186
|
+
- SUPERSEDES: One memory fully replaces another (stronger than "updates")
|
|
187
|
+
- DEPENDS_ON: One memory's validity requires the other (prerequisite)
|
|
188
|
+
- CAUSED_BY: One event/decision directly caused the other
|
|
189
|
+
- VALIDATES: One memory confirms or provides evidence for the other
|
|
190
|
+
|
|
191
|
+
Choose the MOST SPECIFIC type. Prefer specific types over "relates_to".
|
|
192
|
+
|
|
193
|
+
MEMORY PAIRS:
|
|
194
|
+
${pairsBlock}
|
|
195
|
+
|
|
196
|
+
Respond with ONLY a JSON array of relationship type strings, one per pair, in order.
|
|
197
|
+
Example for 3 pairs: ["CAUSED_BY", "extends", "VALIDATES"]
|
|
198
|
+
|
|
199
|
+
No explanations. Just the JSON array.`;
|
|
200
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -27,6 +27,10 @@ export interface MemoryLink {
|
|
|
27
27
|
strength: number;
|
|
28
28
|
created_at: string;
|
|
29
29
|
}
|
|
30
|
+
/** All valid relationship types for memory links.
|
|
31
|
+
* lowercase = heuristic (legacy), UPPER_SNAKE_CASE = LLM-classified (v0.7+). */
|
|
32
|
+
export declare const VALID_RELATIONSHIP_TYPES: readonly ["derives", "updates", "extends", "relates_to", "CONTRADICTS", "SUPERSEDES", "DEPENDS_ON", "CAUSED_BY", "VALIDATES"];
|
|
33
|
+
export type RelationshipType = typeof VALID_RELATIONSHIP_TYPES[number];
|
|
30
34
|
/** A search result with scoring metadata. */
|
|
31
35
|
export interface MemorySearchResult {
|
|
32
36
|
id: string;
|
|
@@ -76,6 +80,8 @@ export interface ConsolidationReport {
|
|
|
76
80
|
};
|
|
77
81
|
links?: {
|
|
78
82
|
auto_linked: number;
|
|
83
|
+
llm_classified?: number;
|
|
84
|
+
heuristic_fallback?: number;
|
|
79
85
|
failed: number;
|
|
80
86
|
};
|
|
81
87
|
decay_prune?: {
|
package/dist/types.js
CHANGED
|
@@ -4,3 +4,10 @@
|
|
|
4
4
|
* Ported from the Python hicortex codebase.
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.VALID_RELATIONSHIP_TYPES = void 0;
|
|
8
|
+
/** All valid relationship types for memory links.
|
|
9
|
+
* lowercase = heuristic (legacy), UPPER_SNAKE_CASE = LLM-classified (v0.7+). */
|
|
10
|
+
exports.VALID_RELATIONSHIP_TYPES = [
|
|
11
|
+
"derives", "updates", "extends", "relates_to",
|
|
12
|
+
"CONTRADICTS", "SUPERSEDES", "DEPENDS_ON", "CAUSED_BY", "VALIDATES",
|
|
13
|
+
];
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "hicortex",
|
|
3
3
|
"name": "Hicortex — Long-term Memory That Learns",
|
|
4
4
|
"description": "Your agents remember past decisions, avoid repeated mistakes, and get smarter every day. Nightly reflection generates actionable lessons that automatically update agent behavior.",
|
|
5
|
-
"version": "0.7.
|
|
5
|
+
"version": "0.7.1",
|
|
6
6
|
"kind": "lifecycle",
|
|
7
7
|
"skills": ["./skills/hicortex-memory", "./skills/hicortex-learn", "./skills/hicortex-activate"],
|
|
8
8
|
"configSchema": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gamaze/hicortex",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
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": {
|