@psiclawops/hypermem 0.7.0 → 0.8.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 (79) hide show
  1. package/ARCHITECTURE.md +30 -38
  2. package/README.md +83 -35
  3. package/dist/background-indexer.d.ts +14 -3
  4. package/dist/background-indexer.d.ts.map +1 -1
  5. package/dist/background-indexer.js +126 -18
  6. package/dist/budget-policy.d.ts +22 -0
  7. package/dist/budget-policy.d.ts.map +1 -0
  8. package/dist/budget-policy.js +27 -0
  9. package/dist/cache.d.ts +11 -0
  10. package/dist/cache.d.ts.map +1 -1
  11. package/dist/compositor-utils.d.ts +31 -0
  12. package/dist/compositor-utils.d.ts.map +1 -0
  13. package/dist/compositor-utils.js +47 -0
  14. package/dist/compositor.d.ts +163 -1
  15. package/dist/compositor.d.ts.map +1 -1
  16. package/dist/compositor.js +862 -130
  17. package/dist/content-hash.d.ts +43 -0
  18. package/dist/content-hash.d.ts.map +1 -0
  19. package/dist/content-hash.js +75 -0
  20. package/dist/context-store.d.ts +54 -0
  21. package/dist/context-store.d.ts.map +1 -1
  22. package/dist/context-store.js +102 -0
  23. package/dist/contradiction-audit-store.d.ts +54 -0
  24. package/dist/contradiction-audit-store.d.ts.map +1 -0
  25. package/dist/contradiction-audit-store.js +88 -0
  26. package/dist/contradiction-resolution-policy.d.ts +21 -0
  27. package/dist/contradiction-resolution-policy.d.ts.map +1 -0
  28. package/dist/contradiction-resolution-policy.js +17 -0
  29. package/dist/degradation.d.ts +102 -0
  30. package/dist/degradation.d.ts.map +1 -0
  31. package/dist/degradation.js +141 -0
  32. package/dist/dreaming-promoter.d.ts +38 -0
  33. package/dist/dreaming-promoter.d.ts.map +1 -1
  34. package/dist/dreaming-promoter.js +68 -2
  35. package/dist/index.d.ts +68 -6
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +402 -26
  38. package/dist/knowledge-lint.d.ts +2 -0
  39. package/dist/knowledge-lint.d.ts.map +1 -1
  40. package/dist/knowledge-lint.js +40 -1
  41. package/dist/library-schema.d.ts +7 -2
  42. package/dist/library-schema.d.ts.map +1 -1
  43. package/dist/library-schema.js +236 -1
  44. package/dist/message-store.d.ts +64 -1
  45. package/dist/message-store.d.ts.map +1 -1
  46. package/dist/message-store.js +137 -1
  47. package/dist/open-domain.js +1 -1
  48. package/dist/proactive-pass.d.ts +2 -2
  49. package/dist/proactive-pass.d.ts.map +1 -1
  50. package/dist/proactive-pass.js +66 -12
  51. package/dist/replay-recovery.d.ts +29 -0
  52. package/dist/replay-recovery.d.ts.map +1 -0
  53. package/dist/replay-recovery.js +82 -0
  54. package/dist/reranker.d.ts +95 -0
  55. package/dist/reranker.d.ts.map +1 -0
  56. package/dist/reranker.js +308 -0
  57. package/dist/schema.d.ts +1 -1
  58. package/dist/schema.d.ts.map +1 -1
  59. package/dist/schema.js +46 -1
  60. package/dist/session-flusher.d.ts +2 -2
  61. package/dist/session-flusher.d.ts.map +1 -1
  62. package/dist/session-flusher.js +1 -1
  63. package/dist/temporal-store.js +2 -2
  64. package/dist/tool-artifact-store.d.ts +98 -0
  65. package/dist/tool-artifact-store.d.ts.map +1 -0
  66. package/dist/tool-artifact-store.js +244 -0
  67. package/dist/topic-detector.js +2 -2
  68. package/dist/topic-store.d.ts +6 -0
  69. package/dist/topic-store.d.ts.map +1 -1
  70. package/dist/topic-store.js +39 -0
  71. package/dist/types.d.ts +233 -1
  72. package/dist/types.d.ts.map +1 -1
  73. package/dist/vector-store.d.ts +2 -1
  74. package/dist/vector-store.d.ts.map +1 -1
  75. package/dist/vector-store.js +3 -0
  76. package/dist/version.d.ts +10 -10
  77. package/dist/version.d.ts.map +1 -1
  78. package/dist/version.js +10 -10
  79. package/package.json +6 -4
@@ -0,0 +1,43 @@
1
+ /**
2
+ * hypermem Content Hash
3
+ *
4
+ * Content-addressed hashing for fact/knowledge deduplication.
5
+ * Uses BLAKE3 (via @noble/hashes) for fast, collision-resistant fingerprints.
6
+ *
7
+ * Dedup semantics are per-agent: the same content stored by two different
8
+ * agents is intentional and should not be merged. Check with:
9
+ * WHERE content_hash = ? AND agent_id = ?
10
+ */
11
+ import type { DatabaseSync } from 'node:sqlite';
12
+ /**
13
+ * Compute a BLAKE3 content hash for deduplication.
14
+ * Returns a 64-character hex string (32 bytes).
15
+ *
16
+ * Normalization: collapses whitespace, trims, lowercases
17
+ * before hashing. This catches trivial reformatting differences
18
+ * that shouldn't create duplicate facts.
19
+ */
20
+ export declare function contentHash(text: string): string;
21
+ /**
22
+ * SQL migration statements for adding content_hash columns and indexes.
23
+ *
24
+ * Use non-unique indexes — different agents may legitimately store identical
25
+ * facts. Dedup check is always scoped per agent:
26
+ * WHERE content_hash = ? AND agent_id = ?
27
+ */
28
+ export declare const SCHEMA_MIGRATIONS: {
29
+ addFactsContentHash: string;
30
+ addKnowledgeContentHash: string;
31
+ indexFactsContentHash: string;
32
+ indexKnowledgeContentHash: string;
33
+ };
34
+ /**
35
+ * Backfill content_hash for existing rows that have NULL hashes.
36
+ * Meant to run once after schema migration.
37
+ * Returns count of rows updated.
38
+ *
39
+ * Defensive: returns 0 if the content_hash column does not exist on the
40
+ * target table (e.g. migration hasn't run yet or table is missing).
41
+ */
42
+ export declare function backfillContentHashes(db: DatabaseSync, table: 'facts' | 'knowledge'): number;
43
+ //# sourceMappingURL=content-hash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-hash.d.ts","sourceRoot":"","sources":["../src/content-hash.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIhD;AAED;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB;;;;;CAK7B,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,YAAY,EAChB,KAAK,EAAE,OAAO,GAAG,WAAW,GAC3B,MAAM,CAgCR"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * hypermem Content Hash
3
+ *
4
+ * Content-addressed hashing for fact/knowledge deduplication.
5
+ * Uses BLAKE3 (via @noble/hashes) for fast, collision-resistant fingerprints.
6
+ *
7
+ * Dedup semantics are per-agent: the same content stored by two different
8
+ * agents is intentional and should not be merged. Check with:
9
+ * WHERE content_hash = ? AND agent_id = ?
10
+ */
11
+ import { blake3 } from '@noble/hashes/blake3.js';
12
+ /**
13
+ * Compute a BLAKE3 content hash for deduplication.
14
+ * Returns a 64-character hex string (32 bytes).
15
+ *
16
+ * Normalization: collapses whitespace, trims, lowercases
17
+ * before hashing. This catches trivial reformatting differences
18
+ * that shouldn't create duplicate facts.
19
+ */
20
+ export function contentHash(text) {
21
+ const normalized = text.toLowerCase().replace(/\s+/g, ' ').trim();
22
+ const bytes = new TextEncoder().encode(normalized);
23
+ return Buffer.from(blake3(bytes)).toString('hex');
24
+ }
25
+ /**
26
+ * SQL migration statements for adding content_hash columns and indexes.
27
+ *
28
+ * Use non-unique indexes — different agents may legitimately store identical
29
+ * facts. Dedup check is always scoped per agent:
30
+ * WHERE content_hash = ? AND agent_id = ?
31
+ */
32
+ export const SCHEMA_MIGRATIONS = {
33
+ addFactsContentHash: `ALTER TABLE facts ADD COLUMN content_hash TEXT`,
34
+ addKnowledgeContentHash: `ALTER TABLE knowledge ADD COLUMN content_hash TEXT`,
35
+ indexFactsContentHash: `CREATE INDEX IF NOT EXISTS idx_facts_content_hash ON facts(content_hash)`,
36
+ indexKnowledgeContentHash: `CREATE INDEX IF NOT EXISTS idx_knowledge_content_hash ON knowledge(content_hash)`,
37
+ };
38
+ /**
39
+ * Backfill content_hash for existing rows that have NULL hashes.
40
+ * Meant to run once after schema migration.
41
+ * Returns count of rows updated.
42
+ *
43
+ * Defensive: returns 0 if the content_hash column does not exist on the
44
+ * target table (e.g. migration hasn't run yet or table is missing).
45
+ */
46
+ export function backfillContentHashes(db, table) {
47
+ // Defensive: verify the column exists before querying it.
48
+ const pragmaRows = db
49
+ .prepare(`PRAGMA table_info(${table})`)
50
+ .all();
51
+ const hasColumn = pragmaRows.some((r) => r['name'] === 'content_hash');
52
+ if (!hasColumn)
53
+ return 0;
54
+ const rows = db
55
+ .prepare(`SELECT id, content FROM ${table} WHERE content_hash IS NULL`)
56
+ .all();
57
+ if (rows.length === 0)
58
+ return 0;
59
+ const update = db.prepare(`UPDATE ${table} SET content_hash = ? WHERE id = ?`);
60
+ // Single transaction for the entire batch — much faster than autocommit per row.
61
+ // Node 22 DatabaseSync does not have .transaction(); use manual BEGIN/COMMIT.
62
+ db.exec('BEGIN');
63
+ try {
64
+ for (const row of rows) {
65
+ update.run(contentHash(row.content), row.id);
66
+ }
67
+ db.exec('COMMIT');
68
+ }
69
+ catch (err) {
70
+ db.exec('ROLLBACK');
71
+ throw err;
72
+ }
73
+ return rows.length;
74
+ }
75
+ //# sourceMappingURL=content-hash.js.map
@@ -63,6 +63,60 @@ export declare function updateContextHead(db: DatabaseSync, contextId: number, m
63
63
  * Idempotent — no-op if already archived.
64
64
  */
65
65
  export declare function archiveContext(db: DatabaseSync, contextId: number): void;
66
+ /**
67
+ * Get any context by id, regardless of status.
68
+ * Returns null if not found.
69
+ *
70
+ * @boundary INSPECTION ONLY — not a mining entry point.
71
+ * @policy See specs/DAG_HELPER_POLICY.md for helper classifications.
72
+ * Do not use this function to retrieve messages for active composition or
73
+ * historical mining. Use getArchivedContext + mineArchivedContext for archived
74
+ * mining, and getActiveContext for composition-path access.
75
+ */
76
+ export declare function getContextById(db: DatabaseSync, contextId: number): Context | null;
77
+ /**
78
+ * Get all archived or forked contexts for an agent.
79
+ * Optionally filter by sessionKey and/or limit.
80
+ * Returns in reverse-chronological order (most recently updated first).
81
+ *
82
+ * @policy See specs/DAG_HELPER_POLICY.md. This is the operator-safe
83
+ * archived-context enumeration path.
84
+ */
85
+ export declare function getArchivedContexts(db: DatabaseSync, agentId: string, opts?: {
86
+ sessionKey?: string;
87
+ limit?: number;
88
+ }): Context[];
89
+ /**
90
+ * Get an archived or forked context by id.
91
+ * Returns null if the context does not exist OR if it is active.
92
+ *
93
+ * @policy See specs/DAG_HELPER_POLICY.md. This is the operator-safe
94
+ * single-context lookup path.
95
+ */
96
+ export declare function getArchivedContext(db: DatabaseSync, contextId: number): Context | null;
97
+ /**
98
+ * Walk the parent_context_id chain upward from the given context.
99
+ * Returns contexts in leaf-to-root order (starting context first).
100
+ * Includes the starting context itself.
101
+ * Caps traversal depth at 100 to avoid corrupt loops.
102
+ *
103
+ * @boundary STATUS-CROSSING BY DESIGN — this function traverses across
104
+ * active, archived, and forked contexts without filtering by status.
105
+ * @policy See specs/DAG_HELPER_POLICY.md for the call-site filtering rule.
106
+ * If you need only archived/forked contexts in the lineage chain, filter
107
+ * the returned array at the call site (e.g. `.filter(c => c.status !== 'active')`).
108
+ */
109
+ export declare function getContextLineage(db: DatabaseSync, contextId: number): Context[];
110
+ /**
111
+ * Get direct fork children of a context (contexts with parent_context_id = parentContextId).
112
+ * Returns in ascending creation order.
113
+ *
114
+ * @boundary STATUS-CROSSING BY DESIGN — returns children regardless of their status
115
+ * (may include active, archived, or forked children).
116
+ * @policy See specs/DAG_HELPER_POLICY.md for the call-site filtering rule.
117
+ * Filter at the call site if archived-only results are needed.
118
+ */
119
+ export declare function getForkChildren(db: DatabaseSync, parentContextId: number): Context[];
66
120
  /**
67
121
  * Rotate a session's active context: archive the current active context
68
122
  * and create a new one, optionally linking back via parent_context_id.
@@ -1 +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"}
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,cAAc,CAC5B,EAAE,EAAE,YAAY,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,GAAG,IAAI,CAOhB;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,EAAE,EAAE,YAAY,EAChB,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE;IACL,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GACA,OAAO,EAAE,CAkBX;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,YAAY,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,GAAG,IAAI,CAOhB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,YAAY,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,EAAE,CAqBX;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,EAAE,EAAE,YAAY,EAChB,eAAe,EAAE,MAAM,GACtB,OAAO,EAAE,CAMX;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,YAAY,EAChB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,GACrB,OAAO,CA2BT"}
@@ -141,6 +141,108 @@ export function archiveContext(db, contextId) {
141
141
  const now = nowIso();
142
142
  db.prepare(`UPDATE contexts SET status = 'archived', updated_at = ? WHERE id = ? AND status != 'archived'`).run(now, contextId);
143
143
  }
144
+ /**
145
+ * Get any context by id, regardless of status.
146
+ * Returns null if not found.
147
+ *
148
+ * @boundary INSPECTION ONLY — not a mining entry point.
149
+ * @policy See specs/DAG_HELPER_POLICY.md for helper classifications.
150
+ * Do not use this function to retrieve messages for active composition or
151
+ * historical mining. Use getArchivedContext + mineArchivedContext for archived
152
+ * mining, and getActiveContext for composition-path access.
153
+ */
154
+ export function getContextById(db, contextId) {
155
+ const row = db
156
+ .prepare('SELECT * FROM contexts WHERE id = ?')
157
+ .get(contextId);
158
+ if (!row)
159
+ return null;
160
+ return parseContextRow(row);
161
+ }
162
+ /**
163
+ * Get all archived or forked contexts for an agent.
164
+ * Optionally filter by sessionKey and/or limit.
165
+ * Returns in reverse-chronological order (most recently updated first).
166
+ *
167
+ * @policy See specs/DAG_HELPER_POLICY.md. This is the operator-safe
168
+ * archived-context enumeration path.
169
+ */
170
+ export function getArchivedContexts(db, agentId, opts) {
171
+ let sql = "SELECT * FROM contexts WHERE agent_id = ? AND status IN ('archived', 'forked')";
172
+ const params = [agentId];
173
+ if (opts?.sessionKey) {
174
+ sql += ' AND session_key = ?';
175
+ params.push(opts.sessionKey);
176
+ }
177
+ sql += ' ORDER BY updated_at DESC';
178
+ if (opts?.limit) {
179
+ sql += ' LIMIT ?';
180
+ params.push(opts.limit);
181
+ }
182
+ const rows = db.prepare(sql).all(...params);
183
+ return rows.map(parseContextRow);
184
+ }
185
+ /**
186
+ * Get an archived or forked context by id.
187
+ * Returns null if the context does not exist OR if it is active.
188
+ *
189
+ * @policy See specs/DAG_HELPER_POLICY.md. This is the operator-safe
190
+ * single-context lookup path.
191
+ */
192
+ export function getArchivedContext(db, contextId) {
193
+ const row = db
194
+ .prepare("SELECT * FROM contexts WHERE id = ? AND status IN ('archived', 'forked')")
195
+ .get(contextId);
196
+ if (!row)
197
+ return null;
198
+ return parseContextRow(row);
199
+ }
200
+ /**
201
+ * Walk the parent_context_id chain upward from the given context.
202
+ * Returns contexts in leaf-to-root order (starting context first).
203
+ * Includes the starting context itself.
204
+ * Caps traversal depth at 100 to avoid corrupt loops.
205
+ *
206
+ * @boundary STATUS-CROSSING BY DESIGN — this function traverses across
207
+ * active, archived, and forked contexts without filtering by status.
208
+ * @policy See specs/DAG_HELPER_POLICY.md for the call-site filtering rule.
209
+ * If you need only archived/forked contexts in the lineage chain, filter
210
+ * the returned array at the call site (e.g. `.filter(c => c.status !== 'active')`).
211
+ */
212
+ export function getContextLineage(db, contextId) {
213
+ const lineage = [];
214
+ const visited = new Set();
215
+ let currentId = contextId;
216
+ while (currentId !== null && lineage.length < 100) {
217
+ if (visited.has(currentId))
218
+ break; // loop guard
219
+ visited.add(currentId);
220
+ const row = db
221
+ .prepare('SELECT * FROM contexts WHERE id = ?')
222
+ .get(currentId);
223
+ if (!row)
224
+ break;
225
+ const ctx = parseContextRow(row);
226
+ lineage.push(ctx);
227
+ currentId = ctx.parentContextId;
228
+ }
229
+ return lineage;
230
+ }
231
+ /**
232
+ * Get direct fork children of a context (contexts with parent_context_id = parentContextId).
233
+ * Returns in ascending creation order.
234
+ *
235
+ * @boundary STATUS-CROSSING BY DESIGN — returns children regardless of their status
236
+ * (may include active, archived, or forked children).
237
+ * @policy See specs/DAG_HELPER_POLICY.md for the call-site filtering rule.
238
+ * Filter at the call site if archived-only results are needed.
239
+ */
240
+ export function getForkChildren(db, parentContextId) {
241
+ const rows = db
242
+ .prepare('SELECT * FROM contexts WHERE parent_context_id = ? ORDER BY created_at ASC')
243
+ .all(parentContextId);
244
+ return rows.map(parseContextRow);
245
+ }
144
246
  /**
145
247
  * Rotate a session's active context: archive the current active context
146
248
  * and create a new one, optionally linking back via parent_context_id.
@@ -0,0 +1,54 @@
1
+ /**
2
+ * hypermem Contradiction Audit Store
3
+ *
4
+ * Lightweight audit trail for contradiction detections during background indexing.
5
+ * Records when the indexer identifies a new fact candidate that contradicts an
6
+ * existing stored fact, without auto-resolving (autoResolve: false path).
7
+ *
8
+ * Stored in library.db under the contradiction_audits table (created on demand).
9
+ * Used for observability and future contradiction resolution tooling.
10
+ */
11
+ import type { DatabaseSync } from 'node:sqlite';
12
+ import type { ContradictionCandidate } from './contradiction-detector.js';
13
+ export interface ContradictionAuditEntry {
14
+ id: number;
15
+ agentId: string;
16
+ newContent: string;
17
+ newDomain: string | null;
18
+ existingFactId: number;
19
+ existingContent: string;
20
+ similarityScore: number;
21
+ contradictionScore: number;
22
+ reason: string;
23
+ sourceRef: string | null;
24
+ createdAt: string;
25
+ }
26
+ export declare class ContradictionAuditStore {
27
+ private readonly db;
28
+ constructor(db: DatabaseSync);
29
+ private ensureTable;
30
+ /**
31
+ * Record a detected contradiction between a new fact candidate and an existing fact.
32
+ *
33
+ * @param agentId - Agent whose fact was being indexed
34
+ * @param newFact - The incoming fact candidate (not yet stored)
35
+ * @param candidate - The contradiction candidate from ContradictionDetector
36
+ * @param opts - Optional metadata (sourceRef = "msg:<id>" etc.)
37
+ */
38
+ recordFactAudit(agentId: string, newFact: {
39
+ content: string;
40
+ domain?: string | null;
41
+ }, candidate: ContradictionCandidate, opts?: {
42
+ sourceRef?: string;
43
+ status?: string;
44
+ }): void;
45
+ /**
46
+ * Fetch recent audit entries for an agent (most recent first).
47
+ */
48
+ getRecentAudits(agentId: string, limit?: number): ContradictionAuditEntry[];
49
+ /**
50
+ * Count unresolved audits for an agent.
51
+ */
52
+ countAudits(agentId: string): number;
53
+ }
54
+ //# sourceMappingURL=contradiction-audit-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contradiction-audit-store.d.ts","sourceRoot":"","sources":["../src/contradiction-audit-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAE1E,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,uBAAuB;IACtB,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAAF,EAAE,EAAE,YAAY;IAI7C,OAAO,CAAC,WAAW;IAiCnB;;;;;;;OAOG;IACH,eAAe,CACb,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,EACpD,SAAS,EAAE,sBAAsB,EACjC,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAC7C,IAAI;IAqBP;;OAEG;IACH,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,uBAAuB,EAAE;IAYvE;;OAEG;IACH,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;CAMrC"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * hypermem Contradiction Audit Store
3
+ *
4
+ * Lightweight audit trail for contradiction detections during background indexing.
5
+ * Records when the indexer identifies a new fact candidate that contradicts an
6
+ * existing stored fact, without auto-resolving (autoResolve: false path).
7
+ *
8
+ * Stored in library.db under the contradiction_audits table (created on demand).
9
+ * Used for observability and future contradiction resolution tooling.
10
+ */
11
+ export class ContradictionAuditStore {
12
+ db;
13
+ constructor(db) {
14
+ this.db = db;
15
+ this.ensureTable();
16
+ }
17
+ ensureTable() {
18
+ // Synced with library-schema.ts v19 shape so in-memory test DBs match production.
19
+ // In real usage, setupLibraryDb() runs first and this CREATE TABLE is a no-op.
20
+ this.db.exec(`
21
+ CREATE TABLE IF NOT EXISTS contradiction_audits (
22
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
23
+ agent_id TEXT NOT NULL,
24
+ entity_type TEXT NOT NULL CHECK(entity_type IN ('fact')),
25
+ new_content TEXT NOT NULL,
26
+ new_domain TEXT,
27
+ existing_fact_id INTEGER NOT NULL,
28
+ existing_content TEXT NOT NULL,
29
+ similarity_score REAL NOT NULL DEFAULT 0,
30
+ contradiction_score REAL NOT NULL DEFAULT 0,
31
+ reason TEXT NOT NULL,
32
+ detector TEXT NOT NULL DEFAULT 'heuristic_v1',
33
+ suggested_resolution TEXT NOT NULL DEFAULT 'review',
34
+ source_ref TEXT,
35
+ status TEXT NOT NULL DEFAULT 'pending'
36
+ CHECK(status IN ('pending', 'accepted', 'dismissed', 'auto-superseded', 'auto-invalidated')),
37
+ resolution_notes TEXT,
38
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
39
+ resolved_at TEXT
40
+ );
41
+ CREATE INDEX IF NOT EXISTS idx_contradiction_audits_agent_status
42
+ ON contradiction_audits (agent_id, status, created_at DESC);
43
+ CREATE INDEX IF NOT EXISTS idx_contradiction_audits_existing_fact
44
+ ON contradiction_audits (existing_fact_id, status);
45
+ CREATE INDEX IF NOT EXISTS idx_contradiction_audits_agent
46
+ ON contradiction_audits (agent_id, created_at DESC);
47
+ `);
48
+ }
49
+ /**
50
+ * Record a detected contradiction between a new fact candidate and an existing fact.
51
+ *
52
+ * @param agentId - Agent whose fact was being indexed
53
+ * @param newFact - The incoming fact candidate (not yet stored)
54
+ * @param candidate - The contradiction candidate from ContradictionDetector
55
+ * @param opts - Optional metadata (sourceRef = "msg:<id>" etc.)
56
+ */
57
+ recordFactAudit(agentId, newFact, candidate, opts) {
58
+ const status = opts?.status ?? 'pending';
59
+ this.db.prepare(`
60
+ INSERT INTO contradiction_audits
61
+ (agent_id, entity_type, new_content, new_domain, existing_fact_id, existing_content,
62
+ similarity_score, contradiction_score, reason, source_ref, status, created_at)
63
+ VALUES (?, 'fact', ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))
64
+ `).run(agentId, newFact.content, newFact.domain ?? null, candidate.existingFactId, candidate.existingContent, candidate.similarityScore, candidate.contradictionScore, candidate.reason, opts?.sourceRef ?? null, status);
65
+ }
66
+ /**
67
+ * Fetch recent audit entries for an agent (most recent first).
68
+ */
69
+ getRecentAudits(agentId, limit = 20) {
70
+ return this.db.prepare(`
71
+ SELECT
72
+ id, agent_id, new_content, new_domain, existing_fact_id, existing_content,
73
+ similarity_score, contradiction_score, reason, source_ref, created_at
74
+ FROM contradiction_audits
75
+ WHERE agent_id = ?
76
+ ORDER BY created_at DESC
77
+ LIMIT ?
78
+ `).all(agentId, limit);
79
+ }
80
+ /**
81
+ * Count unresolved audits for an agent.
82
+ */
83
+ countAudits(agentId) {
84
+ const row = this.db.prepare('SELECT COUNT(*) as cnt FROM contradiction_audits WHERE agent_id = ?').get(agentId);
85
+ return row?.cnt ?? 0;
86
+ }
87
+ }
88
+ //# sourceMappingURL=contradiction-audit-store.js.map
@@ -0,0 +1,21 @@
1
+ /**
2
+ * hypermem Contradiction Resolution Policy
3
+ *
4
+ * Defines thresholds and flags that control how the background indexer acts
5
+ * on detected contradictions during fact ingest.
6
+ *
7
+ * Tiers (by contradictionScore):
8
+ * >= autoSupersedeThreshold → mark old fact superseded, remove stale vector
9
+ * >= autoInvalidateThreshold → mark old fact invalid (no supersede linkage)
10
+ * below autoInvalidateThreshold → log-only (pending review)
11
+ */
12
+ export interface ContradictionResolutionPolicy {
13
+ /** Score threshold at or above which the old fact is auto-superseded by the new one. Default: 0.80 */
14
+ autoSupersedeThreshold: number;
15
+ /** Score threshold at or above which the old fact is auto-invalidated. Default: 0.60 */
16
+ autoInvalidateThreshold: number;
17
+ /** When true, always write an audit row regardless of tier. Default: true */
18
+ alwaysAudit: boolean;
19
+ }
20
+ export declare const DEFAULT_CONTRADICTION_POLICY: ContradictionResolutionPolicy;
21
+ //# sourceMappingURL=contradiction-resolution-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contradiction-resolution-policy.d.ts","sourceRoot":"","sources":["../src/contradiction-resolution-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,6BAA6B;IAC5C,sGAAsG;IACtG,sBAAsB,EAAE,MAAM,CAAC;IAC/B,wFAAwF;IACxF,uBAAuB,EAAE,MAAM,CAAC;IAChC,6EAA6E;IAC7E,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,eAAO,MAAM,4BAA4B,EAAE,6BAI1C,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * hypermem Contradiction Resolution Policy
3
+ *
4
+ * Defines thresholds and flags that control how the background indexer acts
5
+ * on detected contradictions during fact ingest.
6
+ *
7
+ * Tiers (by contradictionScore):
8
+ * >= autoSupersedeThreshold → mark old fact superseded, remove stale vector
9
+ * >= autoInvalidateThreshold → mark old fact invalid (no supersede linkage)
10
+ * below autoInvalidateThreshold → log-only (pending review)
11
+ */
12
+ export const DEFAULT_CONTRADICTION_POLICY = {
13
+ autoSupersedeThreshold: 0.80,
14
+ autoInvalidateThreshold: 0.60,
15
+ alwaysAudit: true,
16
+ };
17
+ //# sourceMappingURL=contradiction-resolution-policy.js.map
@@ -0,0 +1,102 @@
1
+ /**
2
+ * HyperMem Canonical Degradation Contracts, Phase C0.2
3
+ *
4
+ * Defines the typed surfaces, reason enum, and format builders for degraded
5
+ * prompt-visible outputs. These shapes stay in volatile context and should not
6
+ * cross the stable-prefix boundary.
7
+ */
8
+ /**
9
+ * Closed set of reasons for any degradation decision.
10
+ * Use these values in telemetry and tests instead of ad-hoc strings.
11
+ */
12
+ export type DegradationReason = 'gradient_t2_prose' | 'gradient_t3_stub' | 'eviction_oversize' | 'eviction_turn0_trim' | 'wave_guard_pressure_high' | 'wave_guard_pressure_elevated' | 'budget_cluster_drop' | 'artifact_oversize' | 'artifact_fetch_hint' | 'replay_cold_redis' | 'replay_stabilizing' | 'replay_exited' | 'pressure_mismatch' | 'unknown';
13
+ /** All valid DegradationReason values as a readonly array. */
14
+ export declare const DEGRADATION_REASONS: readonly ["gradient_t2_prose", "gradient_t3_stub", "eviction_oversize", "eviction_turn0_trim", "wave_guard_pressure_high", "wave_guard_pressure_elevated", "budget_cluster_drop", "artifact_oversize", "artifact_fetch_hint", "replay_cold_redis", "replay_stabilizing", "replay_exited", "pressure_mismatch", "unknown"];
15
+ /** Field-length caps for canonical degraded strings. */
16
+ export declare const DEGRADATION_LIMITS: {
17
+ readonly toolName: 64;
18
+ readonly toolId: 64;
19
+ readonly reason: 48;
20
+ readonly toolSummary: 120;
21
+ readonly toolArtifactId: 64;
22
+ readonly artifactId: 64;
23
+ readonly artifactPath: 160;
24
+ readonly artifactFetchHint: 80;
25
+ readonly replayState: 32;
26
+ readonly replaySummary: 120;
27
+ };
28
+ export declare function isDegradationReason(value: string): value is DegradationReason;
29
+ /**
30
+ * Canonical shape for a degraded tool call/result pair.
31
+ *
32
+ * Prompt-visible format:
33
+ * [tool:<name> id=<id> status=ejected reason=<reason> summary=<stub>]
34
+ *
35
+ * Optional artifact pointer (Phase 1 of tool_artifacts):
36
+ * [tool:<name> id=<id> status=ejected reason=<reason> artifact=<artifactId> summary=<stub>]
37
+ *
38
+ * The `artifact=` field is backwards-compatible and optional. When present, it
39
+ * lets the compositor rehydrate the full tool result payload from the
40
+ * tool_artifacts table without needing to rewrite the transcript.
41
+ */
42
+ export interface ToolChainStub {
43
+ name: string;
44
+ id: string;
45
+ status: 'ejected';
46
+ reason: DegradationReason;
47
+ summary: string;
48
+ /** Optional durable pointer into the tool_artifacts table. */
49
+ artifactId?: string;
50
+ }
51
+ export declare function formatToolChainStub(stub: ToolChainStub): string;
52
+ export declare function parseToolChainStub(text: string): ToolChainStub | null;
53
+ export declare function isToolChainStub(text: string): boolean;
54
+ /**
55
+ * Canonical shape for a degraded oversized artifact replaced by a pointer.
56
+ *
57
+ * Prompt-visible format:
58
+ * [artifact:<id> path=<path> size=<tokens> status=degraded fetch=<hint>]
59
+ */
60
+ export interface ArtifactRef {
61
+ id: string;
62
+ path: string;
63
+ sizeTokens: number;
64
+ status: 'degraded';
65
+ reason: DegradationReason;
66
+ fetchHint: string;
67
+ }
68
+ export declare function formatArtifactRef(ref: ArtifactRef): string;
69
+ export declare function parseArtifactRef(text: string): ArtifactRef | null;
70
+ export declare function isArtifactRef(text: string): boolean;
71
+ /**
72
+ * State of the replay recovery window.
73
+ */
74
+ export type ReplayState = 'entering' | 'stabilizing' | 'exited';
75
+ export declare function isReplayState(value: string): value is ReplayState;
76
+ /**
77
+ * Canonical shape for a replay recovery mode marker.
78
+ *
79
+ * Prompt-visible format:
80
+ * [replay state=<state> status=bounded reason=<reason> summary=<stub>]
81
+ */
82
+ export interface ReplayMarker {
83
+ state: ReplayState;
84
+ status: 'bounded';
85
+ reason: DegradationReason;
86
+ summary: string;
87
+ }
88
+ export declare function formatReplayMarker(marker: ReplayMarker): string;
89
+ export declare function parseReplayMarker(text: string): ReplayMarker | null;
90
+ export declare function isReplayMarker(text: string): boolean;
91
+ export declare function isDegradedContent(text: string): boolean;
92
+ export interface DegradationEvent {
93
+ event: 'degradation';
94
+ ts: string;
95
+ agentId: string;
96
+ sessionKey: string;
97
+ degradationClass: 'tool_chain' | 'artifact' | 'replay';
98
+ reason: DegradationReason;
99
+ tokensSaved: number;
100
+ emittedText: string;
101
+ }
102
+ //# sourceMappingURL=degradation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"degradation.d.ts","sourceRoot":"","sources":["../src/degradation.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GACzB,mBAAmB,GACnB,kBAAkB,GAClB,mBAAmB,GACnB,qBAAqB,GACrB,0BAA0B,GAC1B,8BAA8B,GAC9B,qBAAqB,GACrB,mBAAmB,GACnB,qBAAqB,GACrB,mBAAmB,GACnB,oBAAoB,GACpB,eAAe,GACf,mBAAmB,GACnB,SAAS,CAAC;AAEd,8DAA8D;AAC9D,eAAO,MAAM,mBAAmB,2TAeiB,CAAC;AAElD,wDAAwD;AACxD,eAAO,MAAM,kBAAkB;;;;;;;;;;;CAWrB,CAAC;AAiBX,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,iBAAiB,CAE7E;AAID;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,SAAS,CAAC;IAClB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,8DAA8D;IAC9D,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAKD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAS/D;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAQrE;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAErD;AAID;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,UAAU,CAAC;IACnB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;CACnB;AAID,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,CAM1D;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAajE;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEnD;AAID;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,aAAa,GAAG,QAAQ,CAAC;AAEhE,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,WAAW,CAEjE;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,WAAW,CAAC;IACnB,MAAM,EAAE,SAAS,CAAC;IAClB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;CACjB;AAID,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAK/D;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAWnE;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD;AAID,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEvD;AAID,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,aAAa,CAAC;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,YAAY,GAAG,UAAU,GAAG,QAAQ,CAAC;IACvD,MAAM,EAAE,iBAAiB,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB"}