@psiclawops/hypermem 0.5.0 → 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.
Files changed (160) hide show
  1. package/dist/background-indexer.d.ts +132 -0
  2. package/dist/background-indexer.d.ts.map +1 -0
  3. package/dist/background-indexer.js +1044 -0
  4. package/dist/cache.d.ts +110 -0
  5. package/dist/cache.d.ts.map +1 -0
  6. package/dist/cache.js +495 -0
  7. package/dist/compaction-fence.d.ts +89 -0
  8. package/dist/compaction-fence.d.ts.map +1 -0
  9. package/dist/compaction-fence.js +153 -0
  10. package/dist/compositor.d.ts +226 -0
  11. package/dist/compositor.d.ts.map +1 -0
  12. package/dist/compositor.js +2558 -0
  13. package/dist/content-type-classifier.d.ts +41 -0
  14. package/dist/content-type-classifier.d.ts.map +1 -0
  15. package/dist/content-type-classifier.js +181 -0
  16. package/dist/cross-agent.d.ts +62 -0
  17. package/dist/cross-agent.d.ts.map +1 -0
  18. package/dist/cross-agent.js +259 -0
  19. package/dist/db.d.ts +131 -0
  20. package/dist/db.d.ts.map +1 -0
  21. package/dist/db.js +402 -0
  22. package/dist/desired-state-store.d.ts +100 -0
  23. package/dist/desired-state-store.d.ts.map +1 -0
  24. package/dist/desired-state-store.js +222 -0
  25. package/dist/doc-chunk-store.d.ts +140 -0
  26. package/dist/doc-chunk-store.d.ts.map +1 -0
  27. package/dist/doc-chunk-store.js +391 -0
  28. package/dist/doc-chunker.d.ts +99 -0
  29. package/dist/doc-chunker.d.ts.map +1 -0
  30. package/dist/doc-chunker.js +324 -0
  31. package/dist/dreaming-promoter.d.ts +86 -0
  32. package/dist/dreaming-promoter.d.ts.map +1 -0
  33. package/dist/dreaming-promoter.js +381 -0
  34. package/dist/episode-store.d.ts +49 -0
  35. package/dist/episode-store.d.ts.map +1 -0
  36. package/dist/episode-store.js +135 -0
  37. package/dist/fact-store.d.ts +75 -0
  38. package/dist/fact-store.d.ts.map +1 -0
  39. package/dist/fact-store.js +236 -0
  40. package/dist/fleet-store.d.ts +144 -0
  41. package/dist/fleet-store.d.ts.map +1 -0
  42. package/dist/fleet-store.js +276 -0
  43. package/dist/fos-mod.d.ts +178 -0
  44. package/dist/fos-mod.d.ts.map +1 -0
  45. package/dist/fos-mod.js +416 -0
  46. package/dist/hybrid-retrieval.d.ts +64 -0
  47. package/dist/hybrid-retrieval.d.ts.map +1 -0
  48. package/dist/hybrid-retrieval.js +344 -0
  49. package/dist/image-eviction.d.ts +49 -0
  50. package/dist/image-eviction.d.ts.map +1 -0
  51. package/dist/image-eviction.js +251 -0
  52. package/dist/index.d.ts +650 -0
  53. package/dist/index.d.ts.map +1 -0
  54. package/dist/index.js +1072 -0
  55. package/dist/keystone-scorer.d.ts +51 -0
  56. package/dist/keystone-scorer.d.ts.map +1 -0
  57. package/dist/keystone-scorer.js +52 -0
  58. package/dist/knowledge-graph.d.ts +110 -0
  59. package/dist/knowledge-graph.d.ts.map +1 -0
  60. package/dist/knowledge-graph.js +305 -0
  61. package/dist/knowledge-lint.d.ts +29 -0
  62. package/dist/knowledge-lint.d.ts.map +1 -0
  63. package/dist/knowledge-lint.js +116 -0
  64. package/dist/knowledge-store.d.ts +72 -0
  65. package/dist/knowledge-store.d.ts.map +1 -0
  66. package/dist/knowledge-store.js +247 -0
  67. package/dist/library-schema.d.ts +22 -0
  68. package/dist/library-schema.d.ts.map +1 -0
  69. package/dist/library-schema.js +1038 -0
  70. package/dist/message-store.d.ts +89 -0
  71. package/dist/message-store.d.ts.map +1 -0
  72. package/dist/message-store.js +323 -0
  73. package/dist/metrics-dashboard.d.ts +114 -0
  74. package/dist/metrics-dashboard.d.ts.map +1 -0
  75. package/dist/metrics-dashboard.js +260 -0
  76. package/dist/obsidian-exporter.d.ts +57 -0
  77. package/dist/obsidian-exporter.d.ts.map +1 -0
  78. package/dist/obsidian-exporter.js +274 -0
  79. package/dist/obsidian-watcher.d.ts +147 -0
  80. package/dist/obsidian-watcher.d.ts.map +1 -0
  81. package/dist/obsidian-watcher.js +403 -0
  82. package/dist/open-domain.d.ts +46 -0
  83. package/dist/open-domain.d.ts.map +1 -0
  84. package/dist/open-domain.js +125 -0
  85. package/dist/preference-store.d.ts +54 -0
  86. package/dist/preference-store.d.ts.map +1 -0
  87. package/dist/preference-store.js +109 -0
  88. package/dist/preservation-gate.d.ts +82 -0
  89. package/dist/preservation-gate.d.ts.map +1 -0
  90. package/dist/preservation-gate.js +150 -0
  91. package/dist/proactive-pass.d.ts +63 -0
  92. package/dist/proactive-pass.d.ts.map +1 -0
  93. package/dist/proactive-pass.js +239 -0
  94. package/dist/profiles.d.ts +44 -0
  95. package/dist/profiles.d.ts.map +1 -0
  96. package/dist/profiles.js +227 -0
  97. package/dist/provider-translator.d.ts +50 -0
  98. package/dist/provider-translator.d.ts.map +1 -0
  99. package/dist/provider-translator.js +403 -0
  100. package/dist/rate-limiter.d.ts +76 -0
  101. package/dist/rate-limiter.d.ts.map +1 -0
  102. package/dist/rate-limiter.js +179 -0
  103. package/dist/repair-tool-pairs.d.ts +38 -0
  104. package/dist/repair-tool-pairs.d.ts.map +1 -0
  105. package/dist/repair-tool-pairs.js +138 -0
  106. package/dist/retrieval-policy.d.ts +51 -0
  107. package/dist/retrieval-policy.d.ts.map +1 -0
  108. package/dist/retrieval-policy.js +77 -0
  109. package/dist/schema.d.ts +15 -0
  110. package/dist/schema.d.ts.map +1 -0
  111. package/dist/schema.js +229 -0
  112. package/dist/secret-scanner.d.ts +51 -0
  113. package/dist/secret-scanner.d.ts.map +1 -0
  114. package/dist/secret-scanner.js +248 -0
  115. package/dist/seed.d.ts +108 -0
  116. package/dist/seed.d.ts.map +1 -0
  117. package/dist/seed.js +177 -0
  118. package/dist/session-flusher.d.ts +53 -0
  119. package/dist/session-flusher.d.ts.map +1 -0
  120. package/dist/session-flusher.js +69 -0
  121. package/dist/session-topic-map.d.ts +41 -0
  122. package/dist/session-topic-map.d.ts.map +1 -0
  123. package/dist/session-topic-map.js +77 -0
  124. package/dist/spawn-context.d.ts +54 -0
  125. package/dist/spawn-context.d.ts.map +1 -0
  126. package/dist/spawn-context.js +159 -0
  127. package/dist/system-store.d.ts +73 -0
  128. package/dist/system-store.d.ts.map +1 -0
  129. package/dist/system-store.js +182 -0
  130. package/dist/temporal-store.d.ts +80 -0
  131. package/dist/temporal-store.d.ts.map +1 -0
  132. package/dist/temporal-store.js +149 -0
  133. package/dist/topic-detector.d.ts +35 -0
  134. package/dist/topic-detector.d.ts.map +1 -0
  135. package/dist/topic-detector.js +249 -0
  136. package/dist/topic-store.d.ts +45 -0
  137. package/dist/topic-store.d.ts.map +1 -0
  138. package/dist/topic-store.js +136 -0
  139. package/dist/topic-synthesizer.d.ts +51 -0
  140. package/dist/topic-synthesizer.d.ts.map +1 -0
  141. package/dist/topic-synthesizer.js +315 -0
  142. package/dist/trigger-registry.d.ts +63 -0
  143. package/dist/trigger-registry.d.ts.map +1 -0
  144. package/dist/trigger-registry.js +163 -0
  145. package/dist/types.d.ts +533 -0
  146. package/dist/types.d.ts.map +1 -0
  147. package/dist/types.js +9 -0
  148. package/dist/vector-store.d.ts +170 -0
  149. package/dist/vector-store.d.ts.map +1 -0
  150. package/dist/vector-store.js +677 -0
  151. package/dist/version.d.ts +34 -0
  152. package/dist/version.d.ts.map +1 -0
  153. package/dist/version.js +34 -0
  154. package/dist/wiki-page-emitter.d.ts +65 -0
  155. package/dist/wiki-page-emitter.d.ts.map +1 -0
  156. package/dist/wiki-page-emitter.js +258 -0
  157. package/dist/work-store.d.ts +112 -0
  158. package/dist/work-store.d.ts.map +1 -0
  159. package/dist/work-store.js +273 -0
  160. package/package.json +1 -1
@@ -0,0 +1,125 @@
1
+ /**
2
+ * open-domain.ts — Open-domain query detection and FTS5 retrieval
3
+ *
4
+ * LoCoMo benchmark open-domain questions are broad, exploratory, and have no
5
+ * topical anchor. They span the full conversation history and require content
6
+ * that may have been filtered out by the quality gate (isQualityFact). The
7
+ * fix: detect open-domain queries and run a separate FTS5 search against raw
8
+ * messages_fts, bypassing the quality filter entirely.
9
+ *
10
+ * Detection heuristics (conservative — false positives add noise):
11
+ * - Short query with no named entities (no TitleCase tokens)
12
+ * - Broad interrogative patterns (what did, how did, tell me about, etc.)
13
+ * - No temporal signals (those go to the temporal retrieval path)
14
+ * - No specific identifiers (URLs, IDs, ticket numbers, version strings)
15
+ *
16
+ * Retrieval: MessageStore.searchMessages() against messages_fts — covers all
17
+ * raw message history regardless of quality gate.
18
+ */
19
+ // ── Open-domain signal patterns ───────────────────────────────────────────
20
+ const BROAD_INTERROGATIVE = /\b(what did|what does|what has|what was|what were|what is|how did|how does|how has|tell me about|describe|explain|summarize|overview|recap|what do you know about|what have|who is|who was|who did)\b/i;
21
+ const SPECIFIC_ANCHOR = /\b([A-Z][a-z]{2,}(?:\s+[A-Z][a-z]{2,})+|v\d+\.\d+|#\d{2,}|https?:\/\/|[A-Z]{2,}-\d+)\b/;
22
+ const TEMPORAL_SIGNALS = /\b(before|after|when|last\s+\w+|yesterday|today|recently|between|since|until|ago|this\s+week|this\s+month|in\s+(january|february|march|april|may|june|july|august|september|october|november|december))\b/i;
23
+ /**
24
+ * Returns true if the query looks like an open-domain question:
25
+ * broad, exploratory, no specific anchors, no temporal signals.
26
+ */
27
+ export function isOpenDomainQuery(query) {
28
+ if (!query || query.trim().length < 8)
29
+ return false;
30
+ // Has temporal signals → temporal path handles it
31
+ if (TEMPORAL_SIGNALS.test(query))
32
+ return false;
33
+ // Has specific named entity / version / ticket anchor → not open-domain
34
+ if (SPECIFIC_ANCHOR.test(query))
35
+ return false;
36
+ // Must match a broad interrogative pattern
37
+ if (!BROAD_INTERROGATIVE.test(query))
38
+ return false;
39
+ // Sanity: query should not be too long (long queries are usually specific)
40
+ const wordCount = query.trim().split(/\s+/).length;
41
+ if (wordCount > 20)
42
+ return false;
43
+ return true;
44
+ }
45
+ // ── FTS5 query builder ────────────────────────────────────────────────────
46
+ /**
47
+ * Build a FTS5 MATCH query from a broad question.
48
+ * Strips stop words, question words, and punctuation.
49
+ * Returns up to 6 prefix-matched terms joined with OR.
50
+ */
51
+ export function buildOpenDomainFtsQuery(query) {
52
+ const STOP_WORDS = new Set([
53
+ 'what', 'did', 'does', 'has', 'was', 'were', 'is', 'are', 'how',
54
+ 'tell', 'me', 'about', 'describe', 'explain', 'summarize', 'overview',
55
+ 'recap', 'who', 'do', 'you', 'know', 'have', 'the', 'a', 'an', 'of',
56
+ 'in', 'on', 'at', 'to', 'for', 'and', 'or', 'but', 'with', 'from',
57
+ ]);
58
+ const terms = query
59
+ .toLowerCase()
60
+ .replace(/[^a-z0-9\s]/g, ' ')
61
+ .split(/\s+/)
62
+ .filter(w => w.length >= 3 && !STOP_WORDS.has(w))
63
+ .slice(0, 6)
64
+ .map(w => `${w}*`);
65
+ if (terms.length === 0)
66
+ return null;
67
+ return terms.join(' OR ');
68
+ }
69
+ /**
70
+ * Search raw message history via FTS5 for open-domain queries.
71
+ * Returns up to `limit` matching messages, deduplicated against existing context.
72
+ *
73
+ * @param db — agent messages DB (contains messages_fts)
74
+ * @param query — the user's query
75
+ * @param existingContent — already-assembled context (for dedup)
76
+ * @param limit — max results (default 10)
77
+ */
78
+ export function searchOpenDomain(db, query, existingContent, limit = 10) {
79
+ const ftsQuery = buildOpenDomainFtsQuery(query);
80
+ if (!ftsQuery)
81
+ return [];
82
+ try {
83
+ const rows = db.prepare(`
84
+ WITH fts_matches AS (
85
+ SELECT rowid, rank
86
+ FROM messages_fts
87
+ WHERE messages_fts MATCH ?
88
+ ORDER BY rank
89
+ LIMIT ?
90
+ )
91
+ SELECT
92
+ m.role,
93
+ m.text_content AS content,
94
+ m.created_at AS createdAt
95
+ FROM messages m
96
+ JOIN fts_matches ON m.id = fts_matches.rowid
97
+ WHERE m.text_content IS NOT NULL
98
+ AND m.text_content != ''
99
+ AND m.is_heartbeat = 0
100
+ ORDER BY fts_matches.rank
101
+ `).all(ftsQuery, limit * 2);
102
+ // Deduplicate against existing context and filter short content
103
+ const seen = new Set();
104
+ const results = [];
105
+ for (const row of rows) {
106
+ if (!row.content || row.content.trim().length < 20)
107
+ continue;
108
+ const fingerprint = row.content.slice(0, 80);
109
+ if (seen.has(fingerprint))
110
+ continue;
111
+ if (existingContent.includes(fingerprint))
112
+ continue;
113
+ seen.add(fingerprint);
114
+ results.push(row);
115
+ if (results.length >= limit)
116
+ break;
117
+ }
118
+ return results;
119
+ }
120
+ catch {
121
+ // FTS query may fail on special characters — degrade silently
122
+ return [];
123
+ }
124
+ }
125
+ //# sourceMappingURL=open-domain.js.map
@@ -0,0 +1,54 @@
1
+ /**
2
+ * hypermem Preference Store
3
+ *
4
+ * Behavioral patterns observed about people, systems, and workflows.
5
+ * Lives in the central library DB.
6
+ * "ragesaq prefers architectural stability" is a preference, not a fact.
7
+ */
8
+ import type { DatabaseSync } from 'node:sqlite';
9
+ export interface Preference {
10
+ id: number;
11
+ subject: string;
12
+ domain: string;
13
+ key: string;
14
+ value: string;
15
+ agentId: string;
16
+ confidence: number;
17
+ visibility: string;
18
+ sourceType: string;
19
+ sourceRef: string | null;
20
+ createdAt: string;
21
+ updatedAt: string;
22
+ }
23
+ export declare class PreferenceStore {
24
+ private readonly db;
25
+ constructor(db: DatabaseSync);
26
+ /**
27
+ * Set or update a preference. Upserts on (subject, domain, key).
28
+ */
29
+ set(subject: string, key: string, value: string, opts?: {
30
+ domain?: string;
31
+ agentId?: string;
32
+ confidence?: number;
33
+ visibility?: string;
34
+ sourceType?: string;
35
+ sourceRef?: string;
36
+ }): Preference;
37
+ /**
38
+ * Get a specific preference.
39
+ */
40
+ get(subject: string, key: string, domain?: string): Preference | null;
41
+ /**
42
+ * Get all preferences for a subject.
43
+ */
44
+ getForSubject(subject: string, domain?: string): Preference[];
45
+ /**
46
+ * Search preferences by value content.
47
+ */
48
+ search(query: string, subject?: string): Preference[];
49
+ /**
50
+ * Delete a preference.
51
+ */
52
+ delete(subject: string, key: string, domain?: string): boolean;
53
+ }
54
+ //# sourceMappingURL=preference-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preference-store.d.ts","sourceRoot":"","sources":["../src/preference-store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAMhD,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAmBD,qBAAa,eAAe;IACd,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAAF,EAAE,EAAE,YAAY;IAE7C;;OAEG;IACH,GAAG,CACD,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE;QACL,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GACA,UAAU;IA+Cb;;OAEG;IACH,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,MAAkB,GAAG,UAAU,GAAG,IAAI;IAQhF;;OAEG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,UAAU,EAAE;IAe7D;;OAEG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,UAAU,EAAE;IAerD;;OAEG;IACH,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,MAAkB,GAAG,OAAO;CAO1E"}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * hypermem Preference Store
3
+ *
4
+ * Behavioral patterns observed about people, systems, and workflows.
5
+ * Lives in the central library DB.
6
+ * "ragesaq prefers architectural stability" is a preference, not a fact.
7
+ */
8
+ function nowIso() {
9
+ return new Date().toISOString();
10
+ }
11
+ function parseRow(row) {
12
+ return {
13
+ id: row.id,
14
+ subject: row.subject,
15
+ domain: row.domain,
16
+ key: row.key,
17
+ value: row.value,
18
+ agentId: row.agent_id,
19
+ confidence: row.confidence,
20
+ visibility: row.visibility || 'fleet',
21
+ sourceType: row.source_type || 'observation',
22
+ sourceRef: row.source_ref || null,
23
+ createdAt: row.created_at,
24
+ updatedAt: row.updated_at,
25
+ };
26
+ }
27
+ export class PreferenceStore {
28
+ db;
29
+ constructor(db) {
30
+ this.db = db;
31
+ }
32
+ /**
33
+ * Set or update a preference. Upserts on (subject, domain, key).
34
+ */
35
+ set(subject, key, value, opts) {
36
+ const now = nowIso();
37
+ const domain = opts?.domain || 'general';
38
+ const result = this.db.prepare(`
39
+ INSERT INTO preferences (subject, domain, key, value, agent_id, confidence,
40
+ visibility, source_type, source_ref, created_at, updated_at)
41
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
42
+ ON CONFLICT(subject, domain, key) DO UPDATE SET
43
+ value = excluded.value,
44
+ confidence = excluded.confidence,
45
+ agent_id = excluded.agent_id,
46
+ source_type = excluded.source_type,
47
+ source_ref = excluded.source_ref,
48
+ updated_at = excluded.updated_at
49
+ `).run(subject, domain, key, value, opts?.agentId || 'system', opts?.confidence || 1.0, opts?.visibility || 'fleet', opts?.sourceType || 'observation', opts?.sourceRef || null, now, now);
50
+ const id = Number(result.lastInsertRowid);
51
+ return {
52
+ id,
53
+ subject,
54
+ domain,
55
+ key,
56
+ value,
57
+ agentId: opts?.agentId || 'system',
58
+ confidence: opts?.confidence || 1.0,
59
+ visibility: opts?.visibility || 'fleet',
60
+ sourceType: opts?.sourceType || 'observation',
61
+ sourceRef: opts?.sourceRef || null,
62
+ createdAt: now,
63
+ updatedAt: now,
64
+ };
65
+ }
66
+ /**
67
+ * Get a specific preference.
68
+ */
69
+ get(subject, key, domain = 'general') {
70
+ const row = this.db.prepare('SELECT * FROM preferences WHERE subject = ? AND domain = ? AND key = ?').get(subject, domain, key);
71
+ return row ? parseRow(row) : null;
72
+ }
73
+ /**
74
+ * Get all preferences for a subject.
75
+ */
76
+ getForSubject(subject, domain) {
77
+ let sql = 'SELECT * FROM preferences WHERE subject = ?';
78
+ const params = [subject];
79
+ if (domain) {
80
+ sql += ' AND domain = ?';
81
+ params.push(domain);
82
+ }
83
+ sql += ' ORDER BY domain, key';
84
+ const rows = this.db.prepare(sql).all(...params);
85
+ return rows.map(parseRow);
86
+ }
87
+ /**
88
+ * Search preferences by value content.
89
+ */
90
+ search(query, subject) {
91
+ let sql = 'SELECT * FROM preferences WHERE (value LIKE ? OR key LIKE ?)';
92
+ const params = [`%${query}%`, `%${query}%`];
93
+ if (subject) {
94
+ sql += ' AND subject = ?';
95
+ params.push(subject);
96
+ }
97
+ sql += ' ORDER BY confidence DESC LIMIT 20';
98
+ const rows = this.db.prepare(sql).all(...params);
99
+ return rows.map(parseRow);
100
+ }
101
+ /**
102
+ * Delete a preference.
103
+ */
104
+ delete(subject, key, domain = 'general') {
105
+ const result = this.db.prepare('DELETE FROM preferences WHERE subject = ? AND domain = ? AND key = ?').run(subject, domain, key);
106
+ return result.changes > 0;
107
+ }
108
+ }
109
+ //# sourceMappingURL=preference-store.js.map
@@ -0,0 +1,82 @@
1
+ /**
2
+ * hypermem Preservation Gate
3
+ *
4
+ * Verifies that a proposed compaction summary preserves the semantic
5
+ * content of its source messages by measuring geometric fidelity in
6
+ * embedding space.
7
+ *
8
+ * Before a summary replaces raw messages, it must pass two checks:
9
+ *
10
+ * 1. Centroid Alignment — the summary embedding must be close to the
11
+ * centroid of the source message embeddings (cos similarity).
12
+ *
13
+ * 2. Source Coverage — the summary must have positive cosine similarity
14
+ * with each individual source message (averaged).
15
+ *
16
+ * If the combined preservation score falls below the threshold, the
17
+ * summary is rejected. The caller should fall back to extractive
18
+ * compaction (concatenation/selection) rather than accepting a
19
+ * semantically drifted summary.
20
+ *
21
+ * This prevents the silent failure mode where a confident summarizer
22
+ * produces fluent text that has drifted away from the original meaning
23
+ * in vector space — making it unretrievable by the very system that
24
+ * will later search for it.
25
+ *
26
+ * Inspired by the Nomic-space preservation gate in openclaw-memory-libravdb
27
+ * (mathematics-v2.md §5.3), adapted for our Ollama + sqlite-vec stack.
28
+ */
29
+ import { type EmbeddingConfig } from './vector-store.js';
30
+ export interface PreservationResult {
31
+ /** Cosine similarity between summary and source centroid */
32
+ alignment: number;
33
+ /** Average positive cosine similarity between summary and each source */
34
+ coverage: number;
35
+ /** Combined score: (alignment + coverage) / 2, clamped to [0, 1] */
36
+ score: number;
37
+ /** Whether the summary passed the preservation gate */
38
+ passed: boolean;
39
+ /** The threshold used for the gate */
40
+ threshold: number;
41
+ }
42
+ export interface PreservationConfig {
43
+ /**
44
+ * Minimum combined preservation score for a summary to be accepted.
45
+ * Default: 0.65 (same as libravdb's shipped default).
46
+ *
47
+ * At 0.65, the summary must be meaningfully close to the source cluster.
48
+ * Lower values accept more drift; higher values are stricter.
49
+ * Range: [0, 1].
50
+ */
51
+ threshold: number;
52
+ /** Embedding config for Ollama calls (used by async path only) */
53
+ embedding?: Partial<EmbeddingConfig>;
54
+ }
55
+ /**
56
+ * Verify that a summary preserves its source content in embedding space.
57
+ *
58
+ * SYNCHRONOUS PATH — for when you already have pre-computed embeddings
59
+ * (e.g., from the background indexer or vector store cache).
60
+ *
61
+ * This is the preferred path: no network calls, no async, deterministic.
62
+ *
63
+ * @param summaryEmbedding - The embedding of the proposed summary
64
+ * @param sourceEmbeddings - Embeddings of the source messages being replaced
65
+ * @param config - Preservation threshold config
66
+ */
67
+ export declare function verifyPreservationFromVectors(summaryEmbedding: Float32Array, sourceEmbeddings: Float32Array[], config?: Partial<PreservationConfig>): PreservationResult;
68
+ /**
69
+ * Verify that a summary preserves its source content in embedding space.
70
+ *
71
+ * ASYNC PATH — generates embeddings via Ollama on demand.
72
+ * Use when pre-computed embeddings aren't available.
73
+ *
74
+ * This makes N+1 embedding calls (1 for summary, N for sources if not cached).
75
+ * For batch compaction, prefer pre-computing embeddings and using the sync path.
76
+ *
77
+ * @param summaryText - The proposed summary text
78
+ * @param sourceTexts - The source message texts being replaced
79
+ * @param config - Preservation threshold and embedding config
80
+ */
81
+ export declare function verifyPreservation(summaryText: string, sourceTexts: string[], config?: Partial<PreservationConfig>): Promise<PreservationResult>;
82
+ //# sourceMappingURL=preservation-gate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preservation-gate.d.ts","sourceRoot":"","sources":["../src/preservation-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAsB,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAI7E,MAAM,WAAW,kBAAkB;IACjC,4DAA4D;IAC5D,SAAS,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,QAAQ,EAAE,MAAM,CAAC;IACjB,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,MAAM,EAAE,OAAO,CAAC;IAChB,sCAAsC;IACtC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC;;;;;;;OAOG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,kEAAkE;IAClE,SAAS,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;CACtC;AA4DD;;;;;;;;;;;GAWG;AACH,wBAAgB,6BAA6B,CAC3C,gBAAgB,EAAE,YAAY,EAC9B,gBAAgB,EAAE,YAAY,EAAE,EAChC,MAAM,GAAE,OAAO,CAAC,kBAAkB,CAAM,GACvC,kBAAkB,CAmCpB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,kBAAkB,CACtC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EAAE,EACrB,MAAM,GAAE,OAAO,CAAC,kBAAkB,CAAM,GACvC,OAAO,CAAC,kBAAkB,CAAC,CAqB7B"}
@@ -0,0 +1,150 @@
1
+ /**
2
+ * hypermem Preservation Gate
3
+ *
4
+ * Verifies that a proposed compaction summary preserves the semantic
5
+ * content of its source messages by measuring geometric fidelity in
6
+ * embedding space.
7
+ *
8
+ * Before a summary replaces raw messages, it must pass two checks:
9
+ *
10
+ * 1. Centroid Alignment — the summary embedding must be close to the
11
+ * centroid of the source message embeddings (cos similarity).
12
+ *
13
+ * 2. Source Coverage — the summary must have positive cosine similarity
14
+ * with each individual source message (averaged).
15
+ *
16
+ * If the combined preservation score falls below the threshold, the
17
+ * summary is rejected. The caller should fall back to extractive
18
+ * compaction (concatenation/selection) rather than accepting a
19
+ * semantically drifted summary.
20
+ *
21
+ * This prevents the silent failure mode where a confident summarizer
22
+ * produces fluent text that has drifted away from the original meaning
23
+ * in vector space — making it unretrievable by the very system that
24
+ * will later search for it.
25
+ *
26
+ * Inspired by the Nomic-space preservation gate in openclaw-memory-libravdb
27
+ * (mathematics-v2.md §5.3), adapted for our Ollama + sqlite-vec stack.
28
+ */
29
+ import { generateEmbeddings } from './vector-store.js';
30
+ const DEFAULT_PRESERVATION_CONFIG = {
31
+ threshold: 0.65,
32
+ };
33
+ // ─── Math Utilities ─────────────────────────────────────────────
34
+ /**
35
+ * Cosine similarity between two Float32Arrays.
36
+ * Returns value in [-1, 1]. Handles zero-norm vectors gracefully (returns 0).
37
+ */
38
+ function cosineSimilarity(a, b) {
39
+ if (a.length !== b.length) {
40
+ throw new Error(`Vector dimension mismatch: ${a.length} vs ${b.length}`);
41
+ }
42
+ let dot = 0;
43
+ let normA = 0;
44
+ let normB = 0;
45
+ for (let i = 0; i < a.length; i++) {
46
+ dot += a[i] * b[i];
47
+ normA += a[i] * a[i];
48
+ normB += b[i] * b[i];
49
+ }
50
+ const denom = Math.sqrt(normA) * Math.sqrt(normB);
51
+ if (denom === 0)
52
+ return 0;
53
+ return dot / denom;
54
+ }
55
+ /**
56
+ * Compute the centroid (element-wise mean) of an array of vectors.
57
+ */
58
+ function computeCentroid(vectors) {
59
+ if (vectors.length === 0) {
60
+ throw new Error('Cannot compute centroid of empty vector set');
61
+ }
62
+ const dim = vectors[0].length;
63
+ const centroid = new Float32Array(dim);
64
+ for (const vec of vectors) {
65
+ for (let i = 0; i < dim; i++) {
66
+ centroid[i] += vec[i];
67
+ }
68
+ }
69
+ const n = vectors.length;
70
+ for (let i = 0; i < dim; i++) {
71
+ centroid[i] /= n;
72
+ }
73
+ return centroid;
74
+ }
75
+ // ─── Preservation Gate ──────────────────────────────────────────
76
+ /**
77
+ * Verify that a summary preserves its source content in embedding space.
78
+ *
79
+ * SYNCHRONOUS PATH — for when you already have pre-computed embeddings
80
+ * (e.g., from the background indexer or vector store cache).
81
+ *
82
+ * This is the preferred path: no network calls, no async, deterministic.
83
+ *
84
+ * @param summaryEmbedding - The embedding of the proposed summary
85
+ * @param sourceEmbeddings - Embeddings of the source messages being replaced
86
+ * @param config - Preservation threshold config
87
+ */
88
+ export function verifyPreservationFromVectors(summaryEmbedding, sourceEmbeddings, config = {}) {
89
+ const threshold = config.threshold ?? DEFAULT_PRESERVATION_CONFIG.threshold;
90
+ if (sourceEmbeddings.length === 0) {
91
+ return {
92
+ alignment: 0,
93
+ coverage: 0,
94
+ score: 0,
95
+ passed: false,
96
+ threshold,
97
+ };
98
+ }
99
+ // 1. Centroid alignment
100
+ const centroid = computeCentroid(sourceEmbeddings);
101
+ const alignment = cosineSimilarity(summaryEmbedding, centroid);
102
+ // 2. Source coverage (average positive cosine similarity)
103
+ let coverageSum = 0;
104
+ for (const src of sourceEmbeddings) {
105
+ coverageSum += Math.max(0, cosineSimilarity(summaryEmbedding, src));
106
+ }
107
+ const coverage = coverageSum / sourceEmbeddings.length;
108
+ // 3. Combined score, clamped to [0, 1]
109
+ const rawScore = (alignment + coverage) / 2;
110
+ const score = Math.max(0, Math.min(1, rawScore));
111
+ return {
112
+ alignment,
113
+ coverage,
114
+ score,
115
+ passed: score >= threshold,
116
+ threshold,
117
+ };
118
+ }
119
+ /**
120
+ * Verify that a summary preserves its source content in embedding space.
121
+ *
122
+ * ASYNC PATH — generates embeddings via Ollama on demand.
123
+ * Use when pre-computed embeddings aren't available.
124
+ *
125
+ * This makes N+1 embedding calls (1 for summary, N for sources if not cached).
126
+ * For batch compaction, prefer pre-computing embeddings and using the sync path.
127
+ *
128
+ * @param summaryText - The proposed summary text
129
+ * @param sourceTexts - The source message texts being replaced
130
+ * @param config - Preservation threshold and embedding config
131
+ */
132
+ export async function verifyPreservation(summaryText, sourceTexts, config = {}) {
133
+ const threshold = config.threshold ?? DEFAULT_PRESERVATION_CONFIG.threshold;
134
+ if (sourceTexts.length === 0) {
135
+ return {
136
+ alignment: 0,
137
+ coverage: 0,
138
+ score: 0,
139
+ passed: false,
140
+ threshold,
141
+ };
142
+ }
143
+ // Batch all texts into one embedding call for efficiency
144
+ const allTexts = [summaryText, ...sourceTexts];
145
+ const allEmbeddings = await generateEmbeddings(allTexts, config.embedding);
146
+ const summaryEmbedding = allEmbeddings[0];
147
+ const sourceEmbeddings = allEmbeddings.slice(1);
148
+ return verifyPreservationFromVectors(summaryEmbedding, sourceEmbeddings, config);
149
+ }
150
+ //# sourceMappingURL=preservation-gate.js.map
@@ -0,0 +1,63 @@
1
+ /**
2
+ * hypermem Proactive Passes
3
+ *
4
+ * Background maintenance passes that run between indexer ticks to keep
5
+ * message storage lean. Two passes:
6
+ *
7
+ * 1. Noise Sweep — deletes low/zero-signal messages outside the recent
8
+ * window (heartbeats, acks, empty strings, control tokens).
9
+ *
10
+ * 2. Tool Decay — truncates oversized tool_results outside the recent
11
+ * window in-place, preserving JSON structure but collapsing large
12
+ * content blobs into a byte-count placeholder.
13
+ *
14
+ * Both passes are:
15
+ * - Synchronous (DatabaseSync, no async)
16
+ * - Wrapped in transactions (atomic)
17
+ * - Best-effort: catch all errors, log, and return a zero-change result
18
+ *
19
+ * Ported and adapted from ClawText proactive-pass.ts.
20
+ * hypermem schema differences vs ClawText:
21
+ * - No content_type column — we classify on the fly via classifyContentType()
22
+ * - No external payload store — we truncate content inline in tool_results JSON
23
+ * - No ClawText-specific dependencies (payload-store, tool-tracker, etc.)
24
+ */
25
+ import type { DatabaseSync } from 'node:sqlite';
26
+ export interface NoiseSweepResult {
27
+ messagesDeleted: number;
28
+ passType: 'noise_sweep';
29
+ }
30
+ export interface ToolDecayResult {
31
+ messagesUpdated: number;
32
+ bytesFreed: number;
33
+ passType: 'tool_decay';
34
+ }
35
+ /**
36
+ * Delete noise and heartbeat messages outside the recent window.
37
+ *
38
+ * "Outside the recent window" means message_index < maxIndex - recentWindowSize.
39
+ * Messages inside the window are never deleted, even if they are noise —
40
+ * the model may still reference them in the current turn.
41
+ *
42
+ * Deletions are wrapped in a single transaction. The FTS5 trigger handles
43
+ * index cleanup automatically (msg_fts_ad fires on DELETE).
44
+ */
45
+ export declare function runNoiseSweep(db: DatabaseSync, conversationId: number, recentWindowSize?: number): NoiseSweepResult;
46
+ /**
47
+ * Truncate oversized tool_results outside the recent window.
48
+ *
49
+ * Strategy:
50
+ * 1. Find messages whose tool_results JSON string is > 2000 chars total,
51
+ * outside the recent window.
52
+ * 2. Parse the JSON array.
53
+ * 3. For each result entry where the `content` field exceeds 500 chars,
54
+ * replace `content` with `[tool result truncated — N bytes]`.
55
+ * 4. Re-serialize and write back.
56
+ *
57
+ * The JSON structure is preserved (array of result objects). Only the
58
+ * oversized `content` values are collapsed.
59
+ *
60
+ * Mutations are committed in a single transaction.
61
+ */
62
+ export declare function runToolDecay(db: DatabaseSync, conversationId: number, recentWindowSize?: number): ToolDecayResult;
63
+ //# sourceMappingURL=proactive-pass.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proactive-pass.d.ts","sourceRoot":"","sources":["../src/proactive-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKhD,MAAM,WAAW,gBAAgB;IAC/B,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,aAAa,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,YAAY,CAAC;CACxB;AA6CD;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,EAAE,EAAE,YAAY,EAChB,cAAc,EAAE,MAAM,EACtB,gBAAgB,GAAE,MAAW,GAC5B,gBAAgB,CA2ElB;AAID;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAC1B,EAAE,EAAE,YAAY,EAChB,cAAc,EAAE,MAAM,EACtB,gBAAgB,GAAE,MAAW,GAC5B,eAAe,CAiGjB"}