mark-improving-agent 2.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.
- package/README.md +335 -0
- package/VERSION +1 -0
- package/bin/cli.js +12 -0
- package/dist/agent/context.js +78 -0
- package/dist/agent/index.js +6 -0
- package/dist/agent/runtime.js +195 -0
- package/dist/agent/task-graph.js +209 -0
- package/dist/agent/types.js +1 -0
- package/dist/cli/index.js +206 -0
- package/dist/core/cognition/active-inference.js +296 -0
- package/dist/core/cognition/cognitive-architecture.js +263 -0
- package/dist/core/cognition/dual-process.js +102 -0
- package/dist/core/cognition/index.js +13 -0
- package/dist/core/cognition/learning-from-failure.js +184 -0
- package/dist/core/cognition/meta-agent.js +407 -0
- package/dist/core/cognition/metacognition.js +322 -0
- package/dist/core/cognition/react.js +177 -0
- package/dist/core/cognition/retrieval-anchor.js +99 -0
- package/dist/core/cognition/self-evolution.js +294 -0
- package/dist/core/cognition/self-verification.js +190 -0
- package/dist/core/cognition/thought-graph.js +495 -0
- package/dist/core/cognition/tool-augmented-llm.js +188 -0
- package/dist/core/cognition/tool-execution-verifier.js +204 -0
- package/dist/core/collaboration/agentic-loop.js +165 -0
- package/dist/core/collaboration/index.js +3 -0
- package/dist/core/collaboration/multi-agent-system.js +186 -0
- package/dist/core/collaboration/multi-agent.js +110 -0
- package/dist/core/consciousness/emotion-engine.js +101 -0
- package/dist/core/consciousness/flow-machine.js +121 -0
- package/dist/core/consciousness/index.js +4 -0
- package/dist/core/consciousness/personality.js +103 -0
- package/dist/core/consciousness/types.js +1 -0
- package/dist/core/emotional-protocol.js +54 -0
- package/dist/core/evolution/engine.js +194 -0
- package/dist/core/evolution/goal-engine.js +153 -0
- package/dist/core/evolution/index.js +6 -0
- package/dist/core/evolution/meta-learning.js +172 -0
- package/dist/core/evolution/reflection.js +158 -0
- package/dist/core/evolution/self-healer.js +139 -0
- package/dist/core/evolution/types.js +1 -0
- package/dist/core/healing-rl.js +266 -0
- package/dist/core/heartbeat.js +408 -0
- package/dist/core/identity/index.js +3 -0
- package/dist/core/identity/reflexion.js +165 -0
- package/dist/core/identity/self-model.js +274 -0
- package/dist/core/identity/self-verifier.js +158 -0
- package/dist/core/identity/types.js +12 -0
- package/dist/core/lesson-bank.js +301 -0
- package/dist/core/memory/adaptive-rag.js +440 -0
- package/dist/core/memory/archive-store.js +187 -0
- package/dist/core/memory/dream-consolidation.js +366 -0
- package/dist/core/memory/embedder.js +130 -0
- package/dist/core/memory/hopfield-network.js +128 -0
- package/dist/core/memory/index.js +9 -0
- package/dist/core/memory/knowledge-graph.js +151 -0
- package/dist/core/memory/spaced-repetition.js +113 -0
- package/dist/core/memory/store.js +404 -0
- package/dist/core/memory/types.js +1 -0
- package/dist/core/psychology/analysis.js +456 -0
- package/dist/core/psychology/index.js +1 -0
- package/dist/core/rollback-manager.js +191 -0
- package/dist/core/security/index.js +1 -0
- package/dist/core/security/privacy.js +132 -0
- package/dist/core/truth-teller.js +253 -0
- package/dist/core/truthfulness.js +99 -0
- package/dist/core/types.js +2 -0
- package/dist/event/bus.js +47 -0
- package/dist/index.js +8 -0
- package/dist/skills/dag.js +181 -0
- package/dist/skills/index.js +5 -0
- package/dist/skills/registry.js +40 -0
- package/dist/skills/types.js +1 -0
- package/dist/storage/archive.js +77 -0
- package/dist/storage/checkpoint.js +119 -0
- package/dist/storage/types.js +1 -0
- package/dist/utils/config.js +81 -0
- package/dist/utils/logger.js +49 -0
- package/dist/version.js +1 -0
- package/package.json +37 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knowledge Graph - Agent K Architecture Integration
|
|
3
|
+
*
|
|
4
|
+
* Structured memory with temporal context and relationship tracking.
|
|
5
|
+
* Based on Agent K paper: 4-layer cognitive architecture with KG.
|
|
6
|
+
*/
|
|
7
|
+
import { randomUUID } from 'crypto';
|
|
8
|
+
import { atomicWriteJSON, ensureDir, readJSON } from '../../storage/archive.js';
|
|
9
|
+
const MAX_CONNECTIONS_PER_NODE = 20;
|
|
10
|
+
function createDefaultNode(partial) {
|
|
11
|
+
const now = Date.now();
|
|
12
|
+
return {
|
|
13
|
+
...partial,
|
|
14
|
+
id: `kg-${now}-${randomUUID().slice(0, 8)}`,
|
|
15
|
+
createdAt: now,
|
|
16
|
+
lastAccessed: now,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function createKnowledgeGraph(dataDir) {
|
|
20
|
+
let nodes = new Map();
|
|
21
|
+
let edges = new Map();
|
|
22
|
+
let isDirty = false;
|
|
23
|
+
const nodesFile = `${dataDir}/knowledge-graph-nodes.json`;
|
|
24
|
+
const edgesFile = `${dataDir}/knowledge-graph-edges.json`;
|
|
25
|
+
async function persist() {
|
|
26
|
+
if (!isDirty)
|
|
27
|
+
return;
|
|
28
|
+
await atomicWriteJSON(nodesFile, Object.fromEntries(nodes));
|
|
29
|
+
await atomicWriteJSON(edgesFile, Object.fromEntries(edges));
|
|
30
|
+
isDirty = false;
|
|
31
|
+
}
|
|
32
|
+
async function boot() {
|
|
33
|
+
await ensureDir(dataDir);
|
|
34
|
+
const loadedNodes = await readJSON(nodesFile, {});
|
|
35
|
+
const loadedEdges = await readJSON(edgesFile, {});
|
|
36
|
+
nodes = new Map(Object.entries(loadedNodes || {}));
|
|
37
|
+
edges = new Map(Object.entries(loadedEdges || {}));
|
|
38
|
+
}
|
|
39
|
+
function addNode(partial) {
|
|
40
|
+
const node = createDefaultNode(partial);
|
|
41
|
+
nodes.set(node.id, node);
|
|
42
|
+
isDirty = true;
|
|
43
|
+
return node;
|
|
44
|
+
}
|
|
45
|
+
function addEdge(partial) {
|
|
46
|
+
// Validate both nodes exist
|
|
47
|
+
if (!nodes.has(partial.sourceId) || !nodes.has(partial.targetId)) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
// Limit connections per node
|
|
51
|
+
const sourceNode = nodes.get(partial.sourceId);
|
|
52
|
+
if (sourceNode.connections.length >= MAX_CONNECTIONS_PER_NODE) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
const edge = {
|
|
56
|
+
...partial,
|
|
57
|
+
id: `kge-${Date.now()}-${randomUUID().slice(0, 8)}`,
|
|
58
|
+
createdAt: Date.now(),
|
|
59
|
+
};
|
|
60
|
+
edges.set(edge.id, edge);
|
|
61
|
+
// Update node connections
|
|
62
|
+
if (!sourceNode.connections.includes(partial.targetId)) {
|
|
63
|
+
sourceNode.connections.push(partial.targetId);
|
|
64
|
+
}
|
|
65
|
+
const targetNode = nodes.get(partial.targetId);
|
|
66
|
+
if (partial.bidirectional && !targetNode.connections.includes(partial.sourceId)) {
|
|
67
|
+
targetNode.connections.push(partial.sourceId);
|
|
68
|
+
}
|
|
69
|
+
isDirty = true;
|
|
70
|
+
return edge;
|
|
71
|
+
}
|
|
72
|
+
function getNode(id) {
|
|
73
|
+
const node = nodes.get(id) ?? null;
|
|
74
|
+
if (node) {
|
|
75
|
+
node.lastAccessed = Date.now();
|
|
76
|
+
isDirty = true;
|
|
77
|
+
}
|
|
78
|
+
return node;
|
|
79
|
+
}
|
|
80
|
+
function getConnectedNodes(nodeId, relation) {
|
|
81
|
+
const node = nodes.get(nodeId);
|
|
82
|
+
if (!node)
|
|
83
|
+
return [];
|
|
84
|
+
const connected = [];
|
|
85
|
+
for (const edge of edges.values()) {
|
|
86
|
+
if (edge.sourceId === nodeId || (edge.bidirectional && edge.targetId === nodeId)) {
|
|
87
|
+
if (relation && edge.relation !== relation)
|
|
88
|
+
continue;
|
|
89
|
+
const connectedId = edge.sourceId === nodeId ? edge.targetId : edge.sourceId;
|
|
90
|
+
const connectedNode = nodes.get(connectedId);
|
|
91
|
+
if (connectedNode) {
|
|
92
|
+
connectedNode.lastAccessed = Date.now();
|
|
93
|
+
connected.push(connectedNode);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return connected;
|
|
98
|
+
}
|
|
99
|
+
function search(query) {
|
|
100
|
+
const lowerQuery = query.toLowerCase();
|
|
101
|
+
const results = [];
|
|
102
|
+
for (const node of nodes.values()) {
|
|
103
|
+
if (node.name.toLowerCase().includes(lowerQuery) ||
|
|
104
|
+
node.description.toLowerCase().includes(lowerQuery)) {
|
|
105
|
+
results.push(node);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return results.sort((a, b) => b.importance - a.importance);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Reflect on a node - increment reflection count and update status
|
|
112
|
+
* Based on Agent K's reflection mechanism
|
|
113
|
+
*/
|
|
114
|
+
function reflect(nodeId) {
|
|
115
|
+
const node = nodes.get(nodeId);
|
|
116
|
+
if (!node)
|
|
117
|
+
return null;
|
|
118
|
+
node.reflectionCount = (node.reflectionCount ?? 0) + 1;
|
|
119
|
+
node.lastAccessed = Date.now();
|
|
120
|
+
isDirty = true;
|
|
121
|
+
return node;
|
|
122
|
+
}
|
|
123
|
+
function getStats() {
|
|
124
|
+
let totalConnections = 0;
|
|
125
|
+
let mostConnected = [];
|
|
126
|
+
for (const node of nodes.values()) {
|
|
127
|
+
totalConnections += node.connections.length;
|
|
128
|
+
if (node.connections.length > 0) {
|
|
129
|
+
mostConnected.push({ id: node.id, count: node.connections.length });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
mostConnected.sort((a, b) => b.count - a.count);
|
|
133
|
+
return {
|
|
134
|
+
nodes: nodes.size,
|
|
135
|
+
edges: edges.size,
|
|
136
|
+
avgConnections: nodes.size > 0 ? totalConnections / nodes.size : 0,
|
|
137
|
+
mostConnected: mostConnected.slice(0, 5).map(m => m.id),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
addNode,
|
|
142
|
+
addEdge,
|
|
143
|
+
getNode,
|
|
144
|
+
getConnectedNodes,
|
|
145
|
+
search,
|
|
146
|
+
reflect,
|
|
147
|
+
getStats,
|
|
148
|
+
persist,
|
|
149
|
+
boot,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// SM-2 Spaced Repetition Algorithm
|
|
2
|
+
// Based on SuperMemo 2 algorithm for memory reinforcement
|
|
3
|
+
import { randomUUID } from 'crypto';
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
5
|
+
import { dirname } from 'path';
|
|
6
|
+
export function createSpacedRepetition(dataDir) {
|
|
7
|
+
const stateFile = `${dataDir}/sr-state.json`;
|
|
8
|
+
let state = { entries: {} };
|
|
9
|
+
function loadState() {
|
|
10
|
+
try {
|
|
11
|
+
if (existsSync(stateFile)) {
|
|
12
|
+
const raw = readFileSync(stateFile, 'utf-8');
|
|
13
|
+
const parsed = JSON.parse(raw);
|
|
14
|
+
state = parsed;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// Use defaults
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function persistState() {
|
|
22
|
+
try {
|
|
23
|
+
const dir = dirname(stateFile);
|
|
24
|
+
if (!existsSync(dir)) {
|
|
25
|
+
mkdirSync(dir, { recursive: true });
|
|
26
|
+
}
|
|
27
|
+
const tmp = stateFile + '.tmp.' + randomUUID().slice(0, 8);
|
|
28
|
+
writeFileSync(tmp, JSON.stringify(state, null, 2), 'utf-8');
|
|
29
|
+
require('fs').renameSync(tmp, stateFile);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// Persistence failure is non-fatal
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// Load state on creation
|
|
36
|
+
loadState();
|
|
37
|
+
function recordReview(id, quality) {
|
|
38
|
+
const now = Date.now();
|
|
39
|
+
let entry = state.entries[id];
|
|
40
|
+
if (!entry) {
|
|
41
|
+
entry = {
|
|
42
|
+
id,
|
|
43
|
+
easeFactor: 2.5,
|
|
44
|
+
interval: 0,
|
|
45
|
+
repetitions: 0,
|
|
46
|
+
nextReviewAt: now,
|
|
47
|
+
lastReviewedAt: now,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
// SM-2 algorithm
|
|
51
|
+
if (quality >= 3) {
|
|
52
|
+
// Successful recall
|
|
53
|
+
if (entry.repetitions === 0) {
|
|
54
|
+
entry.interval = 1;
|
|
55
|
+
}
|
|
56
|
+
else if (entry.repetitions === 1) {
|
|
57
|
+
entry.interval = 6;
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
entry.interval = Math.round(entry.interval * entry.easeFactor);
|
|
61
|
+
}
|
|
62
|
+
entry.repetitions++;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
// Failed recall - reset
|
|
66
|
+
entry.interval = 1;
|
|
67
|
+
entry.repetitions = 0;
|
|
68
|
+
}
|
|
69
|
+
// Update ease factor: EF' = EF + (0.1 - (5-q) * (0.08 + (5-q) * 0.02))
|
|
70
|
+
const q = quality;
|
|
71
|
+
entry.easeFactor = Math.max(1.3, entry.easeFactor + (0.1 - (5 - q) * (0.08 + (5 - q) * 0.02)));
|
|
72
|
+
// Set next review time
|
|
73
|
+
entry.nextReviewAt = now + entry.interval * 24 * 60 * 60 * 1000;
|
|
74
|
+
entry.lastReviewedAt = now;
|
|
75
|
+
state.entries[id] = entry;
|
|
76
|
+
persistState();
|
|
77
|
+
}
|
|
78
|
+
function getNextReviewDate(id) {
|
|
79
|
+
const entry = state.entries[id];
|
|
80
|
+
return entry ? entry.nextReviewAt : null;
|
|
81
|
+
}
|
|
82
|
+
function getDueEntries(entries) {
|
|
83
|
+
const now = Date.now();
|
|
84
|
+
return entries.filter(entry => {
|
|
85
|
+
const srEntry = state.entries[entry.id];
|
|
86
|
+
return !srEntry || srEntry.nextReviewAt <= now;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
function getSpacedRepetitionStats() {
|
|
90
|
+
const now = Date.now();
|
|
91
|
+
let dueCount = 0;
|
|
92
|
+
let masteredCount = 0;
|
|
93
|
+
let learningCount = 0;
|
|
94
|
+
for (const entry of Object.values(state.entries)) {
|
|
95
|
+
if (entry.nextReviewAt <= now) {
|
|
96
|
+
dueCount++;
|
|
97
|
+
}
|
|
98
|
+
if (entry.repetitions >= 5 && entry.easeFactor >= 2.5) {
|
|
99
|
+
masteredCount++;
|
|
100
|
+
}
|
|
101
|
+
else if (entry.repetitions > 0) {
|
|
102
|
+
learningCount++;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return { dueCount, masteredCount, learningCount };
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
recordReview,
|
|
109
|
+
getNextReviewDate,
|
|
110
|
+
getDueEntries,
|
|
111
|
+
getSpacedRepetitionStats,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import { randomUUID } from 'crypto';
|
|
2
|
+
import { atomicWriteJSON, ensureDir, readJSON } from '../../storage/archive.js';
|
|
3
|
+
import { cosineSimilarity } from './embedder.js';
|
|
4
|
+
import { events } from '../../event/bus.js';
|
|
5
|
+
import { createDreamEngine } from './dream-consolidation.js';
|
|
6
|
+
import { createSpacedRepetition } from './spaced-repetition.js';
|
|
7
|
+
const STORE_VERSION = 1;
|
|
8
|
+
const SAVE_DEBOUNCE_MS = 500;
|
|
9
|
+
/**
|
|
10
|
+
* Source weight constants for importance scoring.
|
|
11
|
+
*/
|
|
12
|
+
const SOURCE_WEIGHTS = {
|
|
13
|
+
user_correction: 1.0,
|
|
14
|
+
decision_verified: 0.85,
|
|
15
|
+
self: 0.7,
|
|
16
|
+
system: 0.5,
|
|
17
|
+
};
|
|
18
|
+
function computeImportance(baseImportance, source, timestamp) {
|
|
19
|
+
const sourceWeight = source ? SOURCE_WEIGHTS[source] ?? 0.5 : 0.5;
|
|
20
|
+
const ageMs = Date.now() - timestamp;
|
|
21
|
+
const ageDays = ageMs / (1000 * 60 * 60 * 24);
|
|
22
|
+
const recencyBonus = Math.max(0, 0.1 - ageDays * 0.01);
|
|
23
|
+
return Math.min(1.0, baseImportance * sourceWeight + recencyBonus);
|
|
24
|
+
}
|
|
25
|
+
export function createMemoryStore(options) {
|
|
26
|
+
const dataDir = options.dataDir;
|
|
27
|
+
const embedder = options.embedder;
|
|
28
|
+
const learnedTtlDays = options.learnedTtlDays ?? 30;
|
|
29
|
+
const maxEphemeral = options.maxEphemeral ?? 1000;
|
|
30
|
+
// Three separate in-memory Maps
|
|
31
|
+
const coreMap = new Map();
|
|
32
|
+
const learnedMap = new Map();
|
|
33
|
+
const ephemeralMap = new Map();
|
|
34
|
+
// Dream engine and spaced repetition
|
|
35
|
+
const dreamEngine = createDreamEngine(dataDir);
|
|
36
|
+
const spacedRepetition = createSpacedRepetition(dataDir);
|
|
37
|
+
// File paths
|
|
38
|
+
const coreFile = `${dataDir}/core.json`;
|
|
39
|
+
const learnedFile = `${dataDir}/learned.json`;
|
|
40
|
+
const ephemeralFile = `${dataDir}/ephemeral.json`;
|
|
41
|
+
// Dirty flags and debounce timers
|
|
42
|
+
let coreDirty = false;
|
|
43
|
+
let learnedDirty = false;
|
|
44
|
+
let ephemeralDirty = false;
|
|
45
|
+
let saveTimer = null;
|
|
46
|
+
function getTierMap(tier) {
|
|
47
|
+
switch (tier) {
|
|
48
|
+
case 'core': return coreMap;
|
|
49
|
+
case 'learned': return learnedMap;
|
|
50
|
+
case 'ephemeral': return ephemeralMap;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function getTierFile(tier) {
|
|
54
|
+
switch (tier) {
|
|
55
|
+
case 'core': return coreFile;
|
|
56
|
+
case 'learned': return learnedFile;
|
|
57
|
+
case 'ephemeral': return ephemeralFile;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function markDirty(tier) {
|
|
61
|
+
switch (tier) {
|
|
62
|
+
case 'core':
|
|
63
|
+
coreDirty = true;
|
|
64
|
+
break;
|
|
65
|
+
case 'learned':
|
|
66
|
+
learnedDirty = true;
|
|
67
|
+
break;
|
|
68
|
+
case 'ephemeral':
|
|
69
|
+
ephemeralDirty = true;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
scheduleSave();
|
|
73
|
+
}
|
|
74
|
+
async function saveTier(tier) {
|
|
75
|
+
const map = getTierMap(tier);
|
|
76
|
+
const file = getTierFile(tier);
|
|
77
|
+
const data = { entries: Object.fromEntries(map) };
|
|
78
|
+
await atomicWriteJSON(file, data);
|
|
79
|
+
switch (tier) {
|
|
80
|
+
case 'core':
|
|
81
|
+
coreDirty = false;
|
|
82
|
+
break;
|
|
83
|
+
case 'learned':
|
|
84
|
+
learnedDirty = false;
|
|
85
|
+
break;
|
|
86
|
+
case 'ephemeral':
|
|
87
|
+
ephemeralDirty = false;
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function scheduleSave() {
|
|
92
|
+
if (saveTimer !== null) {
|
|
93
|
+
clearTimeout(saveTimer);
|
|
94
|
+
}
|
|
95
|
+
saveTimer = setTimeout(async () => {
|
|
96
|
+
try {
|
|
97
|
+
await Promise.all([
|
|
98
|
+
coreDirty ? saveTier('core') : Promise.resolve(),
|
|
99
|
+
learnedDirty ? saveTier('learned') : Promise.resolve(),
|
|
100
|
+
ephemeralDirty ? saveTier('ephemeral') : Promise.resolve(),
|
|
101
|
+
]);
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
console.error('Memory store auto-save failed:', err);
|
|
105
|
+
}
|
|
106
|
+
saveTimer = null;
|
|
107
|
+
}, SAVE_DEBOUNCE_MS);
|
|
108
|
+
}
|
|
109
|
+
async function saveAllIfDirty() {
|
|
110
|
+
await Promise.all([
|
|
111
|
+
coreDirty ? saveTier('core') : Promise.resolve(),
|
|
112
|
+
learnedDirty ? saveTier('learned') : Promise.resolve(),
|
|
113
|
+
ephemeralDirty ? saveTier('ephemeral') : Promise.resolve(),
|
|
114
|
+
]);
|
|
115
|
+
}
|
|
116
|
+
async function evictOldestEphemeral() {
|
|
117
|
+
const entries = Array.from(ephemeralMap.values());
|
|
118
|
+
entries.sort((a, b) => a.timestamp - b.timestamp);
|
|
119
|
+
const toRemove = Math.ceil(ephemeralMap.size * 0.1); // Remove 10%
|
|
120
|
+
for (let i = 0; i < toRemove && i < entries.length; i++) {
|
|
121
|
+
ephemeralMap.delete(entries[i].id);
|
|
122
|
+
}
|
|
123
|
+
if (toRemove > 0) {
|
|
124
|
+
markDirty('ephemeral');
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// Memory store implementation with Agent K enhancements
|
|
128
|
+
const store = (content, importance, tags = [], tier = 'learned', source = 'system', options) => {
|
|
129
|
+
const id = randomUUID();
|
|
130
|
+
const timestamp = Date.now();
|
|
131
|
+
const embedding = embedder.embed(content);
|
|
132
|
+
// Determine effective tier based on importance
|
|
133
|
+
let effectiveTier = tier;
|
|
134
|
+
if (tier === 'learned' && importance > 0.85) {
|
|
135
|
+
effectiveTier = 'core';
|
|
136
|
+
}
|
|
137
|
+
const entry = {
|
|
138
|
+
id,
|
|
139
|
+
tier: effectiveTier,
|
|
140
|
+
content,
|
|
141
|
+
importance,
|
|
142
|
+
tags,
|
|
143
|
+
timestamp,
|
|
144
|
+
accessCount: 0,
|
|
145
|
+
lastAccessed: timestamp,
|
|
146
|
+
ttl: effectiveTier === 'learned' ? learnedTtlDays * 24 * 60 * 60 * 1000 : undefined,
|
|
147
|
+
source,
|
|
148
|
+
embedding,
|
|
149
|
+
// Agent K enhancements
|
|
150
|
+
temporalContext: options?.temporalContext ?? {
|
|
151
|
+
isOngoing: true,
|
|
152
|
+
},
|
|
153
|
+
reflectionCount: 0,
|
|
154
|
+
verificationStatus: 'unverified',
|
|
155
|
+
};
|
|
156
|
+
getTierMap(effectiveTier).set(id, entry);
|
|
157
|
+
markDirty(effectiveTier);
|
|
158
|
+
// Record successful store as spaced repetition review (quality=4)
|
|
159
|
+
spacedRepetition.recordReview(id, 4);
|
|
160
|
+
// Enforce ephemeral limit
|
|
161
|
+
if (ephemeralMap.size > maxEphemeral) {
|
|
162
|
+
evictOldestEphemeral();
|
|
163
|
+
}
|
|
164
|
+
events.emit('memory:store', entry);
|
|
165
|
+
return entry;
|
|
166
|
+
};
|
|
167
|
+
const recall = async (id) => {
|
|
168
|
+
let entry = coreMap.get(id) ?? learnedMap.get(id) ?? ephemeralMap.get(id);
|
|
169
|
+
if (entry) {
|
|
170
|
+
entry = { ...entry, accessCount: entry.accessCount + 1, lastAccessed: Date.now() };
|
|
171
|
+
getTierMap(entry.tier).set(id, entry);
|
|
172
|
+
events.emit('memory:recall', { id: entry.id, found: true });
|
|
173
|
+
}
|
|
174
|
+
return entry ?? null;
|
|
175
|
+
};
|
|
176
|
+
const forget = async (id) => {
|
|
177
|
+
for (const [tid, map] of [['core', coreMap], ['learned', learnedMap], ['ephemeral', ephemeralMap]]) {
|
|
178
|
+
if (map.has(id)) {
|
|
179
|
+
map.delete(id);
|
|
180
|
+
markDirty(tid);
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return false;
|
|
185
|
+
};
|
|
186
|
+
const search = (query, limit = 10) => {
|
|
187
|
+
const queryEmbedding = embedder.embed(query);
|
|
188
|
+
const results = [];
|
|
189
|
+
const allEntries = [
|
|
190
|
+
...Array.from(coreMap.values()),
|
|
191
|
+
...Array.from(learnedMap.values()),
|
|
192
|
+
...Array.from(ephemeralMap.values()),
|
|
193
|
+
];
|
|
194
|
+
for (const entry of allEntries) {
|
|
195
|
+
if (!entry.embedding)
|
|
196
|
+
continue;
|
|
197
|
+
const score = cosineSimilarity(queryEmbedding, entry.embedding);
|
|
198
|
+
if (score > 0.5) {
|
|
199
|
+
results.push({
|
|
200
|
+
entry,
|
|
201
|
+
score,
|
|
202
|
+
reason: 'semantic',
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// Sort by score descending
|
|
207
|
+
results.sort((a, b) => b.score - a.score);
|
|
208
|
+
// Reinforce memories on successful recall (spaced repetition)
|
|
209
|
+
for (const result of results) {
|
|
210
|
+
spacedRepetition.recordReview(result.entry.id, 4);
|
|
211
|
+
}
|
|
212
|
+
return results.slice(0, limit);
|
|
213
|
+
};
|
|
214
|
+
const searchByTags = async (tags, limit = 20) => {
|
|
215
|
+
const lowerTags = tags.map(t => t.toLowerCase());
|
|
216
|
+
const results = [];
|
|
217
|
+
const allEntries = [
|
|
218
|
+
...Array.from(coreMap.values()),
|
|
219
|
+
...Array.from(learnedMap.values()),
|
|
220
|
+
...Array.from(ephemeralMap.values()),
|
|
221
|
+
];
|
|
222
|
+
for (const entry of allEntries) {
|
|
223
|
+
if (entry.tags.some(tag => lowerTags.includes(tag.toLowerCase()))) {
|
|
224
|
+
results.push(entry);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// Sort by importance descending
|
|
228
|
+
results.sort((a, b) => b.importance - a.importance);
|
|
229
|
+
return results.slice(0, limit);
|
|
230
|
+
};
|
|
231
|
+
const recallRecent = async (limit = 20) => {
|
|
232
|
+
const results = [
|
|
233
|
+
...Array.from(coreMap.values()),
|
|
234
|
+
...Array.from(learnedMap.values()),
|
|
235
|
+
...Array.from(ephemeralMap.values()),
|
|
236
|
+
];
|
|
237
|
+
results.sort((a, b) => b.timestamp - a.timestamp);
|
|
238
|
+
return results.slice(0, limit);
|
|
239
|
+
};
|
|
240
|
+
const recallHighValue = async (minImportance = 0.8) => {
|
|
241
|
+
const results = [
|
|
242
|
+
...Array.from(coreMap.values()),
|
|
243
|
+
...Array.from(learnedMap.values()),
|
|
244
|
+
...Array.from(ephemeralMap.values()),
|
|
245
|
+
];
|
|
246
|
+
return results
|
|
247
|
+
.filter(e => e.importance >= minImportance)
|
|
248
|
+
.sort((a, b) => b.importance - a.importance);
|
|
249
|
+
};
|
|
250
|
+
const distill = async () => {
|
|
251
|
+
let promoted = 0;
|
|
252
|
+
const now = Date.now();
|
|
253
|
+
for (const [id, entry] of learnedMap) {
|
|
254
|
+
// Promote learned entries with high importance and age
|
|
255
|
+
const ageMs = now - entry.timestamp;
|
|
256
|
+
const ageDays = ageMs / (1000 * 60 * 60 * 24);
|
|
257
|
+
if (entry.importance > 0.85 || ageDays > 60) {
|
|
258
|
+
learnedMap.delete(id);
|
|
259
|
+
const promotedEntry = {
|
|
260
|
+
...entry,
|
|
261
|
+
tier: 'core',
|
|
262
|
+
importance: Math.min(1, entry.importance + 0.05),
|
|
263
|
+
};
|
|
264
|
+
coreMap.set(id, promotedEntry);
|
|
265
|
+
markDirty('learned');
|
|
266
|
+
markDirty('core');
|
|
267
|
+
promoted++;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return promoted;
|
|
271
|
+
};
|
|
272
|
+
const prune = async () => {
|
|
273
|
+
let pruned = 0;
|
|
274
|
+
const now = Date.now();
|
|
275
|
+
for (const [id, entry] of learnedMap) {
|
|
276
|
+
if (entry.ttl) {
|
|
277
|
+
const ageMs = now - entry.timestamp;
|
|
278
|
+
if (ageMs > entry.ttl) {
|
|
279
|
+
learnedMap.delete(id);
|
|
280
|
+
markDirty('learned');
|
|
281
|
+
pruned++;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return pruned;
|
|
286
|
+
};
|
|
287
|
+
const getStats = async () => {
|
|
288
|
+
const allEntries = [
|
|
289
|
+
...Array.from(coreMap.values()),
|
|
290
|
+
...Array.from(learnedMap.values()),
|
|
291
|
+
...Array.from(ephemeralMap.values()),
|
|
292
|
+
];
|
|
293
|
+
const avgImportance = allEntries.length > 0
|
|
294
|
+
? allEntries.reduce((sum, e) => sum + e.importance, 0) / allEntries.length
|
|
295
|
+
: 0;
|
|
296
|
+
return {
|
|
297
|
+
core: coreMap.size,
|
|
298
|
+
learned: learnedMap.size,
|
|
299
|
+
ephemeral: ephemeralMap.size,
|
|
300
|
+
avgImportance,
|
|
301
|
+
archived: 0,
|
|
302
|
+
};
|
|
303
|
+
};
|
|
304
|
+
const boot = async () => {
|
|
305
|
+
await ensureDir(dataDir);
|
|
306
|
+
const loadTier = async (file) => {
|
|
307
|
+
const map = new Map();
|
|
308
|
+
const data = await readJSON(file, { entries: {} });
|
|
309
|
+
if (data && data.entries) {
|
|
310
|
+
for (const [id, entry] of Object.entries(data.entries)) {
|
|
311
|
+
map.set(id, entry);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return map;
|
|
315
|
+
};
|
|
316
|
+
try {
|
|
317
|
+
const loadedCore = await loadTier(coreFile);
|
|
318
|
+
if (loadedCore.size > 0)
|
|
319
|
+
coreMap.clear();
|
|
320
|
+
loadedCore.forEach((v, k) => coreMap.set(k, v));
|
|
321
|
+
}
|
|
322
|
+
catch (err) {
|
|
323
|
+
console.error(`Failed to load core tier from ${coreFile}:`, err);
|
|
324
|
+
}
|
|
325
|
+
try {
|
|
326
|
+
const loadedLearned = await loadTier(learnedFile);
|
|
327
|
+
if (loadedLearned.size > 0)
|
|
328
|
+
learnedMap.clear();
|
|
329
|
+
loadedLearned.forEach((v, k) => learnedMap.set(k, v));
|
|
330
|
+
}
|
|
331
|
+
catch (err) {
|
|
332
|
+
console.error(`Failed to load learned tier from ${learnedFile}:`, err);
|
|
333
|
+
}
|
|
334
|
+
try {
|
|
335
|
+
const loadedEphemeral = await loadTier(ephemeralFile);
|
|
336
|
+
if (loadedEphemeral.size > 0)
|
|
337
|
+
ephemeralMap.clear();
|
|
338
|
+
loadedEphemeral.forEach((v, k) => ephemeralMap.set(k, v));
|
|
339
|
+
}
|
|
340
|
+
catch (err) {
|
|
341
|
+
console.error(`Failed to load ephemeral tier from ${ephemeralFile}:`, err);
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
const shutdown = async () => {
|
|
345
|
+
if (saveTimer) {
|
|
346
|
+
clearTimeout(saveTimer);
|
|
347
|
+
saveTimer = null;
|
|
348
|
+
}
|
|
349
|
+
await saveAllIfDirty();
|
|
350
|
+
};
|
|
351
|
+
const exportAll = () => {
|
|
352
|
+
const data = {
|
|
353
|
+
version: STORE_VERSION,
|
|
354
|
+
core: { entries: Object.fromEntries(coreMap) },
|
|
355
|
+
learned: { entries: Object.fromEntries(learnedMap) },
|
|
356
|
+
ephemeral: { entries: Object.fromEntries(ephemeralMap) },
|
|
357
|
+
};
|
|
358
|
+
return JSON.stringify(data, null, 2);
|
|
359
|
+
};
|
|
360
|
+
// Dream consolidation
|
|
361
|
+
async function dream() {
|
|
362
|
+
const allEntries = [
|
|
363
|
+
...Array.from(coreMap.values()),
|
|
364
|
+
...Array.from(learnedMap.values()),
|
|
365
|
+
...Array.from(ephemeralMap.values()),
|
|
366
|
+
];
|
|
367
|
+
return dreamEngine.dream(allEntries);
|
|
368
|
+
}
|
|
369
|
+
// Spaced repetition
|
|
370
|
+
function getDueMemories() {
|
|
371
|
+
const allEntries = [
|
|
372
|
+
...Array.from(coreMap.values()),
|
|
373
|
+
...Array.from(learnedMap.values()),
|
|
374
|
+
...Array.from(ephemeralMap.values()),
|
|
375
|
+
];
|
|
376
|
+
return spacedRepetition.getDueEntries(allEntries);
|
|
377
|
+
}
|
|
378
|
+
function recordReview(id, quality) {
|
|
379
|
+
spacedRepetition.recordReview(id, quality);
|
|
380
|
+
}
|
|
381
|
+
// Memory health
|
|
382
|
+
function getMemoryHealth() {
|
|
383
|
+
return dreamEngine.getMemoryHealth();
|
|
384
|
+
}
|
|
385
|
+
return {
|
|
386
|
+
store,
|
|
387
|
+
recall,
|
|
388
|
+
forget,
|
|
389
|
+
search,
|
|
390
|
+
searchByTags,
|
|
391
|
+
recallRecent,
|
|
392
|
+
recallHighValue,
|
|
393
|
+
distill,
|
|
394
|
+
prune,
|
|
395
|
+
getStats,
|
|
396
|
+
boot,
|
|
397
|
+
shutdown,
|
|
398
|
+
exportAll,
|
|
399
|
+
dream,
|
|
400
|
+
getDueMemories,
|
|
401
|
+
recordReview,
|
|
402
|
+
getMemoryHealth,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|