@gamaze/hicortex 0.7.0 → 0.10.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 +57 -39
- package/dist/claude-md.d.ts +9 -21
- package/dist/claude-md.js +9 -241
- package/dist/cli.d.ts +3 -2
- package/dist/cli.js +29 -11
- package/dist/consolidate.js +76 -26
- package/dist/db.js +24 -0
- package/dist/embedder.d.ts +11 -0
- package/dist/embedder.js +27 -0
- package/dist/extensions.d.ts +41 -88
- package/dist/extensions.js +36 -61
- package/dist/features.d.ts +21 -25
- package/dist/features.js +47 -83
- package/dist/graph.d.ts +1 -1
- package/dist/graph.js +13 -7
- package/dist/hermes-transcript-reader.d.ts +27 -0
- package/dist/hermes-transcript-reader.js +134 -0
- package/dist/index.d.ts +16 -4
- package/dist/index.js +252 -344
- package/dist/init.d.ts +41 -1
- package/dist/init.js +545 -190
- package/dist/lesson-selection.d.ts +62 -0
- package/dist/lesson-selection.js +159 -0
- package/dist/lessons-context.d.ts +17 -0
- package/dist/lessons-context.js +96 -0
- package/dist/llm.d.ts +42 -29
- package/dist/llm.js +89 -270
- package/dist/mcp-server.d.ts +0 -1
- package/dist/mcp-server.js +407 -88
- package/dist/nightly.d.ts +9 -6
- package/dist/nightly.js +197 -357
- package/dist/oc-transcript-reader.d.ts +20 -0
- package/dist/oc-transcript-reader.js +61 -0
- package/dist/pi-transcript-reader.d.ts +1 -0
- package/dist/prompts.d.ts +5 -0
- package/dist/prompts.js +29 -0
- package/dist/status.js +22 -2
- package/dist/storage.d.ts +7 -1
- package/dist/storage.js +28 -7
- package/dist/transcript-reader.d.ts +19 -0
- package/dist/transcript-reader.js +17 -3
- package/dist/types.d.ts +16 -0
- package/dist/types.js +7 -0
- package/dist/uninstall.js +31 -1
- package/hermes-plugin/hicortex/README.md +77 -0
- package/hermes-plugin/hicortex/__init__.py +17 -0
- package/hermes-plugin/hicortex/client.py +162 -0
- package/hermes-plugin/hicortex/config.py +105 -0
- package/hermes-plugin/hicortex/plugin.yaml +12 -0
- package/hermes-plugin/hicortex/provider.py +432 -0
- package/openclaw.plugin.json +17 -44
- package/package.json +7 -5
- package/dist/pro-loader.d.ts +0 -33
- package/dist/pro-loader.js +0 -187
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenClaw transcript reader — reads OC session JSONL files.
|
|
3
|
+
*
|
|
4
|
+
* OC persists sessions at ~/.openclaw/agents/<agentId>/sessions/*.jsonl in the
|
|
5
|
+
* Pi version-3 event format (OpenClaw is Pi-runtime based): event types
|
|
6
|
+
* `session`, `model_change`, `thinking_level_change`, `message`, `custom`.
|
|
7
|
+
* The Pi parser handles the format; this wrapper only adapts the directory
|
|
8
|
+
* layout (one extra `agents/<agentId>` level) and sets OC provenance.
|
|
9
|
+
*
|
|
10
|
+
* Known limitation: rotated files (`*.jsonl.reset.<ts>`) are not read — only
|
|
11
|
+
* live `*.jsonl` files. Server-side session dedup keeps re-reads idempotent.
|
|
12
|
+
*/
|
|
13
|
+
import { type TranscriptBatch } from "./pi-transcript-reader.js";
|
|
14
|
+
/**
|
|
15
|
+
* Read OpenClaw session transcripts modified after `since`.
|
|
16
|
+
*
|
|
17
|
+
* @param since Only return sessions with mtime > this date
|
|
18
|
+
* @param agentsDir Override the OC agents directory (default: ~/.openclaw/agents/)
|
|
19
|
+
*/
|
|
20
|
+
export declare function readOcTranscripts(since: Date, agentsDir?: string): TranscriptBatch[];
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* OpenClaw transcript reader — reads OC session JSONL files.
|
|
4
|
+
*
|
|
5
|
+
* OC persists sessions at ~/.openclaw/agents/<agentId>/sessions/*.jsonl in the
|
|
6
|
+
* Pi version-3 event format (OpenClaw is Pi-runtime based): event types
|
|
7
|
+
* `session`, `model_change`, `thinking_level_change`, `message`, `custom`.
|
|
8
|
+
* The Pi parser handles the format; this wrapper only adapts the directory
|
|
9
|
+
* layout (one extra `agents/<agentId>` level) and sets OC provenance.
|
|
10
|
+
*
|
|
11
|
+
* Known limitation: rotated files (`*.jsonl.reset.<ts>`) are not read — only
|
|
12
|
+
* live `*.jsonl` files. Server-side session dedup keeps re-reads idempotent.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.readOcTranscripts = readOcTranscripts;
|
|
16
|
+
const node_fs_1 = require("node:fs");
|
|
17
|
+
const node_path_1 = require("node:path");
|
|
18
|
+
const node_os_1 = require("node:os");
|
|
19
|
+
const pi_transcript_reader_js_1 = require("./pi-transcript-reader.js");
|
|
20
|
+
const DEFAULT_OC_AGENTS_DIR = (0, node_path_1.join)((0, node_os_1.homedir)(), ".openclaw", "agents");
|
|
21
|
+
/**
|
|
22
|
+
* Read OpenClaw session transcripts modified after `since`.
|
|
23
|
+
*
|
|
24
|
+
* @param since Only return sessions with mtime > this date
|
|
25
|
+
* @param agentsDir Override the OC agents directory (default: ~/.openclaw/agents/)
|
|
26
|
+
*/
|
|
27
|
+
function readOcTranscripts(since, agentsDir = DEFAULT_OC_AGENTS_DIR) {
|
|
28
|
+
let agentIds;
|
|
29
|
+
try {
|
|
30
|
+
agentIds = (0, node_fs_1.readdirSync)(agentsDir);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// No OpenClaw install — not an error.
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
const batches = [];
|
|
37
|
+
for (const agentId of agentIds) {
|
|
38
|
+
const agentPath = (0, node_path_1.join)(agentsDir, agentId);
|
|
39
|
+
try {
|
|
40
|
+
if (!(0, node_fs_1.statSync)(agentPath).isDirectory())
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
// agents/<agentId>/ contains a `sessions/` child with *.jsonl — exactly
|
|
47
|
+
// the <root>/<projectDir>/*.jsonl shape readPiTranscripts walks.
|
|
48
|
+
for (const batch of (0, pi_transcript_reader_js_1.readPiTranscripts)(since, agentPath)) {
|
|
49
|
+
batches.push({
|
|
50
|
+
...batch,
|
|
51
|
+
// The Pi walk labels the project from the cwd or the "sessions" dir
|
|
52
|
+
// name — the agent id is the meaningful label for OC.
|
|
53
|
+
projectName: batch.projectName && batch.projectName !== "sessions"
|
|
54
|
+
? batch.projectName
|
|
55
|
+
: agentId,
|
|
56
|
+
sourceAgent: `openclaw/${agentId}`,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return batches;
|
|
61
|
+
}
|
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/status.js
CHANGED
|
@@ -9,6 +9,7 @@ const node_path_1 = require("node:path");
|
|
|
9
9
|
const node_os_1 = require("node:os");
|
|
10
10
|
const node_child_process_1 = require("node:child_process");
|
|
11
11
|
const db_js_1 = require("./db.js");
|
|
12
|
+
const features_js_1 = require("./features.js");
|
|
12
13
|
const HICORTEX_HOME = (0, node_path_1.join)((0, node_os_1.homedir)(), ".hicortex");
|
|
13
14
|
const CC_SETTINGS = (0, node_path_1.join)((0, node_os_1.homedir)(), ".claude", "settings.json");
|
|
14
15
|
const OC_CONFIG = (0, node_path_1.join)((0, node_os_1.homedir)(), ".openclaw", "openclaw.json");
|
|
@@ -34,15 +35,34 @@ async function runStatus() {
|
|
|
34
35
|
console.log(`DB error: ${err instanceof Error ? err.message : String(err)}`);
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
|
-
//
|
|
38
|
+
// Config: license key + auth token
|
|
38
39
|
const configPath = (0, node_path_1.join)(HICORTEX_HOME, "config.json");
|
|
39
40
|
let licenseKey = "";
|
|
41
|
+
let savedAuthToken = "";
|
|
42
|
+
let isClientMode = false;
|
|
40
43
|
try {
|
|
41
44
|
const config = JSON.parse((0, node_fs_1.readFileSync)(configPath, "utf-8"));
|
|
42
45
|
licenseKey = config.licenseKey ?? "";
|
|
46
|
+
savedAuthToken = config.authToken ?? "";
|
|
47
|
+
isClientMode = config.mode === "client";
|
|
43
48
|
}
|
|
44
49
|
catch { /* no config */ }
|
|
45
|
-
|
|
50
|
+
const validated = (0, features_js_1.getValidatedLicense)();
|
|
51
|
+
if (validated?.valid && validated.tier) {
|
|
52
|
+
console.log(`License: ${validated.tier} (licensed)`);
|
|
53
|
+
}
|
|
54
|
+
else if (licenseKey) {
|
|
55
|
+
console.log(`License: key configured (not yet validated)`);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
console.log(`License: noncommercial (no key)`);
|
|
59
|
+
}
|
|
60
|
+
if (!isClientMode && savedAuthToken) {
|
|
61
|
+
console.log(`Auth token: ${savedAuthToken} (clients connect with this token)`);
|
|
62
|
+
}
|
|
63
|
+
else if (!isClientMode && !savedAuthToken) {
|
|
64
|
+
console.log(`Auth token: not configured (run: npx @gamaze/hicortex init)`);
|
|
65
|
+
}
|
|
46
66
|
console.log();
|
|
47
67
|
// Adapters
|
|
48
68
|
console.log("Adapters:");
|
package/dist/storage.d.ts
CHANGED
|
@@ -9,7 +9,13 @@ import type { Memory, MemoryLink, InsertMemoryOptions } from "./types.js";
|
|
|
9
9
|
*/
|
|
10
10
|
export declare function embedToBlob(embedding: Float32Array): Buffer;
|
|
11
11
|
/**
|
|
12
|
-
* Insert a memory and its vector embedding. Returns the
|
|
12
|
+
* Insert a memory and its vector embedding. Returns the memory's UUID.
|
|
13
|
+
*
|
|
14
|
+
* Idempotent on `sourceSession`: if a memory with that source_session already
|
|
15
|
+
* exists (UNIQUE index from migration v4), the insert is skipped and the
|
|
16
|
+
* EXISTING memory's id is returned (no vector re-insert). This lets `/ingest`
|
|
17
|
+
* and `/distill` safely retry a segment without double-inserting. Callers that
|
|
18
|
+
* omit sourceSession (NULL — nightly distillation, tests) never collide.
|
|
13
19
|
*/
|
|
14
20
|
export declare function insertMemory(db: Database.Database, content: string, embedding: Float32Array, opts?: InsertMemoryOptions): string;
|
|
15
21
|
/**
|
package/dist/storage.js
CHANGED
|
@@ -43,18 +43,39 @@ function rowToMemory(row) {
|
|
|
43
43
|
// Single memory CRUD
|
|
44
44
|
// ---------------------------------------------------------------------------
|
|
45
45
|
/**
|
|
46
|
-
* Insert a memory and its vector embedding. Returns the
|
|
46
|
+
* Insert a memory and its vector embedding. Returns the memory's UUID.
|
|
47
|
+
*
|
|
48
|
+
* Idempotent on `sourceSession`: if a memory with that source_session already
|
|
49
|
+
* exists (UNIQUE index from migration v4), the insert is skipped and the
|
|
50
|
+
* EXISTING memory's id is returned (no vector re-insert). This lets `/ingest`
|
|
51
|
+
* and `/distill` safely retry a segment without double-inserting. Callers that
|
|
52
|
+
* omit sourceSession (NULL — nightly distillation, tests) never collide.
|
|
47
53
|
*/
|
|
48
54
|
function insertMemory(db, content, embedding, opts = {}) {
|
|
49
55
|
const id = (0, node_crypto_1.randomUUID)();
|
|
50
56
|
const ts = opts.createdAt ?? nowIso();
|
|
51
57
|
const ingestedTs = nowIso();
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
+
const sourceSession = opts.sourceSession ?? null;
|
|
59
|
+
const result = db
|
|
60
|
+
.prepare(`INSERT OR IGNORE INTO memories
|
|
61
|
+
(id, content, base_strength, last_accessed, access_count,
|
|
62
|
+
created_at, ingested_at, source_agent, source_session, project,
|
|
63
|
+
privacy, memory_type)
|
|
64
|
+
VALUES (?, ?, ?, ?, 0, ?, ?, ?, ?, ?, ?, ?)`)
|
|
65
|
+
.run(id, content, opts.baseStrength ?? 0.5, ts, ts, ingestedTs, opts.sourceAgent ?? "default", sourceSession, opts.project ?? null, opts.privacy ?? "WORK", opts.memoryType ?? "episode");
|
|
66
|
+
if (result.changes > 0) {
|
|
67
|
+
// New row — store its vector.
|
|
68
|
+
db.prepare("INSERT INTO memory_vectors (id, embedding) VALUES (?, ?)").run(id, embedToBlob(embedding));
|
|
69
|
+
return id;
|
|
70
|
+
}
|
|
71
|
+
// Collision on UNIQUE source_session — return the existing memory's id.
|
|
72
|
+
if (sourceSession) {
|
|
73
|
+
const existing = db
|
|
74
|
+
.prepare("SELECT id FROM memories WHERE source_session = ?")
|
|
75
|
+
.get(sourceSession);
|
|
76
|
+
if (existing)
|
|
77
|
+
return existing.id;
|
|
78
|
+
}
|
|
58
79
|
return id;
|
|
59
80
|
}
|
|
60
81
|
/**
|
|
@@ -12,7 +12,26 @@ export interface TranscriptBatch {
|
|
|
12
12
|
projectName: string;
|
|
13
13
|
date: string;
|
|
14
14
|
entries: unknown[];
|
|
15
|
+
/**
|
|
16
|
+
* Optional source-agent label (e.g. "hermes/lenny"). When set, the nightly
|
|
17
|
+
* pipeline uses it verbatim for provenance instead of the default
|
|
18
|
+
* `claude-code/<project>`. Lets per-harness readers stamp their own origin.
|
|
19
|
+
*/
|
|
20
|
+
sourceAgent?: string;
|
|
15
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Cheap pre-filter: skip CC session FILES with fewer than this many raw JSONL
|
|
24
|
+
* lines/entries — they're degenerate (aborted/empty) and not worth parsing.
|
|
25
|
+
*
|
|
26
|
+
* This is deliberately NOT the "is there meaningful content" gate. That is the
|
|
27
|
+
* post-denoise `transcript.length < MIN_CONVERSATION_CHARS` (200) check in
|
|
28
|
+
* nightly.ts, which measures actual conversation after tool/system noise is
|
|
29
|
+
* stripped. Raw entry count is a lossy proxy — a dense 2-message exchange can
|
|
30
|
+
* be very meaningful — so it's used only as a degenerate-file floor here, and
|
|
31
|
+
* intentionally NOT applied to the Hermes reader (which lets the 200-char
|
|
32
|
+
* content gate decide, so short dense sessions aren't dropped on count).
|
|
33
|
+
*/
|
|
34
|
+
export declare const MIN_TRANSCRIPT_ENTRIES = 4;
|
|
16
35
|
/**
|
|
17
36
|
* Read all CC transcripts modified since `since`.
|
|
18
37
|
* Returns one batch per session file.
|
|
@@ -9,10 +9,24 @@
|
|
|
9
9
|
* and feeds them to the existing distiller pipeline.
|
|
10
10
|
*/
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.MIN_TRANSCRIPT_ENTRIES = void 0;
|
|
12
13
|
exports.readCcTranscripts = readCcTranscripts;
|
|
13
14
|
const node_fs_1 = require("node:fs");
|
|
14
15
|
const node_path_1 = require("node:path");
|
|
15
16
|
const node_os_1 = require("node:os");
|
|
17
|
+
/**
|
|
18
|
+
* Cheap pre-filter: skip CC session FILES with fewer than this many raw JSONL
|
|
19
|
+
* lines/entries — they're degenerate (aborted/empty) and not worth parsing.
|
|
20
|
+
*
|
|
21
|
+
* This is deliberately NOT the "is there meaningful content" gate. That is the
|
|
22
|
+
* post-denoise `transcript.length < MIN_CONVERSATION_CHARS` (200) check in
|
|
23
|
+
* nightly.ts, which measures actual conversation after tool/system noise is
|
|
24
|
+
* stripped. Raw entry count is a lossy proxy — a dense 2-message exchange can
|
|
25
|
+
* be very meaningful — so it's used only as a degenerate-file floor here, and
|
|
26
|
+
* intentionally NOT applied to the Hermes reader (which lets the 200-char
|
|
27
|
+
* content gate decide, so short dense sessions aren't dropped on count).
|
|
28
|
+
*/
|
|
29
|
+
exports.MIN_TRANSCRIPT_ENTRIES = 4;
|
|
16
30
|
const CC_PROJECTS_DIR = (0, node_path_1.join)((0, node_os_1.homedir)(), ".claude", "projects");
|
|
17
31
|
/**
|
|
18
32
|
* Read all CC transcripts modified since `since`.
|
|
@@ -81,8 +95,8 @@ function parseTranscriptFile(filePath, projectName) {
|
|
|
81
95
|
return null;
|
|
82
96
|
}
|
|
83
97
|
const lines = raw.split("\n").filter((l) => l.trim());
|
|
84
|
-
if (lines.length <
|
|
85
|
-
return null; //
|
|
98
|
+
if (lines.length < exports.MIN_TRANSCRIPT_ENTRIES)
|
|
99
|
+
return null; // degenerate/empty file
|
|
86
100
|
const entries = [];
|
|
87
101
|
let lastTimestamp = "";
|
|
88
102
|
for (const line of lines) {
|
|
@@ -97,7 +111,7 @@ function parseTranscriptFile(filePath, projectName) {
|
|
|
97
111
|
// Skip malformed lines
|
|
98
112
|
}
|
|
99
113
|
}
|
|
100
|
-
if (entries.length <
|
|
114
|
+
if (entries.length < exports.MIN_TRANSCRIPT_ENTRIES)
|
|
101
115
|
return null;
|
|
102
116
|
// Extract session ID from filename (UUID.jsonl)
|
|
103
117
|
const sessionId = (0, node_path_1.basename)(filePath, ".jsonl");
|
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?: {
|
|
@@ -94,11 +100,21 @@ export interface ConsolidationReport {
|
|
|
94
100
|
/** Plugin configuration from openclaw.plugin.json configSchema. */
|
|
95
101
|
export interface HicortexConfig {
|
|
96
102
|
licenseKey?: string;
|
|
103
|
+
/** Hicortex server URL. Defaults to http://127.0.0.1:8787 (co-located server). */
|
|
104
|
+
serverUrl?: string;
|
|
105
|
+
/** Bearer token for the Hicortex server. Localhost bypasses auth by default. */
|
|
106
|
+
authToken?: string;
|
|
107
|
+
/** @deprecated Use the Hicortex server for distillation and consolidation. */
|
|
97
108
|
llmBaseUrl?: string;
|
|
109
|
+
/** @deprecated Use the Hicortex server for distillation and consolidation. */
|
|
98
110
|
llmApiKey?: string;
|
|
111
|
+
/** @deprecated Use the Hicortex server for distillation and consolidation. */
|
|
99
112
|
llmModel?: string;
|
|
113
|
+
/** @deprecated Use the Hicortex server for distillation and consolidation. */
|
|
100
114
|
reflectModel?: string;
|
|
115
|
+
/** @deprecated Consolidation is owned by the server nightly. */
|
|
101
116
|
consolidateHour?: number;
|
|
117
|
+
/** @deprecated The OC plugin no longer opens its own database. */
|
|
102
118
|
dbPath?: string;
|
|
103
119
|
}
|
|
104
120
|
/** Response from license validation API. */
|
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/dist/uninstall.js
CHANGED
|
@@ -82,7 +82,37 @@ async function runUninstall() {
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
console.log(" ✓ Removed /learn and /hicortex-activate commands");
|
|
85
|
-
// 4. Remove
|
|
85
|
+
// 4. Remove SessionStart hook (JSON merge — filter out entries containing "lessons-context")
|
|
86
|
+
try {
|
|
87
|
+
const raw = (0, node_fs_1.readFileSync)(CC_SETTINGS, "utf-8");
|
|
88
|
+
const settings = JSON.parse(raw);
|
|
89
|
+
const hooks = settings.hooks;
|
|
90
|
+
const sessionStart = hooks && Array.isArray(hooks.SessionStart) ? hooks.SessionStart : null;
|
|
91
|
+
if (hooks && sessionStart) {
|
|
92
|
+
const before = sessionStart.length;
|
|
93
|
+
const filtered = sessionStart.filter((entry) => {
|
|
94
|
+
if (typeof entry !== "object" || entry === null)
|
|
95
|
+
return true;
|
|
96
|
+
const e = entry;
|
|
97
|
+
if (Array.isArray(e.hooks)) {
|
|
98
|
+
return !e.hooks.some((h) => {
|
|
99
|
+
if (typeof h !== "object" || h === null)
|
|
100
|
+
return false;
|
|
101
|
+
const hook = h;
|
|
102
|
+
return typeof hook.command === "string" && hook.command.includes("lessons-context");
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return true;
|
|
106
|
+
});
|
|
107
|
+
if (filtered.length < before) {
|
|
108
|
+
hooks.SessionStart = filtered;
|
|
109
|
+
(0, node_fs_1.writeFileSync)(CC_SETTINGS, JSON.stringify(settings, null, 2));
|
|
110
|
+
console.log(" ✓ Removed SessionStart lessons-context hook");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch { /* no settings file or parse error — nothing to remove */ }
|
|
115
|
+
// 5. Remove CLAUDE.md block (old static block from pre-0.9.0; may still exist on upgrades)
|
|
86
116
|
if ((0, claude_md_js_1.removeLessonsBlock)(CLAUDE_MD)) {
|
|
87
117
|
console.log(" ✓ Removed Hicortex Learnings block from CLAUDE.md");
|
|
88
118
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Hicortex memory plugin for Hermes
|
|
2
|
+
|
|
3
|
+
> **Install:** `hermes plugins install gamaze-labs/hicortex-hermes-plugin` → `hermes memory setup hicortex` → restart your gateway.
|
|
4
|
+
>
|
|
5
|
+
> The [gamaze-labs/hicortex-hermes-plugin](https://github.com/gamaze-labs/hicortex-hermes-plugin) repo is a **generated read-only mirror** of `hermes-plugin/hicortex/` in the main Hicortex repo — do not open PRs there. Requires a running [Hicortex server](https://hicortex.gamaze.com/docs/installation.html) (local or remote) for recall; capture of Hermes sessions is handled by the server machine's nightly job.
|
|
6
|
+
|
|
7
|
+
Gives [Hermes](https://github.com/nousresearch/hermes-agent) agents self-learning memory backed by a [Hicortex](https://hicortex.gamaze.com/) server: their experience is distilled into lessons overnight, and they wake up wiser. **Recall-only:** the plugin retrieves relevant memories on every turn and injects distilled lessons into the system prompt. It has **no local LLM, no capture, no cron** — it is a thin recall shim.
|
|
8
|
+
|
|
9
|
+
**Capture happens centrally.** A nightly reader on the Hicortex server distills each agent's own session store (Hermes keeps full history in `~/.hermes/profiles/<agent>/state.db`), so nothing needs to be captured in real time. See `specs/2026-07-01-memory-capture-architecture.md` in the main repo.
|
|
10
|
+
|
|
11
|
+
## How it works
|
|
12
|
+
|
|
13
|
+
| Hermes hook | What it does | Hicortex call |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| `prefetch(query)` | recall relevant memories before each turn | `GET /search` |
|
|
16
|
+
| `queue_prefetch(query)` | background recall for the next turn | `GET /search` |
|
|
17
|
+
| `system_prompt_block()` | inject distilled lessons + memory index | `GET /lessons` |
|
|
18
|
+
| `get_tool_schemas()` | exposes the 8 unified tools + `hicortex_recall_recent` | see tool table below |
|
|
19
|
+
|
|
20
|
+
That's the whole surface. No `sync_turn`, no compaction/session-end capture — those are intentionally absent.
|
|
21
|
+
|
|
22
|
+
### Tools (unified 8 + 1 Hermes-specific)
|
|
23
|
+
|
|
24
|
+
| Tool | REST call | Description |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| `hicortex_search` | `GET /search` | Semantic search over long-term memory |
|
|
27
|
+
| `hicortex_context` | `GET /context` | Recent context memories by project |
|
|
28
|
+
| `hicortex_ingest` | `POST /ingest` | Store a new memory |
|
|
29
|
+
| `hicortex_lessons` | `GET /lessons` | Get distilled lessons |
|
|
30
|
+
| `hicortex_index` | `GET /index` | Knowledge domain index |
|
|
31
|
+
| `hicortex_graph` | `GET /graph` | Graph queries (neighbors/hubs/path) |
|
|
32
|
+
| `hicortex_update` | `POST /update` | Update a memory (re-embeds on content change) |
|
|
33
|
+
| `hicortex_delete` | `POST /delete` | Permanently delete a memory and its links |
|
|
34
|
+
| `hicortex_recall_recent` | `GET /context` | Hermes-specific alias for context recall |
|
|
35
|
+
|
|
36
|
+
## Prerequisites
|
|
37
|
+
|
|
38
|
+
- A reachable Hicortex server (default `http://localhost:8787`). Stand one up with `npx @gamaze/hicortex init`.
|
|
39
|
+
- The server needs the REST `/search`, `/context`, `/lessons` endpoints (Hicortex ≥ 0.7).
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
Hermes discovers user-installed providers from `$HERMES_HOME/plugins/<name>/`:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
cp -r hermes-plugin/hicortex "$HERMES_HOME/plugins/hicortex"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
(No `pip install` — the plugin is stdlib-only.)
|
|
50
|
+
|
|
51
|
+
## Configure & activate
|
|
52
|
+
|
|
53
|
+
Use Hermes' own tooling — it discovers this plugin automatically and writes `config.yaml` correctly (**never hand-edit `config.yaml` with scripts/regex**):
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
hermes memory setup # select "hicortex", enter the server URL/token when prompted
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Run it once per profile if you use Hermes profiles. Hermes allows **one** external memory provider at a time, so disable Honcho (or any other) first, then restart the gateway.
|
|
60
|
+
|
|
61
|
+
Config fields (`hicortex_url`, `default_project`, `recall_limit`, `privacy_filter`) can also be written to `$HERMES_HOME/plugins/hicortex/config.json` directly. The auth token is a **secret** — set it via env, not the JSON file:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
export HICORTEX_AUTH_TOKEN=hctx-default-token # or your custom token
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Env overrides: `HICORTEX_URL`, `HICORTEX_AUTH_TOKEN`.
|
|
68
|
+
|
|
69
|
+
## Topology
|
|
70
|
+
|
|
71
|
+
- **Server host:** runs Hicortex. Set `hicortex_url: http://localhost:8787` (localhost bypasses auth).
|
|
72
|
+
- **Other Hermes boxes:** set `hicortex_url` to the server's Tailscale hostname (e.g. `http://memory-server:8787`) and `HICORTEX_AUTH_TOKEN` to the server's token. Each box recalls from the same shared brain.
|
|
73
|
+
|
|
74
|
+
## Notes
|
|
75
|
+
|
|
76
|
+
- Localhost requests skip auth; remote requests require the bearer token.
|
|
77
|
+
- Recall failures are non-fatal — the plugin returns empty context and the turn proceeds.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Hicortex memory provider plugin for Hermes — recall-only.
|
|
2
|
+
|
|
3
|
+
Recall: prefetch() -> GET /search (relevant memories before each turn)
|
|
4
|
+
tools -> hicortex_search / hicortex_recall_recent
|
|
5
|
+
system_prompt_block -> lessons injected into the system prompt
|
|
6
|
+
|
|
7
|
+
Capture is NOT the plugin's job. A nightly reader on the Hicortex server
|
|
8
|
+
distills each agent's own session store (Hermes: ~/.hermes/profiles/<agent>/
|
|
9
|
+
state.db) centrally. The plugin has no local LLM, no spool, no timer, and no
|
|
10
|
+
capture path.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from agent.memory_provider import MemoryProvider # noqa: F401 (loader scans for this name)
|
|
14
|
+
|
|
15
|
+
from .provider import HicortexProvider
|
|
16
|
+
|
|
17
|
+
__all__ = ["HicortexProvider"]
|