@jungjaehoon/mama-core 1.3.3 → 1.4.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.
Files changed (56) hide show
  1. package/README.md +67 -19
  2. package/db/migrations/001-initial-decision-graph.sql +6 -1
  3. package/db/migrations/002-add-error-patterns.sql +1 -1
  4. package/db/migrations/014-add-is-static-column.sql +5 -0
  5. package/db/migrations/015-add-fts5-search.sql +34 -0
  6. package/db/migrations/016-add-memory-v2-columns.sql +14 -0
  7. package/db/migrations/017-create-memory-scopes.sql +10 -0
  8. package/db/migrations/018-create-memory-scope-bindings.sql +15 -0
  9. package/db/migrations/019-create-memory-events.sql +18 -0
  10. package/db/migrations/020-create-audit-findings.sql +18 -0
  11. package/db/migrations/021-create-memory-truth.sql +20 -0
  12. package/db/migrations/022-create-channel-summaries.sql +9 -0
  13. package/db/migrations/023-create-channel-summary-state.sql +9 -0
  14. package/db/migrations/024-add-event-date.sql +9 -0
  15. package/db/migrations/025-extend-kind-check.sql +59 -0
  16. package/dist/config-loader.d.ts +1 -1
  17. package/dist/config-loader.js +3 -3
  18. package/dist/db-adapter/base-adapter.d.ts +2 -2
  19. package/dist/db-adapter/index.js +2 -8
  20. package/dist/db-adapter/node-sqlite-adapter.d.ts +6 -1
  21. package/dist/db-adapter/node-sqlite-adapter.js +99 -45
  22. package/dist/db-manager.d.ts +32 -4
  23. package/dist/db-manager.js +133 -8
  24. package/dist/decision-formatter.d.ts +2 -0
  25. package/dist/embedding-server/mobile/session-manager.js +22 -7
  26. package/dist/embeddings.d.ts +2 -2
  27. package/dist/embeddings.js +2 -2
  28. package/dist/index.d.ts +5 -1
  29. package/dist/index.js +31 -3
  30. package/dist/mama-api.d.ts +67 -2
  31. package/dist/mama-api.js +295 -80
  32. package/dist/memory/api.d.ts +81 -0
  33. package/dist/memory/api.js +972 -0
  34. package/dist/memory/bootstrap-builder.d.ts +10 -0
  35. package/dist/memory/bootstrap-builder.js +66 -0
  36. package/dist/memory/channel-summary-state-store.d.ts +19 -0
  37. package/dist/memory/channel-summary-state-store.js +223 -0
  38. package/dist/memory/channel-summary-store.d.ts +8 -0
  39. package/dist/memory/channel-summary-store.js +41 -0
  40. package/dist/memory/event-store.d.ts +5 -0
  41. package/dist/memory/event-store.js +72 -0
  42. package/dist/memory/evolution-engine.d.ts +22 -0
  43. package/dist/memory/evolution-engine.js +133 -0
  44. package/dist/memory/extraction-prompt.d.ts +4 -0
  45. package/dist/memory/extraction-prompt.js +89 -0
  46. package/dist/memory/finding-store.d.ts +5 -0
  47. package/dist/memory/finding-store.js +75 -0
  48. package/dist/memory/profile-builder.d.ts +3 -0
  49. package/dist/memory/profile-builder.js +23 -0
  50. package/dist/memory/scope-store.d.ts +5 -0
  51. package/dist/memory/scope-store.js +13 -0
  52. package/dist/memory/truth-store.d.ts +11 -0
  53. package/dist/memory/truth-store.js +102 -0
  54. package/dist/memory/types.d.ts +267 -0
  55. package/dist/memory/types.js +83 -0
  56. package/package.json +4 -1
@@ -0,0 +1,10 @@
1
+ import type { MemoryAgentBootstrap, MemoryScopeRef } from './types.js';
2
+ interface BuildMemoryAgentBootstrapParams {
3
+ scopes: MemoryScopeRef[];
4
+ channelKey?: string;
5
+ currentGoal?: string;
6
+ mainAgentState?: MemoryAgentBootstrap['main_agent_state'];
7
+ }
8
+ export declare function buildMemoryAgentBootstrap(params: BuildMemoryAgentBootstrapParams): Promise<MemoryAgentBootstrap>;
9
+ export {};
10
+ //# sourceMappingURL=bootstrap-builder.d.ts.map
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildMemoryAgentBootstrap = buildMemoryAgentBootstrap;
4
+ const event_store_js_1 = require("./event-store.js");
5
+ const finding_store_js_1 = require("./finding-store.js");
6
+ const profile_builder_js_1 = require("./profile-builder.js");
7
+ const truth_store_js_1 = require("./truth-store.js");
8
+ const channel_summary_store_js_1 = require("./channel-summary-store.js");
9
+ async function buildMemoryAgentBootstrap(params) {
10
+ const [truthRows, findings, recentEvents, channelSummary] = await Promise.all([
11
+ (0, truth_store_js_1.queryRelevantTruth)({
12
+ query: '',
13
+ scopes: params.scopes,
14
+ includeHistory: true,
15
+ }),
16
+ (0, finding_store_js_1.listOpenAuditFindings)(),
17
+ (0, event_store_js_1.listRecentMemoryEvents)(10),
18
+ params.channelKey ? (0, channel_summary_store_js_1.getChannelSummary)(params.channelKey) : Promise.resolve(null),
19
+ ]);
20
+ const scopedTruth = truthRows.filter((row) => row.scope_refs.some((scopeRef) => params.scopes.some((scope) => scope.kind === scopeRef.kind && scope.id === scopeRef.id)));
21
+ const profile = (0, profile_builder_js_1.classifyProfileEntries)(scopedTruth.map((row) => ({
22
+ id: row.memory_id,
23
+ topic: row.topic,
24
+ kind: 'decision',
25
+ summary: row.effective_summary,
26
+ details: row.effective_details,
27
+ confidence: row.trust_score,
28
+ status: row.truth_status === 'quarantined' ? 'stale' : row.truth_status,
29
+ scopes: row.scope_refs,
30
+ source: {
31
+ package: 'mama-core',
32
+ source_type: 'truth_projection',
33
+ },
34
+ created_at: 0,
35
+ updated_at: 0,
36
+ })));
37
+ return {
38
+ current_goal: params.currentGoal,
39
+ scope_context: params.scopes,
40
+ channel_summary_markdown: channelSummary?.summary_markdown,
41
+ truth_snapshot: scopedTruth.map((row) => ({
42
+ id: row.memory_id,
43
+ topic: row.topic,
44
+ summary: row.effective_summary,
45
+ trust_score: row.trust_score,
46
+ })),
47
+ open_audit_findings: findings.map((finding) => ({
48
+ id: finding.finding_id,
49
+ kind: finding.kind,
50
+ severity: finding.severity,
51
+ summary: finding.summary,
52
+ })),
53
+ recent_memory_events: recentEvents.map((event) => ({
54
+ id: event.event_id,
55
+ type: event.event_type,
56
+ topic: event.topic,
57
+ created_at: event.created_at,
58
+ })),
59
+ profile_snapshot: {
60
+ static: profile.static.map((entry) => ({ id: entry.id, summary: entry.summary })),
61
+ dynamic: profile.dynamic.map((entry) => ({ id: entry.id, summary: entry.summary })),
62
+ },
63
+ main_agent_state: params.mainAgentState,
64
+ };
65
+ }
66
+ //# sourceMappingURL=bootstrap-builder.js.map
@@ -0,0 +1,19 @@
1
+ import type { ChannelSummaryStateRecord, MemoryAuditAck, MemoryScopeRef } from './types.js';
2
+ export declare function getChannelSummaryState(channelKey: string): Promise<ChannelSummaryStateRecord | null>;
3
+ export declare function recordChannelAudit(input: {
4
+ channelKey: string;
5
+ turnId: string;
6
+ topic: string;
7
+ scopeRefs: MemoryScopeRef[];
8
+ ack: MemoryAuditAck;
9
+ savedMemories?: Array<{
10
+ id: string;
11
+ topic: string;
12
+ summary: string;
13
+ }>;
14
+ }): Promise<{
15
+ eventIds: string[];
16
+ findingIds: string[];
17
+ state: ChannelSummaryStateRecord;
18
+ }>;
19
+ //# sourceMappingURL=channel-summary-state-store.d.ts.map
@@ -0,0 +1,223 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getChannelSummaryState = getChannelSummaryState;
7
+ exports.recordChannelAudit = recordChannelAudit;
8
+ const node_crypto_1 = __importDefault(require("node:crypto"));
9
+ const db_manager_js_1 = require("../db-manager.js");
10
+ const channel_summary_store_js_1 = require("./channel-summary-store.js");
11
+ const event_store_js_1 = require("./event-store.js");
12
+ const finding_store_js_1 = require("./finding-store.js");
13
+ const MAX_ACTIVE_DECISIONS = 6;
14
+ const MAX_RECENT_MILESTONES = 8;
15
+ const MAX_RECENT_AUDIT_OUTCOMES = 6;
16
+ function truncate(text, maxLength) {
17
+ if (text.length <= maxLength) {
18
+ return text;
19
+ }
20
+ return `${text.slice(0, maxLength - 1)}…`;
21
+ }
22
+ function createStateHash(state) {
23
+ return node_crypto_1.default.createHash('sha1').update(JSON.stringify(state)).digest('hex');
24
+ }
25
+ function deserializeState(row) {
26
+ const parsed = JSON.parse(String(row.state_json));
27
+ return {
28
+ channel_key: String(row.channel_key),
29
+ active_topic: parsed.active_topic,
30
+ active_decisions: parsed.active_decisions ?? [],
31
+ recent_milestones: parsed.recent_milestones ?? [],
32
+ recent_audit_outcomes: parsed.recent_audit_outcomes ?? [],
33
+ state_hash: String(row.state_hash),
34
+ updated_at: Number(row.updated_at),
35
+ };
36
+ }
37
+ function createEmptyState(channelKey) {
38
+ const now = Date.now();
39
+ const base = {
40
+ active_topic: undefined,
41
+ active_decisions: [],
42
+ recent_milestones: [],
43
+ recent_audit_outcomes: [],
44
+ };
45
+ return {
46
+ channel_key: channelKey,
47
+ ...base,
48
+ state_hash: createStateHash(base),
49
+ updated_at: now,
50
+ };
51
+ }
52
+ function createLegacySeedState(channelKey, legacySummary) {
53
+ const base = {
54
+ active_topic: undefined,
55
+ active_decisions: [],
56
+ recent_milestones: [
57
+ {
58
+ topic: 'legacy_summary',
59
+ action: 'no_op',
60
+ summary: legacySummary.summary_markdown.replace(/\s+/g, ' ').trim(),
61
+ timestamp: legacySummary.updated_at,
62
+ },
63
+ ],
64
+ recent_audit_outcomes: [],
65
+ };
66
+ return {
67
+ channel_key: channelKey,
68
+ ...base,
69
+ state_hash: createStateHash(base),
70
+ updated_at: legacySummary.updated_at,
71
+ };
72
+ }
73
+ function renderChannelSummaryMarkdown(state) {
74
+ const lines = ['## Channel Summary'];
75
+ if (state.active_topic) {
76
+ lines.push(`- Current topic: ${state.active_topic}`);
77
+ }
78
+ if (state.active_decisions.length > 0) {
79
+ lines.push('', '## Active Decisions');
80
+ for (const decision of state.active_decisions) {
81
+ lines.push(`- ${decision.topic}: ${truncate(decision.summary, 160)}`);
82
+ }
83
+ }
84
+ if (state.recent_milestones.length > 0) {
85
+ lines.push('', '## Recent Milestones');
86
+ for (const milestone of state.recent_milestones) {
87
+ lines.push(`- ${milestone.topic} (${milestone.action}): ${truncate(milestone.summary, 140)}`);
88
+ }
89
+ }
90
+ if (state.recent_audit_outcomes.length > 0) {
91
+ lines.push('', '## Audit Signals');
92
+ for (const outcome of state.recent_audit_outcomes) {
93
+ const note = outcome.reason ? ` — ${truncate(outcome.reason, 120)}` : '';
94
+ lines.push(`- ${outcome.topic}: ${outcome.status}${note}`);
95
+ }
96
+ }
97
+ return lines.join('\n');
98
+ }
99
+ function reduceState(previous, input) {
100
+ const next = {
101
+ ...previous,
102
+ active_decisions: previous.active_decisions.slice(),
103
+ recent_milestones: previous.recent_milestones.slice(),
104
+ recent_audit_outcomes: previous.recent_audit_outcomes.slice(),
105
+ updated_at: input.timestamp,
106
+ };
107
+ next.recent_audit_outcomes.unshift({
108
+ topic: input.topic,
109
+ status: input.ack.status,
110
+ reason: input.ack.reason,
111
+ timestamp: input.timestamp,
112
+ });
113
+ next.recent_audit_outcomes = next.recent_audit_outcomes.slice(0, MAX_RECENT_AUDIT_OUTCOMES);
114
+ if (input.ack.status === 'applied' && input.savedMemories && input.savedMemories.length > 0) {
115
+ next.active_topic = input.savedMemories[0]?.topic ?? input.topic;
116
+ for (const memory of input.savedMemories) {
117
+ next.active_decisions = next.active_decisions.filter((entry) => entry.memory_id !== memory.id && entry.topic !== memory.topic);
118
+ next.active_decisions.unshift({
119
+ memory_id: memory.id,
120
+ topic: memory.topic,
121
+ summary: memory.summary,
122
+ updated_at: input.timestamp,
123
+ });
124
+ next.recent_milestones.unshift({
125
+ topic: memory.topic,
126
+ action: input.ack.action,
127
+ summary: memory.summary,
128
+ timestamp: input.timestamp,
129
+ memory_id: memory.id,
130
+ });
131
+ }
132
+ next.active_decisions = next.active_decisions.slice(0, MAX_ACTIVE_DECISIONS);
133
+ next.recent_milestones = next.recent_milestones.slice(0, MAX_RECENT_MILESTONES);
134
+ }
135
+ const stateHash = createStateHash({
136
+ active_topic: next.active_topic,
137
+ active_decisions: next.active_decisions,
138
+ recent_milestones: next.recent_milestones,
139
+ recent_audit_outcomes: next.recent_audit_outcomes,
140
+ });
141
+ return {
142
+ ...next,
143
+ state_hash: stateHash,
144
+ };
145
+ }
146
+ async function getChannelSummaryState(channelKey) {
147
+ await (0, db_manager_js_1.initDB)();
148
+ const adapter = (0, db_manager_js_1.getAdapter)();
149
+ const row = adapter
150
+ .prepare(`
151
+ SELECT channel_key, state_json, state_hash, updated_at
152
+ FROM channel_summary_state
153
+ WHERE channel_key = ?
154
+ `)
155
+ .get(channelKey);
156
+ return row ? deserializeState(row) : null;
157
+ }
158
+ async function recordChannelAudit(input) {
159
+ await (0, db_manager_js_1.initDB)();
160
+ const adapter = (0, db_manager_js_1.getAdapter)();
161
+ const timestamp = Date.now();
162
+ const legacySummary = await (0, channel_summary_store_js_1.getChannelSummary)(input.channelKey);
163
+ const previous = (await getChannelSummaryState(input.channelKey)) ??
164
+ (legacySummary
165
+ ? createLegacySeedState(input.channelKey, legacySummary)
166
+ : createEmptyState(input.channelKey));
167
+ const eventIds = [];
168
+ const findingIds = [];
169
+ const eventType = input.ack.status === 'failed'
170
+ ? 'audit_failed'
171
+ : input.ack.action === 'no_op'
172
+ ? 'no_op'
173
+ : input.ack.action;
174
+ eventIds.push(await (0, event_store_js_1.appendMemoryEvent)({
175
+ event_type: eventType,
176
+ actor: 'memory_agent',
177
+ source_turn_id: input.turnId,
178
+ memory_id: input.savedMemories?.[0]?.id,
179
+ topic: input.topic,
180
+ scope_refs: input.scopeRefs,
181
+ reason: input.ack.reason,
182
+ created_at: timestamp,
183
+ }));
184
+ if (input.ack.status === 'failed') {
185
+ findingIds.push(await (0, finding_store_js_1.createAuditFinding)({
186
+ kind: 'unsupported_claim',
187
+ severity: 'high',
188
+ summary: input.ack.reason ?? `memory audit failed for ${input.topic}`,
189
+ evidence_refs: eventIds,
190
+ affected_memory_ids: input.savedMemories?.map((memory) => memory.id) ?? [],
191
+ recommended_action: 'consult_memory',
192
+ }));
193
+ }
194
+ const state = reduceState(previous, {
195
+ topic: input.topic,
196
+ ack: input.ack,
197
+ savedMemories: input.savedMemories,
198
+ timestamp,
199
+ });
200
+ adapter
201
+ .prepare(`
202
+ INSERT OR REPLACE INTO channel_summary_state (
203
+ channel_key,
204
+ state_json,
205
+ state_hash,
206
+ updated_at
207
+ )
208
+ VALUES (?, ?, ?, ?)
209
+ `)
210
+ .run(input.channelKey, JSON.stringify({
211
+ active_topic: state.active_topic,
212
+ active_decisions: state.active_decisions,
213
+ recent_milestones: state.recent_milestones,
214
+ recent_audit_outcomes: state.recent_audit_outcomes,
215
+ }), state.state_hash, timestamp);
216
+ await (0, channel_summary_store_js_1.upsertChannelSummary)({
217
+ channelKey: input.channelKey,
218
+ summaryMarkdown: renderChannelSummaryMarkdown(state),
219
+ deltaHash: state.state_hash,
220
+ });
221
+ return { eventIds, findingIds, state };
222
+ }
223
+ //# sourceMappingURL=channel-summary-state-store.js.map
@@ -0,0 +1,8 @@
1
+ import type { ChannelSummaryRecord } from './types.js';
2
+ export declare function upsertChannelSummary(input: {
3
+ channelKey: string;
4
+ summaryMarkdown: string;
5
+ deltaHash?: string;
6
+ }): Promise<void>;
7
+ export declare function getChannelSummary(channelKey: string): Promise<ChannelSummaryRecord | null>;
8
+ //# sourceMappingURL=channel-summary-store.d.ts.map
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.upsertChannelSummary = upsertChannelSummary;
4
+ exports.getChannelSummary = getChannelSummary;
5
+ const db_manager_js_1 = require("../db-manager.js");
6
+ function toChannelSummary(row) {
7
+ return {
8
+ channel_key: String(row.channel_key),
9
+ summary_markdown: String(row.summary_markdown),
10
+ delta_hash: typeof row.delta_hash === 'string' ? row.delta_hash : undefined,
11
+ updated_at: Number(row.updated_at),
12
+ };
13
+ }
14
+ async function upsertChannelSummary(input) {
15
+ await (0, db_manager_js_1.initDB)();
16
+ const adapter = (0, db_manager_js_1.getAdapter)();
17
+ adapter
18
+ .prepare(`
19
+ INSERT OR REPLACE INTO channel_summaries (
20
+ channel_key,
21
+ summary_markdown,
22
+ delta_hash,
23
+ updated_at
24
+ )
25
+ VALUES (?, ?, ?, ?)
26
+ `)
27
+ .run(input.channelKey, input.summaryMarkdown, input.deltaHash ?? null, Date.now());
28
+ }
29
+ async function getChannelSummary(channelKey) {
30
+ await (0, db_manager_js_1.initDB)();
31
+ const adapter = (0, db_manager_js_1.getAdapter)();
32
+ const row = adapter
33
+ .prepare(`
34
+ SELECT channel_key, summary_markdown, delta_hash, updated_at
35
+ FROM channel_summaries
36
+ WHERE channel_key = ?
37
+ `)
38
+ .get(channelKey);
39
+ return row ? toChannelSummary(row) : null;
40
+ }
41
+ //# sourceMappingURL=channel-summary-store.js.map
@@ -0,0 +1,5 @@
1
+ import type { MemoryEventRecord } from './types.js';
2
+ export declare function appendMemoryEvent(input: Omit<MemoryEventRecord, 'event_id'>): Promise<string>;
3
+ export declare function listMemoryEventsForTopic(topic: string): Promise<MemoryEventRecord[]>;
4
+ export declare function listRecentMemoryEvents(limit?: number): Promise<MemoryEventRecord[]>;
5
+ //# sourceMappingURL=event-store.d.ts.map
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.appendMemoryEvent = appendMemoryEvent;
7
+ exports.listMemoryEventsForTopic = listMemoryEventsForTopic;
8
+ exports.listRecentMemoryEvents = listRecentMemoryEvents;
9
+ const node_crypto_1 = __importDefault(require("node:crypto"));
10
+ const db_manager_js_1 = require("../db-manager.js");
11
+ function deserializeEvent(row) {
12
+ return {
13
+ event_id: String(row.event_id),
14
+ event_type: row.event_type,
15
+ actor: row.actor,
16
+ source_turn_id: typeof row.source_turn_id === 'string' && row.source_turn_id.length > 0
17
+ ? row.source_turn_id
18
+ : undefined,
19
+ memory_id: typeof row.memory_id === 'string' && row.memory_id.length > 0 ? row.memory_id : undefined,
20
+ topic: typeof row.topic === 'string' && row.topic.length > 0 ? row.topic : undefined,
21
+ scope_refs: JSON.parse(String(row.scope_refs)),
22
+ evidence_refs: typeof row.evidence_refs === 'string'
23
+ ? JSON.parse(row.evidence_refs)
24
+ : undefined,
25
+ reason: typeof row.reason === 'string' ? row.reason : undefined,
26
+ created_at: Number(row.created_at),
27
+ };
28
+ }
29
+ async function appendMemoryEvent(input) {
30
+ await (0, db_manager_js_1.initDB)();
31
+ const adapter = (0, db_manager_js_1.getAdapter)();
32
+ const eventId = `evt_${node_crypto_1.default.randomUUID().replace(/-/g, '')}`;
33
+ adapter
34
+ .prepare(`
35
+ INSERT INTO memory_events (
36
+ event_id, event_type, actor, source_turn_id, memory_id, topic,
37
+ scope_refs, evidence_refs, reason, created_at
38
+ )
39
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
40
+ `)
41
+ .run(eventId, input.event_type, input.actor, input.source_turn_id ?? null, input.memory_id ?? null, input.topic ?? null, JSON.stringify(input.scope_refs), input.evidence_refs ? JSON.stringify(input.evidence_refs) : null, input.reason ?? null, input.created_at);
42
+ return eventId;
43
+ }
44
+ async function listMemoryEventsForTopic(topic) {
45
+ await (0, db_manager_js_1.initDB)();
46
+ const adapter = (0, db_manager_js_1.getAdapter)();
47
+ const rows = adapter
48
+ .prepare(`
49
+ SELECT event_id, event_type, actor, source_turn_id, memory_id, topic,
50
+ scope_refs, evidence_refs, reason, created_at
51
+ FROM memory_events
52
+ WHERE topic = ?
53
+ ORDER BY created_at DESC
54
+ `)
55
+ .all(topic);
56
+ return rows.map(deserializeEvent);
57
+ }
58
+ async function listRecentMemoryEvents(limit = 10) {
59
+ await (0, db_manager_js_1.initDB)();
60
+ const adapter = (0, db_manager_js_1.getAdapter)();
61
+ const rows = adapter
62
+ .prepare(`
63
+ SELECT event_id, event_type, actor, source_turn_id, memory_id, topic,
64
+ scope_refs, evidence_refs, reason, created_at
65
+ FROM memory_events
66
+ ORDER BY created_at DESC
67
+ LIMIT ?
68
+ `)
69
+ .all(limit);
70
+ return rows.map(deserializeEvent);
71
+ }
72
+ //# sourceMappingURL=event-store.js.map
@@ -0,0 +1,22 @@
1
+ import type { MemoryEdge, MemoryRecord } from './types.js';
2
+ interface EvolutionInput {
3
+ incoming: Pick<MemoryRecord, 'topic' | 'summary' | 'kind'>;
4
+ existing: Array<Pick<MemoryRecord, 'id' | 'topic' | 'summary' | 'kind'> & {
5
+ _semanticMatch?: boolean;
6
+ }>;
7
+ }
8
+ export interface EvolutionResult {
9
+ edges: MemoryEdge[];
10
+ }
11
+ /**
12
+ * Resolve how an incoming memory relates to existing memories.
13
+ *
14
+ * Supersede rules (conservative — avoid information loss):
15
+ * 1. Raw → Extracted: structured fact replaces raw conversation (same topic, raw kind)
16
+ * 2. Same fact update: same topic + high summary overlap (≥0.6) between two extracted facts
17
+ *
18
+ * Everything else → builds_on (preserves independent facts under same topic)
19
+ */
20
+ export declare function resolveMemoryEvolution(input: EvolutionInput): EvolutionResult;
21
+ export {};
22
+ //# sourceMappingURL=evolution-engine.d.ts.map
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveMemoryEvolution = resolveMemoryEvolution;
4
+ const STOP_WORDS = new Set([
5
+ 'the',
6
+ 'is',
7
+ 'a',
8
+ 'an',
9
+ 'to',
10
+ 'in',
11
+ 'on',
12
+ 'of',
13
+ 'for',
14
+ 'and',
15
+ 'or',
16
+ 'but',
17
+ 'it',
18
+ 'we',
19
+ 'i',
20
+ 'this',
21
+ 'that',
22
+ 'with',
23
+ 'as',
24
+ 'at',
25
+ 'by',
26
+ 'from',
27
+ 'be',
28
+ 'are',
29
+ 'was',
30
+ 'were',
31
+ 'been',
32
+ 'has',
33
+ 'have',
34
+ 'had',
35
+ 'do',
36
+ 'does',
37
+ 'did',
38
+ 'will',
39
+ 'would',
40
+ 'can',
41
+ 'could',
42
+ 'should',
43
+ 'not',
44
+ 'no',
45
+ 'so',
46
+ 'if',
47
+ 'up',
48
+ 'out',
49
+ 'all',
50
+ 'its',
51
+ 'user',
52
+ 'assistant',
53
+ ]);
54
+ const MIN_TOKEN_LENGTH = 3;
55
+ function summaryOverlapRatio(left, right) {
56
+ const tokenize = (s) => new Set(s
57
+ .toLowerCase()
58
+ .split(/[^a-z0-9_]+/)
59
+ .filter((t) => t.length >= MIN_TOKEN_LENGTH && !STOP_WORDS.has(t)));
60
+ const leftTokens = tokenize(left);
61
+ const rightTokens = tokenize(right);
62
+ if (leftTokens.size === 0 && rightTokens.size === 0) {
63
+ return left.toLowerCase().trim() === right.toLowerCase().trim() ? 1 : 0;
64
+ }
65
+ const smaller = leftTokens.size <= rightTokens.size ? leftTokens : rightTokens;
66
+ const larger = leftTokens.size > rightTokens.size ? leftTokens : rightTokens;
67
+ let shared = 0;
68
+ for (const t of smaller) {
69
+ if (larger.has(t))
70
+ shared++;
71
+ }
72
+ return shared / Math.max(smaller.size, 1);
73
+ }
74
+ /**
75
+ * Resolve how an incoming memory relates to existing memories.
76
+ *
77
+ * Supersede rules (conservative — avoid information loss):
78
+ * 1. Raw → Extracted: structured fact replaces raw conversation (same topic, raw kind)
79
+ * 2. Same fact update: same topic + high summary overlap (≥0.6) between two extracted facts
80
+ *
81
+ * Everything else → builds_on (preserves independent facts under same topic)
82
+ */
83
+ function resolveMemoryEvolution(input) {
84
+ const edges = [];
85
+ for (const existing of input.existing) {
86
+ if (existing.topic === input.incoming.topic) {
87
+ // Rule 1: Raw → Extracted supersede (ingestConversation saves raw first, then extracted)
88
+ // Raw records have kind='fact' but summary starts with conversation text (role prefixes)
89
+ const existingIsRaw = existing.kind === 'fact' && /^(user:|assistant:)/i.test(existing.summary.trim());
90
+ if (existingIsRaw) {
91
+ edges.push({
92
+ from_id: 'incoming',
93
+ to_id: existing.id,
94
+ type: 'supersedes',
95
+ reason: 'Structured extraction replaces raw conversation',
96
+ });
97
+ continue;
98
+ }
99
+ // Rule 2: Same fact update — supersede if summaries share meaningful overlap.
100
+ // Threshold 0.3 balances: "Use SQLite" → "Switch to PostgreSQL" (same topic, genuine update)
101
+ // vs "User's cat Luna" / "User's wedding plan" (same topic, independent facts).
102
+ const overlap = summaryOverlapRatio(existing.summary, input.incoming.summary);
103
+ if (overlap >= 0.3) {
104
+ edges.push({
105
+ from_id: 'incoming',
106
+ to_id: existing.id,
107
+ type: 'supersedes',
108
+ reason: `Updated fact (${(overlap * 100).toFixed(0)}% overlap)`,
109
+ });
110
+ continue;
111
+ }
112
+ // Different content under same topic → independent facts, link as builds_on
113
+ edges.push({
114
+ from_id: 'incoming',
115
+ to_id: existing.id,
116
+ type: 'builds_on',
117
+ reason: `Related but distinct (${(overlap * 100).toFixed(0)}% overlap)`,
118
+ });
119
+ continue;
120
+ }
121
+ // Semantic candidates (vector similarity >= 0.82) → builds_on
122
+ if (existing._semanticMatch) {
123
+ edges.push({
124
+ from_id: 'incoming',
125
+ to_id: existing.id,
126
+ type: 'builds_on',
127
+ reason: 'Semantically similar memory detected via vector search',
128
+ });
129
+ }
130
+ }
131
+ return { edges };
132
+ }
133
+ //# sourceMappingURL=evolution-engine.js.map
@@ -0,0 +1,4 @@
1
+ import { type ConversationMessage, type ExtractedMemoryUnit } from './types.js';
2
+ export declare function buildExtractionPrompt(messages: ConversationMessage[], existingTopics?: string[]): string;
3
+ export declare function parseExtractionResponse(response: string): ExtractedMemoryUnit[];
4
+ //# sourceMappingURL=extraction-prompt.d.ts.map