@psiclawops/hypermem 0.5.6 → 0.7.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.
Files changed (67) hide show
  1. package/README.md +11 -54
  2. package/dist/background-indexer.d.ts.map +1 -1
  3. package/dist/background-indexer.js +26 -18
  4. package/dist/cache.d.ts.map +1 -1
  5. package/dist/cache.js +16 -2
  6. package/dist/compositor.d.ts.map +1 -1
  7. package/dist/compositor.js +146 -19
  8. package/dist/context-backfill.d.ts +46 -0
  9. package/dist/context-backfill.d.ts.map +1 -0
  10. package/dist/context-backfill.js +113 -0
  11. package/dist/context-store.d.ts +77 -0
  12. package/dist/context-store.d.ts.map +1 -0
  13. package/dist/context-store.js +177 -0
  14. package/dist/contradiction-detector.d.ts +78 -0
  15. package/dist/contradiction-detector.d.ts.map +1 -0
  16. package/dist/contradiction-detector.js +362 -0
  17. package/dist/cross-agent.d.ts +12 -0
  18. package/dist/cross-agent.d.ts.map +1 -1
  19. package/dist/cross-agent.js +31 -19
  20. package/dist/db.d.ts.map +1 -1
  21. package/dist/db.js +8 -0
  22. package/dist/dreaming-promoter.d.ts +1 -1
  23. package/dist/dreaming-promoter.js +1 -1
  24. package/dist/expertise-store.d.ts +129 -0
  25. package/dist/expertise-store.d.ts.map +1 -0
  26. package/dist/expertise-store.js +342 -0
  27. package/dist/fact-store.d.ts +15 -0
  28. package/dist/fact-store.d.ts.map +1 -1
  29. package/dist/fact-store.js +52 -5
  30. package/dist/index.d.ts +9 -3
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +27 -6
  33. package/dist/library-schema.d.ts +1 -1
  34. package/dist/library-schema.d.ts.map +1 -1
  35. package/dist/library-schema.js +72 -2
  36. package/dist/message-store.d.ts +31 -2
  37. package/dist/message-store.d.ts.map +1 -1
  38. package/dist/message-store.js +131 -17
  39. package/dist/preference-store.d.ts +1 -1
  40. package/dist/preference-store.js +1 -1
  41. package/dist/profiles.d.ts +3 -1
  42. package/dist/profiles.d.ts.map +1 -1
  43. package/dist/profiles.js +8 -0
  44. package/dist/repair-tool-pairs.d.ts.map +1 -1
  45. package/dist/repair-tool-pairs.js +73 -2
  46. package/dist/schema.d.ts +1 -1
  47. package/dist/schema.d.ts.map +1 -1
  48. package/dist/schema.js +27 -1
  49. package/dist/seed.d.ts +1 -1
  50. package/dist/seed.js +1 -1
  51. package/dist/session-flusher.d.ts +2 -2
  52. package/dist/session-flusher.js +2 -2
  53. package/dist/spawn-context.d.ts +1 -1
  54. package/dist/spawn-context.js +1 -1
  55. package/dist/temporal-store.d.ts +1 -0
  56. package/dist/temporal-store.d.ts.map +1 -1
  57. package/dist/topic-synthesizer.js +1 -1
  58. package/dist/trigger-registry.d.ts +1 -1
  59. package/dist/trigger-registry.js +4 -4
  60. package/dist/types.d.ts +15 -3
  61. package/dist/types.d.ts.map +1 -1
  62. package/dist/vector-store.d.ts +10 -1
  63. package/dist/vector-store.d.ts.map +1 -1
  64. package/dist/vector-store.js +353 -0
  65. package/dist/version.d.ts +5 -5
  66. package/dist/version.js +5 -5
  67. package/package.json +3 -2
@@ -0,0 +1,46 @@
1
+ /**
2
+ * hypermem Context Backfill
3
+ *
4
+ * One-time migration that creates context rows for existing conversations
5
+ * that don't yet have an active context. Designed to be idempotent — running
6
+ * it multiple times produces the same result without modifying existing data.
7
+ *
8
+ * Also provides parent chain backfill (Phase 2): reconstructs parent_id/depth
9
+ * for legacy flat conversations that were created before the Turn DAG model.
10
+ */
11
+ import type { DatabaseSync } from 'node:sqlite';
12
+ /**
13
+ * Backfill active contexts for all existing conversations.
14
+ *
15
+ * For each conversation without an active context:
16
+ * 1. Creates an active context via getOrCreateActiveContext
17
+ * 2. If the conversation has messages, advances the head pointer
18
+ * to the highest message ID
19
+ *
20
+ * @returns counts of created and skipped conversations
21
+ */
22
+ export declare function backfillContexts(db: DatabaseSync): {
23
+ created: number;
24
+ skipped: number;
25
+ };
26
+ /**
27
+ * Backfill parent_id and depth for existing messages that lack them.
28
+ *
29
+ * Reconstructs a linear chain per conversation ordered by message_index:
30
+ * - first message: parent_id = NULL, depth = 0
31
+ * - each subsequent: parent_id = previous.id, depth = previous.depth + 1
32
+ *
33
+ * Idempotent: only touches messages where parent_id IS NULL AND depth = 0
34
+ * AND there is more than one message or the message is not the first.
35
+ * In practice, we simply skip messages that already have parent_id set.
36
+ *
37
+ * Also stamps context_id on messages that lack it, using the active context
38
+ * for their conversation.
39
+ *
40
+ * @returns counts of messages updated and conversations processed
41
+ */
42
+ export declare function backfillParentChains(db: DatabaseSync): {
43
+ conversationsProcessed: number;
44
+ messagesUpdated: number;
45
+ };
46
+ //# sourceMappingURL=context-backfill.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-backfill.d.ts","sourceRoot":"","sources":["../src/context-backfill.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,YAAY,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAwCvF;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,YAAY,GACf;IAAE,sBAAsB,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;CAAE,CA+D7D"}
@@ -0,0 +1,113 @@
1
+ /**
2
+ * hypermem Context Backfill
3
+ *
4
+ * One-time migration that creates context rows for existing conversations
5
+ * that don't yet have an active context. Designed to be idempotent — running
6
+ * it multiple times produces the same result without modifying existing data.
7
+ *
8
+ * Also provides parent chain backfill (Phase 2): reconstructs parent_id/depth
9
+ * for legacy flat conversations that were created before the Turn DAG model.
10
+ */
11
+ import { getOrCreateActiveContext, updateContextHead } from './context-store.js';
12
+ /**
13
+ * Backfill active contexts for all existing conversations.
14
+ *
15
+ * For each conversation without an active context:
16
+ * 1. Creates an active context via getOrCreateActiveContext
17
+ * 2. If the conversation has messages, advances the head pointer
18
+ * to the highest message ID
19
+ *
20
+ * @returns counts of created and skipped conversations
21
+ */
22
+ export function backfillContexts(db) {
23
+ let created = 0;
24
+ let skipped = 0;
25
+ const conversations = db
26
+ .prepare('SELECT id, agent_id, session_key FROM conversations')
27
+ .all();
28
+ for (const conv of conversations) {
29
+ // Check if an active context already exists for this conversation
30
+ const existing = db
31
+ .prepare("SELECT id FROM contexts WHERE conversation_id = ? AND status = 'active'")
32
+ .get(conv.id);
33
+ if (existing) {
34
+ skipped++;
35
+ continue;
36
+ }
37
+ // Find the max message ID for this conversation (may be null if no messages)
38
+ const maxRow = db
39
+ .prepare('SELECT MAX(id) as max_id FROM messages WHERE conversation_id = ?')
40
+ .get(conv.id);
41
+ const maxId = maxRow?.max_id ?? null;
42
+ // Create the active context
43
+ const context = getOrCreateActiveContext(db, conv.agent_id, conv.session_key, conv.id);
44
+ // If conversation has messages, advance head to the latest
45
+ if (maxId !== null) {
46
+ updateContextHead(db, context.id, maxId);
47
+ }
48
+ created++;
49
+ }
50
+ return { created, skipped };
51
+ }
52
+ /**
53
+ * Backfill parent_id and depth for existing messages that lack them.
54
+ *
55
+ * Reconstructs a linear chain per conversation ordered by message_index:
56
+ * - first message: parent_id = NULL, depth = 0
57
+ * - each subsequent: parent_id = previous.id, depth = previous.depth + 1
58
+ *
59
+ * Idempotent: only touches messages where parent_id IS NULL AND depth = 0
60
+ * AND there is more than one message or the message is not the first.
61
+ * In practice, we simply skip messages that already have parent_id set.
62
+ *
63
+ * Also stamps context_id on messages that lack it, using the active context
64
+ * for their conversation.
65
+ *
66
+ * @returns counts of messages updated and conversations processed
67
+ */
68
+ export function backfillParentChains(db) {
69
+ let conversationsProcessed = 0;
70
+ let messagesUpdated = 0;
71
+ // Get all conversations that have at least one non-first message without parent_id.
72
+ // This is the idempotency guard: after backfill, all messages at index > 0
73
+ // have parent_id set. The first message (index 0) legitimately has parent_id = NULL.
74
+ const conversations = db
75
+ .prepare(`SELECT DISTINCT conversation_id
76
+ FROM messages
77
+ WHERE message_index > 0 AND parent_id IS NULL
78
+ ORDER BY conversation_id`)
79
+ .all();
80
+ const updateStmt = db.prepare('UPDATE messages SET parent_id = ?, depth = ?, context_id = COALESCE(context_id, ?) WHERE id = ?');
81
+ for (const { conversation_id: convId } of conversations) {
82
+ // Get active context for this conversation (if any)
83
+ const ctxRow = db
84
+ .prepare("SELECT id FROM contexts WHERE conversation_id = ? AND status = 'active' LIMIT 1")
85
+ .get(convId);
86
+ const contextId = ctxRow?.id ?? null;
87
+ // Get all messages for this conversation without parent_id, ordered by message_index
88
+ const messages = db
89
+ .prepare('SELECT id, message_index FROM messages WHERE conversation_id = ? AND parent_id IS NULL ORDER BY message_index ASC')
90
+ .all(convId);
91
+ if (messages.length === 0)
92
+ continue;
93
+ // Also get the last message that already HAS a parent_id (if any),
94
+ // so we can chain the backfilled messages after it.
95
+ const lastChainedRow = db
96
+ .prepare('SELECT id, depth FROM messages WHERE conversation_id = ? AND parent_id IS NOT NULL ORDER BY message_index DESC LIMIT 1')
97
+ .get(convId);
98
+ let prevId = lastChainedRow?.id ?? null;
99
+ let prevDepth = lastChainedRow?.depth ?? -1;
100
+ // If there's no prior chain AND the first unlinked message is truly
101
+ // message_index 0, start the chain from scratch
102
+ for (const msg of messages) {
103
+ const depth = prevDepth + 1;
104
+ updateStmt.run(prevId, depth, contextId, msg.id);
105
+ prevId = msg.id;
106
+ prevDepth = depth;
107
+ messagesUpdated++;
108
+ }
109
+ conversationsProcessed++;
110
+ }
111
+ return { conversationsProcessed, messagesUpdated };
112
+ }
113
+ //# sourceMappingURL=context-backfill.js.map
@@ -0,0 +1,77 @@
1
+ /**
2
+ * hypermem Context Store
3
+ *
4
+ * Manages the `contexts` table — a durable record of agent conversation
5
+ * contexts that tracks which session is active, what the current head
6
+ * message is, and supports archival and forking.
7
+ *
8
+ * Each agent + session pair has at most one active context at a time.
9
+ * Contexts are the anchor point for the compositor: they track the
10
+ * head message (most recent message included in composed context) and
11
+ * link back to the underlying conversation.
12
+ *
13
+ * Design principles:
14
+ * - All functions take DatabaseSync as first arg (standalone, no classes)
15
+ * - Fully idempotent — safe to call on every startup
16
+ * - Head pointer is monotone-forward (never moves backward)
17
+ * - Archive is idempotent (no-op if already archived)
18
+ */
19
+ import type { DatabaseSync } from 'node:sqlite';
20
+ export interface Context {
21
+ id: number;
22
+ agentId: string;
23
+ sessionKey: string;
24
+ conversationId: number;
25
+ headMessageId: number | null;
26
+ parentContextId: number | null;
27
+ status: 'active' | 'archived' | 'forked';
28
+ createdAt: string;
29
+ updatedAt: string;
30
+ metadataJson: string | null;
31
+ }
32
+ /**
33
+ * Add the contexts table and related indexes to an existing messages.db.
34
+ * Also ALTERs the messages table to add a context_id foreign key column.
35
+ * Idempotent — safe to call on every startup.
36
+ */
37
+ export declare function ensureContextSchema(db: DatabaseSync): void;
38
+ /**
39
+ * Get the active context for an agent + session pair.
40
+ * Returns null if no active context exists.
41
+ */
42
+ export declare function getActiveContext(db: DatabaseSync, agentId: string, sessionKey: string): Context | null;
43
+ /**
44
+ * Get the active context for an agent + session, creating one if none exists.
45
+ *
46
+ * If an active context already exists, returns it unchanged.
47
+ * Otherwise INSERTs a new context with status='active', head_message_id=NULL,
48
+ * and the given conversationId.
49
+ *
50
+ * Idempotent — safe to call repeatedly.
51
+ */
52
+ export declare function getOrCreateActiveContext(db: DatabaseSync, agentId: string, sessionKey: string, conversationId: number): Context;
53
+ /**
54
+ * Update the head message pointer for a context.
55
+ *
56
+ * Monotone forward: only updates if messageId > current head_message_id
57
+ * (or current is NULL). This prevents accidental regression of the head
58
+ * pointer, matching the compaction-fence monotone progress pattern.
59
+ */
60
+ export declare function updateContextHead(db: DatabaseSync, contextId: number, messageId: number): void;
61
+ /**
62
+ * Archive a context, setting its status to 'archived'.
63
+ * Idempotent — no-op if already archived.
64
+ */
65
+ export declare function archiveContext(db: DatabaseSync, contextId: number): void;
66
+ /**
67
+ * Rotate a session's active context: archive the current active context
68
+ * and create a new one, optionally linking back via parent_context_id.
69
+ *
70
+ * Used on session restarts/rotations so the new context starts with a
71
+ * clean head pointer instead of inheriting the stale tail.
72
+ *
73
+ * Returns the newly created active context.
74
+ * If no active context exists, simply creates one (no archive step).
75
+ */
76
+ export declare function rotateSessionContext(db: DatabaseSync, agentId: string, sessionKey: string, conversationId: number): Context;
77
+ //# sourceMappingURL=context-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-store.d.ts","sourceRoot":"","sources":["../src/context-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAIhD,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,MAAM,EAAE,QAAQ,GAAG,UAAU,GAAG,QAAQ,CAAC;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAyBD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAmC1D;AAID;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,YAAY,EAChB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,GACjB,OAAO,GAAG,IAAI,CAShB;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACtC,EAAE,EAAE,YAAY,EAChB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,GACrB,OAAO,CAwBT;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,YAAY,EAChB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,IAAI,CAeN;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,EAAE,EAAE,YAAY,EAChB,SAAS,EAAE,MAAM,GAChB,IAAI,CAMN;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,YAAY,EAChB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,GACrB,OAAO,CA2BT"}
@@ -0,0 +1,177 @@
1
+ /**
2
+ * hypermem Context Store
3
+ *
4
+ * Manages the `contexts` table — a durable record of agent conversation
5
+ * contexts that tracks which session is active, what the current head
6
+ * message is, and supports archival and forking.
7
+ *
8
+ * Each agent + session pair has at most one active context at a time.
9
+ * Contexts are the anchor point for the compositor: they track the
10
+ * head message (most recent message included in composed context) and
11
+ * link back to the underlying conversation.
12
+ *
13
+ * Design principles:
14
+ * - All functions take DatabaseSync as first arg (standalone, no classes)
15
+ * - Fully idempotent — safe to call on every startup
16
+ * - Head pointer is monotone-forward (never moves backward)
17
+ * - Archive is idempotent (no-op if already archived)
18
+ */
19
+ // ─── Internal Helpers ───────────────────────────────────────────
20
+ function parseContextRow(row) {
21
+ return {
22
+ id: row.id,
23
+ agentId: row.agent_id,
24
+ sessionKey: row.session_key,
25
+ conversationId: row.conversation_id,
26
+ headMessageId: row.head_message_id ?? null,
27
+ parentContextId: row.parent_context_id ?? null,
28
+ status: row.status,
29
+ createdAt: row.created_at,
30
+ updatedAt: row.updated_at,
31
+ metadataJson: row.metadata_json ?? null,
32
+ };
33
+ }
34
+ function nowIso() {
35
+ return new Date().toISOString();
36
+ }
37
+ // ─── Schema ─────────────────────────────────────────────────────
38
+ /**
39
+ * Add the contexts table and related indexes to an existing messages.db.
40
+ * Also ALTERs the messages table to add a context_id foreign key column.
41
+ * Idempotent — safe to call on every startup.
42
+ */
43
+ export function ensureContextSchema(db) {
44
+ db.exec(`
45
+ CREATE TABLE IF NOT EXISTS contexts (
46
+ id INTEGER PRIMARY KEY,
47
+ agent_id TEXT NOT NULL,
48
+ session_key TEXT NOT NULL,
49
+ conversation_id INTEGER REFERENCES conversations(id),
50
+ head_message_id INTEGER REFERENCES messages(id),
51
+ parent_context_id INTEGER REFERENCES contexts(id),
52
+ status TEXT NOT NULL DEFAULT 'active',
53
+ created_at TEXT NOT NULL,
54
+ updated_at TEXT NOT NULL,
55
+ metadata_json TEXT
56
+ )
57
+ `);
58
+ db.exec(`
59
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_contexts_active_session
60
+ ON contexts(agent_id, session_key, status)
61
+ WHERE status = 'active'
62
+ `);
63
+ db.exec(`
64
+ CREATE INDEX IF NOT EXISTS idx_contexts_head
65
+ ON contexts(head_message_id)
66
+ `);
67
+ // ALTER messages table to add context_id column (PRAGMA guard)
68
+ const msgCols = db.prepare('PRAGMA table_info(messages)').all().map(r => r.name);
69
+ if (!msgCols.includes('context_id')) {
70
+ db.exec('ALTER TABLE messages ADD COLUMN context_id INTEGER REFERENCES contexts(id)');
71
+ }
72
+ }
73
+ // ─── Context Operations ─────────────────────────────────────────
74
+ /**
75
+ * Get the active context for an agent + session pair.
76
+ * Returns null if no active context exists.
77
+ */
78
+ export function getActiveContext(db, agentId, sessionKey) {
79
+ const row = db
80
+ .prepare('SELECT * FROM contexts WHERE agent_id = ? AND session_key = ? AND status = ?')
81
+ .get(agentId, sessionKey, 'active');
82
+ if (!row)
83
+ return null;
84
+ return parseContextRow(row);
85
+ }
86
+ /**
87
+ * Get the active context for an agent + session, creating one if none exists.
88
+ *
89
+ * If an active context already exists, returns it unchanged.
90
+ * Otherwise INSERTs a new context with status='active', head_message_id=NULL,
91
+ * and the given conversationId.
92
+ *
93
+ * Idempotent — safe to call repeatedly.
94
+ */
95
+ export function getOrCreateActiveContext(db, agentId, sessionKey, conversationId) {
96
+ const existing = getActiveContext(db, agentId, sessionKey);
97
+ if (existing)
98
+ return existing;
99
+ const now = nowIso();
100
+ const result = db
101
+ .prepare(`INSERT INTO contexts (agent_id, session_key, conversation_id, head_message_id, parent_context_id, status, created_at, updated_at, metadata_json)
102
+ VALUES (?, ?, ?, NULL, NULL, 'active', ?, ?, NULL)`)
103
+ .run(agentId, sessionKey, conversationId, now, now);
104
+ return {
105
+ id: Number(result.lastInsertRowid),
106
+ agentId,
107
+ sessionKey,
108
+ conversationId,
109
+ headMessageId: null,
110
+ parentContextId: null,
111
+ status: 'active',
112
+ createdAt: now,
113
+ updatedAt: now,
114
+ metadataJson: null,
115
+ };
116
+ }
117
+ /**
118
+ * Update the head message pointer for a context.
119
+ *
120
+ * Monotone forward: only updates if messageId > current head_message_id
121
+ * (or current is NULL). This prevents accidental regression of the head
122
+ * pointer, matching the compaction-fence monotone progress pattern.
123
+ */
124
+ export function updateContextHead(db, contextId, messageId) {
125
+ const now = nowIso();
126
+ const row = db
127
+ .prepare('SELECT head_message_id FROM contexts WHERE id = ?')
128
+ .get(contextId);
129
+ if (!row)
130
+ return;
131
+ // Monotone forward: only advance, never regress
132
+ if (row.head_message_id === null || messageId > row.head_message_id) {
133
+ db.prepare('UPDATE contexts SET head_message_id = ?, updated_at = ? WHERE id = ?').run(messageId, now, contextId);
134
+ }
135
+ }
136
+ /**
137
+ * Archive a context, setting its status to 'archived'.
138
+ * Idempotent — no-op if already archived.
139
+ */
140
+ export function archiveContext(db, contextId) {
141
+ const now = nowIso();
142
+ db.prepare(`UPDATE contexts SET status = 'archived', updated_at = ? WHERE id = ? AND status != 'archived'`).run(now, contextId);
143
+ }
144
+ /**
145
+ * Rotate a session's active context: archive the current active context
146
+ * and create a new one, optionally linking back via parent_context_id.
147
+ *
148
+ * Used on session restarts/rotations so the new context starts with a
149
+ * clean head pointer instead of inheriting the stale tail.
150
+ *
151
+ * Returns the newly created active context.
152
+ * If no active context exists, simply creates one (no archive step).
153
+ */
154
+ export function rotateSessionContext(db, agentId, sessionKey, conversationId) {
155
+ const existing = getActiveContext(db, agentId, sessionKey);
156
+ if (existing) {
157
+ archiveContext(db, existing.id);
158
+ }
159
+ const now = nowIso();
160
+ const result = db
161
+ .prepare(`INSERT INTO contexts (agent_id, session_key, conversation_id, head_message_id, parent_context_id, status, created_at, updated_at, metadata_json)
162
+ VALUES (?, ?, ?, NULL, ?, 'active', ?, ?, NULL)`)
163
+ .run(agentId, sessionKey, conversationId, existing?.id ?? null, now, now);
164
+ return {
165
+ id: Number(result.lastInsertRowid),
166
+ agentId,
167
+ sessionKey,
168
+ conversationId,
169
+ headMessageId: null,
170
+ parentContextId: existing?.id ?? null,
171
+ status: 'active',
172
+ createdAt: now,
173
+ updatedAt: now,
174
+ metadataJson: null,
175
+ };
176
+ }
177
+ //# sourceMappingURL=context-store.js.map
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Contradiction Detector — heuristic-based contradiction detection for the fact store.
3
+ *
4
+ * Detects when a newly ingested fact contradicts existing active facts using
5
+ * vector similarity (when available) and FTS candidate retrieval, scored by
6
+ * pattern-based heuristics (negation, numeric conflict, state conflict, temporal).
7
+ *
8
+ * No LLM calls — v1 is purely heuristic. LLM-enhanced scoring is a future item.
9
+ */
10
+ import type { FactStore } from './fact-store.js';
11
+ import type { VectorStore } from './vector-store.js';
12
+ export interface ContradictionCandidate {
13
+ existingFactId: number;
14
+ existingContent: string;
15
+ similarityScore: number;
16
+ contradictionScore: number;
17
+ reason: string;
18
+ }
19
+ export interface ContradictionResult {
20
+ contradictions: ContradictionCandidate[];
21
+ autoResolved: boolean;
22
+ resolvedCount: number;
23
+ }
24
+ export interface ContradictionDetectorConfig {
25
+ /** Minimum similarity to consider as candidate. Default: 0.6 */
26
+ minSimilarity?: number;
27
+ /** Minimum contradiction score for auto-resolution. Default: 0.85 */
28
+ autoResolveThreshold?: number;
29
+ /** Max candidates to evaluate per ingest. Default: 10 */
30
+ maxCandidates?: number;
31
+ /** Enable auto-resolution. Default: true */
32
+ autoResolve?: boolean;
33
+ }
34
+ export declare class ContradictionDetector {
35
+ private readonly factStore;
36
+ private readonly vectorStore?;
37
+ private readonly config;
38
+ constructor(factStore: FactStore, vectorStore?: VectorStore | undefined, config?: ContradictionDetectorConfig);
39
+ /**
40
+ * On fact ingest, check if the new fact contradicts existing active facts.
41
+ * Uses vector similarity (when available) + FTS to find candidates, then
42
+ * scores each candidate with heuristic contradiction checks.
43
+ */
44
+ detectOnIngest(agentId: string, newFact: {
45
+ content: string;
46
+ domain?: string;
47
+ }): Promise<ContradictionResult>;
48
+ /**
49
+ * Resolve a detected contradiction between an existing fact and a new fact.
50
+ */
51
+ resolveContradiction(oldFactId: number, newFactId: number, resolution: 'supersede' | 'keep-both' | 'reject-new'): void;
52
+ /**
53
+ * Auto-resolve high-confidence contradictions: newer supersedes older.
54
+ * Only resolves candidates above the autoResolveThreshold.
55
+ *
56
+ * @param agentId - The agent whose facts are being resolved (for audit trail)
57
+ * @param candidates - Scored contradiction candidates from detectOnIngest
58
+ * @returns Count of auto-resolved contradictions
59
+ */
60
+ autoResolve(_agentId: string, candidates: ContradictionCandidate[]): Promise<number>;
61
+ /**
62
+ * Find candidate facts that might contradict the new fact.
63
+ * Uses vector search (if available) and FTS, deduplicates, and returns
64
+ * up to maxCandidates results above minSimilarity.
65
+ */
66
+ private findCandidates;
67
+ /**
68
+ * Score a candidate fact against the new fact content for contradiction.
69
+ * Returns a ContradictionCandidate if any heuristic fires, null otherwise.
70
+ */
71
+ private scoreContradiction;
72
+ /**
73
+ * Compute Jaccard-like token overlap between two texts.
74
+ * Returns 0-1 where 1 means identical token sets.
75
+ */
76
+ private tokenOverlapSimilarity;
77
+ }
78
+ //# sourceMappingURL=contradiction-detector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contradiction-detector.d.ts","sourceRoot":"","sources":["../src/contradiction-detector.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAsB,MAAM,mBAAmB,CAAC;AAIzE,MAAM,WAAW,sBAAsB;IACrC,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC,cAAc,EAAE,sBAAsB,EAAE,CAAC;IACzC,YAAY,EAAE,OAAO,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,2BAA2B;IAC1C,gEAAgE;IAChE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qEAAqE;IACrE,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,yDAAyD;IACzD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,4CAA4C;IAC5C,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAkKD,qBAAa,qBAAqB;IAI9B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;IAJ/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwC;gBAG5C,SAAS,EAAE,SAAS,EACpB,WAAW,CAAC,EAAE,WAAW,YAAA,EAC1C,MAAM,CAAC,EAAE,2BAA2B;IAKtC;;;;OAIG;IACG,cAAc,CAClB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAC5C,OAAO,CAAC,mBAAmB,CAAC;IAuB/B;;OAEG;IACH,oBAAoB,CAClB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,WAAW,GAAG,WAAW,GAAG,YAAY,GACnD,IAAI;IAcP;;;;;;;OAOG;IACG,WAAW,CACf,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,sBAAsB,EAAE,GACnC,OAAO,CAAC,MAAM,CAAC;IAoBlB;;;;OAIG;YACW,cAAc;IA0E5B;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IA0D1B;;;OAGG;IACH,OAAO,CAAC,sBAAsB;CAa/B"}