@199-bio/engram 0.4.3 → 0.5.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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consolidator.d.ts","sourceRoot":"","sources":["../../src/consolidation/consolidator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,cAAc,EAAU,MAAM,EAAE,MAAM,wBAAwB,CAAC;AA8CxE,UAAU,kBAAkB;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2BAA2B,CAAC,EAAE,MAAM,CAAC;CACtC;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,EAAE,CAAiB;gBAEf,EAAE,EAAE,cAAc;IAS9B,YAAY,IAAI,OAAO;IAIvB;;;OAGG;IACG,WAAW,CAAC,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC;QAC3D,cAAc,EAAE,MAAM,CAAC;QACvB,mBAAmB,EAAE,MAAM,CAAC;QAC5B,iBAAiB,EAAE,MAAM,CAAC;KAC3B,CAAC;IAwEF;;OAEG;YACW,gBAAgB;IAoE9B;;OAEG;IACG,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAuHjE;;OAEG;IACH,SAAS,IAAI;QACX,UAAU,EAAE,OAAO,CAAC;QACpB,sBAAsB,EAAE,MAAM,CAAC;QAC/B,YAAY,EAAE,MAAM,CAAC;QACrB,wBAAwB,EAAE,MAAM,CAAC;KAClC;CAYF"}
package/dist/index.js CHANGED
@@ -42,7 +42,7 @@ async function initialize() {
42
42
  // ============ MCP Server ============
43
43
  const server = new Server({
44
44
  name: "engram",
45
- version: "0.4.3",
45
+ version: "0.5.1",
46
46
  }, {
47
47
  capabilities: {
48
48
  tools: {},
@@ -1 +1 @@
1
- {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/storage/database.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/storage/database.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/web/server.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAiBtD,UAAU,gBAAgB;IACxB,EAAE,EAAE,cAAc,CAAC;IACnB,KAAK,EAAE,cAAc,CAAC;IACtB,MAAM,EAAE,YAAY,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,EAAE,CAAiB;IAC3B,OAAO,CAAC,KAAK,CAAiB;IAC9B,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,IAAI,CAAc;IAC1B,OAAO,CAAC,IAAI,CAAS;gBAET,OAAO,EAAE,gBAAgB;IAY/B,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAkB9B,IAAI,IAAI,IAAI;YAOE,aAAa;YA+Bb,SAAS;YAwMT,WAAW;IAgCzB,OAAO,CAAC,SAAS;CAclB"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/web/server.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAkBtD,UAAU,gBAAgB;IACxB,EAAE,EAAE,cAAc,CAAC;IACnB,KAAK,EAAE,cAAc,CAAC;IACtB,MAAM,EAAE,YAAY,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,EAAE,CAAiB;IAC3B,OAAO,CAAC,KAAK,CAAiB;IAC9B,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,IAAI,CAAc;IAC1B,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,IAAI,CAAS;gBAET,OAAO,EAAE,gBAAgB;IAa/B,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAkB9B,IAAI,IAAI,IAAI;YAOE,aAAa;YA+Bb,SAAS;YA8ST,WAAW;IAgCzB,OAAO,CAAC,SAAS;CAclB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@199-bio/engram",
3
- "version": "0.4.3",
3
+ "version": "0.5.1",
4
4
  "description": "Give Claude a perfect memory. Local-first MCP server with hybrid search.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -0,0 +1,376 @@
1
+ /**
2
+ * Memory Consolidator
3
+ *
4
+ * Uses Opus 4.5 with extended thinking to consolidate memories into digests
5
+ * and detect contradictions. Inspired by how the brain consolidates
6
+ * short-term memories into long-term storage during sleep.
7
+ *
8
+ * Levels:
9
+ * - L1: Session digests (consolidate recent memories)
10
+ * - L2: Topic clusters (group related digests)
11
+ * - L3: Entity profiles (comprehensive view of each entity)
12
+ */
13
+
14
+ import Anthropic from "@anthropic-ai/sdk";
15
+ import { EngramDatabase, Memory, Digest } from "../storage/database.js";
16
+
17
+ const CONSOLIDATION_SYSTEM = `You are a high-quality memory consolidation system for a personal AI assistant. Your goal is to create comprehensive, nuanced digests that preserve the richness of human experience and relationships.
18
+
19
+ ## Your Tasks
20
+
21
+ 1. **CONSOLIDATE**: Synthesize memories into a detailed digest that:
22
+ - Preserves ALL specific facts: names, dates, numbers, locations, preferences
23
+ - Captures relationships, emotions, context, and nuance
24
+ - Maintains chronological awareness (what happened when)
25
+ - Notes patterns, recurring themes, and changes over time
26
+ - Includes direct quotes when they reveal personality or important details
27
+
28
+ 2. **DETECT CONTRADICTIONS**: Flag genuinely conflicting information:
29
+ - Different dates/times for the same event
30
+ - Conflicting facts about the same person/thing
31
+ - Changed preferences or circumstances (note if this might be natural evolution vs. error)
32
+
33
+ ## Output Format (JSON)
34
+ {
35
+ "digest": "Comprehensive summary preserving all important details, context, and nuance. Multiple paragraphs are fine for complex topics.",
36
+ "topic": "Short topic label (2-5 words)",
37
+ "contradictions": [
38
+ {
39
+ "description": "Precise description of the conflict",
40
+ "memory_ids": ["id1", "id2"]
41
+ }
42
+ ]
43
+ }
44
+
45
+ ## Quality Standards
46
+ - NEVER sacrifice important details for brevity
47
+ - Include temporal context (when things happened/changed)
48
+ - Preserve personality, preferences, and relationship dynamics
49
+ - If memories span different time periods, note the evolution
50
+ - Only flag true contradictions, not incomplete information or natural life changes`;
51
+
52
+ interface ConsolidationResult {
53
+ digest: string;
54
+ topic: string;
55
+ contradictions: Array<{
56
+ description: string;
57
+ memory_ids: string[];
58
+ }>;
59
+ }
60
+
61
+ interface ConsolidateOptions {
62
+ batchSize?: number;
63
+ minMemoriesForConsolidation?: number;
64
+ }
65
+
66
+ export class Consolidator {
67
+ private client: Anthropic | null = null;
68
+ private db: EngramDatabase;
69
+
70
+ constructor(db: EngramDatabase) {
71
+ this.db = db;
72
+
73
+ const apiKey = process.env.ANTHROPIC_API_KEY;
74
+ if (apiKey) {
75
+ this.client = new Anthropic({ apiKey });
76
+ }
77
+ }
78
+
79
+ isConfigured(): boolean {
80
+ return this.client !== null;
81
+ }
82
+
83
+ /**
84
+ * Run consolidation on unconsolidated memories
85
+ * Returns number of digests created and contradictions found
86
+ */
87
+ async consolidate(options: ConsolidateOptions = {}): Promise<{
88
+ digestsCreated: number;
89
+ contradictionsFound: number;
90
+ memoriesProcessed: number;
91
+ }> {
92
+ if (!this.client) {
93
+ throw new Error("Consolidator not configured - set ANTHROPIC_API_KEY");
94
+ }
95
+
96
+ const { batchSize = 15, minMemoriesForConsolidation = 5 } = options;
97
+
98
+ // Get unconsolidated memories
99
+ const memories = this.db.getUnconsolidatedMemories(undefined, 100);
100
+
101
+ if (memories.length < minMemoriesForConsolidation) {
102
+ return {
103
+ digestsCreated: 0,
104
+ contradictionsFound: 0,
105
+ memoriesProcessed: 0,
106
+ };
107
+ }
108
+
109
+ let digestsCreated = 0;
110
+ let contradictionsFound = 0;
111
+ let memoriesProcessed = 0;
112
+
113
+ // Process in batches
114
+ for (let i = 0; i < memories.length; i += batchSize) {
115
+ const batch = memories.slice(i, i + batchSize);
116
+ if (batch.length < 3) break; // Skip tiny batches
117
+
118
+ try {
119
+ const result = await this.consolidateBatch(batch);
120
+
121
+ if (result) {
122
+ // Create digest
123
+ const memoryIds = batch.map((m) => m.id);
124
+ const periodStart = new Date(
125
+ Math.min(...batch.map((m) => m.timestamp.getTime()))
126
+ );
127
+ const periodEnd = new Date(
128
+ Math.max(...batch.map((m) => m.timestamp.getTime()))
129
+ );
130
+
131
+ this.db.createDigest(result.digest, 1, memoryIds, {
132
+ topic: result.topic,
133
+ periodStart,
134
+ periodEnd,
135
+ });
136
+ digestsCreated++;
137
+ memoriesProcessed += batch.length;
138
+
139
+ // Create contradictions
140
+ for (const c of result.contradictions) {
141
+ if (c.memory_ids.length >= 2) {
142
+ // Find the actual memory IDs from our batch
143
+ const [idA, idB] = c.memory_ids.slice(0, 2);
144
+ const memA = batch.find((m) => m.id === idA);
145
+ const memB = batch.find((m) => m.id === idB);
146
+
147
+ if (memA && memB) {
148
+ this.db.createContradiction(memA.id, memB.id, c.description);
149
+ contradictionsFound++;
150
+ }
151
+ }
152
+ }
153
+ }
154
+ } catch (error) {
155
+ console.error("[Consolidator] Batch consolidation failed:", error);
156
+ // Continue with next batch
157
+ }
158
+ }
159
+
160
+ return { digestsCreated, contradictionsFound, memoriesProcessed };
161
+ }
162
+
163
+ /**
164
+ * Consolidate a batch of memories using Opus 4.5 with extended thinking
165
+ */
166
+ private async consolidateBatch(
167
+ memories: Memory[]
168
+ ): Promise<ConsolidationResult | null> {
169
+ if (!this.client) return null;
170
+
171
+ // Format memories for the prompt
172
+ const memoriesText = memories
173
+ .map(
174
+ (m) =>
175
+ `[${m.id}] (${m.timestamp.toISOString().split("T")[0]}) ${m.content}`
176
+ )
177
+ .join("\n\n");
178
+
179
+ const userPrompt = `Synthesize these ${memories.length} memories into a comprehensive digest.
180
+
181
+ Think deeply about:
182
+ - What are the key facts, events, and details?
183
+ - Who are the people involved and how do they relate?
184
+ - What preferences, opinions, or patterns emerge?
185
+ - Is there a chronological narrative or timeline?
186
+ - Are there any contradictions between memories?
187
+
188
+ MEMORIES:
189
+ ${memoriesText}
190
+
191
+ Create a detailed digest that preserves all important information. Respond with JSON only.`;
192
+
193
+ try {
194
+ const response = await this.client.messages.create({
195
+ model: "claude-opus-4-5-20251101",
196
+ max_tokens: 16000,
197
+ temperature: 1, // Required for extended thinking
198
+ thinking: {
199
+ type: "enabled",
200
+ budget_tokens: 10000, // High budget for thorough analysis
201
+ },
202
+ messages: [
203
+ {
204
+ role: "user",
205
+ content: userPrompt,
206
+ },
207
+ ],
208
+ system: CONSOLIDATION_SYSTEM,
209
+ });
210
+
211
+ // Extract text response (skip thinking blocks)
212
+ let text = "";
213
+ for (const block of response.content) {
214
+ if (block.type === "text") {
215
+ text = block.text;
216
+ break;
217
+ }
218
+ }
219
+
220
+ if (!text) return null;
221
+
222
+ // Parse JSON response
223
+ const jsonMatch = text.match(/\{[\s\S]*\}/);
224
+ if (!jsonMatch) return null;
225
+
226
+ const result = JSON.parse(jsonMatch[0]) as ConsolidationResult;
227
+ return result;
228
+ } catch (error) {
229
+ console.error("[Consolidator] API call failed:", error);
230
+ return null;
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Create an entity profile by consolidating all observations about an entity
236
+ */
237
+ async consolidateEntity(entityId: string): Promise<Digest | null> {
238
+ if (!this.client) {
239
+ throw new Error("Consolidator not configured - set ANTHROPIC_API_KEY");
240
+ }
241
+
242
+ const entity = this.db.getEntity(entityId);
243
+ if (!entity) return null;
244
+
245
+ const observations = this.db.getEntityObservations(entityId);
246
+ if (observations.length < 2) return null;
247
+
248
+ // Get source memories for each observation
249
+ const memories: Memory[] = [];
250
+ for (const obs of observations) {
251
+ if (obs.source_memory_id) {
252
+ const mem = this.db.getMemory(obs.source_memory_id);
253
+ if (mem) memories.push(mem);
254
+ }
255
+ }
256
+
257
+ if (memories.length < 2) return null;
258
+
259
+ // Consolidate with entity context
260
+ const memoriesText = memories
261
+ .map(
262
+ (m) =>
263
+ `[${m.id}] (${m.timestamp.toISOString().split("T")[0]}) ${m.content}`
264
+ )
265
+ .join("\n\n");
266
+
267
+ const userPrompt = `Create a comprehensive, detailed profile for "${entity.name}" (${entity.type}).
268
+
269
+ This profile will serve as the authoritative reference for everything known about this ${entity.type}. Include:
270
+ - All biographical/descriptive facts
271
+ - Relationships with other people/entities
272
+ - Preferences, opinions, personality traits
273
+ - Timeline of events and changes over time
274
+ - Notable quotes or characteristic expressions
275
+ - Any context that helps understand this ${entity.type}
276
+
277
+ MEMORIES ABOUT ${entity.name}:
278
+ ${memoriesText}
279
+
280
+ Create a rich, detailed profile. Do not summarize away important nuances. Respond with JSON only.`;
281
+
282
+ try {
283
+ const response = await this.client.messages.create({
284
+ model: "claude-opus-4-5-20251101",
285
+ max_tokens: 16000,
286
+ temperature: 1, // Required for extended thinking
287
+ thinking: {
288
+ type: "enabled",
289
+ budget_tokens: 16000, // Maximum thinking for entity profiles
290
+ },
291
+ messages: [
292
+ {
293
+ role: "user",
294
+ content: userPrompt,
295
+ },
296
+ ],
297
+ system: CONSOLIDATION_SYSTEM,
298
+ });
299
+
300
+ let text = "";
301
+ for (const block of response.content) {
302
+ if (block.type === "text") {
303
+ text = block.text;
304
+ break;
305
+ }
306
+ }
307
+
308
+ if (!text) return null;
309
+
310
+ const jsonMatch = text.match(/\{[\s\S]*\}/);
311
+ if (!jsonMatch) return null;
312
+
313
+ const result = JSON.parse(jsonMatch[0]) as ConsolidationResult;
314
+
315
+ // Create level 3 entity profile digest
316
+ const memoryIds = memories.map((m) => m.id);
317
+ const periodStart = new Date(
318
+ Math.min(...memories.map((m) => m.timestamp.getTime()))
319
+ );
320
+ const periodEnd = new Date(
321
+ Math.max(...memories.map((m) => m.timestamp.getTime()))
322
+ );
323
+
324
+ const digest = this.db.createDigest(result.digest, 3, memoryIds, {
325
+ topic: `Profile: ${entity.name}`,
326
+ entityId: entity.id,
327
+ periodStart,
328
+ periodEnd,
329
+ });
330
+
331
+ // Record any contradictions
332
+ for (const c of result.contradictions) {
333
+ if (c.memory_ids.length >= 2) {
334
+ const [idA, idB] = c.memory_ids.slice(0, 2);
335
+ const memA = memories.find((m) => m.id === idA);
336
+ const memB = memories.find((m) => m.id === idB);
337
+
338
+ if (memA && memB) {
339
+ this.db.createContradiction(
340
+ memA.id,
341
+ memB.id,
342
+ c.description,
343
+ entity.id
344
+ );
345
+ }
346
+ }
347
+ }
348
+
349
+ return digest;
350
+ } catch (error) {
351
+ console.error("[Consolidator] Entity profile failed:", error);
352
+ return null;
353
+ }
354
+ }
355
+
356
+ /**
357
+ * Get consolidation status
358
+ */
359
+ getStatus(): {
360
+ configured: boolean;
361
+ unconsolidatedMemories: number;
362
+ totalDigests: number;
363
+ unresolvedContradictions: number;
364
+ } {
365
+ const unconsolidated = this.db.getUnconsolidatedMemories(undefined, 1000);
366
+ const digests = this.db.getDigests(undefined, 1000);
367
+ const contradictions = this.db.getContradictions(false, 1000);
368
+
369
+ return {
370
+ configured: this.isConfigured(),
371
+ unconsolidatedMemories: unconsolidated.length,
372
+ totalDigests: digests.length,
373
+ unresolvedContradictions: contradictions.length,
374
+ };
375
+ }
376
+ }
package/src/index.ts CHANGED
@@ -60,7 +60,7 @@ async function initialize(): Promise<void> {
60
60
  const server = new Server(
61
61
  {
62
62
  name: "engram",
63
- version: "0.4.3",
63
+ version: "0.5.1",
64
64
  },
65
65
  {
66
66
  capabilities: {
@@ -45,6 +45,30 @@ export interface Relation {
45
45
  created_at: Date;
46
46
  }
47
47
 
48
+ export interface Digest {
49
+ id: string;
50
+ content: string;
51
+ level: number; // 1 = session, 2 = topic, 3 = entity profile
52
+ topic: string | null;
53
+ entity_id: string | null;
54
+ source_count: number;
55
+ created_at: Date;
56
+ period_start: Date;
57
+ period_end: Date;
58
+ }
59
+
60
+ export interface Contradiction {
61
+ id: string;
62
+ entity_id: string | null;
63
+ memory_id_a: string;
64
+ memory_id_b: string;
65
+ description: string;
66
+ resolved: boolean;
67
+ resolution: string | null;
68
+ created_at: Date;
69
+ resolved_at: Date | null;
70
+ }
71
+
48
72
  export class EngramDatabase {
49
73
  private db: Database.Database;
50
74
  private stmtCache: Map<string, Database.Statement> = new Map();
@@ -154,6 +178,54 @@ export class EngramDatabase {
154
178
  CREATE INDEX IF NOT EXISTS idx_relations_to ON relations(to_entity);
155
179
  CREATE INDEX IF NOT EXISTS idx_relations_type ON relations(type);
156
180
  `);
181
+
182
+ // Digests table (consolidated memories)
183
+ this.db.exec(`
184
+ CREATE TABLE IF NOT EXISTS digests (
185
+ id TEXT PRIMARY KEY,
186
+ content TEXT NOT NULL,
187
+ level INTEGER DEFAULT 1,
188
+ topic TEXT,
189
+ entity_id TEXT REFERENCES entities(id) ON DELETE SET NULL,
190
+ source_count INTEGER DEFAULT 0,
191
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
192
+ period_start DATETIME,
193
+ period_end DATETIME
194
+ );
195
+
196
+ CREATE INDEX IF NOT EXISTS idx_digests_level ON digests(level);
197
+ CREATE INDEX IF NOT EXISTS idx_digests_entity ON digests(entity_id);
198
+ CREATE INDEX IF NOT EXISTS idx_digests_period ON digests(period_start, period_end);
199
+ `);
200
+
201
+ // Digest sources (links digests to their source memories)
202
+ this.db.exec(`
203
+ CREATE TABLE IF NOT EXISTS digest_sources (
204
+ digest_id TEXT NOT NULL REFERENCES digests(id) ON DELETE CASCADE,
205
+ memory_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
206
+ PRIMARY KEY (digest_id, memory_id)
207
+ );
208
+
209
+ CREATE INDEX IF NOT EXISTS idx_digest_sources_memory ON digest_sources(memory_id);
210
+ `);
211
+
212
+ // Contradictions table (detected conflicts)
213
+ this.db.exec(`
214
+ CREATE TABLE IF NOT EXISTS contradictions (
215
+ id TEXT PRIMARY KEY,
216
+ entity_id TEXT REFERENCES entities(id) ON DELETE SET NULL,
217
+ memory_id_a TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
218
+ memory_id_b TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
219
+ description TEXT NOT NULL,
220
+ resolved INTEGER DEFAULT 0,
221
+ resolution TEXT,
222
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
223
+ resolved_at DATETIME
224
+ );
225
+
226
+ CREATE INDEX IF NOT EXISTS idx_contradictions_entity ON contradictions(entity_id);
227
+ CREATE INDEX IF NOT EXISTS idx_contradictions_resolved ON contradictions(resolved);
228
+ `);
157
229
  }
158
230
 
159
231
  // ============ Memory Operations ============
@@ -628,6 +700,157 @@ export class EngramDatabase {
628
700
  return { entities, relations: allRelations, observations };
629
701
  }
630
702
 
703
+ // ============ Digest Operations ============
704
+
705
+ createDigest(
706
+ content: string,
707
+ level: number,
708
+ sourceMemoryIds: string[],
709
+ options: {
710
+ topic?: string;
711
+ entityId?: string;
712
+ periodStart?: Date;
713
+ periodEnd?: Date;
714
+ } = {}
715
+ ): Digest {
716
+ const id = randomUUID();
717
+ const stmt = this.db.prepare(`
718
+ INSERT INTO digests (id, content, level, topic, entity_id, source_count, period_start, period_end)
719
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
720
+ `);
721
+ stmt.run(
722
+ id,
723
+ content,
724
+ level,
725
+ options.topic || null,
726
+ options.entityId || null,
727
+ sourceMemoryIds.length,
728
+ options.periodStart?.toISOString() || null,
729
+ options.periodEnd?.toISOString() || null
730
+ );
731
+
732
+ // Link source memories
733
+ const linkStmt = this.db.prepare(
734
+ "INSERT OR IGNORE INTO digest_sources (digest_id, memory_id) VALUES (?, ?)"
735
+ );
736
+ for (const memoryId of sourceMemoryIds) {
737
+ linkStmt.run(id, memoryId);
738
+ }
739
+
740
+ return this.getDigest(id)!;
741
+ }
742
+
743
+ getDigest(id: string): Digest | null {
744
+ const row = this.stmt("SELECT * FROM digests WHERE id = ?").get(id) as Record<string, unknown> | undefined;
745
+ return row ? this.rowToDigest(row) : null;
746
+ }
747
+
748
+ getDigests(level?: number, limit: number = 100): Digest[] {
749
+ let sql = "SELECT * FROM digests";
750
+ const params: unknown[] = [];
751
+
752
+ if (level !== undefined) {
753
+ sql += " WHERE level = ?";
754
+ params.push(level);
755
+ }
756
+
757
+ sql += " ORDER BY created_at DESC LIMIT ?";
758
+ params.push(limit);
759
+
760
+ const rows = this.db.prepare(sql).all(...params) as Record<string, unknown>[];
761
+ return rows.map((row) => this.rowToDigest(row));
762
+ }
763
+
764
+ getDigestSources(digestId: string): Memory[] {
765
+ const rows = this.stmt(`
766
+ SELECT m.* FROM memories m
767
+ JOIN digest_sources ds ON ds.memory_id = m.id
768
+ WHERE ds.digest_id = ?
769
+ ORDER BY m.timestamp DESC
770
+ `).all(digestId) as Record<string, unknown>[];
771
+ return rows.map((row) => this.rowToMemory(row));
772
+ }
773
+
774
+ getUnconsolidatedMemories(since?: Date, limit: number = 100): Memory[] {
775
+ let sql = `
776
+ SELECT m.* FROM memories m
777
+ LEFT JOIN digest_sources ds ON ds.memory_id = m.id
778
+ WHERE ds.digest_id IS NULL
779
+ `;
780
+ const params: unknown[] = [];
781
+
782
+ if (since) {
783
+ sql += " AND m.timestamp >= ?";
784
+ params.push(since.toISOString());
785
+ }
786
+
787
+ sql += " ORDER BY m.timestamp DESC LIMIT ?";
788
+ params.push(limit);
789
+
790
+ const rows = this.db.prepare(sql).all(...params) as Record<string, unknown>[];
791
+ return rows.map((row) => this.rowToMemory(row));
792
+ }
793
+
794
+ deleteDigest(id: string): boolean {
795
+ const stmt = this.db.prepare("DELETE FROM digests WHERE id = ?");
796
+ const result = stmt.run(id);
797
+ return result.changes > 0;
798
+ }
799
+
800
+ // ============ Contradiction Operations ============
801
+
802
+ createContradiction(
803
+ memoryIdA: string,
804
+ memoryIdB: string,
805
+ description: string,
806
+ entityId?: string
807
+ ): Contradiction {
808
+ const id = randomUUID();
809
+ const stmt = this.db.prepare(`
810
+ INSERT INTO contradictions (id, entity_id, memory_id_a, memory_id_b, description)
811
+ VALUES (?, ?, ?, ?, ?)
812
+ `);
813
+ stmt.run(id, entityId || null, memoryIdA, memoryIdB, description);
814
+ return this.getContradiction(id)!;
815
+ }
816
+
817
+ getContradiction(id: string): Contradiction | null {
818
+ const row = this.stmt("SELECT * FROM contradictions WHERE id = ?").get(id) as Record<string, unknown> | undefined;
819
+ return row ? this.rowToContradiction(row) : null;
820
+ }
821
+
822
+ getContradictions(resolved?: boolean, limit: number = 100): Contradiction[] {
823
+ let sql = "SELECT * FROM contradictions";
824
+ const params: unknown[] = [];
825
+
826
+ if (resolved !== undefined) {
827
+ sql += " WHERE resolved = ?";
828
+ params.push(resolved ? 1 : 0);
829
+ }
830
+
831
+ sql += " ORDER BY created_at DESC LIMIT ?";
832
+ params.push(limit);
833
+
834
+ const rows = this.db.prepare(sql).all(...params) as Record<string, unknown>[];
835
+ return rows.map((row) => this.rowToContradiction(row));
836
+ }
837
+
838
+ resolveContradiction(id: string, resolution: string): boolean {
839
+ const stmt = this.db.prepare(`
840
+ UPDATE contradictions
841
+ SET resolved = 1, resolution = ?, resolved_at = CURRENT_TIMESTAMP
842
+ WHERE id = ?
843
+ `);
844
+ const result = stmt.run(resolution, id);
845
+ return result.changes > 0;
846
+ }
847
+
848
+ deleteContradiction(id: string): boolean {
849
+ const stmt = this.db.prepare("DELETE FROM contradictions WHERE id = ?");
850
+ const result = stmt.run(id);
851
+ return result.changes > 0;
852
+ }
853
+
631
854
  // ============ Statistics ============
632
855
 
633
856
  getStats(): {
@@ -635,15 +858,26 @@ export class EngramDatabase {
635
858
  entities: number;
636
859
  relations: number;
637
860
  observations: number;
861
+ digests: number;
862
+ contradictions: number;
638
863
  } {
639
- // Single query for all stats - much faster than 4 separate queries
864
+ // Single query for all stats - much faster than separate queries
640
865
  const row = this.stmt(`
641
866
  SELECT
642
867
  (SELECT COUNT(*) FROM memories) as memories,
643
868
  (SELECT COUNT(*) FROM entities) as entities,
644
869
  (SELECT COUNT(*) FROM relations) as relations,
645
- (SELECT COUNT(*) FROM observations) as observations
646
- `).get() as { memories: number; entities: number; relations: number; observations: number };
870
+ (SELECT COUNT(*) FROM observations) as observations,
871
+ (SELECT COUNT(*) FROM digests) as digests,
872
+ (SELECT COUNT(*) FROM contradictions WHERE resolved = 0) as contradictions
873
+ `).get() as {
874
+ memories: number;
875
+ entities: number;
876
+ relations: number;
877
+ observations: number;
878
+ digests: number;
879
+ contradictions: number;
880
+ };
647
881
 
648
882
  return row;
649
883
  }
@@ -710,4 +944,32 @@ export class EngramDatabase {
710
944
  created_at: new Date(row.created_at as string),
711
945
  };
712
946
  }
947
+
948
+ private rowToDigest(row: Record<string, unknown>): Digest {
949
+ return {
950
+ id: row.id as string,
951
+ content: row.content as string,
952
+ level: row.level as number,
953
+ topic: row.topic as string | null,
954
+ entity_id: row.entity_id as string | null,
955
+ source_count: row.source_count as number,
956
+ created_at: new Date(row.created_at as string),
957
+ period_start: row.period_start ? new Date(row.period_start as string) : new Date(),
958
+ period_end: row.period_end ? new Date(row.period_end as string) : new Date(),
959
+ };
960
+ }
961
+
962
+ private rowToContradiction(row: Record<string, unknown>): Contradiction {
963
+ return {
964
+ id: row.id as string,
965
+ entity_id: row.entity_id as string | null,
966
+ memory_id_a: row.memory_id_a as string,
967
+ memory_id_b: row.memory_id_b as string,
968
+ description: row.description as string,
969
+ resolved: Boolean(row.resolved),
970
+ resolution: row.resolution as string | null,
971
+ created_at: new Date(row.created_at as string),
972
+ resolved_at: row.resolved_at ? new Date(row.resolved_at as string) : null,
973
+ };
974
+ }
713
975
  }
package/src/web/server.ts CHANGED
@@ -11,6 +11,7 @@ import { EngramDatabase } from "../storage/database.js";
11
11
  import { KnowledgeGraph } from "../graph/knowledge-graph.js";
12
12
  import { HybridSearch } from "../retrieval/hybrid.js";
13
13
  import { ChatHandler } from "./chat-handler.js";
14
+ import { Consolidator } from "../consolidation/consolidator.js";
14
15
 
15
16
  const __filename = fileURLToPath(import.meta.url);
16
17
  const __dirname = path.dirname(__filename);
@@ -39,6 +40,7 @@ export class EngramWebServer {
39
40
  private graph: KnowledgeGraph;
40
41
  private search: HybridSearch;
41
42
  private chat: ChatHandler;
43
+ private consolidator: Consolidator;
42
44
  private port: number;
43
45
 
44
46
  constructor(options: WebServerOptions) {
@@ -50,6 +52,7 @@ export class EngramWebServer {
50
52
  graph: options.graph,
51
53
  search: options.search,
52
54
  });
55
+ this.consolidator = new Consolidator(options.db);
53
56
  this.port = options.port || 3847;
54
57
  }
55
58
 
@@ -304,6 +307,108 @@ export class EngramWebServer {
304
307
  return;
305
308
  }
306
309
 
310
+ // ============ Consolidation Endpoints ============
311
+
312
+ // GET /api/consolidation/status - get consolidation status
313
+ if (pathname === "/api/consolidation/status" && method === "GET") {
314
+ const status = this.consolidator.getStatus();
315
+ res.end(JSON.stringify(status));
316
+ return;
317
+ }
318
+
319
+ // POST /api/consolidation/run - run consolidation
320
+ if (pathname === "/api/consolidation/run" && method === "POST") {
321
+ if (!this.consolidator.isConfigured()) {
322
+ res.writeHead(503);
323
+ res.end(JSON.stringify({
324
+ error: "Consolidation not available - set ANTHROPIC_API_KEY",
325
+ }));
326
+ return;
327
+ }
328
+
329
+ try {
330
+ const result = await this.consolidator.consolidate();
331
+ res.end(JSON.stringify(result));
332
+ } catch (error) {
333
+ res.writeHead(500);
334
+ res.end(JSON.stringify({
335
+ error: error instanceof Error ? error.message : "Consolidation failed",
336
+ }));
337
+ }
338
+ return;
339
+ }
340
+
341
+ // GET /api/digests - list all digests
342
+ if (pathname === "/api/digests" && method === "GET") {
343
+ const level = url.searchParams.get("level");
344
+ const limit = parseInt(url.searchParams.get("limit") || "100");
345
+ const digests = this.db.getDigests(
346
+ level ? parseInt(level) : undefined,
347
+ limit
348
+ );
349
+ res.end(JSON.stringify({ digests }));
350
+ return;
351
+ }
352
+
353
+ // GET /api/digests/:id/sources - get source memories for a digest
354
+ const digestSourcesMatch = pathname.match(/^\/api\/digests\/([a-f0-9-]+)\/sources$/);
355
+ if (digestSourcesMatch && method === "GET") {
356
+ const id = digestSourcesMatch[1];
357
+ const sources = this.db.getDigestSources(id);
358
+ res.end(JSON.stringify({ sources }));
359
+ return;
360
+ }
361
+
362
+ // GET /api/contradictions - list contradictions
363
+ if (pathname === "/api/contradictions" && method === "GET") {
364
+ const resolved = url.searchParams.get("resolved");
365
+ const limit = parseInt(url.searchParams.get("limit") || "100");
366
+ const contradictions = this.db.getContradictions(
367
+ resolved !== null ? resolved === "true" : undefined,
368
+ limit
369
+ );
370
+
371
+ // Enrich with memory content
372
+ const enriched = contradictions.map((c) => {
373
+ const memA = this.db.getMemory(c.memory_id_a);
374
+ const memB = this.db.getMemory(c.memory_id_b);
375
+ const entity = c.entity_id ? this.db.getEntity(c.entity_id) : null;
376
+ return {
377
+ ...c,
378
+ memory_a: memA,
379
+ memory_b: memB,
380
+ entity: entity,
381
+ };
382
+ });
383
+
384
+ res.end(JSON.stringify({ contradictions: enriched }));
385
+ return;
386
+ }
387
+
388
+ // POST /api/contradictions/:id/resolve - resolve a contradiction
389
+ const resolveMatch = pathname.match(/^\/api\/contradictions\/([a-f0-9-]+)\/resolve$/);
390
+ if (resolveMatch && method === "POST") {
391
+ const id = resolveMatch[1];
392
+ const { resolution } = body as { resolution: string };
393
+ if (!resolution) {
394
+ res.writeHead(400);
395
+ res.end(JSON.stringify({ error: "Resolution is required" }));
396
+ return;
397
+ }
398
+ const success = this.db.resolveContradiction(id, resolution);
399
+ res.end(JSON.stringify({ success }));
400
+ return;
401
+ }
402
+
403
+ // DELETE /api/contradictions/:id - dismiss a contradiction
404
+ const contradictionMatch = pathname.match(/^\/api\/contradictions\/([a-f0-9-]+)$/);
405
+ if (contradictionMatch && method === "DELETE") {
406
+ const id = contradictionMatch[1];
407
+ const success = this.db.deleteContradiction(id);
408
+ res.end(JSON.stringify({ success }));
409
+ return;
410
+ }
411
+
307
412
  // 404 for unknown API routes
308
413
  res.writeHead(404);
309
414
  res.end(JSON.stringify({ error: "Not found" }));
@@ -14,6 +14,7 @@ const views = {
14
14
  memories: document.getElementById('memories-view'),
15
15
  entities: document.getElementById('entities-view'),
16
16
  graph: document.getElementById('graph-view'),
17
+ consolidation: document.getElementById('consolidation-view'),
17
18
  };
18
19
 
19
20
  const statsEl = document.getElementById('stats');
@@ -61,7 +62,14 @@ function formatDate(dateStr) {
61
62
  // Load stats
62
63
  async function loadStats() {
63
64
  const stats = await api('/api/stats');
64
- statsEl.textContent = `${stats.memories} memories \u00b7 ${stats.entities} entities \u00b7 ${stats.relations} relations`;
65
+ let text = `${stats.memories} memories \u00b7 ${stats.entities} entities \u00b7 ${stats.relations} relations`;
66
+ if (stats.digests > 0) {
67
+ text += ` \u00b7 ${stats.digests} digests`;
68
+ }
69
+ if (stats.contradictions > 0) {
70
+ text += ` \u00b7 ${stats.contradictions} contradictions`;
71
+ }
72
+ statsEl.textContent = text;
65
73
  }
66
74
 
67
75
  // Load memories
@@ -308,8 +316,213 @@ function switchView(view) {
308
316
  if (view === 'memories') loadMemories(searchInput.value);
309
317
  if (view === 'entities') loadEntities(entityTypeFilter.value);
310
318
  if (view === 'graph') loadGraph();
319
+ if (view === 'consolidation') loadConsolidation();
320
+ }
321
+
322
+ // ============ Consolidation ============
323
+
324
+ const contradictionsList = document.getElementById('contradictions-list');
325
+ const digestsList = document.getElementById('digests-list');
326
+ const unconsolidatedCount = document.getElementById('unconsolidated-count');
327
+ const digestsCount = document.getElementById('digests-count');
328
+ const contradictionsCount = document.getElementById('contradictions-count');
329
+ const runConsolidationBtn = document.getElementById('run-consolidation-btn');
330
+ const contradictionModal = document.getElementById('contradiction-modal');
331
+ const contradictionModalBody = document.getElementById('contradiction-modal-body');
332
+ const contradictionForm = document.getElementById('contradiction-form');
333
+ const contradictionResolution = document.getElementById('contradiction-resolution');
334
+
335
+ let currentContradictionId = null;
336
+
337
+ // Load consolidation view
338
+ async function loadConsolidation() {
339
+ await Promise.all([
340
+ loadConsolidationStatus(),
341
+ loadContradictions(),
342
+ loadDigests(),
343
+ ]);
344
+ }
345
+
346
+ // Load consolidation status
347
+ async function loadConsolidationStatus() {
348
+ try {
349
+ const status = await api('/api/consolidation/status');
350
+ unconsolidatedCount.textContent = status.unconsolidatedMemories;
351
+ digestsCount.textContent = status.totalDigests;
352
+ contradictionsCount.textContent = status.unresolvedContradictions;
353
+ runConsolidationBtn.disabled = !status.configured;
354
+ if (!status.configured) {
355
+ runConsolidationBtn.title = 'Set ANTHROPIC_API_KEY to enable';
356
+ }
357
+ } catch (e) {
358
+ console.error('Failed to load consolidation status', e);
359
+ }
360
+ }
361
+
362
+ // Load contradictions
363
+ async function loadContradictions() {
364
+ try {
365
+ const data = await api('/api/contradictions?resolved=false');
366
+
367
+ if (data.contradictions.length === 0) {
368
+ contradictionsList.innerHTML = '<div class="empty-state">No contradictions found</div>';
369
+ return;
370
+ }
371
+
372
+ contradictionsList.innerHTML = data.contradictions.map(c => `
373
+ <div class="list-item contradiction-item" data-id="${c.id}">
374
+ ${c.entity ? `<span class="entity-tag">${escapeHtml(c.entity.name)}</span>` : ''}
375
+ <div class="description">${escapeHtml(c.description)}</div>
376
+ <div class="memories">
377
+ <div class="memory-quote">
378
+ ${escapeHtml(c.memory_a?.content || 'Memory deleted')}
379
+ <span class="date">${c.memory_a ? formatDate(c.memory_a.timestamp) : ''}</span>
380
+ </div>
381
+ <div class="memory-quote">
382
+ ${escapeHtml(c.memory_b?.content || 'Memory deleted')}
383
+ <span class="date">${c.memory_b ? formatDate(c.memory_b.timestamp) : ''}</span>
384
+ </div>
385
+ </div>
386
+ <div class="actions">
387
+ <button class="resolve-btn" data-id="${c.id}">Resolve</button>
388
+ </div>
389
+ </div>
390
+ `).join('');
391
+
392
+ // Attach event listeners
393
+ contradictionsList.querySelectorAll('.resolve-btn').forEach(btn => {
394
+ btn.addEventListener('click', (e) => {
395
+ e.stopPropagation();
396
+ openContradictionModal(btn.dataset.id);
397
+ });
398
+ });
399
+ } catch (e) {
400
+ console.error('Failed to load contradictions', e);
401
+ contradictionsList.innerHTML = '<div class="empty-state">Failed to load contradictions</div>';
402
+ }
403
+ }
404
+
405
+ // Load digests
406
+ async function loadDigests() {
407
+ try {
408
+ const data = await api('/api/digests');
409
+
410
+ if (data.digests.length === 0) {
411
+ digestsList.innerHTML = '<div class="empty-state">No digests yet. Run consolidation to create summaries.</div>';
412
+ return;
413
+ }
414
+
415
+ digestsList.innerHTML = data.digests.map(d => `
416
+ <div class="list-item digest-item">
417
+ <div class="content">${escapeHtml(d.content)}</div>
418
+ <div class="meta">
419
+ ${d.topic ? `<span class="topic">${escapeHtml(d.topic)}</span>` : ''}
420
+ <span>Level ${d.level}</span>
421
+ <span>${d.source_count} memories</span>
422
+ <span>${formatDate(d.created_at)}</span>
423
+ </div>
424
+ </div>
425
+ `).join('');
426
+ } catch (e) {
427
+ console.error('Failed to load digests', e);
428
+ digestsList.innerHTML = '<div class="empty-state">Failed to load digests</div>';
429
+ }
430
+ }
431
+
432
+ // Run consolidation
433
+ async function runConsolidation() {
434
+ runConsolidationBtn.disabled = true;
435
+ runConsolidationBtn.textContent = 'Consolidating...';
436
+
437
+ try {
438
+ const result = await api('/api/consolidation/run', { method: 'POST' });
439
+ alert(`Consolidation complete!\n\nDigests created: ${result.digestsCreated}\nContradictions found: ${result.contradictionsFound}\nMemories processed: ${result.memoriesProcessed}`);
440
+ await loadConsolidation();
441
+ } catch (e) {
442
+ console.error('Consolidation failed', e);
443
+ alert('Consolidation failed. Check console for details.');
444
+ } finally {
445
+ runConsolidationBtn.disabled = false;
446
+ runConsolidationBtn.textContent = 'Run Consolidation';
447
+ }
448
+ }
449
+
450
+ // Open contradiction modal
451
+ function openContradictionModal(id) {
452
+ const item = contradictionsList.querySelector(`[data-id="${id}"]`);
453
+ if (!item) return;
454
+
455
+ currentContradictionId = id;
456
+
457
+ // Copy the memories to the modal
458
+ const description = item.querySelector('.description').textContent;
459
+ const memories = item.querySelectorAll('.memory-quote');
460
+
461
+ contradictionModalBody.innerHTML = `
462
+ <p><strong>${escapeHtml(description)}</strong></p>
463
+ <div class="memory-quote">${memories[0].innerHTML}</div>
464
+ <div class="memory-quote">${memories[1].innerHTML}</div>
465
+ `;
466
+
467
+ contradictionResolution.value = '';
468
+ contradictionModal.classList.remove('hidden');
469
+ }
470
+
471
+ // Close contradiction modal
472
+ function closeContradictionModal() {
473
+ contradictionModal.classList.add('hidden');
474
+ currentContradictionId = null;
475
+ }
476
+
477
+ // Resolve contradiction
478
+ async function resolveContradiction(resolution) {
479
+ if (!currentContradictionId || !resolution.trim()) return;
480
+
481
+ try {
482
+ await api(`/api/contradictions/${currentContradictionId}/resolve`, {
483
+ method: 'POST',
484
+ body: { resolution: resolution.trim() },
485
+ });
486
+ closeContradictionModal();
487
+ await loadConsolidation();
488
+ await loadStats();
489
+ } catch (e) {
490
+ console.error('Failed to resolve contradiction', e);
491
+ alert('Failed to resolve contradiction');
492
+ }
493
+ }
494
+
495
+ // Dismiss contradiction
496
+ async function dismissContradiction() {
497
+ if (!currentContradictionId) return;
498
+ if (!confirm('Dismiss this contradiction without resolution?')) return;
499
+
500
+ try {
501
+ await api(`/api/contradictions/${currentContradictionId}`, { method: 'DELETE' });
502
+ closeContradictionModal();
503
+ await loadConsolidation();
504
+ await loadStats();
505
+ } catch (e) {
506
+ console.error('Failed to dismiss contradiction', e);
507
+ alert('Failed to dismiss contradiction');
508
+ }
311
509
  }
312
510
 
511
+ // Event listeners for consolidation
512
+ runConsolidationBtn.addEventListener('click', runConsolidation);
513
+
514
+ contradictionForm.addEventListener('submit', (e) => {
515
+ e.preventDefault();
516
+ resolveContradiction(contradictionResolution.value);
517
+ });
518
+
519
+ document.getElementById('contradiction-cancel').addEventListener('click', closeContradictionModal);
520
+ document.getElementById('contradiction-dismiss').addEventListener('click', dismissContradiction);
521
+
522
+ contradictionModal.addEventListener('click', (e) => {
523
+ if (e.target === contradictionModal) closeContradictionModal();
524
+ });
525
+
313
526
  // Event listeners
314
527
  document.querySelectorAll('.nav-btn').forEach(btn => {
315
528
  btn.addEventListener('click', () => switchView(btn.dataset.view));
@@ -13,6 +13,7 @@
13
13
  <button class="nav-btn active" data-view="memories">Memories</button>
14
14
  <button class="nav-btn" data-view="entities">Entities</button>
15
15
  <button class="nav-btn" data-view="graph">Graph</button>
16
+ <button class="nav-btn" data-view="consolidation">Consolidation</button>
16
17
  </nav>
17
18
  <div class="stats" id="stats"></div>
18
19
  <button id="chat-toggle" class="chat-toggle" title="Open Chat Assistant">Chat</button>
@@ -53,6 +54,32 @@
53
54
  <section id="graph-view" class="view">
54
55
  <div id="graph-container"></div>
55
56
  </section>
57
+
58
+ <!-- Consolidation View -->
59
+ <section id="consolidation-view" class="view">
60
+ <div class="consolidation-header">
61
+ <div class="consolidation-status" id="consolidation-status">
62
+ <span class="status-item"><strong>Unconsolidated:</strong> <span id="unconsolidated-count">-</span></span>
63
+ <span class="status-item"><strong>Digests:</strong> <span id="digests-count">-</span></span>
64
+ <span class="status-item"><strong>Contradictions:</strong> <span id="contradictions-count">-</span></span>
65
+ </div>
66
+ <button id="run-consolidation-btn">Run Consolidation</button>
67
+ </div>
68
+
69
+ <div class="consolidation-sections">
70
+ <div class="section">
71
+ <h2>Contradictions</h2>
72
+ <p class="section-desc">Conflicting information detected during consolidation. Review and resolve.</p>
73
+ <div id="contradictions-list" class="list"></div>
74
+ </div>
75
+
76
+ <div class="section">
77
+ <h2>Digests</h2>
78
+ <p class="section-desc">Consolidated summaries of your memories.</p>
79
+ <div id="digests-list" class="list"></div>
80
+ </div>
81
+ </div>
82
+ </section>
56
83
  </main>
57
84
 
58
85
  <!-- Modal for adding/editing -->
@@ -91,6 +118,25 @@
91
118
  </div>
92
119
  </div>
93
120
 
121
+ <!-- Contradiction Resolution Modal -->
122
+ <div id="contradiction-modal" class="modal hidden">
123
+ <div class="modal-content modal-wide">
124
+ <h2>Resolve Contradiction</h2>
125
+ <div id="contradiction-modal-body"></div>
126
+ <form id="contradiction-form">
127
+ <label>
128
+ Resolution:
129
+ <textarea id="contradiction-resolution" placeholder="Explain how you resolved this contradiction..." rows="3"></textarea>
130
+ </label>
131
+ <div class="modal-actions">
132
+ <button type="button" id="contradiction-dismiss">Dismiss</button>
133
+ <button type="button" id="contradiction-cancel">Cancel</button>
134
+ <button type="submit">Resolve</button>
135
+ </div>
136
+ </form>
137
+ </div>
138
+ </div>
139
+
94
140
  <!-- Chat Panel -->
95
141
  <aside id="chat-panel" class="chat-panel hidden">
96
142
  <div class="chat-header">
@@ -638,6 +638,199 @@ body.chat-open main {
638
638
  margin-right: 400px;
639
639
  }
640
640
 
641
+ /* Consolidation View */
642
+ .consolidation-header {
643
+ display: flex;
644
+ justify-content: space-between;
645
+ align-items: center;
646
+ margin-bottom: 2rem;
647
+ padding: 1rem 1.5rem;
648
+ background: var(--bg-secondary);
649
+ border: 1px solid var(--border);
650
+ }
651
+
652
+ .consolidation-status {
653
+ display: flex;
654
+ gap: 2rem;
655
+ font-size: 0.9375rem;
656
+ }
657
+
658
+ .status-item {
659
+ color: var(--text-secondary);
660
+ }
661
+
662
+ .status-item strong {
663
+ color: var(--text-primary);
664
+ font-weight: 500;
665
+ }
666
+
667
+ #run-consolidation-btn {
668
+ background: var(--accent);
669
+ }
670
+
671
+ #run-consolidation-btn:hover {
672
+ background: var(--accent-hover);
673
+ }
674
+
675
+ #run-consolidation-btn:disabled {
676
+ background: var(--text-muted);
677
+ }
678
+
679
+ .consolidation-sections {
680
+ display: flex;
681
+ flex-direction: column;
682
+ gap: 2.5rem;
683
+ }
684
+
685
+ .consolidation-sections .section h2 {
686
+ margin-bottom: 0.5rem;
687
+ }
688
+
689
+ .section-desc {
690
+ font-size: 0.875rem;
691
+ color: var(--text-muted);
692
+ margin-bottom: 1rem;
693
+ }
694
+
695
+ /* Contradiction item */
696
+ .contradiction-item {
697
+ background: var(--bg-secondary);
698
+ border: 1px solid var(--border);
699
+ border-left: 3px solid var(--danger);
700
+ padding: 1.25rem 1.5rem;
701
+ }
702
+
703
+ .contradiction-item .description {
704
+ font-size: 1rem;
705
+ line-height: 1.6;
706
+ margin-bottom: 1rem;
707
+ font-weight: 500;
708
+ }
709
+
710
+ .contradiction-item .memories {
711
+ display: flex;
712
+ flex-direction: column;
713
+ gap: 0.75rem;
714
+ margin-bottom: 1rem;
715
+ }
716
+
717
+ .contradiction-item .memory-quote {
718
+ padding: 0.75rem 1rem;
719
+ background: var(--bg-tertiary);
720
+ font-size: 0.875rem;
721
+ line-height: 1.5;
722
+ color: var(--text-secondary);
723
+ }
724
+
725
+ .contradiction-item .memory-quote .date {
726
+ display: block;
727
+ font-size: 0.75rem;
728
+ color: var(--text-muted);
729
+ margin-top: 0.375rem;
730
+ }
731
+
732
+ .contradiction-item .entity-tag {
733
+ display: inline-block;
734
+ font-size: 0.75rem;
735
+ padding: 0.125rem 0.5rem;
736
+ background: var(--bg-tertiary);
737
+ color: var(--text-muted);
738
+ margin-bottom: 0.75rem;
739
+ }
740
+
741
+ .contradiction-item .actions {
742
+ padding-top: 1rem;
743
+ border-top: 1px solid var(--border);
744
+ }
745
+
746
+ .contradiction-item .actions button {
747
+ font-size: 0.8125rem;
748
+ padding: 0.5rem 1rem;
749
+ background: var(--text-primary);
750
+ color: var(--bg-primary);
751
+ }
752
+
753
+ /* Digest item */
754
+ .digest-item {
755
+ background: var(--bg-secondary);
756
+ border: 1px solid var(--border);
757
+ padding: 1.25rem 1.5rem;
758
+ }
759
+
760
+ .digest-item .content {
761
+ font-size: 1rem;
762
+ line-height: 1.7;
763
+ margin-bottom: 1rem;
764
+ }
765
+
766
+ .digest-item .meta {
767
+ display: flex;
768
+ gap: 1.5rem;
769
+ font-size: 0.8125rem;
770
+ color: var(--text-muted);
771
+ }
772
+
773
+ .digest-item .topic {
774
+ font-weight: 500;
775
+ color: var(--accent);
776
+ }
777
+
778
+ /* Modal wide */
779
+ .modal-wide {
780
+ max-width: 800px;
781
+ }
782
+
783
+ .modal-wide .memory-quote {
784
+ padding: 0.75rem 1rem;
785
+ background: var(--bg-secondary);
786
+ font-size: 0.875rem;
787
+ line-height: 1.5;
788
+ margin-bottom: 0.75rem;
789
+ border: 1px solid var(--border);
790
+ }
791
+
792
+ .modal-wide .memory-quote .date {
793
+ display: block;
794
+ font-size: 0.75rem;
795
+ color: var(--text-muted);
796
+ margin-top: 0.375rem;
797
+ }
798
+
799
+ .modal-wide label {
800
+ display: block;
801
+ margin-top: 1.5rem;
802
+ font-size: 0.875rem;
803
+ color: var(--text-secondary);
804
+ }
805
+
806
+ .modal-wide textarea {
807
+ width: 100%;
808
+ margin-top: 0.5rem;
809
+ font-family: inherit;
810
+ font-size: 0.9375rem;
811
+ padding: 0.75rem 1rem;
812
+ border: 1px solid var(--border);
813
+ background: var(--bg-secondary);
814
+ color: var(--text-primary);
815
+ resize: vertical;
816
+ }
817
+
818
+ .modal-wide textarea:focus {
819
+ outline: none;
820
+ border-color: var(--accent);
821
+ }
822
+
823
+ #contradiction-dismiss {
824
+ background: var(--bg-tertiary);
825
+ color: var(--text-secondary);
826
+ margin-right: auto;
827
+ }
828
+
829
+ #contradiction-dismiss:hover {
830
+ background: var(--border);
831
+ color: var(--text-primary);
832
+ }
833
+
641
834
  /* Responsive */
642
835
  @media (max-width: 640px) {
643
836
  header {