claude-memory-layer 1.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.
- package/.claude-plugin/commands/memory-forget.md +42 -0
- package/.claude-plugin/commands/memory-history.md +34 -0
- package/.claude-plugin/commands/memory-import.md +56 -0
- package/.claude-plugin/commands/memory-list.md +37 -0
- package/.claude-plugin/commands/memory-search.md +36 -0
- package/.claude-plugin/commands/memory-stats.md +34 -0
- package/.claude-plugin/hooks.json +59 -0
- package/.claude-plugin/plugin.json +24 -0
- package/.history/package_20260201112328.json +45 -0
- package/.history/package_20260201113602.json +45 -0
- package/.history/package_20260201113713.json +45 -0
- package/.history/package_20260201114110.json +45 -0
- package/Memo.txt +558 -0
- package/README.md +520 -0
- package/context.md +636 -0
- package/dist/.claude-plugin/commands/memory-forget.md +42 -0
- package/dist/.claude-plugin/commands/memory-history.md +34 -0
- package/dist/.claude-plugin/commands/memory-import.md +56 -0
- package/dist/.claude-plugin/commands/memory-list.md +37 -0
- package/dist/.claude-plugin/commands/memory-search.md +36 -0
- package/dist/.claude-plugin/commands/memory-stats.md +34 -0
- package/dist/.claude-plugin/hooks.json +59 -0
- package/dist/.claude-plugin/plugin.json +24 -0
- package/dist/cli/index.js +3539 -0
- package/dist/cli/index.js.map +7 -0
- package/dist/core/index.js +4408 -0
- package/dist/core/index.js.map +7 -0
- package/dist/hooks/session-end.js +2971 -0
- package/dist/hooks/session-end.js.map +7 -0
- package/dist/hooks/session-start.js +2969 -0
- package/dist/hooks/session-start.js.map +7 -0
- package/dist/hooks/stop.js +3123 -0
- package/dist/hooks/stop.js.map +7 -0
- package/dist/hooks/user-prompt-submit.js +2960 -0
- package/dist/hooks/user-prompt-submit.js.map +7 -0
- package/dist/services/memory-service.js +2931 -0
- package/dist/services/memory-service.js.map +7 -0
- package/package.json +45 -0
- package/plan.md +1642 -0
- package/scripts/build.ts +102 -0
- package/spec.md +624 -0
- package/specs/citations-system/context.md +243 -0
- package/specs/citations-system/plan.md +495 -0
- package/specs/citations-system/spec.md +371 -0
- package/specs/endless-mode/context.md +305 -0
- package/specs/endless-mode/plan.md +620 -0
- package/specs/endless-mode/spec.md +455 -0
- package/specs/entity-edge-model/context.md +401 -0
- package/specs/entity-edge-model/plan.md +459 -0
- package/specs/entity-edge-model/spec.md +391 -0
- package/specs/evidence-aligner-v2/context.md +401 -0
- package/specs/evidence-aligner-v2/plan.md +303 -0
- package/specs/evidence-aligner-v2/spec.md +312 -0
- package/specs/mcp-desktop-integration/context.md +278 -0
- package/specs/mcp-desktop-integration/plan.md +550 -0
- package/specs/mcp-desktop-integration/spec.md +494 -0
- package/specs/post-tool-use-hook/context.md +319 -0
- package/specs/post-tool-use-hook/plan.md +469 -0
- package/specs/post-tool-use-hook/spec.md +364 -0
- package/specs/private-tags/context.md +288 -0
- package/specs/private-tags/plan.md +412 -0
- package/specs/private-tags/spec.md +345 -0
- package/specs/progressive-disclosure/context.md +346 -0
- package/specs/progressive-disclosure/plan.md +663 -0
- package/specs/progressive-disclosure/spec.md +415 -0
- package/specs/task-entity-system/context.md +297 -0
- package/specs/task-entity-system/plan.md +301 -0
- package/specs/task-entity-system/spec.md +314 -0
- package/specs/vector-outbox-v2/context.md +470 -0
- package/specs/vector-outbox-v2/plan.md +562 -0
- package/specs/vector-outbox-v2/spec.md +466 -0
- package/specs/web-viewer-ui/context.md +384 -0
- package/specs/web-viewer-ui/plan.md +797 -0
- package/specs/web-viewer-ui/spec.md +516 -0
- package/src/cli/index.ts +570 -0
- package/src/core/canonical-key.ts +186 -0
- package/src/core/citation-generator.ts +63 -0
- package/src/core/consolidated-store.ts +279 -0
- package/src/core/consolidation-worker.ts +384 -0
- package/src/core/context-formatter.ts +276 -0
- package/src/core/continuity-manager.ts +336 -0
- package/src/core/edge-repo.ts +324 -0
- package/src/core/embedder.ts +124 -0
- package/src/core/entity-repo.ts +342 -0
- package/src/core/event-store.ts +672 -0
- package/src/core/evidence-aligner.ts +635 -0
- package/src/core/graduation.ts +365 -0
- package/src/core/index.ts +32 -0
- package/src/core/matcher.ts +210 -0
- package/src/core/metadata-extractor.ts +203 -0
- package/src/core/privacy/filter.ts +179 -0
- package/src/core/privacy/index.ts +20 -0
- package/src/core/privacy/tag-parser.ts +145 -0
- package/src/core/progressive-retriever.ts +415 -0
- package/src/core/retriever.ts +235 -0
- package/src/core/task/blocker-resolver.ts +325 -0
- package/src/core/task/index.ts +9 -0
- package/src/core/task/task-matcher.ts +238 -0
- package/src/core/task/task-projector.ts +345 -0
- package/src/core/task/task-resolver.ts +414 -0
- package/src/core/types.ts +841 -0
- package/src/core/vector-outbox.ts +295 -0
- package/src/core/vector-store.ts +182 -0
- package/src/core/vector-worker.ts +488 -0
- package/src/core/working-set-store.ts +244 -0
- package/src/hooks/post-tool-use.ts +127 -0
- package/src/hooks/session-end.ts +78 -0
- package/src/hooks/session-start.ts +57 -0
- package/src/hooks/stop.ts +78 -0
- package/src/hooks/user-prompt-submit.ts +54 -0
- package/src/mcp/handlers.ts +212 -0
- package/src/mcp/index.ts +47 -0
- package/src/mcp/tools.ts +78 -0
- package/src/server/api/citations.ts +101 -0
- package/src/server/api/events.ts +101 -0
- package/src/server/api/index.ts +18 -0
- package/src/server/api/search.ts +98 -0
- package/src/server/api/sessions.ts +111 -0
- package/src/server/api/stats.ts +97 -0
- package/src/server/index.ts +91 -0
- package/src/services/memory-service.ts +626 -0
- package/src/services/session-history-importer.ts +367 -0
- package/tests/canonical-key.test.ts +101 -0
- package/tests/evidence-aligner.test.ts +152 -0
- package/tests/matcher.test.ts +112 -0
- package/tsconfig.json +24 -0
- package/vitest.config.ts +15 -0
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Graduation Pipeline - AXIOMMIND L0→L1→L2→L3→L4
|
|
3
|
+
*
|
|
4
|
+
* L0: EventStore (raw events, append-only)
|
|
5
|
+
* L1: Structured JSON (session summaries, patterns)
|
|
6
|
+
* L2: Type Candidates (Idris2-inspired, validated schemas)
|
|
7
|
+
* L3: Verified Knowledge (cross-session validated)
|
|
8
|
+
* L4: Active/Searchable (indexed, readily available)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { EventStore } from './event-store.js';
|
|
12
|
+
import type {
|
|
13
|
+
MemoryEvent,
|
|
14
|
+
MemoryLevel,
|
|
15
|
+
GraduationResult,
|
|
16
|
+
Insight,
|
|
17
|
+
InsightType
|
|
18
|
+
} from './types.js';
|
|
19
|
+
|
|
20
|
+
export interface GraduationCriteria {
|
|
21
|
+
minAccessCount: number;
|
|
22
|
+
minConfidence: number;
|
|
23
|
+
minCrossSessionRefs: number;
|
|
24
|
+
maxAgeDays: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface LevelCriteria {
|
|
28
|
+
L0toL1: GraduationCriteria;
|
|
29
|
+
L1toL2: GraduationCriteria;
|
|
30
|
+
L2toL3: GraduationCriteria;
|
|
31
|
+
L3toL4: GraduationCriteria;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const DEFAULT_CRITERIA: LevelCriteria = {
|
|
35
|
+
L0toL1: {
|
|
36
|
+
minAccessCount: 1,
|
|
37
|
+
minConfidence: 0.5,
|
|
38
|
+
minCrossSessionRefs: 0,
|
|
39
|
+
maxAgeDays: 30
|
|
40
|
+
},
|
|
41
|
+
L1toL2: {
|
|
42
|
+
minAccessCount: 3,
|
|
43
|
+
minConfidence: 0.7,
|
|
44
|
+
minCrossSessionRefs: 1,
|
|
45
|
+
maxAgeDays: 60
|
|
46
|
+
},
|
|
47
|
+
L2toL3: {
|
|
48
|
+
minAccessCount: 5,
|
|
49
|
+
minConfidence: 0.85,
|
|
50
|
+
minCrossSessionRefs: 2,
|
|
51
|
+
maxAgeDays: 90
|
|
52
|
+
},
|
|
53
|
+
L3toL4: {
|
|
54
|
+
minAccessCount: 10,
|
|
55
|
+
minConfidence: 0.92,
|
|
56
|
+
minCrossSessionRefs: 3,
|
|
57
|
+
maxAgeDays: 180
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export interface EventMetrics {
|
|
62
|
+
eventId: string;
|
|
63
|
+
accessCount: number;
|
|
64
|
+
lastAccessed: Date;
|
|
65
|
+
crossSessionRefs: number;
|
|
66
|
+
confidence: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export class GraduationPipeline {
|
|
70
|
+
private readonly eventStore: EventStore;
|
|
71
|
+
private readonly criteria: LevelCriteria;
|
|
72
|
+
private readonly metrics: Map<string, EventMetrics> = new Map();
|
|
73
|
+
|
|
74
|
+
constructor(
|
|
75
|
+
eventStore: EventStore,
|
|
76
|
+
criteria: Partial<LevelCriteria> = {}
|
|
77
|
+
) {
|
|
78
|
+
this.eventStore = eventStore;
|
|
79
|
+
this.criteria = {
|
|
80
|
+
L0toL1: { ...DEFAULT_CRITERIA.L0toL1, ...criteria.L0toL1 },
|
|
81
|
+
L1toL2: { ...DEFAULT_CRITERIA.L1toL2, ...criteria.L1toL2 },
|
|
82
|
+
L2toL3: { ...DEFAULT_CRITERIA.L2toL3, ...criteria.L2toL3 },
|
|
83
|
+
L3toL4: { ...DEFAULT_CRITERIA.L3toL4, ...criteria.L3toL4 }
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Record an access to an event (used for graduation scoring)
|
|
89
|
+
*/
|
|
90
|
+
recordAccess(eventId: string, fromSessionId: string, confidence: number = 1.0): void {
|
|
91
|
+
const existing = this.metrics.get(eventId);
|
|
92
|
+
|
|
93
|
+
if (existing) {
|
|
94
|
+
existing.accessCount++;
|
|
95
|
+
existing.lastAccessed = new Date();
|
|
96
|
+
existing.confidence = Math.max(existing.confidence, confidence);
|
|
97
|
+
// Track cross-session references
|
|
98
|
+
// This would need more sophisticated tracking in production
|
|
99
|
+
} else {
|
|
100
|
+
this.metrics.set(eventId, {
|
|
101
|
+
eventId,
|
|
102
|
+
accessCount: 1,
|
|
103
|
+
lastAccessed: new Date(),
|
|
104
|
+
crossSessionRefs: 0,
|
|
105
|
+
confidence
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Evaluate if an event should graduate to the next level
|
|
112
|
+
*/
|
|
113
|
+
async evaluateGraduation(eventId: string, currentLevel: MemoryLevel): Promise<GraduationResult> {
|
|
114
|
+
const metrics = this.metrics.get(eventId);
|
|
115
|
+
|
|
116
|
+
if (!metrics) {
|
|
117
|
+
return {
|
|
118
|
+
eventId,
|
|
119
|
+
fromLevel: currentLevel,
|
|
120
|
+
toLevel: currentLevel,
|
|
121
|
+
success: false,
|
|
122
|
+
reason: 'No metrics available for event'
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const nextLevel = this.getNextLevel(currentLevel);
|
|
127
|
+
if (!nextLevel) {
|
|
128
|
+
return {
|
|
129
|
+
eventId,
|
|
130
|
+
fromLevel: currentLevel,
|
|
131
|
+
toLevel: currentLevel,
|
|
132
|
+
success: false,
|
|
133
|
+
reason: 'Already at maximum level'
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const criteria = this.getCriteria(currentLevel, nextLevel);
|
|
138
|
+
const evaluation = this.checkCriteria(metrics, criteria);
|
|
139
|
+
|
|
140
|
+
if (evaluation.passed) {
|
|
141
|
+
// Update level in event store
|
|
142
|
+
await this.eventStore.updateMemoryLevel(eventId, nextLevel);
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
eventId,
|
|
146
|
+
fromLevel: currentLevel,
|
|
147
|
+
toLevel: nextLevel,
|
|
148
|
+
success: true
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
eventId,
|
|
154
|
+
fromLevel: currentLevel,
|
|
155
|
+
toLevel: currentLevel,
|
|
156
|
+
success: false,
|
|
157
|
+
reason: evaluation.reason
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Run graduation evaluation for all events at a given level
|
|
163
|
+
*/
|
|
164
|
+
async graduateBatch(level: MemoryLevel): Promise<GraduationResult[]> {
|
|
165
|
+
const results: GraduationResult[] = [];
|
|
166
|
+
|
|
167
|
+
for (const [eventId, metrics] of this.metrics) {
|
|
168
|
+
const result = await this.evaluateGraduation(eventId, level);
|
|
169
|
+
results.push(result);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return results;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Extract insights from graduated events (L1+)
|
|
177
|
+
*/
|
|
178
|
+
extractInsights(events: MemoryEvent[]): Insight[] {
|
|
179
|
+
const insights: Insight[] = [];
|
|
180
|
+
|
|
181
|
+
// Pattern detection: Look for repeated themes
|
|
182
|
+
const patterns = this.detectPatterns(events);
|
|
183
|
+
for (const pattern of patterns) {
|
|
184
|
+
insights.push({
|
|
185
|
+
id: crypto.randomUUID(),
|
|
186
|
+
insightType: 'pattern',
|
|
187
|
+
content: pattern.description,
|
|
188
|
+
canonicalKey: pattern.key,
|
|
189
|
+
confidence: pattern.confidence,
|
|
190
|
+
sourceEvents: pattern.eventIds,
|
|
191
|
+
createdAt: new Date(),
|
|
192
|
+
lastUpdated: new Date()
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Preference detection: Look for user preferences
|
|
197
|
+
const preferences = this.detectPreferences(events);
|
|
198
|
+
for (const pref of preferences) {
|
|
199
|
+
insights.push({
|
|
200
|
+
id: crypto.randomUUID(),
|
|
201
|
+
insightType: 'preference',
|
|
202
|
+
content: pref.description,
|
|
203
|
+
canonicalKey: pref.key,
|
|
204
|
+
confidence: pref.confidence,
|
|
205
|
+
sourceEvents: pref.eventIds,
|
|
206
|
+
createdAt: new Date(),
|
|
207
|
+
lastUpdated: new Date()
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return insights;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Get the next level in the graduation pipeline
|
|
216
|
+
*/
|
|
217
|
+
private getNextLevel(current: MemoryLevel): MemoryLevel | null {
|
|
218
|
+
const levels: MemoryLevel[] = ['L0', 'L1', 'L2', 'L3', 'L4'];
|
|
219
|
+
const currentIndex = levels.indexOf(current);
|
|
220
|
+
|
|
221
|
+
if (currentIndex === -1 || currentIndex >= levels.length - 1) {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return levels[currentIndex + 1];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Get criteria for level transition
|
|
230
|
+
*/
|
|
231
|
+
private getCriteria(from: MemoryLevel, to: MemoryLevel): GraduationCriteria {
|
|
232
|
+
const key = `${from}to${to}` as keyof LevelCriteria;
|
|
233
|
+
return this.criteria[key] || DEFAULT_CRITERIA.L0toL1;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Check if metrics meet criteria
|
|
238
|
+
*/
|
|
239
|
+
private checkCriteria(
|
|
240
|
+
metrics: EventMetrics,
|
|
241
|
+
criteria: GraduationCriteria
|
|
242
|
+
): { passed: boolean; reason?: string } {
|
|
243
|
+
if (metrics.accessCount < criteria.minAccessCount) {
|
|
244
|
+
return {
|
|
245
|
+
passed: false,
|
|
246
|
+
reason: `Access count ${metrics.accessCount} < ${criteria.minAccessCount}`
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (metrics.confidence < criteria.minConfidence) {
|
|
251
|
+
return {
|
|
252
|
+
passed: false,
|
|
253
|
+
reason: `Confidence ${metrics.confidence} < ${criteria.minConfidence}`
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (metrics.crossSessionRefs < criteria.minCrossSessionRefs) {
|
|
258
|
+
return {
|
|
259
|
+
passed: false,
|
|
260
|
+
reason: `Cross-session refs ${metrics.crossSessionRefs} < ${criteria.minCrossSessionRefs}`
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const ageDays = (Date.now() - metrics.lastAccessed.getTime()) / (1000 * 60 * 60 * 24);
|
|
265
|
+
if (ageDays > criteria.maxAgeDays) {
|
|
266
|
+
return {
|
|
267
|
+
passed: false,
|
|
268
|
+
reason: `Event too old: ${ageDays.toFixed(1)} days > ${criteria.maxAgeDays}`
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return { passed: true };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Detect patterns in events
|
|
277
|
+
*/
|
|
278
|
+
private detectPatterns(events: MemoryEvent[]): Array<{
|
|
279
|
+
key: string;
|
|
280
|
+
description: string;
|
|
281
|
+
confidence: number;
|
|
282
|
+
eventIds: string[];
|
|
283
|
+
}> {
|
|
284
|
+
// Simple pattern detection: group by canonical key and look for repeats
|
|
285
|
+
const keyGroups = new Map<string, MemoryEvent[]>();
|
|
286
|
+
|
|
287
|
+
for (const event of events) {
|
|
288
|
+
const existing = keyGroups.get(event.canonicalKey) || [];
|
|
289
|
+
existing.push(event);
|
|
290
|
+
keyGroups.set(event.canonicalKey, existing);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const patterns: Array<{
|
|
294
|
+
key: string;
|
|
295
|
+
description: string;
|
|
296
|
+
confidence: number;
|
|
297
|
+
eventIds: string[];
|
|
298
|
+
}> = [];
|
|
299
|
+
|
|
300
|
+
for (const [key, groupEvents] of keyGroups) {
|
|
301
|
+
if (groupEvents.length >= 2) {
|
|
302
|
+
patterns.push({
|
|
303
|
+
key,
|
|
304
|
+
description: `Repeated topic: ${key.slice(0, 50)}`,
|
|
305
|
+
confidence: Math.min(1.0, groupEvents.length / 5),
|
|
306
|
+
eventIds: groupEvents.map(e => e.id)
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return patterns;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Detect user preferences from events
|
|
316
|
+
*/
|
|
317
|
+
private detectPreferences(events: MemoryEvent[]): Array<{
|
|
318
|
+
key: string;
|
|
319
|
+
description: string;
|
|
320
|
+
confidence: number;
|
|
321
|
+
eventIds: string[];
|
|
322
|
+
}> {
|
|
323
|
+
// Simple preference detection: look for keywords
|
|
324
|
+
const preferenceKeywords = ['prefer', 'like', 'want', 'always', 'never', 'favorite'];
|
|
325
|
+
const preferences: Array<{
|
|
326
|
+
key: string;
|
|
327
|
+
description: string;
|
|
328
|
+
confidence: number;
|
|
329
|
+
eventIds: string[];
|
|
330
|
+
}> = [];
|
|
331
|
+
|
|
332
|
+
for (const event of events) {
|
|
333
|
+
if (event.eventType !== 'user_prompt') continue;
|
|
334
|
+
|
|
335
|
+
const lowerContent = event.content.toLowerCase();
|
|
336
|
+
for (const keyword of preferenceKeywords) {
|
|
337
|
+
if (lowerContent.includes(keyword)) {
|
|
338
|
+
preferences.push({
|
|
339
|
+
key: `preference_${keyword}_${event.id.slice(0, 8)}`,
|
|
340
|
+
description: `User preference: ${event.content.slice(0, 100)}`,
|
|
341
|
+
confidence: 0.7,
|
|
342
|
+
eventIds: [event.id]
|
|
343
|
+
});
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return preferences;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Get graduation statistics
|
|
354
|
+
*/
|
|
355
|
+
async getStats(): Promise<{ level: string; count: number }[]> {
|
|
356
|
+
return this.eventStore.getLevelStats();
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Create graduation pipeline with default settings
|
|
362
|
+
*/
|
|
363
|
+
export function createGraduationPipeline(eventStore: EventStore): GraduationPipeline {
|
|
364
|
+
return new GraduationPipeline(eventStore);
|
|
365
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core module exports
|
|
3
|
+
* AXIOMMIND Memory Graduation Pipeline
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Types
|
|
7
|
+
export * from './types.js';
|
|
8
|
+
|
|
9
|
+
// Canonical Key (identity)
|
|
10
|
+
export * from './canonical-key.js';
|
|
11
|
+
|
|
12
|
+
// Storage
|
|
13
|
+
export * from './event-store.js';
|
|
14
|
+
export * from './entity-repo.js';
|
|
15
|
+
export * from './edge-repo.js';
|
|
16
|
+
|
|
17
|
+
// Vector
|
|
18
|
+
export * from './vector-store.js';
|
|
19
|
+
export * from './embedder.js';
|
|
20
|
+
export * from './vector-worker.js';
|
|
21
|
+
export * from './vector-outbox.js';
|
|
22
|
+
|
|
23
|
+
// Matching & Alignment
|
|
24
|
+
export * from './matcher.js';
|
|
25
|
+
export * from './evidence-aligner.js';
|
|
26
|
+
|
|
27
|
+
// Retrieval & Graduation
|
|
28
|
+
export * from './retriever.js';
|
|
29
|
+
export * from './graduation.js';
|
|
30
|
+
|
|
31
|
+
// Task Entity System
|
|
32
|
+
export * from './task/index.js';
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AXIOMMIND Matcher - Weighted scoring with confidence classification
|
|
3
|
+
* Implements matching thresholds: high (≥0.92), suggested (≥0.75), none (<0.75)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
MemoryEvent,
|
|
8
|
+
MemoryMatch,
|
|
9
|
+
MatchResult,
|
|
10
|
+
MatchConfidence,
|
|
11
|
+
MATCH_THRESHOLDS
|
|
12
|
+
} from './types.js';
|
|
13
|
+
import { SearchResult } from './vector-store.js';
|
|
14
|
+
|
|
15
|
+
export interface MatchWeights {
|
|
16
|
+
semanticSimilarity: number;
|
|
17
|
+
ftsScore: number;
|
|
18
|
+
recencyBonus: number;
|
|
19
|
+
statusWeight: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface MatcherConfig {
|
|
23
|
+
weights: MatchWeights;
|
|
24
|
+
minCombinedScore: number;
|
|
25
|
+
minGap: number;
|
|
26
|
+
suggestionThreshold: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const DEFAULT_CONFIG: MatcherConfig = {
|
|
30
|
+
weights: {
|
|
31
|
+
semanticSimilarity: 0.4,
|
|
32
|
+
ftsScore: 0.25,
|
|
33
|
+
recencyBonus: 0.2,
|
|
34
|
+
statusWeight: 0.15
|
|
35
|
+
},
|
|
36
|
+
minCombinedScore: 0.92,
|
|
37
|
+
minGap: 0.03,
|
|
38
|
+
suggestionThreshold: 0.75
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export class Matcher {
|
|
42
|
+
private readonly config: MatcherConfig;
|
|
43
|
+
|
|
44
|
+
constructor(config: Partial<MatcherConfig> = {}) {
|
|
45
|
+
this.config = {
|
|
46
|
+
...DEFAULT_CONFIG,
|
|
47
|
+
...config,
|
|
48
|
+
weights: { ...DEFAULT_CONFIG.weights, ...config.weights }
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Calculate combined score using AXIOMMIND weighted formula
|
|
54
|
+
*/
|
|
55
|
+
calculateCombinedScore(
|
|
56
|
+
semanticScore: number,
|
|
57
|
+
ftsScore: number = 0,
|
|
58
|
+
recencyDays: number = 0,
|
|
59
|
+
isActive: boolean = true
|
|
60
|
+
): number {
|
|
61
|
+
const { weights } = this.config;
|
|
62
|
+
|
|
63
|
+
// Recency bonus: decays over 30 days
|
|
64
|
+
const recencyBonus = Math.max(0, 1 - recencyDays / 30);
|
|
65
|
+
|
|
66
|
+
// Status weight: active events get full weight
|
|
67
|
+
const statusMultiplier = isActive ? 1.0 : 0.7;
|
|
68
|
+
|
|
69
|
+
const combinedScore =
|
|
70
|
+
weights.semanticSimilarity * semanticScore +
|
|
71
|
+
weights.ftsScore * ftsScore +
|
|
72
|
+
weights.recencyBonus * recencyBonus +
|
|
73
|
+
weights.statusWeight * statusMultiplier;
|
|
74
|
+
|
|
75
|
+
return Math.min(1.0, combinedScore);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Classify match confidence based on AXIOMMIND thresholds
|
|
80
|
+
*/
|
|
81
|
+
classifyConfidence(
|
|
82
|
+
topScore: number,
|
|
83
|
+
secondScore: number | null
|
|
84
|
+
): MatchConfidence {
|
|
85
|
+
const { minCombinedScore, minGap, suggestionThreshold } = this.config;
|
|
86
|
+
|
|
87
|
+
// Calculate gap (infinity if no second match)
|
|
88
|
+
const gap = secondScore !== null ? topScore - secondScore : Infinity;
|
|
89
|
+
|
|
90
|
+
// High confidence: score ≥ 0.92 AND gap ≥ 0.03
|
|
91
|
+
if (topScore >= minCombinedScore && gap >= minGap) {
|
|
92
|
+
return 'high';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Suggested: score ≥ 0.75
|
|
96
|
+
if (topScore >= suggestionThreshold) {
|
|
97
|
+
return 'suggested';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// No match
|
|
101
|
+
return 'none';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Match search results to find best memory
|
|
106
|
+
*/
|
|
107
|
+
matchSearchResults(
|
|
108
|
+
results: SearchResult[],
|
|
109
|
+
getEventAge: (eventId: string) => number
|
|
110
|
+
): MatchResult {
|
|
111
|
+
if (results.length === 0) {
|
|
112
|
+
return {
|
|
113
|
+
match: null,
|
|
114
|
+
confidence: 'none'
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Calculate combined scores
|
|
119
|
+
const scoredResults = results.map(result => {
|
|
120
|
+
const ageDays = getEventAge(result.eventId);
|
|
121
|
+
const combinedScore = this.calculateCombinedScore(
|
|
122
|
+
result.score,
|
|
123
|
+
0, // FTS score - would need to be passed in
|
|
124
|
+
ageDays,
|
|
125
|
+
true // Assume active
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
result,
|
|
130
|
+
combinedScore
|
|
131
|
+
};
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Sort by combined score
|
|
135
|
+
scoredResults.sort((a, b) => b.combinedScore - a.combinedScore);
|
|
136
|
+
|
|
137
|
+
const topResult = scoredResults[0];
|
|
138
|
+
const secondScore = scoredResults.length > 1 ? scoredResults[1].combinedScore : null;
|
|
139
|
+
|
|
140
|
+
// Classify confidence
|
|
141
|
+
const confidence = this.classifyConfidence(topResult.combinedScore, secondScore);
|
|
142
|
+
|
|
143
|
+
// Build match result
|
|
144
|
+
const match: MemoryMatch = {
|
|
145
|
+
event: {
|
|
146
|
+
id: topResult.result.eventId,
|
|
147
|
+
eventType: topResult.result.eventType as 'user_prompt' | 'agent_response' | 'session_summary',
|
|
148
|
+
sessionId: topResult.result.sessionId,
|
|
149
|
+
timestamp: new Date(topResult.result.timestamp),
|
|
150
|
+
content: topResult.result.content,
|
|
151
|
+
canonicalKey: '', // Would need to be fetched
|
|
152
|
+
dedupeKey: '' // Would need to be fetched
|
|
153
|
+
},
|
|
154
|
+
score: topResult.combinedScore
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const gap = secondScore !== null ? topResult.combinedScore - secondScore : undefined;
|
|
158
|
+
|
|
159
|
+
// Build alternatives for suggested matches
|
|
160
|
+
const alternatives = confidence === 'suggested'
|
|
161
|
+
? scoredResults.slice(1, 4).map(sr => ({
|
|
162
|
+
event: {
|
|
163
|
+
id: sr.result.eventId,
|
|
164
|
+
eventType: sr.result.eventType as 'user_prompt' | 'agent_response' | 'session_summary',
|
|
165
|
+
sessionId: sr.result.sessionId,
|
|
166
|
+
timestamp: new Date(sr.result.timestamp),
|
|
167
|
+
content: sr.result.content,
|
|
168
|
+
canonicalKey: '',
|
|
169
|
+
dedupeKey: ''
|
|
170
|
+
},
|
|
171
|
+
score: sr.combinedScore
|
|
172
|
+
}))
|
|
173
|
+
: undefined;
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
match: confidence !== 'none' ? match : null,
|
|
177
|
+
confidence,
|
|
178
|
+
gap,
|
|
179
|
+
alternatives
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Calculate days between two dates
|
|
185
|
+
*/
|
|
186
|
+
static calculateAgeDays(timestamp: Date): number {
|
|
187
|
+
const now = new Date();
|
|
188
|
+
const diffMs = now.getTime() - timestamp.getTime();
|
|
189
|
+
return diffMs / (1000 * 60 * 60 * 24);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Get current configuration
|
|
194
|
+
*/
|
|
195
|
+
getConfig(): Readonly<MatcherConfig> {
|
|
196
|
+
return { ...this.config };
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Default matcher instance
|
|
202
|
+
*/
|
|
203
|
+
let defaultMatcher: Matcher | null = null;
|
|
204
|
+
|
|
205
|
+
export function getDefaultMatcher(): Matcher {
|
|
206
|
+
if (!defaultMatcher) {
|
|
207
|
+
defaultMatcher = new Matcher();
|
|
208
|
+
}
|
|
209
|
+
return defaultMatcher;
|
|
210
|
+
}
|