@gamaze/hicortex 0.2.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.
@@ -0,0 +1,326 @@
1
+ "use strict";
2
+ /**
3
+ * Storage layer — CRUD operations for the SQLite + sqlite-vec database.
4
+ * Ported from hicortex/storage.py. All functions are synchronous (better-sqlite3).
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.embedToBlob = embedToBlob;
8
+ exports.insertMemory = insertMemory;
9
+ exports.getMemory = getMemory;
10
+ exports.updateMemory = updateMemory;
11
+ exports.strengthenMemory = strengthenMemory;
12
+ exports.deleteMemory = deleteMemory;
13
+ exports.vectorSearch = vectorSearch;
14
+ exports.searchFts = searchFts;
15
+ exports.addLink = addLink;
16
+ exports.getLinks = getLinks;
17
+ exports.deleteLinks = deleteLinks;
18
+ exports.insertMemoriesBatch = insertMemoriesBatch;
19
+ exports.countMemories = countMemories;
20
+ exports.getRecentMemories = getRecentMemories;
21
+ exports.getMemoriesSince = getMemoriesSince;
22
+ exports.getLessons = getLessons;
23
+ exports.getPruneCandidates = getPruneCandidates;
24
+ exports.getAllLinkCounts = getAllLinkCounts;
25
+ exports.getUnscoredMemories = getUnscoredMemories;
26
+ const node_crypto_1 = require("node:crypto");
27
+ // ---------------------------------------------------------------------------
28
+ // Helpers
29
+ // ---------------------------------------------------------------------------
30
+ function nowIso() {
31
+ return new Date().toISOString();
32
+ }
33
+ /**
34
+ * Serialize a Float32Array embedding to a Buffer for sqlite-vec.
35
+ */
36
+ function embedToBlob(embedding) {
37
+ return Buffer.from(embedding.buffer, embedding.byteOffset, embedding.byteLength);
38
+ }
39
+ function rowToMemory(row) {
40
+ return row;
41
+ }
42
+ // ---------------------------------------------------------------------------
43
+ // Single memory CRUD
44
+ // ---------------------------------------------------------------------------
45
+ /**
46
+ * Insert a memory and its vector embedding. Returns the generated UUID.
47
+ */
48
+ function insertMemory(db, content, embedding, opts = {}) {
49
+ const id = (0, node_crypto_1.randomUUID)();
50
+ const ts = opts.createdAt ?? nowIso();
51
+ 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
+ return id;
59
+ }
60
+ /**
61
+ * Get a single memory by ID. Returns null if not found.
62
+ */
63
+ function getMemory(db, memoryId) {
64
+ const row = db
65
+ .prepare("SELECT * FROM memories WHERE id = ?")
66
+ .get(memoryId);
67
+ return row ? rowToMemory(row) : null;
68
+ }
69
+ // Allowed columns for updateMemory
70
+ const ALLOWED_UPDATE_FIELDS = new Set([
71
+ "content",
72
+ "base_strength",
73
+ "last_accessed",
74
+ "access_count",
75
+ "source_agent",
76
+ "source_session",
77
+ "project",
78
+ "privacy",
79
+ "memory_type",
80
+ ]);
81
+ /**
82
+ * Update specific fields on a memory.
83
+ */
84
+ function updateMemory(db, memoryId, fields) {
85
+ const keys = Object.keys(fields);
86
+ if (keys.length === 0)
87
+ return;
88
+ for (const k of keys) {
89
+ if (!ALLOWED_UPDATE_FIELDS.has(k)) {
90
+ throw new Error(`Cannot update field: ${k}`);
91
+ }
92
+ }
93
+ const setClause = keys.map((k) => `"${k}" = ?`).join(", ");
94
+ const values = keys.map((k) => fields[k]);
95
+ values.push(memoryId);
96
+ db.prepare(`UPDATE memories SET ${setClause} WHERE id = ?`).run(...values);
97
+ }
98
+ /**
99
+ * Atomically increment access_count and reset last_accessed.
100
+ */
101
+ function strengthenMemory(db, memoryId, nowIsoStr) {
102
+ db.prepare(`UPDATE memories
103
+ SET access_count = access_count + 1, last_accessed = ?
104
+ WHERE id = ?`).run(nowIsoStr, memoryId);
105
+ }
106
+ /**
107
+ * Delete a memory, its vector, and all its links.
108
+ */
109
+ function deleteMemory(db, memoryId) {
110
+ db.prepare("DELETE FROM memory_links WHERE source_id = ? OR target_id = ?").run(memoryId, memoryId);
111
+ db.prepare("DELETE FROM memory_vectors WHERE id = ?").run(memoryId);
112
+ db.prepare("DELETE FROM memories WHERE id = ?").run(memoryId);
113
+ }
114
+ // ---------------------------------------------------------------------------
115
+ // Vector search
116
+ // ---------------------------------------------------------------------------
117
+ /**
118
+ * Find similar memories by vector distance. Returns memories with distance field.
119
+ */
120
+ function vectorSearch(db, queryEmbedding, limit = 10, excludeIds = []) {
121
+ const rows = db
122
+ .prepare("SELECT id, distance FROM memory_vectors WHERE embedding MATCH ? AND k = ? ORDER BY distance")
123
+ .all(embedToBlob(queryEmbedding), limit);
124
+ const excludeSet = new Set(excludeIds);
125
+ const results = [];
126
+ for (const row of rows) {
127
+ if (excludeSet.has(row.id))
128
+ continue;
129
+ const mem = db
130
+ .prepare("SELECT * FROM memories WHERE id = ?")
131
+ .get(row.id);
132
+ if (!mem)
133
+ continue;
134
+ results.push({ ...rowToMemory(mem), distance: row.distance });
135
+ }
136
+ return results;
137
+ }
138
+ // ---------------------------------------------------------------------------
139
+ // FTS5 search
140
+ // ---------------------------------------------------------------------------
141
+ /**
142
+ * Full-text search using FTS5 BM25 ranking.
143
+ * Returns memories with a rank field (lower is better).
144
+ */
145
+ function searchFts(db, query, limit = 10, privacy, sourceAgent) {
146
+ const conditions = ["memories_fts MATCH ?"];
147
+ const params = [query];
148
+ if (privacy && privacy.length > 0) {
149
+ const placeholders = privacy.map(() => "?").join(", ");
150
+ conditions.push(`m.privacy IN (${placeholders})`);
151
+ params.push(...privacy);
152
+ }
153
+ if (sourceAgent) {
154
+ conditions.push("m.source_agent = ?");
155
+ params.push(sourceAgent);
156
+ }
157
+ const where = conditions.join(" AND ");
158
+ params.push(limit);
159
+ const rows = db
160
+ .prepare(`SELECT m.*, fts.rank
161
+ FROM memories_fts fts
162
+ JOIN memories m ON m.rowid = fts.rowid
163
+ WHERE ${where}
164
+ ORDER BY fts.rank
165
+ LIMIT ?`)
166
+ .all(...params);
167
+ return rows.map((r) => {
168
+ const rank = r.rank;
169
+ const mem = rowToMemory(r);
170
+ return { ...mem, rank };
171
+ });
172
+ }
173
+ // ---------------------------------------------------------------------------
174
+ // Links
175
+ // ---------------------------------------------------------------------------
176
+ /**
177
+ * Create a link between two memories.
178
+ */
179
+ function addLink(db, sourceId, targetId, relationship, strength = 0.5) {
180
+ db.prepare(`INSERT OR REPLACE INTO memory_links
181
+ (source_id, target_id, relationship, strength, created_at)
182
+ VALUES (?, ?, ?, ?, ?)`).run(sourceId, targetId, relationship, strength, nowIso());
183
+ }
184
+ /**
185
+ * Get links for a memory. direction: 'outgoing', 'incoming', or 'both'.
186
+ */
187
+ function getLinks(db, memoryId, direction = "both") {
188
+ let rows;
189
+ if (direction === "outgoing") {
190
+ rows = db
191
+ .prepare("SELECT * FROM memory_links WHERE source_id = ?")
192
+ .all(memoryId);
193
+ }
194
+ else if (direction === "incoming") {
195
+ rows = db
196
+ .prepare("SELECT * FROM memory_links WHERE target_id = ?")
197
+ .all(memoryId);
198
+ }
199
+ else {
200
+ rows = db
201
+ .prepare("SELECT * FROM memory_links WHERE source_id = ? OR target_id = ?")
202
+ .all(memoryId, memoryId);
203
+ }
204
+ return rows;
205
+ }
206
+ /**
207
+ * Delete all links involving a memory.
208
+ */
209
+ function deleteLinks(db, memoryId) {
210
+ db.prepare("DELETE FROM memory_links WHERE source_id = ? OR target_id = ?").run(memoryId, memoryId);
211
+ }
212
+ // ---------------------------------------------------------------------------
213
+ // Batch & query helpers
214
+ // ---------------------------------------------------------------------------
215
+ /**
216
+ * Batch insert memories. Returns count inserted.
217
+ */
218
+ function insertMemoriesBatch(db, memories) {
219
+ const insertMem = db.prepare(`INSERT INTO memories
220
+ (id, content, base_strength, last_accessed, access_count,
221
+ created_at, ingested_at, source_agent, source_session, project,
222
+ privacy, memory_type)
223
+ VALUES (?, ?, ?, ?, 0, ?, ?, ?, ?, ?, ?, ?)`);
224
+ const insertVec = db.prepare("INSERT INTO memory_vectors (id, embedding) VALUES (?, ?)");
225
+ const tx = db.transaction(() => {
226
+ let count = 0;
227
+ for (const mem of memories) {
228
+ const id = (0, node_crypto_1.randomUUID)();
229
+ const ts = nowIso();
230
+ insertMem.run(id, mem.content, mem.baseStrength ?? 0.5, ts, ts, ts, mem.sourceAgent ?? "default", mem.sourceSession ?? null, mem.project ?? null, mem.privacy ?? "WORK", mem.memoryType ?? "episode");
231
+ insertVec.run(id, embedToBlob(mem.embedding));
232
+ count++;
233
+ }
234
+ return count;
235
+ });
236
+ return tx();
237
+ }
238
+ /**
239
+ * Return total memory count.
240
+ */
241
+ function countMemories(db) {
242
+ return db.prepare("SELECT count(*) as cnt FROM memories").get().cnt;
243
+ }
244
+ /**
245
+ * Get memories created in the last N days, newest first.
246
+ */
247
+ function getRecentMemories(db, days = 7, limit = 50) {
248
+ const rows = db
249
+ .prepare(`SELECT * FROM memories
250
+ WHERE created_at >= datetime('now', ?)
251
+ ORDER BY created_at DESC LIMIT ?`)
252
+ .all(`-${days} days`, limit);
253
+ return rows.map(rowToMemory);
254
+ }
255
+ /**
256
+ * Get all memories ingested after a timestamp.
257
+ * Uses ingested_at (when the memory entered the DB) for consolidation correctness.
258
+ */
259
+ function getMemoriesSince(db, sinceIso) {
260
+ const rows = db
261
+ .prepare(`SELECT * FROM memories
262
+ WHERE ingested_at > ?
263
+ ORDER BY ingested_at ASC`)
264
+ .all(sinceIso);
265
+ return rows.map(rowToMemory);
266
+ }
267
+ /**
268
+ * Get lesson-type memories from the last N days.
269
+ */
270
+ function getLessons(db, days = 7, project) {
271
+ const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
272
+ if (project) {
273
+ const rows = db
274
+ .prepare(`SELECT * FROM memories
275
+ WHERE memory_type = 'lesson' AND created_at > ? AND project = ?
276
+ ORDER BY created_at DESC`)
277
+ .all(cutoff, project);
278
+ return rows.map(rowToMemory);
279
+ }
280
+ const rows = db
281
+ .prepare(`SELECT * FROM memories
282
+ WHERE memory_type = 'lesson' AND created_at > ?
283
+ ORDER BY created_at DESC`)
284
+ .all(cutoff);
285
+ return rows.map(rowToMemory);
286
+ }
287
+ /**
288
+ * Get memories older than cutoff with zero access (prune candidates).
289
+ */
290
+ function getPruneCandidates(db, cutoffIso) {
291
+ const rows = db
292
+ .prepare(`SELECT * FROM memories
293
+ WHERE created_at < ? AND access_count = 0
294
+ ORDER BY created_at ASC`)
295
+ .all(cutoffIso);
296
+ return rows.map(rowToMemory);
297
+ }
298
+ /**
299
+ * Get link counts for all memories in a single query.
300
+ * Returns a map of memory_id -> total link count (both directions).
301
+ */
302
+ function getAllLinkCounts(db) {
303
+ const rows = db
304
+ .prepare(`SELECT id, COUNT(*) as cnt FROM (
305
+ SELECT source_id AS id FROM memory_links
306
+ UNION ALL
307
+ SELECT target_id AS id FROM memory_links
308
+ ) GROUP BY id`)
309
+ .all();
310
+ const counts = new Map();
311
+ for (const row of rows) {
312
+ counts.set(row.id, row.cnt);
313
+ }
314
+ return counts;
315
+ }
316
+ /**
317
+ * Get all memories with default base_strength (never scored).
318
+ */
319
+ function getUnscoredMemories(db) {
320
+ const rows = db
321
+ .prepare(`SELECT * FROM memories
322
+ WHERE base_strength = 0.5
323
+ ORDER BY ingested_at ASC`)
324
+ .all();
325
+ return rows.map(rowToMemory);
326
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Type definitions for Hicortex OpenClaw plugin.
3
+ * Ported from the Python hicortex codebase.
4
+ */
5
+ /** A stored memory record. */
6
+ export interface Memory {
7
+ id: string;
8
+ content: string;
9
+ base_strength: number;
10
+ last_accessed: string | null;
11
+ access_count: number;
12
+ created_at: string;
13
+ ingested_at: string;
14
+ source_agent: string;
15
+ source_session: string | null;
16
+ project: string | null;
17
+ privacy: "PUBLIC" | "WORK" | "PERSONAL" | "SENSITIVE";
18
+ memory_type: "episode" | "lesson" | "fact" | "decision";
19
+ }
20
+ /** A link between two memories. */
21
+ export interface MemoryLink {
22
+ source_id: string;
23
+ target_id: string;
24
+ relationship: string;
25
+ strength: number;
26
+ created_at: string;
27
+ }
28
+ /** A search result with scoring metadata. */
29
+ export interface MemorySearchResult {
30
+ id: string;
31
+ content: string;
32
+ score: number;
33
+ effective_strength: number;
34
+ access_count: number;
35
+ memory_type: string;
36
+ project: string | null;
37
+ created_at: string;
38
+ connections: number;
39
+ }
40
+ /** Report returned by the consolidation pipeline. */
41
+ export interface ConsolidationReport {
42
+ started_at: string;
43
+ completed_at?: string;
44
+ dry_run: boolean;
45
+ status: "completed" | "skipped" | "failed";
46
+ elapsed_seconds?: number;
47
+ stages: {
48
+ precheck?: {
49
+ skip: boolean;
50
+ reason: string;
51
+ new_memory_count: number;
52
+ unscored_count: number;
53
+ };
54
+ importance?: {
55
+ scored: number;
56
+ failed: number;
57
+ skipped_budget: number;
58
+ };
59
+ reflection?: {
60
+ lessons_generated: number;
61
+ failed?: boolean;
62
+ skipped?: boolean;
63
+ reason?: string;
64
+ };
65
+ links?: {
66
+ auto_linked: number;
67
+ failed: number;
68
+ };
69
+ decay_prune?: {
70
+ candidates: number;
71
+ pruned: number;
72
+ failed: number;
73
+ };
74
+ };
75
+ budget?: {
76
+ max_calls: number;
77
+ calls_used: number;
78
+ calls_remaining: number;
79
+ calls_by_stage: Record<string, number>;
80
+ };
81
+ }
82
+ /** Plugin configuration from openclaw.plugin.json configSchema. */
83
+ export interface HicortexConfig {
84
+ licenseKey?: string;
85
+ llmBaseUrl?: string;
86
+ llmApiKey?: string;
87
+ llmModel?: string;
88
+ reflectModel?: string;
89
+ consolidateHour?: number;
90
+ dbPath?: string;
91
+ }
92
+ /** Response from license validation API. */
93
+ export interface LicenseInfo {
94
+ valid: boolean;
95
+ tier: "free" | "pro" | "lifetime" | "team";
96
+ features: {
97
+ reflection: boolean;
98
+ vectorSearch: boolean;
99
+ maxMemories: number;
100
+ crossAgent: boolean;
101
+ };
102
+ email?: string;
103
+ expires_at?: string;
104
+ }
105
+ /** Options for inserting a memory. */
106
+ export interface InsertMemoryOptions {
107
+ sourceAgent?: string;
108
+ sourceSession?: string | null;
109
+ project?: string | null;
110
+ privacy?: string;
111
+ memoryType?: string;
112
+ baseStrength?: number;
113
+ createdAt?: string;
114
+ }
115
+ /** Options for vector search. */
116
+ export interface VectorSearchOptions {
117
+ limit?: number;
118
+ excludeIds?: string[];
119
+ }
120
+ /** Options for FTS search. */
121
+ export interface FtsSearchOptions {
122
+ limit?: number;
123
+ privacy?: string[];
124
+ sourceAgent?: string;
125
+ }
126
+ /** Options for retrieval. */
127
+ export interface RetrievalOptions {
128
+ limit?: number;
129
+ project?: string | null;
130
+ privacy?: string[];
131
+ sourceAgent?: string;
132
+ }
package/dist/types.js ADDED
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ /**
3
+ * Type definitions for Hicortex OpenClaw plugin.
4
+ * Ported from the Python hicortex codebase.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,70 @@
1
+ {
2
+ "id": "hicortex",
3
+ "name": "Hicortex — Long-term Memory That Learns",
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.2.0",
6
+ "kind": "lifecycle",
7
+ "skills": ["./skills/hicortex-memory", "./skills/hicortex-learn", "./skills/hicortex-activate"],
8
+ "configSchema": {
9
+ "type": "object",
10
+ "properties": {
11
+ "licenseKey": {
12
+ "type": "string",
13
+ "description": "Hicortex license key (hctx-...). Leave empty for free tier (100 memory cap)."
14
+ },
15
+ "llmBaseUrl": {
16
+ "type": "string",
17
+ "description": "LLM API base URL. Auto-detected from OpenClaw config or env vars if omitted."
18
+ },
19
+ "llmApiKey": {
20
+ "type": "string",
21
+ "description": "LLM API key. Auto-detected from OpenClaw config or env vars if omitted."
22
+ },
23
+ "llmModel": {
24
+ "type": "string",
25
+ "default": "qwen3.5:4b",
26
+ "description": "Model for importance scoring and distillation (local recommended)"
27
+ },
28
+ "reflectModel": {
29
+ "type": "string",
30
+ "default": "qwen3.5:cloud",
31
+ "description": "Model for nightly reflection (cloud recommended)"
32
+ },
33
+ "consolidateHour": {
34
+ "type": "number",
35
+ "default": 2,
36
+ "description": "Hour (0-23, local time) to run nightly consolidation"
37
+ },
38
+ "dbPath": {
39
+ "type": "string",
40
+ "description": "Custom path for the SQLite database file"
41
+ }
42
+ },
43
+ "required": []
44
+ },
45
+ "uiHints": {
46
+ "licenseKey": {
47
+ "label": "License Key",
48
+ "placeholder": "hctx-... (optional, free tier without key)",
49
+ "sensitive": true
50
+ },
51
+ "llmBaseUrl": {
52
+ "label": "LLM API URL",
53
+ "placeholder": "Auto-detected if empty"
54
+ },
55
+ "llmApiKey": {
56
+ "label": "LLM API Key",
57
+ "sensitive": true,
58
+ "placeholder": "Auto-detected if empty"
59
+ },
60
+ "llmModel": {
61
+ "label": "Scoring Model"
62
+ },
63
+ "reflectModel": {
64
+ "label": "Reflection Model"
65
+ },
66
+ "consolidateHour": {
67
+ "label": "Consolidation Hour"
68
+ }
69
+ }
70
+ }
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@gamaze/hicortex",
3
+ "version": "0.2.0",
4
+ "description": "Long-term memory for AI agents \u2014 local embeddings, semantic search, and self-improving reflection. OpenClaw plugin.",
5
+ "main": "dist/index.js",
6
+ "openclaw": {
7
+ "extensions": ["./dist/index.js"]
8
+ },
9
+ "types": "dist/index.d.ts",
10
+ "files": [
11
+ "dist/",
12
+ "skills/",
13
+ "openclaw.plugin.json",
14
+ "README.md"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "test": "vitest run",
19
+ "test:watch": "vitest",
20
+ "prepublishOnly": "npm run build"
21
+ },
22
+ "devDependencies": {
23
+ "typescript": "^5.4.0",
24
+ "vitest": "^3.0.0",
25
+ "@types/node": "^22.0.0",
26
+ "@types/better-sqlite3": "^7.6.0"
27
+ },
28
+ "engines": {
29
+ "node": ">=18"
30
+ },
31
+ "license": "UNLICENSED",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/mha33/hicortex.git",
35
+ "directory": "packages/openclaw-plugin"
36
+ },
37
+ "dependencies": {
38
+ "better-sqlite3": "^11.0.0",
39
+ "sqlite-vec": "^0.1.7",
40
+ "@huggingface/transformers": "^3.0.0"
41
+ }
42
+ }
@@ -0,0 +1,53 @@
1
+ ---
2
+ name: hicortex-activate
3
+ description: Activate a Hicortex license key. Use when the user says they purchased Hicortex, has a license key, or wants to upgrade from the free tier.
4
+ version: 0.2.0
5
+ user-invocable: true
6
+ disable-model-invocation: false
7
+ ---
8
+
9
+ # Activate Hicortex License
10
+
11
+ When the user wants to activate their license key, guide them through it.
12
+
13
+ ## If they provide the key (e.g. `/hicortex-activate hctx-abc123`)
14
+
15
+ Run this command to apply the key:
16
+
17
+ ```bash
18
+ openclaw config set plugins.entries.hicortex.config.licenseKey "THE_KEY_HERE"
19
+ ```
20
+
21
+ Then restart the gateway:
22
+
23
+ ```bash
24
+ openclaw gateway restart
25
+ ```
26
+
27
+ Tell the user: "License activated! Hicortex now has unlimited memory. Your agent will keep learning and improving from every session."
28
+
29
+ ## If they don't have a key yet
30
+
31
+ Tell them:
32
+
33
+ "You can get a license key at https://hicortex.gamaze.com/ — after purchase, you'll receive your key by email. Then come back and tell me the key, and I'll activate it for you."
34
+
35
+ ## If activation fails
36
+
37
+ If the `openclaw config set` command fails, fall back to telling the user to manually add it:
38
+
39
+ "Open ~/.openclaw/openclaw.json, find the hicortex plugin entry, and add your key:
40
+
41
+ ```json
42
+ "config": {
43
+ "licenseKey": "hctx-your-key-here"
44
+ }
45
+ ```
46
+
47
+ Then restart: `openclaw gateway restart`"
48
+
49
+ ## Rules
50
+
51
+ - Never ask the user to open a terminal or edit files unless the automatic method fails
52
+ - Always confirm the key was applied by checking the gateway log after restart
53
+ - Be encouraging — they just bought the product
@@ -0,0 +1,40 @@
1
+ ---
2
+ name: hicortex-learn
3
+ description: Save an explicit learning or insight to Hicortex long-term memory. Use when you discover something worth remembering across sessions — a lesson, a correction, a pattern, a decision.
4
+ version: 0.2.0
5
+ user-invocable: true
6
+ disable-model-invocation: false
7
+ ---
8
+
9
+ # Save Learning to Hicortex
10
+
11
+ When invoked with `/learn <text>`, store the learning in long-term memory via hicortex_ingest.
12
+
13
+ ## Steps
14
+
15
+ 1. Parse the text after `/learn`
16
+ 2. Clean it up into a clear, self-contained statement that will make sense months from now
17
+ 3. Add today's date
18
+ 4. Call the `hicortex_ingest` tool with:
19
+ - `content`: The learning text
20
+ - `project`: "global" (unless clearly project-specific)
21
+ - `memory_type`: "lesson"
22
+
23
+ ## Example
24
+
25
+ ```
26
+ /learn z.ai API uses Bearer auth on all endpoints, not x-api-key
27
+ ```
28
+
29
+ Becomes:
30
+ ```
31
+ hicortex_ingest(content="LEARNING: z.ai API uses Bearer auth on all three endpoints (paas, coding, anthropic). Not x-api-key. (2026-03-24)", project="global", memory_type="lesson")
32
+ ```
33
+
34
+ ## Rules
35
+
36
+ - Keep it concise — one clear statement
37
+ - Include the "why" when relevant
38
+ - Include the date for temporal context
39
+ - Prefix with "LEARNING:" so it's identifiable in search
40
+ - Confirm to the user what was saved (title + confirmation)