@cogmem/engram 0.2.1 → 0.3.1
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 +45 -25
- package/SKILL.md +120 -0
- package/drizzle/meta/0000_snapshot.json +17 -50
- package/drizzle/meta/_journal.json +1 -1
- package/package.json +30 -28
- package/src/cli/commands/encode.ts +15 -6
- package/src/cli/commands/focus.ts +38 -11
- package/src/cli/commands/health.ts +47 -13
- package/src/cli/commands/inspect.ts +7 -2
- package/src/cli/commands/install.ts +122 -0
- package/src/cli/commands/list.ts +3 -1
- package/src/cli/commands/recall.ts +4 -2
- package/src/cli/commands/sleep.ts +31 -11
- package/src/cli/commands/stats.ts +38 -15
- package/src/cli/format.ts +3 -2
- package/src/cli/index.ts +6 -3
- package/src/cli/providers/claude.ts +100 -0
- package/src/cli/providers/index.ts +12 -0
- package/src/cli/providers/types.ts +20 -0
- package/src/config/defaults.ts +5 -2
- package/src/core/activation.ts +43 -11
- package/src/core/associations.ts +22 -19
- package/src/core/chunking.ts +14 -4
- package/src/core/consolidation.ts +21 -6
- package/src/core/encoder.ts +3 -3
- package/src/core/engine.ts +3 -2
- package/src/core/forgetting.ts +11 -4
- package/src/core/memory.ts +2 -1
- package/src/core/procedural-store.ts +1 -1
- package/src/core/recall.ts +32 -19
- package/src/core/reconsolidation.ts +5 -2
- package/src/core/working-memory.ts +8 -4
- package/src/mcp/server.ts +53 -15
- package/src/mcp/tools.ts +108 -45
- package/src/storage/schema.ts +1 -0
- package/src/storage/sqlite.ts +22 -7
package/src/core/activation.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { CognitiveConfig } from "../config/defaults.ts";
|
|
|
4
4
|
export function baseLevelActivation(
|
|
5
5
|
accessTimestamps: number[],
|
|
6
6
|
now: number,
|
|
7
|
-
decayRate: number
|
|
7
|
+
decayRate: number
|
|
8
8
|
): number {
|
|
9
9
|
if (accessTimestamps.length === 0) return -Infinity;
|
|
10
10
|
|
|
@@ -18,13 +18,20 @@ export function baseLevelActivation(
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
// S_ji = S - ln(fan_j)
|
|
21
|
-
export function spreadingActivationStrength(
|
|
21
|
+
export function spreadingActivationStrength(
|
|
22
|
+
maxStrength: number,
|
|
23
|
+
fanCount: number
|
|
24
|
+
): number {
|
|
22
25
|
if (fanCount <= 0) return 0;
|
|
23
26
|
return Math.max(0, maxStrength - Math.log(fanCount));
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
// A_i = B_i + Σ(W_j · S_ji) + ε
|
|
27
|
-
export function totalActivation(
|
|
30
|
+
export function totalActivation(
|
|
31
|
+
baseLevel: number,
|
|
32
|
+
spreadingSum: number,
|
|
33
|
+
noise: number
|
|
34
|
+
): number {
|
|
28
35
|
return baseLevel + spreadingSum + noise;
|
|
29
36
|
}
|
|
30
37
|
|
|
@@ -44,7 +51,7 @@ export function canRetrieve(activation: number, threshold: number): boolean {
|
|
|
44
51
|
export function retrievalLatency(
|
|
45
52
|
activation: number,
|
|
46
53
|
latencyFactor: number,
|
|
47
|
-
latencyExponent: number
|
|
54
|
+
latencyExponent: number
|
|
48
55
|
): number {
|
|
49
56
|
return latencyFactor * Math.exp(-latencyExponent * activation);
|
|
50
57
|
}
|
|
@@ -57,19 +64,44 @@ export function computeActivation(
|
|
|
57
64
|
spreadingSum?: number;
|
|
58
65
|
noiseOverride?: number;
|
|
59
66
|
emotionWeight?: number;
|
|
60
|
-
}
|
|
61
|
-
): {
|
|
62
|
-
|
|
67
|
+
}
|
|
68
|
+
): {
|
|
69
|
+
activation: number;
|
|
70
|
+
baseLevel: number;
|
|
71
|
+
spreading: number;
|
|
72
|
+
noise: number;
|
|
73
|
+
latency: number;
|
|
74
|
+
} {
|
|
75
|
+
const baseLevel = baseLevelActivation(
|
|
76
|
+
accessTimestamps,
|
|
77
|
+
now,
|
|
78
|
+
config.decayRate
|
|
79
|
+
);
|
|
63
80
|
|
|
64
81
|
const emotionBoost = options?.emotionWeight
|
|
65
82
|
? Math.log(1 + options.emotionWeight * config.emotionalBoostFactor)
|
|
66
83
|
: 0;
|
|
67
84
|
|
|
68
85
|
const spreading = options?.spreadingSum ?? 0;
|
|
69
|
-
const noise =
|
|
86
|
+
const noise =
|
|
87
|
+
options?.noiseOverride ?? activationNoise(config.activationNoise);
|
|
70
88
|
|
|
71
|
-
const activation = totalActivation(
|
|
72
|
-
|
|
89
|
+
const activation = totalActivation(
|
|
90
|
+
baseLevel + emotionBoost,
|
|
91
|
+
spreading,
|
|
92
|
+
noise
|
|
93
|
+
);
|
|
94
|
+
const latency = retrievalLatency(
|
|
95
|
+
activation,
|
|
96
|
+
config.latencyFactor,
|
|
97
|
+
config.latencyExponent
|
|
98
|
+
);
|
|
73
99
|
|
|
74
|
-
return {
|
|
100
|
+
return {
|
|
101
|
+
activation,
|
|
102
|
+
baseLevel: baseLevel + emotionBoost,
|
|
103
|
+
spreading,
|
|
104
|
+
noise,
|
|
105
|
+
latency,
|
|
106
|
+
};
|
|
75
107
|
}
|
package/src/core/associations.ts
CHANGED
|
@@ -113,28 +113,27 @@ export function formSemanticAssociations(
|
|
|
113
113
|
storage: EngramStorage,
|
|
114
114
|
memory: Memory,
|
|
115
115
|
now?: number,
|
|
116
|
+
preloadedMemories?: Memory[],
|
|
116
117
|
): Association[] {
|
|
117
118
|
const currentTime = now ?? Date.now();
|
|
118
119
|
const keywords = extractKeywords(memory.content);
|
|
119
120
|
if (keywords.length === 0) return [];
|
|
120
121
|
|
|
121
|
-
const
|
|
122
|
+
const candidates = preloadedMemories ?? storage.getAllMemories();
|
|
123
|
+
const existing = storage.getAssociations(memory.id);
|
|
124
|
+
const linkedSet = new Set(
|
|
125
|
+
existing.flatMap((a) => [`${a.sourceId}:${a.targetId}`, `${a.targetId}:${a.sourceId}`]),
|
|
126
|
+
);
|
|
122
127
|
const formed: Association[] = [];
|
|
123
128
|
|
|
124
|
-
for (const other of
|
|
129
|
+
for (const other of candidates) {
|
|
125
130
|
if (other.id === memory.id) continue;
|
|
126
131
|
|
|
127
132
|
const otherKeywords = extractKeywords(other.content);
|
|
128
133
|
const overlap = keywords.filter((k) => otherKeywords.includes(k));
|
|
129
134
|
|
|
130
135
|
if (overlap.length > 0) {
|
|
131
|
-
|
|
132
|
-
const alreadyLinked = existing.some(
|
|
133
|
-
(a) =>
|
|
134
|
-
(a.sourceId === memory.id && a.targetId === other.id) ||
|
|
135
|
-
(a.sourceId === other.id && a.targetId === memory.id),
|
|
136
|
-
);
|
|
137
|
-
if (alreadyLinked) continue;
|
|
136
|
+
if (linkedSet.has(`${memory.id}:${other.id}`)) continue;
|
|
138
137
|
|
|
139
138
|
const strength = overlap.length / Math.max(keywords.length, otherKeywords.length);
|
|
140
139
|
const assoc = formAssociation(
|
|
@@ -146,6 +145,8 @@ export function formSemanticAssociations(
|
|
|
146
145
|
currentTime,
|
|
147
146
|
);
|
|
148
147
|
formed.push(assoc);
|
|
148
|
+
linkedSet.add(`${memory.id}:${other.id}`);
|
|
149
|
+
linkedSet.add(`${other.id}:${memory.id}`);
|
|
149
150
|
}
|
|
150
151
|
}
|
|
151
152
|
|
|
@@ -156,26 +157,26 @@ export function formEmotionalAssociations(
|
|
|
156
157
|
storage: EngramStorage,
|
|
157
158
|
memory: Memory,
|
|
158
159
|
now?: number,
|
|
160
|
+
preloadedMemories?: Memory[],
|
|
159
161
|
): Association[] {
|
|
160
162
|
if (memory.emotion === "neutral" || memory.emotionWeight <= 0.3) return [];
|
|
161
163
|
|
|
162
164
|
const currentTime = now ?? Date.now();
|
|
163
|
-
const
|
|
165
|
+
const candidates = preloadedMemories ?? storage.getAllMemories();
|
|
166
|
+
const existing = storage.getAssociations(memory.id);
|
|
167
|
+
const emotionalLinks = new Set(
|
|
168
|
+
existing
|
|
169
|
+
.filter((a) => a.type === "emotional")
|
|
170
|
+
.flatMap((a) => [`${a.sourceId}:${a.targetId}`, `${a.targetId}:${a.sourceId}`]),
|
|
171
|
+
);
|
|
164
172
|
const formed: Association[] = [];
|
|
165
173
|
const memoryTier = AROUSAL_TIERS[memory.emotion];
|
|
166
174
|
|
|
167
|
-
for (const other of
|
|
175
|
+
for (const other of candidates) {
|
|
168
176
|
if (other.id === memory.id) continue;
|
|
169
177
|
if (other.emotion === "neutral" || other.emotionWeight <= 0.3) continue;
|
|
170
178
|
|
|
171
|
-
|
|
172
|
-
const alreadyLinked = existing.some(
|
|
173
|
-
(a) =>
|
|
174
|
-
a.type === "emotional" &&
|
|
175
|
-
((a.sourceId === memory.id && a.targetId === other.id) ||
|
|
176
|
-
(a.sourceId === other.id && a.targetId === memory.id)),
|
|
177
|
-
);
|
|
178
|
-
if (alreadyLinked) continue;
|
|
179
|
+
if (emotionalLinks.has(`${memory.id}:${other.id}`)) continue;
|
|
179
180
|
|
|
180
181
|
let strength: number;
|
|
181
182
|
if (memory.emotion === other.emotion) {
|
|
@@ -190,6 +191,8 @@ export function formEmotionalAssociations(
|
|
|
190
191
|
|
|
191
192
|
const assoc = formAssociation(storage, memory.id, other.id, "emotional", strength, currentTime);
|
|
192
193
|
formed.push(assoc);
|
|
194
|
+
emotionalLinks.add(`${memory.id}:${other.id}`);
|
|
195
|
+
emotionalLinks.add(`${other.id}:${memory.id}`);
|
|
193
196
|
}
|
|
194
197
|
|
|
195
198
|
return formed;
|
package/src/core/chunking.ts
CHANGED
|
@@ -62,7 +62,10 @@ class UnionFind {
|
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
export function discoverChunks(
|
|
65
|
+
export function discoverChunks(
|
|
66
|
+
storage: EngramStorage,
|
|
67
|
+
config: CognitiveConfig
|
|
68
|
+
): Chunk[] {
|
|
66
69
|
const allMemories = storage.getAllMemories();
|
|
67
70
|
const memoryMap = new Map<string, Memory>();
|
|
68
71
|
const uf = new UnionFind();
|
|
@@ -77,7 +80,8 @@ export function discoverChunks(storage: EngramStorage, config: CognitiveConfig):
|
|
|
77
80
|
const associations = storage.getAssociations(memory.id);
|
|
78
81
|
for (const assoc of associations) {
|
|
79
82
|
if (assoc.strength < config.chunkingSimilarityThreshold) continue;
|
|
80
|
-
const otherId =
|
|
83
|
+
const otherId =
|
|
84
|
+
assoc.sourceId === memory.id ? assoc.targetId : assoc.sourceId;
|
|
81
85
|
if (!memoryMap.has(otherId)) continue;
|
|
82
86
|
uf.union(memory.id, otherId);
|
|
83
87
|
}
|
|
@@ -89,7 +93,10 @@ export function discoverChunks(storage: EngramStorage, config: CognitiveConfig):
|
|
|
89
93
|
|
|
90
94
|
const members = memberIds.map((id) => memoryMap.get(id)!);
|
|
91
95
|
const chunkId = generateId();
|
|
92
|
-
const keywords = extractKeywords(
|
|
96
|
+
const keywords = extractKeywords(
|
|
97
|
+
members.map((m) => m.content).join(" "),
|
|
98
|
+
3
|
|
99
|
+
);
|
|
93
100
|
const label = keywords.join(" + ") || "chunk";
|
|
94
101
|
|
|
95
102
|
for (const member of members) {
|
|
@@ -103,6 +110,9 @@ export function discoverChunks(storage: EngramStorage, config: CognitiveConfig):
|
|
|
103
110
|
return chunks;
|
|
104
111
|
}
|
|
105
112
|
|
|
106
|
-
export function getChunkMembers(
|
|
113
|
+
export function getChunkMembers(
|
|
114
|
+
storage: EngramStorage,
|
|
115
|
+
chunkId: string
|
|
116
|
+
): Memory[] {
|
|
107
117
|
return storage.getAllMemories().filter((m) => m.chunkId === chunkId);
|
|
108
118
|
}
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import type { CognitiveConfig } from "../config/defaults.ts";
|
|
2
2
|
import type { EngramStorage } from "../storage/sqlite.ts";
|
|
3
|
-
import type { Memory, ConsolidationLog } from "./memory.ts";
|
|
4
|
-
import { generateId } from "./memory.ts";
|
|
5
|
-
import { refreshActivations } from "./forgetting.ts";
|
|
6
3
|
import {
|
|
7
4
|
formSemanticAssociations,
|
|
8
5
|
formTemporalAssociations,
|
|
@@ -10,6 +7,9 @@ import {
|
|
|
10
7
|
formCausalAssociations,
|
|
11
8
|
} from "./associations.ts";
|
|
12
9
|
import { encode } from "./encoder.ts";
|
|
10
|
+
import { refreshActivations } from "./forgetting.ts";
|
|
11
|
+
import type { Memory, ConsolidationLog } from "./memory.ts";
|
|
12
|
+
import { generateId } from "./memory.ts";
|
|
13
13
|
import { extractKeywords, tokenize } from "./search.ts";
|
|
14
14
|
|
|
15
15
|
export interface ConsolidationResult {
|
|
@@ -69,11 +69,26 @@ export function consolidate(
|
|
|
69
69
|
const remainingMemories = storage.getAllMemories();
|
|
70
70
|
for (const memory of remainingMemories) {
|
|
71
71
|
const temporalAssocs = formTemporalAssociations(storage, memory, config, currentTime);
|
|
72
|
-
const semanticAssocs = formSemanticAssociations(
|
|
73
|
-
|
|
72
|
+
const semanticAssocs = formSemanticAssociations(
|
|
73
|
+
storage,
|
|
74
|
+
memory,
|
|
75
|
+
currentTime,
|
|
76
|
+
remainingMemories,
|
|
77
|
+
);
|
|
78
|
+
const emotionalAssocs = formEmotionalAssociations(
|
|
79
|
+
storage,
|
|
80
|
+
memory,
|
|
81
|
+
currentTime,
|
|
82
|
+
remainingMemories,
|
|
83
|
+
);
|
|
74
84
|
const causalAssocs = formCausalAssociations(storage, memory, config, currentTime);
|
|
75
85
|
|
|
76
|
-
for (const assoc of [
|
|
86
|
+
for (const assoc of [
|
|
87
|
+
...temporalAssocs,
|
|
88
|
+
...semanticAssocs,
|
|
89
|
+
...emotionalAssocs,
|
|
90
|
+
...causalAssocs,
|
|
91
|
+
]) {
|
|
77
92
|
result.associationsDiscovered++;
|
|
78
93
|
result.discoveredAssociationPairs.push([assoc.sourceId, assoc.targetId]);
|
|
79
94
|
}
|
package/src/core/encoder.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { CognitiveConfig } from "../config/defaults.ts";
|
|
2
2
|
import type { EngramStorage } from "../storage/sqlite.ts";
|
|
3
|
-
import type { Memory, EncodeInput } from "./memory.ts";
|
|
4
|
-
import { generateMemoryId } from "./memory.ts";
|
|
5
|
-
import { defaultEmotionWeight } from "./emotional-tag.ts";
|
|
6
3
|
import { baseLevelActivation } from "./activation.ts";
|
|
7
4
|
import { formEmotionalAssociations, formCausalAssociations } from "./associations.ts";
|
|
5
|
+
import { defaultEmotionWeight } from "./emotional-tag.ts";
|
|
6
|
+
import type { Memory, EncodeInput } from "./memory.ts";
|
|
7
|
+
import { generateMemoryId } from "./memory.ts";
|
|
8
8
|
|
|
9
9
|
export function encode(
|
|
10
10
|
storage: EngramStorage,
|
package/src/core/engine.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { basename } from "node:path";
|
|
3
|
+
|
|
1
4
|
import type { CognitiveConfig } from "../config/defaults.ts";
|
|
2
5
|
import { loadConfig } from "../config/defaults.ts";
|
|
3
6
|
import { EngramStorage } from "../storage/sqlite.ts";
|
|
4
|
-
import { execSync } from "node:child_process";
|
|
5
|
-
import { basename } from "node:path";
|
|
6
7
|
|
|
7
8
|
export function detectProjectContext(): string | null {
|
|
8
9
|
try {
|
package/src/core/forgetting.ts
CHANGED
|
@@ -3,7 +3,10 @@ import type { EngramStorage } from "../storage/sqlite.ts";
|
|
|
3
3
|
import { baseLevelActivation } from "./activation.ts";
|
|
4
4
|
|
|
5
5
|
// R(t) = e^{-t/S}
|
|
6
|
-
export function ebbinghausRetention(
|
|
6
|
+
export function ebbinghausRetention(
|
|
7
|
+
timeSinceLastRecall: number,
|
|
8
|
+
strength: number
|
|
9
|
+
): number {
|
|
7
10
|
if (strength <= 0) return 0;
|
|
8
11
|
return Math.exp(-timeSinceLastRecall / strength);
|
|
9
12
|
}
|
|
@@ -12,7 +15,7 @@ export function memoryStrength(
|
|
|
12
15
|
recallCount: number,
|
|
13
16
|
emotionWeight: number,
|
|
14
17
|
associationCount: number,
|
|
15
|
-
emotionalBoostFactor: number
|
|
18
|
+
emotionalBoostFactor: number
|
|
16
19
|
): number {
|
|
17
20
|
const recallStrength = 1 + recallCount * 0.8;
|
|
18
21
|
const emotionalStrength = 1 + emotionWeight * emotionalBoostFactor;
|
|
@@ -23,7 +26,7 @@ export function memoryStrength(
|
|
|
23
26
|
export function refreshActivations(
|
|
24
27
|
storage: EngramStorage,
|
|
25
28
|
config: CognitiveConfig,
|
|
26
|
-
now?: number
|
|
29
|
+
now?: number
|
|
27
30
|
): { updated: number; atRisk: number } {
|
|
28
31
|
const currentTime = now ?? Date.now();
|
|
29
32
|
const memories = storage.getAllMemories();
|
|
@@ -34,7 +37,11 @@ export function refreshActivations(
|
|
|
34
37
|
if (memory.type === "procedural") continue;
|
|
35
38
|
|
|
36
39
|
const timestamps = storage.getAccessTimestamps(memory.id);
|
|
37
|
-
const newActivation = baseLevelActivation(
|
|
40
|
+
const newActivation = baseLevelActivation(
|
|
41
|
+
timestamps,
|
|
42
|
+
currentTime,
|
|
43
|
+
config.decayRate
|
|
44
|
+
);
|
|
38
45
|
|
|
39
46
|
const emotionBoost =
|
|
40
47
|
memory.emotionWeight > 0
|
package/src/core/memory.ts
CHANGED
|
@@ -32,7 +32,8 @@ export const AssociationType = {
|
|
|
32
32
|
Emotional: "emotional",
|
|
33
33
|
Causal: "causal",
|
|
34
34
|
} as const;
|
|
35
|
-
export type AssociationType =
|
|
35
|
+
export type AssociationType =
|
|
36
|
+
(typeof AssociationType)[keyof typeof AssociationType];
|
|
36
37
|
|
|
37
38
|
export const AccessType = {
|
|
38
39
|
Encode: "encode",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { CognitiveConfig } from "../config/defaults.ts";
|
|
2
2
|
import type { EngramStorage } from "../storage/sqlite.ts";
|
|
3
|
-
import type { Memory } from "./memory.ts";
|
|
4
3
|
import { encode } from "./encoder.ts";
|
|
4
|
+
import type { Memory } from "./memory.ts";
|
|
5
5
|
|
|
6
6
|
export function encodeProcedural(
|
|
7
7
|
storage: EngramStorage,
|
package/src/core/recall.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import type { CognitiveConfig } from "../config/defaults.ts";
|
|
2
2
|
import type { EngramStorage } from "../storage/sqlite.ts";
|
|
3
|
-
import
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
computeActivation,
|
|
5
|
+
spreadingActivationStrength,
|
|
6
|
+
} from "./activation.ts";
|
|
5
7
|
import { getSpreadingActivationTargets } from "./associations.ts";
|
|
8
|
+
import type { Memory, RecallResult } from "./memory.ts";
|
|
6
9
|
import { getWorkingMemoryIds } from "./working-memory.ts";
|
|
7
10
|
|
|
8
11
|
export function recall(
|
|
@@ -16,7 +19,7 @@ export function recall(
|
|
|
16
19
|
context?: string;
|
|
17
20
|
now?: number;
|
|
18
21
|
deterministic?: boolean;
|
|
19
|
-
}
|
|
22
|
+
}
|
|
20
23
|
): RecallResult[] {
|
|
21
24
|
const now = options?.now ?? Date.now();
|
|
22
25
|
const limit = options?.limit ?? 10;
|
|
@@ -31,21 +34,20 @@ export function recall(
|
|
|
31
34
|
for (const id of ftsIds) seedIds.add(id);
|
|
32
35
|
|
|
33
36
|
if (options?.context) {
|
|
34
|
-
const contextMatches = storage.getMemoriesByContext(
|
|
37
|
+
const contextMatches = storage.getMemoriesByContext(
|
|
38
|
+
options.context,
|
|
39
|
+
options?.type,
|
|
40
|
+
limit * 2
|
|
41
|
+
);
|
|
35
42
|
for (const m of contextMatches) seedIds.add(m.id);
|
|
36
43
|
}
|
|
37
44
|
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
filtered = filtered.filter((m) => m.context?.startsWith(options.context!));
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const sorted = filtered.sort((a, b) => b.activation - a.activation);
|
|
48
|
-
for (const m of sorted.slice(0, limit)) {
|
|
45
|
+
const topByActivation = storage.getTopMemoriesByActivation(
|
|
46
|
+
limit,
|
|
47
|
+
options?.type,
|
|
48
|
+
options?.context
|
|
49
|
+
);
|
|
50
|
+
for (const m of topByActivation) {
|
|
49
51
|
seedIds.add(m.id);
|
|
50
52
|
}
|
|
51
53
|
|
|
@@ -61,7 +63,10 @@ export function recall(
|
|
|
61
63
|
for (const t of targets) {
|
|
62
64
|
candidateIds.add(t.memoryId);
|
|
63
65
|
const existing = graphBoosts.get(t.memoryId) ?? 0;
|
|
64
|
-
graphBoosts.set(
|
|
66
|
+
graphBoosts.set(
|
|
67
|
+
t.memoryId,
|
|
68
|
+
existing + t.activationBoost * primingWeight
|
|
69
|
+
);
|
|
65
70
|
}
|
|
66
71
|
}
|
|
67
72
|
}
|
|
@@ -71,7 +76,11 @@ export function recall(
|
|
|
71
76
|
const m = storage.getMemory(id);
|
|
72
77
|
if (!m) continue;
|
|
73
78
|
if (options?.type && m.type !== options.type) continue;
|
|
74
|
-
if (
|
|
79
|
+
if (
|
|
80
|
+
options?.context &&
|
|
81
|
+
(!m.context || !m.context.startsWith(options.context))
|
|
82
|
+
)
|
|
83
|
+
continue;
|
|
75
84
|
candidateMap.set(m.id, m);
|
|
76
85
|
}
|
|
77
86
|
|
|
@@ -89,9 +98,13 @@ export function recall(
|
|
|
89
98
|
const allAssocs = [...assocFrom, ...assocTo];
|
|
90
99
|
|
|
91
100
|
for (const assoc of allAssocs) {
|
|
92
|
-
const otherId =
|
|
101
|
+
const otherId =
|
|
102
|
+
assoc.sourceId === memory.id ? assoc.targetId : assoc.sourceId;
|
|
93
103
|
const fanCount = storage.getFanCount(otherId);
|
|
94
|
-
const strength = spreadingActivationStrength(
|
|
104
|
+
const strength = spreadingActivationStrength(
|
|
105
|
+
config.maxSpreadingActivation,
|
|
106
|
+
fanCount
|
|
107
|
+
);
|
|
95
108
|
spreadingSum += assoc.strength * strength;
|
|
96
109
|
}
|
|
97
110
|
}
|
|
@@ -12,7 +12,7 @@ export function reconsolidate(
|
|
|
12
12
|
storage: EngramStorage,
|
|
13
13
|
memory: Memory,
|
|
14
14
|
recallContext: ReconsolidationContext,
|
|
15
|
-
config: CognitiveConfig
|
|
15
|
+
config: CognitiveConfig
|
|
16
16
|
): Memory {
|
|
17
17
|
const blendRate = config.reconsolidationBlendRate;
|
|
18
18
|
|
|
@@ -24,7 +24,10 @@ export function reconsolidate(
|
|
|
24
24
|
memory.context = recallContext.newContext;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
if (
|
|
27
|
+
if (
|
|
28
|
+
recallContext.currentEmotion &&
|
|
29
|
+
recallContext.currentEmotion !== memory.emotion
|
|
30
|
+
) {
|
|
28
31
|
const blendedWeight =
|
|
29
32
|
memory.emotionWeight * (1 - blendRate) +
|
|
30
33
|
(recallContext.currentEmotionWeight ?? 0.5) * blendRate;
|
|
@@ -6,7 +6,7 @@ export function pushFocus(
|
|
|
6
6
|
storage: EngramStorage,
|
|
7
7
|
content: string,
|
|
8
8
|
config: CognitiveConfig,
|
|
9
|
-
options?: { memoryRef?: string; now?: number }
|
|
9
|
+
options?: { memoryRef?: string; now?: number }
|
|
10
10
|
): { slot: WorkingMemorySlot; evicted: WorkingMemorySlot | null } {
|
|
11
11
|
const now = options?.now ?? Date.now();
|
|
12
12
|
const currentSlots = storage.getWorkingMemory();
|
|
@@ -14,7 +14,9 @@ export function pushFocus(
|
|
|
14
14
|
let evicted: WorkingMemorySlot | null = null;
|
|
15
15
|
|
|
16
16
|
if (currentSlots.length >= config.workingMemoryCapacity) {
|
|
17
|
-
const oldest = currentSlots.reduce((min, s) =>
|
|
17
|
+
const oldest = currentSlots.reduce((min, s) =>
|
|
18
|
+
s.pushedAt < min.pushedAt ? s : min
|
|
19
|
+
);
|
|
18
20
|
evicted = oldest;
|
|
19
21
|
storage.removeWorkingMemorySlot(oldest.slot);
|
|
20
22
|
}
|
|
@@ -38,7 +40,9 @@ export function popFocus(storage: EngramStorage): WorkingMemorySlot | null {
|
|
|
38
40
|
const slots = storage.getWorkingMemory();
|
|
39
41
|
if (slots.length === 0) return null;
|
|
40
42
|
|
|
41
|
-
const newest = slots.reduce((max, s) =>
|
|
43
|
+
const newest = slots.reduce((max, s) =>
|
|
44
|
+
s.pushedAt > max.pushedAt ? s : max
|
|
45
|
+
);
|
|
42
46
|
|
|
43
47
|
storage.removeWorkingMemorySlot(newest.slot);
|
|
44
48
|
return newest;
|
|
@@ -63,7 +67,7 @@ export function getWorkingMemoryIds(storage: EngramStorage): string[] {
|
|
|
63
67
|
|
|
64
68
|
export function focusUtilization(
|
|
65
69
|
storage: EngramStorage,
|
|
66
|
-
config: CognitiveConfig
|
|
70
|
+
config: CognitiveConfig
|
|
67
71
|
): { used: number; capacity: number; utilization: number } {
|
|
68
72
|
const used = storage.getWorkingMemoryCount();
|
|
69
73
|
return {
|
package/src/mcp/server.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { z } from "zod/v4";
|
|
5
|
+
|
|
5
6
|
import { EngramEngine } from "../core/engine.ts";
|
|
6
7
|
import { MemoryType, Emotion } from "../core/memory.ts";
|
|
7
8
|
import { handleStore, handleRecall, handleManage } from "./tools.ts";
|
|
@@ -22,10 +23,21 @@ server.registerTool(
|
|
|
22
23
|
z.object({
|
|
23
24
|
action: z.literal("encode"),
|
|
24
25
|
content: z.string().describe("Memory content"),
|
|
25
|
-
type: z
|
|
26
|
+
type: z
|
|
27
|
+
.nativeEnum(MemoryType)
|
|
28
|
+
.optional()
|
|
29
|
+
.describe("Memory type (default: semantic)"),
|
|
26
30
|
emotion: z.nativeEnum(Emotion).optional().describe("Emotional tag"),
|
|
27
|
-
emotionWeight: z
|
|
28
|
-
|
|
31
|
+
emotionWeight: z
|
|
32
|
+
.number()
|
|
33
|
+
.min(0)
|
|
34
|
+
.max(1)
|
|
35
|
+
.optional()
|
|
36
|
+
.describe("Emotion intensity 0-1"),
|
|
37
|
+
context: z
|
|
38
|
+
.string()
|
|
39
|
+
.optional()
|
|
40
|
+
.describe("Context tag (e.g. project:acme)"),
|
|
29
41
|
}),
|
|
30
42
|
z.object({
|
|
31
43
|
action: z.literal("encode_batch"),
|
|
@@ -37,7 +49,7 @@ server.registerTool(
|
|
|
37
49
|
emotion: z.nativeEnum(Emotion).optional(),
|
|
38
50
|
emotionWeight: z.number().min(0).max(1).optional(),
|
|
39
51
|
context: z.string().optional(),
|
|
40
|
-
})
|
|
52
|
+
})
|
|
41
53
|
)
|
|
42
54
|
.min(1)
|
|
43
55
|
.max(50)
|
|
@@ -47,12 +59,20 @@ server.registerTool(
|
|
|
47
59
|
action: z.literal("reconsolidate"),
|
|
48
60
|
id: z.string().describe("Memory ID to update"),
|
|
49
61
|
newContext: z.string().optional().describe("New context to blend"),
|
|
50
|
-
currentEmotion: z
|
|
51
|
-
|
|
62
|
+
currentEmotion: z
|
|
63
|
+
.nativeEnum(Emotion)
|
|
64
|
+
.optional()
|
|
65
|
+
.describe("Current emotional state"),
|
|
66
|
+
currentEmotionWeight: z
|
|
67
|
+
.number()
|
|
68
|
+
.min(0)
|
|
69
|
+
.max(1)
|
|
70
|
+
.optional()
|
|
71
|
+
.describe("Emotion intensity"),
|
|
52
72
|
}),
|
|
53
73
|
]),
|
|
54
74
|
},
|
|
55
|
-
async (args) => handleStore(engine, args)
|
|
75
|
+
async (args) => handleStore(engine, args)
|
|
56
76
|
);
|
|
57
77
|
|
|
58
78
|
server.registerTool(
|
|
@@ -67,9 +87,15 @@ server.registerTool(
|
|
|
67
87
|
limit: z.number().optional().describe("Max results (default: 5)"),
|
|
68
88
|
type: z.nativeEnum(MemoryType).optional().describe("Filter by type"),
|
|
69
89
|
context: z.string().optional().describe("Filter by context"),
|
|
70
|
-
associative: z
|
|
90
|
+
associative: z
|
|
91
|
+
.boolean()
|
|
92
|
+
.optional()
|
|
93
|
+
.describe("Spreading activation (default: true)"),
|
|
71
94
|
verbose: z.boolean().optional().describe("Full fields"),
|
|
72
|
-
format: z
|
|
95
|
+
format: z
|
|
96
|
+
.enum(["full", "content", "ids"])
|
|
97
|
+
.optional()
|
|
98
|
+
.describe("Response format (default: full)"),
|
|
73
99
|
}),
|
|
74
100
|
z.object({
|
|
75
101
|
action: z.literal("inspect"),
|
|
@@ -80,15 +106,21 @@ server.registerTool(
|
|
|
80
106
|
type: z.nativeEnum(MemoryType).optional().describe("Filter by type"),
|
|
81
107
|
context: z.string().optional().describe("Filter by context prefix"),
|
|
82
108
|
limit: z.number().optional().describe("Max results (default: 20)"),
|
|
83
|
-
offset: z
|
|
84
|
-
|
|
109
|
+
offset: z
|
|
110
|
+
.number()
|
|
111
|
+
.optional()
|
|
112
|
+
.describe("Skip first N results (default: 0)"),
|
|
113
|
+
format: z
|
|
114
|
+
.enum(["full", "content", "ids"])
|
|
115
|
+
.optional()
|
|
116
|
+
.describe("Response format (default: full)"),
|
|
85
117
|
}),
|
|
86
118
|
z.object({
|
|
87
119
|
action: z.literal("stats"),
|
|
88
120
|
}),
|
|
89
121
|
]),
|
|
90
122
|
},
|
|
91
|
-
async (args) => handleRecall(engine, args)
|
|
123
|
+
async (args) => handleRecall(engine, args)
|
|
92
124
|
);
|
|
93
125
|
|
|
94
126
|
server.registerTool(
|
|
@@ -103,7 +135,10 @@ server.registerTool(
|
|
|
103
135
|
z.object({
|
|
104
136
|
action: z.literal("focus_push"),
|
|
105
137
|
content: z.string().describe("Content to hold in focus"),
|
|
106
|
-
memoryRef: z
|
|
138
|
+
memoryRef: z
|
|
139
|
+
.string()
|
|
140
|
+
.optional()
|
|
141
|
+
.describe("Reference to existing memory ID"),
|
|
107
142
|
}),
|
|
108
143
|
z.object({
|
|
109
144
|
action: z.literal("focus_pop"),
|
|
@@ -117,13 +152,16 @@ server.registerTool(
|
|
|
117
152
|
z.object({
|
|
118
153
|
action: z.literal("recall_to_focus"),
|
|
119
154
|
cue: z.string().describe("Recall cue"),
|
|
120
|
-
limit: z
|
|
155
|
+
limit: z
|
|
156
|
+
.number()
|
|
157
|
+
.optional()
|
|
158
|
+
.describe("Max memories to load (default: 3)"),
|
|
121
159
|
type: z.nativeEnum(MemoryType).optional().describe("Filter by type"),
|
|
122
160
|
context: z.string().optional().describe("Filter by context"),
|
|
123
161
|
}),
|
|
124
162
|
]),
|
|
125
163
|
},
|
|
126
|
-
async (args) => handleManage(engine, args)
|
|
164
|
+
async (args) => handleManage(engine, args)
|
|
127
165
|
);
|
|
128
166
|
|
|
129
167
|
async function main() {
|