@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,58 @@
1
+ import type { CognitiveConfig } from "../config/defaults.ts";
2
+ import type { EngramStorage } from "../storage/sqlite.ts";
3
+ import { baseLevelActivation } from "./activation.ts";
4
+
5
+ // R(t) = e^{-t/S}
6
+ export function ebbinghausRetention(timeSinceLastRecall: number, strength: number): number {
7
+ if (strength <= 0) return 0;
8
+ return Math.exp(-timeSinceLastRecall / strength);
9
+ }
10
+
11
+ export function memoryStrength(
12
+ recallCount: number,
13
+ emotionWeight: number,
14
+ associationCount: number,
15
+ emotionalBoostFactor: number,
16
+ ): number {
17
+ const recallStrength = 1 + recallCount * 0.8;
18
+ const emotionalStrength = 1 + emotionWeight * emotionalBoostFactor;
19
+ const associativeStrength = 1 + Math.log(1 + associationCount) * 0.5;
20
+ return recallStrength * emotionalStrength * associativeStrength;
21
+ }
22
+
23
+ export function refreshActivations(
24
+ storage: EngramStorage,
25
+ config: CognitiveConfig,
26
+ now?: number,
27
+ ): { updated: number; atRisk: number } {
28
+ const currentTime = now ?? Date.now();
29
+ const memories = storage.getAllMemories();
30
+ let updated = 0;
31
+ let atRisk = 0;
32
+
33
+ for (const memory of memories) {
34
+ if (memory.type === "procedural") continue;
35
+
36
+ const timestamps = storage.getAccessTimestamps(memory.id);
37
+ const newActivation = baseLevelActivation(timestamps, currentTime, config.decayRate);
38
+
39
+ const emotionBoost =
40
+ memory.emotionWeight > 0
41
+ ? Math.log(1 + memory.emotionWeight * config.emotionalBoostFactor)
42
+ : 0;
43
+
44
+ const finalActivation = newActivation + emotionBoost;
45
+
46
+ if (Math.abs(finalActivation - memory.activation) > 0.001) {
47
+ memory.activation = finalActivation;
48
+ storage.updateMemory(memory);
49
+ updated++;
50
+ }
51
+
52
+ if (finalActivation < config.retrievalThreshold) {
53
+ atRisk++;
54
+ }
55
+ }
56
+
57
+ return { updated, atRisk };
58
+ }
@@ -0,0 +1,94 @@
1
+ import type { Memory as _Memory } from "../storage/schema.ts";
2
+ export {
3
+ type Memory,
4
+ type AccessLogEntry,
5
+ type Association,
6
+ type WorkingMemorySlot,
7
+ type ConsolidationLog,
8
+ } from "../storage/schema.ts";
9
+
10
+ export const MemoryType = {
11
+ Episodic: "episodic",
12
+ Semantic: "semantic",
13
+ Procedural: "procedural",
14
+ } as const;
15
+ export type MemoryType = (typeof MemoryType)[keyof typeof MemoryType];
16
+
17
+ export const Emotion = {
18
+ Joy: "joy",
19
+ Anxiety: "anxiety",
20
+ Frustration: "frustration",
21
+ Surprise: "surprise",
22
+ Satisfaction: "satisfaction",
23
+ Curiosity: "curiosity",
24
+ Neutral: "neutral",
25
+ } as const;
26
+ export type Emotion = (typeof Emotion)[keyof typeof Emotion];
27
+
28
+ export const AssociationType = {
29
+ Temporal: "temporal",
30
+ Semantic: "semantic",
31
+ CoRecall: "co-recall",
32
+ } as const;
33
+ export type AssociationType = (typeof AssociationType)[keyof typeof AssociationType];
34
+
35
+ export const AccessType = {
36
+ Encode: "encode",
37
+ Recall: "recall",
38
+ Consolidate: "consolidate",
39
+ } as const;
40
+ export type AccessType = (typeof AccessType)[keyof typeof AccessType];
41
+
42
+ export interface EncodeInput {
43
+ content: string;
44
+ type: MemoryType;
45
+ emotion?: Emotion;
46
+ emotionWeight?: number;
47
+ context?: string;
48
+ }
49
+
50
+ export interface RecallResult {
51
+ memory: _Memory;
52
+ activation: number;
53
+ spreadingActivation: number;
54
+ latency: number;
55
+ }
56
+
57
+ export function isValidMemoryType(s: string): s is MemoryType {
58
+ return (Object.values(MemoryType) as string[]).includes(s);
59
+ }
60
+
61
+ const TYPE_PREFIX: Record<MemoryType, string> = {
62
+ episodic: "epi",
63
+ semantic: "sem",
64
+ procedural: "proc",
65
+ };
66
+
67
+ function contentSlug(content: string, maxLen = 30): string {
68
+ return content
69
+ .toLowerCase()
70
+ .replace(/[^a-z0-9]+/g, "-")
71
+ .replace(/^-|-$/g, "")
72
+ .slice(0, maxLen)
73
+ .replace(/-$/, "");
74
+ }
75
+
76
+ function shortHash(input: string): string {
77
+ let h = 0x811c9dc5;
78
+ for (let i = 0; i < input.length; i++) {
79
+ h ^= input.charCodeAt(i);
80
+ h = (h * 0x01000193) >>> 0;
81
+ }
82
+ return h.toString(16).padStart(6, "0").slice(0, 6);
83
+ }
84
+
85
+ export function generateMemoryId(content: string, type: MemoryType): string {
86
+ const prefix = TYPE_PREFIX[type];
87
+ const slug = contentSlug(content);
88
+ const hash = shortHash(content + Date.now());
89
+ return `${prefix}:${slug}:${hash}`;
90
+ }
91
+
92
+ export function generateId(): string {
93
+ return crypto.randomUUID();
94
+ }
@@ -0,0 +1,36 @@
1
+ import type { CognitiveConfig } from "../config/defaults.ts";
2
+ import type { EngramStorage } from "../storage/sqlite.ts";
3
+ import type { Memory } from "./memory.ts";
4
+ import { encode } from "./encoder.ts";
5
+
6
+ export function encodeProcedural(
7
+ storage: EngramStorage,
8
+ content: string,
9
+ config: CognitiveConfig,
10
+ options?: { context?: string; now?: number },
11
+ ): Memory {
12
+ return encode(
13
+ storage,
14
+ {
15
+ content,
16
+ type: "procedural",
17
+ context: options?.context,
18
+ },
19
+ config,
20
+ options?.now,
21
+ );
22
+ }
23
+
24
+ export function getSkills(storage: EngramStorage): Memory[] {
25
+ return storage.getAllMemories("procedural");
26
+ }
27
+
28
+ export function promoteToSkill(storage: EngramStorage, memoryId: string): Memory | null {
29
+ const memory = storage.getMemory(memoryId);
30
+ if (!memory) return null;
31
+
32
+ memory.type = "procedural";
33
+ storage.updateMemory(memory);
34
+
35
+ return memory;
36
+ }
@@ -0,0 +1,102 @@
1
+ import type { CognitiveConfig } from "../config/defaults.ts";
2
+ import type { EngramStorage } from "../storage/sqlite.ts";
3
+ import type { Memory, RecallResult } from "./memory.ts";
4
+ import { computeActivation, spreadingActivationStrength } from "./activation.ts";
5
+
6
+ export function recall(
7
+ storage: EngramStorage,
8
+ cue: string,
9
+ config: CognitiveConfig,
10
+ options?: {
11
+ limit?: number;
12
+ associative?: boolean;
13
+ type?: Memory["type"];
14
+ context?: string;
15
+ now?: number;
16
+ deterministic?: boolean;
17
+ },
18
+ ): RecallResult[] {
19
+ const now = options?.now ?? Date.now();
20
+ const limit = options?.limit ?? 10;
21
+ const associative = options?.associative ?? true;
22
+
23
+ const candidateMap = new Map<string, Memory>();
24
+
25
+ const ftsIds = storage.searchFTS(cue, limit * 2);
26
+ for (const id of ftsIds) {
27
+ const m = storage.getMemory(id);
28
+ if (!m) continue;
29
+ if (options?.type && m.type !== options.type) continue;
30
+ if (options?.context && (!m.context || !m.context.startsWith(options.context))) continue;
31
+ candidateMap.set(m.id, m);
32
+ }
33
+
34
+ const allCandidates = options?.type
35
+ ? storage.getAllMemories(options.type)
36
+ : storage.getAllMemories();
37
+
38
+ let filtered = allCandidates;
39
+ if (options?.context) {
40
+ filtered = filtered.filter((m) => m.context?.startsWith(options.context!));
41
+ }
42
+
43
+ const sorted = filtered.sort((a, b) => b.activation - a.activation);
44
+ for (const m of sorted.slice(0, limit)) {
45
+ candidateMap.set(m.id, m);
46
+ }
47
+
48
+ if (candidateMap.size === 0) return [];
49
+
50
+ const results: RecallResult[] = [];
51
+
52
+ for (const memory of candidateMap.values()) {
53
+ const timestamps = storage.getAccessTimestamps(memory.id);
54
+
55
+ let spreadingSum = 0;
56
+ if (associative) {
57
+ const assocFrom = storage.getAssociationsFrom(memory.id);
58
+ const assocTo = storage.getAssociationsTo(memory.id);
59
+ const allAssocs = [...assocFrom, ...assocTo];
60
+
61
+ for (const assoc of allAssocs) {
62
+ const otherId = assoc.sourceId === memory.id ? assoc.targetId : assoc.sourceId;
63
+ const fanCount = storage.getFanCount(otherId);
64
+ const strength = spreadingActivationStrength(config.maxSpreadingActivation, fanCount);
65
+ spreadingSum += assoc.strength * strength;
66
+ }
67
+ }
68
+
69
+ const { activation, latency } = computeActivation(timestamps, now, config, {
70
+ spreadingSum,
71
+ noiseOverride: options?.deterministic ? 0 : undefined,
72
+ emotionWeight: memory.emotionWeight,
73
+ });
74
+
75
+ if (activation <= config.retrievalThreshold) continue;
76
+
77
+ results.push({
78
+ memory,
79
+ activation,
80
+ spreadingActivation: spreadingSum,
81
+ latency,
82
+ });
83
+ }
84
+
85
+ results.sort((a, b) => b.activation - a.activation);
86
+
87
+ for (const result of results.slice(0, limit)) {
88
+ storage.logAccess(result.memory.id, "recall", now);
89
+ result.memory.recallCount++;
90
+ result.memory.lastRecalledAt = now;
91
+ const newTimestamps = storage.getAccessTimestamps(result.memory.id);
92
+ const recomputed = computeActivation(newTimestamps, now, config, {
93
+ spreadingSum: result.spreadingActivation,
94
+ noiseOverride: 0,
95
+ emotionWeight: result.memory.emotionWeight,
96
+ });
97
+ result.memory.activation = recomputed.activation;
98
+ storage.updateMemory(result.memory);
99
+ }
100
+
101
+ return results.slice(0, limit);
102
+ }
@@ -0,0 +1,42 @@
1
+ import type { CognitiveConfig } from "../config/defaults.ts";
2
+ import type { EngramStorage } from "../storage/sqlite.ts";
3
+ import type { Memory } from "./memory.ts";
4
+
5
+ export interface ReconsolidationContext {
6
+ newContext?: string;
7
+ currentEmotion?: Memory["emotion"];
8
+ currentEmotionWeight?: number;
9
+ }
10
+
11
+ export function reconsolidate(
12
+ storage: EngramStorage,
13
+ memory: Memory,
14
+ recallContext: ReconsolidationContext,
15
+ config: CognitiveConfig,
16
+ ): Memory {
17
+ const blendRate = config.reconsolidationBlendRate;
18
+
19
+ if (recallContext.newContext && memory.context) {
20
+ if (!memory.context.includes(recallContext.newContext)) {
21
+ memory.context = `${memory.context}, ${recallContext.newContext}`;
22
+ }
23
+ } else if (recallContext.newContext && !memory.context) {
24
+ memory.context = recallContext.newContext;
25
+ }
26
+
27
+ if (recallContext.currentEmotion && recallContext.currentEmotion !== memory.emotion) {
28
+ const blendedWeight =
29
+ memory.emotionWeight * (1 - blendRate) +
30
+ (recallContext.currentEmotionWeight ?? 0.5) * blendRate;
31
+
32
+ if (blendRate > 0.3 || memory.emotionWeight < 0.2) {
33
+ memory.emotion = recallContext.currentEmotion;
34
+ }
35
+ memory.emotionWeight = blendedWeight;
36
+ }
37
+
38
+ memory.reconsolidationCount++;
39
+ storage.updateMemory(memory);
40
+
41
+ return memory;
42
+ }
@@ -0,0 +1,24 @@
1
+ export function tokenize(text: string): string[] {
2
+ return text
3
+ .toLowerCase()
4
+ .split(/[^a-z0-9]+/)
5
+ .filter((t) => t.length > 1);
6
+ }
7
+
8
+ function termFrequency(tokens: string[]): Map<string, number> {
9
+ const tf = new Map<string, number>();
10
+ for (const token of tokens) {
11
+ tf.set(token, (tf.get(token) ?? 0) + 1);
12
+ }
13
+ return tf;
14
+ }
15
+
16
+ export function extractKeywords(text: string, maxKeywords = 5): string[] {
17
+ const tokens = tokenize(text);
18
+ const tf = termFrequency(tokens);
19
+
20
+ return [...tf.entries()]
21
+ .sort((a, b) => b[1] - a[1])
22
+ .slice(0, maxKeywords)
23
+ .map(([term]) => term);
24
+ }
@@ -0,0 +1,67 @@
1
+ import type { CognitiveConfig } from "../config/defaults.ts";
2
+ import type { EngramStorage } from "../storage/sqlite.ts";
3
+ import type { WorkingMemorySlot } from "./memory.ts";
4
+
5
+ export function pushFocus(
6
+ storage: EngramStorage,
7
+ content: string,
8
+ config: CognitiveConfig,
9
+ options?: { memoryRef?: string; now?: number },
10
+ ): { slot: WorkingMemorySlot; evicted: WorkingMemorySlot | null } {
11
+ const now = options?.now ?? Date.now();
12
+ const currentSlots = storage.getWorkingMemory();
13
+
14
+ let evicted: WorkingMemorySlot | null = null;
15
+
16
+ if (currentSlots.length >= config.workingMemoryCapacity) {
17
+ const oldest = currentSlots.reduce((min, s) => (s.pushedAt < min.pushedAt ? s : min));
18
+ evicted = oldest;
19
+ storage.removeWorkingMemorySlot(oldest.slot);
20
+ }
21
+
22
+ const usedSlots = new Set(storage.getWorkingMemory().map((s) => s.slot));
23
+ let nextSlot = 0;
24
+ while (usedSlots.has(nextSlot)) nextSlot++;
25
+
26
+ const slot: WorkingMemorySlot = {
27
+ slot: nextSlot,
28
+ memoryRef: options?.memoryRef ?? null,
29
+ content,
30
+ pushedAt: now,
31
+ };
32
+
33
+ storage.pushWorkingMemory(slot);
34
+ return { slot, evicted };
35
+ }
36
+
37
+ export function popFocus(storage: EngramStorage): WorkingMemorySlot | null {
38
+ const slots = storage.getWorkingMemory();
39
+ if (slots.length === 0) return null;
40
+
41
+ const newest = slots.reduce((max, s) => (s.pushedAt > max.pushedAt ? s : max));
42
+
43
+ storage.removeWorkingMemorySlot(newest.slot);
44
+ return newest;
45
+ }
46
+
47
+ export function getFocus(storage: EngramStorage): WorkingMemorySlot[] {
48
+ return storage.getWorkingMemory();
49
+ }
50
+
51
+ export function clearFocus(storage: EngramStorage): number {
52
+ const count = storage.getWorkingMemoryCount();
53
+ storage.clearWorkingMemory();
54
+ return count;
55
+ }
56
+
57
+ export function focusUtilization(
58
+ storage: EngramStorage,
59
+ config: CognitiveConfig,
60
+ ): { used: number; capacity: number; utilization: number } {
61
+ const used = storage.getWorkingMemoryCount();
62
+ return {
63
+ used,
64
+ capacity: config.workingMemoryCapacity,
65
+ utilization: used / config.workingMemoryCapacity,
66
+ };
67
+ }
package/src/index.ts ADDED
@@ -0,0 +1,57 @@
1
+ export { EngramEngine } from "./core/engine.ts";
2
+ export { EngramStorage } from "./storage/sqlite.ts";
3
+ export { encode } from "./core/encoder.ts";
4
+ export { recall } from "./core/recall.ts";
5
+ export { consolidate, type ConsolidationResult } from "./core/consolidation.ts";
6
+ export { reconsolidate, type ReconsolidationContext } from "./core/reconsolidation.ts";
7
+ export {
8
+ pushFocus,
9
+ popFocus,
10
+ getFocus,
11
+ clearFocus,
12
+ focusUtilization,
13
+ } from "./core/working-memory.ts";
14
+ export {
15
+ formAssociation,
16
+ formTemporalAssociations,
17
+ formSemanticAssociations,
18
+ recordCoRecall,
19
+ getSpreadingActivationTargets,
20
+ } from "./core/associations.ts";
21
+ export { encodeProcedural, getSkills, promoteToSkill } from "./core/procedural-store.ts";
22
+ export { discoverChunks, getChunkMembers, type Chunk } from "./core/chunking.ts";
23
+ export {
24
+ baseLevelActivation,
25
+ spreadingActivationStrength,
26
+ totalActivation,
27
+ activationNoise,
28
+ canRetrieve,
29
+ retrievalLatency,
30
+ computeActivation,
31
+ } from "./core/activation.ts";
32
+ export { ebbinghausRetention, memoryStrength, refreshActivations } from "./core/forgetting.ts";
33
+ export { tokenize, extractKeywords } from "./core/search.ts";
34
+ export { defaultEmotionWeight, isValidEmotion } from "./core/emotional-tag.ts";
35
+ export {
36
+ DEFAULT_CONFIG,
37
+ loadConfig,
38
+ resolveDbPath,
39
+ type CognitiveConfig,
40
+ } from "./config/defaults.ts";
41
+ export {
42
+ MemoryType,
43
+ Emotion,
44
+ AssociationType,
45
+ AccessType,
46
+ generateId,
47
+ generateMemoryId,
48
+ } from "./core/memory.ts";
49
+ export type {
50
+ Memory,
51
+ EncodeInput,
52
+ AccessLogEntry,
53
+ Association,
54
+ WorkingMemorySlot,
55
+ ConsolidationLog,
56
+ RecallResult,
57
+ } from "./core/memory.ts";
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env bun
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { z } from "zod/v4";
5
+ import { EngramEngine } from "../core/engine.ts";
6
+ import { MemoryType, Emotion } from "../core/memory.ts";
7
+ import { handleStore, handleRecall, handleManage } from "./tools.ts";
8
+
9
+ const engine = EngramEngine.create();
10
+
11
+ const server = new McpServer({
12
+ name: "engram",
13
+ version: "0.2.0",
14
+ });
15
+
16
+ server.registerTool(
17
+ "memory_store",
18
+ {
19
+ title: "Store Memory",
20
+ description: `Store memories. Requires "action" field.
21
+
22
+ Actions:
23
+ - action:"encode" — Create a new memory. Params: content (required), type? (semantic|episodic|procedural, default semantic), emotion? (neutral|curiosity|surprise|anxiety|satisfaction|frustration|confusion|excitement|calm|urgency), emotionWeight? (0-1), context? (e.g. "project:acme")
24
+ - action:"reconsolidate" — Update an existing memory during recall. Params: id (required), newContext?, currentEmotion?, currentEmotionWeight?`,
25
+ inputSchema: z.discriminatedUnion("action", [
26
+ z.object({
27
+ action: z.literal("encode"),
28
+ content: z.string().describe("Memory content"),
29
+ type: z.nativeEnum(MemoryType).optional().describe("Memory type (default: semantic)"),
30
+ emotion: z.nativeEnum(Emotion).optional().describe("Emotional tag"),
31
+ emotionWeight: z.number().min(0).max(1).optional().describe("Emotion intensity 0-1"),
32
+ context: z.string().optional().describe("Context tag (e.g. project:acme)"),
33
+ }),
34
+ z.object({
35
+ action: z.literal("reconsolidate"),
36
+ id: z.string().describe("Memory ID to update"),
37
+ newContext: z.string().optional().describe("New context to blend"),
38
+ currentEmotion: z.nativeEnum(Emotion).optional().describe("Current emotional state"),
39
+ currentEmotionWeight: z.number().min(0).max(1).optional().describe("Emotion intensity"),
40
+ }),
41
+ ]),
42
+ },
43
+ async (args) => handleStore(engine, args),
44
+ );
45
+
46
+ server.registerTool(
47
+ "memory_recall",
48
+ {
49
+ title: "Recall Memories",
50
+ description: `Retrieve memories. Requires "action" field.
51
+
52
+ Actions:
53
+ - action:"recall" — Cue-based retrieval with spreading activation. Params: cue (required), limit? (default 5), type? (semantic|episodic|procedural), context?, associative? (default true), verbose?
54
+ - action:"inspect" — Examine a specific memory's full lifecycle. Params: id (required, full ID or prefix)
55
+ - action:"stats" — Memory system overview (counts, working memory usage, last consolidation). No extra params.`,
56
+ inputSchema: z.discriminatedUnion("action", [
57
+ z.object({
58
+ action: z.literal("recall").optional().default("recall"),
59
+ cue: z.string().describe("Recall cue"),
60
+ limit: z.number().optional().describe("Max results (default: 5)"),
61
+ type: z.nativeEnum(MemoryType).optional().describe("Filter by type"),
62
+ context: z.string().optional().describe("Filter by context"),
63
+ associative: z.boolean().optional().describe("Spreading activation (default: true)"),
64
+ verbose: z.boolean().optional().describe("Full fields"),
65
+ }),
66
+ z.object({
67
+ action: z.literal("inspect"),
68
+ id: z.string().describe("Memory ID or prefix"),
69
+ }),
70
+ z.object({
71
+ action: z.literal("stats"),
72
+ }),
73
+ ]),
74
+ },
75
+ async (args) => handleRecall(engine, args),
76
+ );
77
+
78
+ server.registerTool(
79
+ "memory_manage",
80
+ {
81
+ title: "Manage Memory",
82
+ description: `Memory maintenance. Requires "action" field.
83
+
84
+ Actions:
85
+ - action:"consolidate" — Run sleep cycle (strengthen, prune, extract patterns, discover links). No extra params.
86
+ - action:"focus_push" — Push to working memory buffer. Params: content (required), memoryRef?
87
+ - action:"focus_pop" — Remove most recent item from working memory. No extra params.
88
+ - action:"focus_get" — View current working memory contents. No extra params.
89
+ - action:"focus_clear" — Clear all working memory. No extra params.`,
90
+ inputSchema: z.discriminatedUnion("action", [
91
+ z.object({
92
+ action: z.literal("consolidate"),
93
+ }),
94
+ z.object({
95
+ action: z.literal("focus_push"),
96
+ content: z.string().describe("Content to hold in focus"),
97
+ memoryRef: z.string().optional().describe("Reference to existing memory ID"),
98
+ }),
99
+ z.object({
100
+ action: z.literal("focus_pop"),
101
+ }),
102
+ z.object({
103
+ action: z.literal("focus_get"),
104
+ }),
105
+ z.object({
106
+ action: z.literal("focus_clear"),
107
+ }),
108
+ ]),
109
+ },
110
+ async (args) => handleManage(engine, args),
111
+ );
112
+
113
+ async function main() {
114
+ const transport = new StdioServerTransport();
115
+ await server.connect(transport);
116
+ console.error("engram MCP server running on stdio");
117
+ }
118
+
119
+ main().catch((error) => {
120
+ console.error("Fatal error:", error);
121
+ process.exit(1);
122
+ });