@cogmem/engram 0.0.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,334 @@
1
+ import type { CognitiveConfig } from "../config/defaults.ts";
2
+ import type { EngramStorage } from "../storage/sqlite.ts";
3
+ import type { EngramEngine } from "../core/engine.ts";
4
+ import { isValidMemoryType } from "../core/memory.ts";
5
+ import { encode } from "../core/encoder.ts";
6
+ import { recall } from "../core/recall.ts";
7
+ import {
8
+ pushFocus,
9
+ popFocus,
10
+ getFocus,
11
+ clearFocus,
12
+ focusUtilization,
13
+ } from "../core/working-memory.ts";
14
+ import { consolidate } from "../core/consolidation.ts";
15
+ import { discoverChunks } from "../core/chunking.ts";
16
+ import { refreshActivations } from "../core/forgetting.ts";
17
+ import { reconsolidate, type ReconsolidationContext } from "../core/reconsolidation.ts";
18
+ import { isValidEmotion } from "../core/emotional-tag.ts";
19
+
20
+ export interface ToolResult {
21
+ [key: string]: unknown;
22
+ content: { type: "text"; text: string }[];
23
+ isError?: boolean;
24
+ }
25
+
26
+ function textResult(text: string): ToolResult {
27
+ return { content: [{ type: "text", text }] };
28
+ }
29
+
30
+ function errorResult(text: string): ToolResult {
31
+ return { content: [{ type: "text", text }], isError: true };
32
+ }
33
+
34
+ export function handleStore(
35
+ engine: EngramEngine,
36
+ args: { action: string; [key: string]: unknown },
37
+ ): ToolResult {
38
+ switch (args.action) {
39
+ case "encode":
40
+ return handleEncode(engine.storage, engine.config, args as any, engine.projectContext);
41
+ case "reconsolidate":
42
+ return handleReconsolidate(engine.storage, engine.config, args as any);
43
+ default:
44
+ return errorResult(`Unknown store action: ${args.action}`);
45
+ }
46
+ }
47
+
48
+ export function handleRecall(
49
+ engine: EngramEngine,
50
+ args: { action?: string; [key: string]: unknown },
51
+ ): ToolResult {
52
+ const action = args.action ?? "recall";
53
+ switch (action) {
54
+ case "recall":
55
+ return handleRecallQuery(engine.storage, engine.config, args as any, engine.projectContext);
56
+ case "inspect":
57
+ return handleInspect(engine.storage, args as any);
58
+ case "stats":
59
+ return handleStats(engine.storage, engine.config);
60
+ default:
61
+ return errorResult(`Unknown recall action: ${action}`);
62
+ }
63
+ }
64
+
65
+ export function handleManage(
66
+ engine: EngramEngine,
67
+ args: { action: string; [key: string]: unknown },
68
+ ): ToolResult {
69
+ switch (args.action) {
70
+ case "consolidate":
71
+ return handleConsolidate(engine.storage, engine.config);
72
+ case "focus_push":
73
+ return handleFocusPush(engine.storage, engine.config, args as any);
74
+ case "focus_pop":
75
+ return handleFocusPop(engine.storage);
76
+ case "focus_get":
77
+ return handleFocusGet(engine.storage, engine.config);
78
+ case "focus_clear":
79
+ return handleFocusClear(engine.storage);
80
+ default:
81
+ return errorResult(`Unknown manage action: ${args.action}`);
82
+ }
83
+ }
84
+
85
+ function handleEncode(
86
+ storage: EngramStorage,
87
+ config: CognitiveConfig,
88
+ args: {
89
+ content: string;
90
+ type?: string;
91
+ emotion?: string;
92
+ emotionWeight?: number;
93
+ context?: string;
94
+ },
95
+ defaultContext?: string | null,
96
+ ): ToolResult {
97
+ const typeStr = args.type ?? "semantic";
98
+ if (!isValidMemoryType(typeStr)) {
99
+ return errorResult(`Invalid type: ${typeStr}. Must be episodic, semantic, or procedural.`);
100
+ }
101
+
102
+ const emotionStr = args.emotion ?? "neutral";
103
+ if (!isValidEmotion(emotionStr)) {
104
+ return errorResult(`Invalid emotion: ${emotionStr}.`);
105
+ }
106
+
107
+ const memory = encode(
108
+ storage,
109
+ {
110
+ content: args.content,
111
+ type: typeStr,
112
+ emotion: emotionStr,
113
+ emotionWeight: args.emotionWeight,
114
+ context: args.context ?? defaultContext ?? undefined,
115
+ },
116
+ config,
117
+ );
118
+
119
+ return textResult(
120
+ JSON.stringify({
121
+ id: memory.id,
122
+ type: memory.type,
123
+ content: memory.content,
124
+ activation: memory.activation,
125
+ emotion: memory.emotion,
126
+ emotionWeight: memory.emotionWeight,
127
+ context: memory.context,
128
+ }),
129
+ );
130
+ }
131
+
132
+ function handleRecallQuery(
133
+ storage: EngramStorage,
134
+ config: CognitiveConfig,
135
+ args: {
136
+ cue: string;
137
+ limit?: number;
138
+ type?: string;
139
+ context?: string;
140
+ associative?: boolean;
141
+ verbose?: boolean;
142
+ },
143
+ defaultContext?: string | null,
144
+ ): ToolResult {
145
+ const typeFilter = args.type && isValidMemoryType(args.type) ? args.type : undefined;
146
+
147
+ const results = recall(storage, args.cue, config, {
148
+ limit: args.limit ?? 5,
149
+ type: typeFilter,
150
+ context: args.context ?? defaultContext ?? undefined,
151
+ associative: args.associative ?? true,
152
+ });
153
+
154
+ if (results.length === 0) {
155
+ return textResult("No memories found above retrieval threshold.");
156
+ }
157
+
158
+ const formatted = args.verbose
159
+ ? results.map((r) => ({
160
+ id: r.memory.id,
161
+ content: r.memory.content,
162
+ type: r.memory.type,
163
+ activation: r.activation,
164
+ spreadingActivation: r.spreadingActivation,
165
+ latency: r.latency,
166
+ emotion: r.memory.emotion,
167
+ emotionWeight: r.memory.emotionWeight,
168
+ recallCount: r.memory.recallCount,
169
+ context: r.memory.context,
170
+ }))
171
+ : results.map((r) => ({
172
+ id: r.memory.id,
173
+ content: r.memory.content,
174
+ context: r.memory.context,
175
+ activation: r.activation,
176
+ }));
177
+
178
+ return textResult(JSON.stringify(formatted));
179
+ }
180
+
181
+ function handleFocusPush(
182
+ storage: EngramStorage,
183
+ config: CognitiveConfig,
184
+ args: { content: string; memoryRef?: string },
185
+ ): ToolResult {
186
+ const { slot, evicted } = pushFocus(storage, args.content, config, {
187
+ memoryRef: args.memoryRef,
188
+ });
189
+
190
+ return textResult(
191
+ JSON.stringify({
192
+ slot: slot.slot,
193
+ content: slot.content,
194
+ evicted: evicted ? { slot: evicted.slot, content: evicted.content } : null,
195
+ }),
196
+ );
197
+ }
198
+
199
+ function handleFocusPop(storage: EngramStorage): ToolResult {
200
+ const popped = popFocus(storage);
201
+ if (!popped) {
202
+ return textResult("Working memory is empty.");
203
+ }
204
+ return textResult(JSON.stringify({ slot: popped.slot, content: popped.content }));
205
+ }
206
+
207
+ function handleFocusGet(storage: EngramStorage, config: CognitiveConfig): ToolResult {
208
+ const slots = getFocus(storage);
209
+ const { used, capacity } = focusUtilization(storage, config);
210
+
211
+ return textResult(
212
+ JSON.stringify({
213
+ used,
214
+ capacity,
215
+ slots: slots.map((s) => ({ slot: s.slot, content: s.content, memoryRef: s.memoryRef })),
216
+ }),
217
+ );
218
+ }
219
+
220
+ function handleFocusClear(storage: EngramStorage): ToolResult {
221
+ const count = clearFocus(storage);
222
+ return textResult(`Cleared ${count} items from working memory.`);
223
+ }
224
+
225
+ function handleConsolidate(storage: EngramStorage, config: CognitiveConfig): ToolResult {
226
+ const result = consolidate(storage, config);
227
+ const chunks = discoverChunks(storage, config);
228
+
229
+ return textResult(
230
+ JSON.stringify({
231
+ memoriesStrengthened: result.memoriesStrengthened,
232
+ memoriesPruned: result.memoriesPruned,
233
+ factsExtracted: result.factsExtracted,
234
+ associationsDiscovered: result.associationsDiscovered,
235
+ chunksFormed: chunks.length,
236
+ extractedFacts: result.extractedFacts,
237
+ prunedIds: result.prunedIds,
238
+ }),
239
+ );
240
+ }
241
+
242
+ function handleStats(storage: EngramStorage, config: CognitiveConfig): ToolResult {
243
+ const { atRisk } = refreshActivations(storage, config);
244
+ const { used, capacity } = focusUtilization(storage, config);
245
+ const lastConsolidation = storage.getLastConsolidation();
246
+
247
+ return textResult(
248
+ JSON.stringify({
249
+ workingMemory: { used, capacity },
250
+ episodic: storage.getMemoryCount("episodic"),
251
+ semantic: storage.getMemoryCount("semantic"),
252
+ procedural: storage.getMemoryCount("procedural"),
253
+ associations: storage.getAssociationCount(),
254
+ atRisk,
255
+ lastConsolidation: lastConsolidation
256
+ ? {
257
+ ranAt: lastConsolidation.ranAt,
258
+ memoriesStrengthened: lastConsolidation.memoriesStrengthened,
259
+ memoriesPruned: lastConsolidation.memoriesPruned,
260
+ }
261
+ : null,
262
+ }),
263
+ );
264
+ }
265
+
266
+ function handleInspect(storage: EngramStorage, args: { id: string }): ToolResult {
267
+ const allMemories = storage.getAllMemories();
268
+ const match = allMemories.find((m) => m.id === args.id || m.id.startsWith(args.id));
269
+
270
+ if (!match) {
271
+ return errorResult(`No memory found matching "${args.id}".`);
272
+ }
273
+
274
+ const accessLog = storage.getAccessLog(match.id);
275
+ const associations = storage.getAssociations(match.id);
276
+
277
+ return textResult(
278
+ JSON.stringify({
279
+ id: match.id,
280
+ type: match.type,
281
+ content: match.content,
282
+ encodedAt: match.encodedAt,
283
+ lastRecalledAt: match.lastRecalledAt,
284
+ recallCount: match.recallCount,
285
+ activation: match.activation,
286
+ emotion: match.emotion,
287
+ emotionWeight: match.emotionWeight,
288
+ context: match.context,
289
+ chunkId: match.chunkId,
290
+ reconsolidationCount: match.reconsolidationCount,
291
+ accessCount: accessLog.length,
292
+ associationCount: associations.length,
293
+ associations: associations.map((a) => ({
294
+ id: a.id,
295
+ targetId: a.sourceId === match.id ? a.targetId : a.sourceId,
296
+ strength: a.strength,
297
+ type: a.type,
298
+ })),
299
+ }),
300
+ );
301
+ }
302
+
303
+ function handleReconsolidate(
304
+ storage: EngramStorage,
305
+ config: CognitiveConfig,
306
+ args: { id: string; newContext?: string; currentEmotion?: string; currentEmotionWeight?: number },
307
+ ): ToolResult {
308
+ const memory = storage.getMemory(args.id);
309
+ if (!memory) {
310
+ return errorResult(`No memory found with id "${args.id}".`);
311
+ }
312
+
313
+ const validEmotion =
314
+ args.currentEmotion && isValidEmotion(args.currentEmotion) ? args.currentEmotion : undefined;
315
+
316
+ const context: ReconsolidationContext = {
317
+ newContext: args.newContext,
318
+ currentEmotion: validEmotion,
319
+ currentEmotionWeight: args.currentEmotionWeight,
320
+ };
321
+
322
+ const updated = reconsolidate(storage, memory, context, config);
323
+
324
+ return textResult(
325
+ JSON.stringify({
326
+ id: updated.id,
327
+ content: updated.content,
328
+ emotion: updated.emotion,
329
+ emotionWeight: updated.emotionWeight,
330
+ context: updated.context,
331
+ reconsolidationCount: updated.reconsolidationCount,
332
+ }),
333
+ );
334
+ }
@@ -0,0 +1,97 @@
1
+ import { sqliteTable, text, integer, real, index, unique } from "drizzle-orm/sqlite-core";
2
+ import { MemoryType, Emotion, AssociationType, AccessType } from "../core/memory.ts";
3
+
4
+ export const memories = sqliteTable(
5
+ "memories",
6
+ {
7
+ id: text("id").primaryKey(),
8
+ type: text("type", {
9
+ enum: Object.values(MemoryType) as [MemoryType, ...MemoryType[]],
10
+ }).notNull(),
11
+ content: text("content").notNull(),
12
+ encodedAt: integer("encoded_at").notNull(),
13
+ lastRecalledAt: integer("last_recalled_at"),
14
+ recallCount: integer("recall_count").notNull().default(0),
15
+ activation: real("activation").notNull().default(0.0),
16
+ emotion: text("emotion", {
17
+ enum: Object.values(Emotion) as [Emotion, ...Emotion[]],
18
+ })
19
+ .notNull()
20
+ .default("neutral"),
21
+ emotionWeight: real("emotion_weight").notNull().default(0.0),
22
+ context: text("context"),
23
+ chunkId: text("chunk_id"),
24
+ reconsolidationCount: integer("reconsolidation_count").notNull().default(0),
25
+ },
26
+ (table) => [
27
+ index("idx_memories_type").on(table.type),
28
+ index("idx_memories_activation").on(table.activation),
29
+ index("idx_memories_encoded_at").on(table.encodedAt),
30
+ index("idx_memories_context").on(table.context),
31
+ index("idx_memories_chunk_id").on(table.chunkId),
32
+ ],
33
+ );
34
+
35
+ export const accessLog = sqliteTable(
36
+ "access_log",
37
+ {
38
+ id: text("id").primaryKey(),
39
+ memoryId: text("memory_id")
40
+ .notNull()
41
+ .references(() => memories.id, { onDelete: "cascade" }),
42
+ accessedAt: integer("accessed_at").notNull(),
43
+ accessType: text("access_type", {
44
+ enum: Object.values(AccessType) as [AccessType, ...AccessType[]],
45
+ }).notNull(),
46
+ },
47
+ (table) => [
48
+ index("idx_access_log_memory_id").on(table.memoryId),
49
+ index("idx_access_log_accessed_at").on(table.accessedAt),
50
+ ],
51
+ );
52
+
53
+ export const associations = sqliteTable(
54
+ "associations",
55
+ {
56
+ id: text("id").primaryKey(),
57
+ sourceId: text("source_id")
58
+ .notNull()
59
+ .references(() => memories.id, { onDelete: "cascade" }),
60
+ targetId: text("target_id")
61
+ .notNull()
62
+ .references(() => memories.id, { onDelete: "cascade" }),
63
+ strength: real("strength").notNull().default(0.5),
64
+ formedAt: integer("formed_at").notNull(),
65
+ type: text("type", {
66
+ enum: Object.values(AssociationType) as [AssociationType, ...AssociationType[]],
67
+ }).notNull(),
68
+ },
69
+ (table) => [
70
+ unique("unique_source_target").on(table.sourceId, table.targetId),
71
+ index("idx_associations_source").on(table.sourceId),
72
+ index("idx_associations_target").on(table.targetId),
73
+ ],
74
+ );
75
+
76
+ export const workingMemory = sqliteTable("working_memory", {
77
+ slot: integer("slot").primaryKey(),
78
+ memoryRef: text("memory_ref"),
79
+ content: text("content").notNull(),
80
+ pushedAt: integer("pushed_at").notNull(),
81
+ });
82
+
83
+ export const consolidationLog = sqliteTable("consolidation_log", {
84
+ id: text("id").primaryKey(),
85
+ ranAt: integer("ran_at").notNull(),
86
+ memoriesStrengthened: integer("memories_strengthened").notNull().default(0),
87
+ memoriesPruned: integer("memories_pruned").notNull().default(0),
88
+ factsExtracted: integer("facts_extracted").notNull().default(0),
89
+ associationsDiscovered: integer("associations_discovered").notNull().default(0),
90
+ });
91
+
92
+ export type Memory = typeof memories.$inferSelect;
93
+ export type NewMemoryRow = typeof memories.$inferInsert;
94
+ export type AccessLogEntry = typeof accessLog.$inferSelect;
95
+ export type Association = typeof associations.$inferSelect;
96
+ export type WorkingMemorySlot = typeof workingMemory.$inferSelect;
97
+ export type ConsolidationLog = typeof consolidationLog.$inferSelect;