@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.
Files changed (54) hide show
  1. package/README.md +57 -39
  2. package/dist/claude-md.d.ts +9 -21
  3. package/dist/claude-md.js +9 -241
  4. package/dist/cli.d.ts +3 -2
  5. package/dist/cli.js +29 -11
  6. package/dist/consolidate.js +76 -26
  7. package/dist/db.js +24 -0
  8. package/dist/embedder.d.ts +11 -0
  9. package/dist/embedder.js +27 -0
  10. package/dist/extensions.d.ts +41 -88
  11. package/dist/extensions.js +36 -61
  12. package/dist/features.d.ts +21 -25
  13. package/dist/features.js +47 -83
  14. package/dist/graph.d.ts +1 -1
  15. package/dist/graph.js +13 -7
  16. package/dist/hermes-transcript-reader.d.ts +27 -0
  17. package/dist/hermes-transcript-reader.js +134 -0
  18. package/dist/index.d.ts +16 -4
  19. package/dist/index.js +252 -344
  20. package/dist/init.d.ts +41 -1
  21. package/dist/init.js +545 -190
  22. package/dist/lesson-selection.d.ts +62 -0
  23. package/dist/lesson-selection.js +159 -0
  24. package/dist/lessons-context.d.ts +17 -0
  25. package/dist/lessons-context.js +96 -0
  26. package/dist/llm.d.ts +42 -29
  27. package/dist/llm.js +89 -270
  28. package/dist/mcp-server.d.ts +0 -1
  29. package/dist/mcp-server.js +407 -88
  30. package/dist/nightly.d.ts +9 -6
  31. package/dist/nightly.js +197 -357
  32. package/dist/oc-transcript-reader.d.ts +20 -0
  33. package/dist/oc-transcript-reader.js +61 -0
  34. package/dist/pi-transcript-reader.d.ts +1 -0
  35. package/dist/prompts.d.ts +5 -0
  36. package/dist/prompts.js +29 -0
  37. package/dist/status.js +22 -2
  38. package/dist/storage.d.ts +7 -1
  39. package/dist/storage.js +28 -7
  40. package/dist/transcript-reader.d.ts +19 -0
  41. package/dist/transcript-reader.js +17 -3
  42. package/dist/types.d.ts +16 -0
  43. package/dist/types.js +7 -0
  44. package/dist/uninstall.js +31 -1
  45. package/hermes-plugin/hicortex/README.md +77 -0
  46. package/hermes-plugin/hicortex/__init__.py +17 -0
  47. package/hermes-plugin/hicortex/client.py +162 -0
  48. package/hermes-plugin/hicortex/config.py +105 -0
  49. package/hermes-plugin/hicortex/plugin.yaml +12 -0
  50. package/hermes-plugin/hicortex/provider.py +432 -0
  51. package/openclaw.plugin.json +17 -44
  52. package/package.json +7 -5
  53. package/dist/pro-loader.d.ts +0 -33
  54. 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
+ }
@@ -31,6 +31,7 @@ export interface TranscriptBatch {
31
31
  projectName: string;
32
32
  date: string;
33
33
  entries: unknown[];
34
+ sourceAgent?: string;
34
35
  }
35
36
  /**
36
37
  * Read Pi session transcripts modified after `since`.
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
- // License
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
- console.log(`License: ${licenseKey ? "configured" : "free tier (250 memories)"}`);
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 generated UUID.
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 generated UUID.
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
- db.prepare(`INSERT INTO memories
53
- (id, content, base_strength, last_accessed, access_count,
54
- created_at, ingested_at, source_agent, source_session, project,
55
- privacy, memory_type)
56
- VALUES (?, ?, ?, ?, 0, ?, ?, ?, ?, ?, ?, ?)`).run(id, content, opts.baseStrength ?? 0.5, ts, ts, ingestedTs, opts.sourceAgent ?? "default", opts.sourceSession ?? null, opts.project ?? null, opts.privacy ?? "WORK", opts.memoryType ?? "episode");
57
- db.prepare("INSERT INTO memory_vectors (id, embedding) VALUES (?, ?)").run(id, embedToBlob(embedding));
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 < 4)
85
- return null; // Too short to be meaningful
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 < 4)
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 CLAUDE.md block
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"]