@psiclawops/hypermem 0.5.0 → 0.5.2

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 (163) hide show
  1. package/ARCHITECTURE.md +12 -3
  2. package/README.md +30 -6
  3. package/bin/hypermem-status.mjs +166 -0
  4. package/dist/background-indexer.d.ts +132 -0
  5. package/dist/background-indexer.d.ts.map +1 -0
  6. package/dist/background-indexer.js +1044 -0
  7. package/dist/cache.d.ts +110 -0
  8. package/dist/cache.d.ts.map +1 -0
  9. package/dist/cache.js +495 -0
  10. package/dist/compaction-fence.d.ts +89 -0
  11. package/dist/compaction-fence.d.ts.map +1 -0
  12. package/dist/compaction-fence.js +153 -0
  13. package/dist/compositor.d.ts +226 -0
  14. package/dist/compositor.d.ts.map +1 -0
  15. package/dist/compositor.js +2558 -0
  16. package/dist/content-type-classifier.d.ts +41 -0
  17. package/dist/content-type-classifier.d.ts.map +1 -0
  18. package/dist/content-type-classifier.js +181 -0
  19. package/dist/cross-agent.d.ts +62 -0
  20. package/dist/cross-agent.d.ts.map +1 -0
  21. package/dist/cross-agent.js +259 -0
  22. package/dist/db.d.ts +131 -0
  23. package/dist/db.d.ts.map +1 -0
  24. package/dist/db.js +402 -0
  25. package/dist/desired-state-store.d.ts +100 -0
  26. package/dist/desired-state-store.d.ts.map +1 -0
  27. package/dist/desired-state-store.js +222 -0
  28. package/dist/doc-chunk-store.d.ts +140 -0
  29. package/dist/doc-chunk-store.d.ts.map +1 -0
  30. package/dist/doc-chunk-store.js +391 -0
  31. package/dist/doc-chunker.d.ts +99 -0
  32. package/dist/doc-chunker.d.ts.map +1 -0
  33. package/dist/doc-chunker.js +324 -0
  34. package/dist/dreaming-promoter.d.ts +86 -0
  35. package/dist/dreaming-promoter.d.ts.map +1 -0
  36. package/dist/dreaming-promoter.js +381 -0
  37. package/dist/episode-store.d.ts +49 -0
  38. package/dist/episode-store.d.ts.map +1 -0
  39. package/dist/episode-store.js +135 -0
  40. package/dist/fact-store.d.ts +75 -0
  41. package/dist/fact-store.d.ts.map +1 -0
  42. package/dist/fact-store.js +236 -0
  43. package/dist/fleet-store.d.ts +144 -0
  44. package/dist/fleet-store.d.ts.map +1 -0
  45. package/dist/fleet-store.js +276 -0
  46. package/dist/fos-mod.d.ts +178 -0
  47. package/dist/fos-mod.d.ts.map +1 -0
  48. package/dist/fos-mod.js +416 -0
  49. package/dist/hybrid-retrieval.d.ts +64 -0
  50. package/dist/hybrid-retrieval.d.ts.map +1 -0
  51. package/dist/hybrid-retrieval.js +344 -0
  52. package/dist/image-eviction.d.ts +49 -0
  53. package/dist/image-eviction.d.ts.map +1 -0
  54. package/dist/image-eviction.js +251 -0
  55. package/dist/index.d.ts +650 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +1072 -0
  58. package/dist/keystone-scorer.d.ts +51 -0
  59. package/dist/keystone-scorer.d.ts.map +1 -0
  60. package/dist/keystone-scorer.js +52 -0
  61. package/dist/knowledge-graph.d.ts +110 -0
  62. package/dist/knowledge-graph.d.ts.map +1 -0
  63. package/dist/knowledge-graph.js +305 -0
  64. package/dist/knowledge-lint.d.ts +29 -0
  65. package/dist/knowledge-lint.d.ts.map +1 -0
  66. package/dist/knowledge-lint.js +116 -0
  67. package/dist/knowledge-store.d.ts +72 -0
  68. package/dist/knowledge-store.d.ts.map +1 -0
  69. package/dist/knowledge-store.js +247 -0
  70. package/dist/library-schema.d.ts +22 -0
  71. package/dist/library-schema.d.ts.map +1 -0
  72. package/dist/library-schema.js +1038 -0
  73. package/dist/message-store.d.ts +89 -0
  74. package/dist/message-store.d.ts.map +1 -0
  75. package/dist/message-store.js +323 -0
  76. package/dist/metrics-dashboard.d.ts +114 -0
  77. package/dist/metrics-dashboard.d.ts.map +1 -0
  78. package/dist/metrics-dashboard.js +260 -0
  79. package/dist/obsidian-exporter.d.ts +57 -0
  80. package/dist/obsidian-exporter.d.ts.map +1 -0
  81. package/dist/obsidian-exporter.js +274 -0
  82. package/dist/obsidian-watcher.d.ts +147 -0
  83. package/dist/obsidian-watcher.d.ts.map +1 -0
  84. package/dist/obsidian-watcher.js +403 -0
  85. package/dist/open-domain.d.ts +46 -0
  86. package/dist/open-domain.d.ts.map +1 -0
  87. package/dist/open-domain.js +125 -0
  88. package/dist/preference-store.d.ts +54 -0
  89. package/dist/preference-store.d.ts.map +1 -0
  90. package/dist/preference-store.js +109 -0
  91. package/dist/preservation-gate.d.ts +82 -0
  92. package/dist/preservation-gate.d.ts.map +1 -0
  93. package/dist/preservation-gate.js +150 -0
  94. package/dist/proactive-pass.d.ts +63 -0
  95. package/dist/proactive-pass.d.ts.map +1 -0
  96. package/dist/proactive-pass.js +239 -0
  97. package/dist/profiles.d.ts +44 -0
  98. package/dist/profiles.d.ts.map +1 -0
  99. package/dist/profiles.js +227 -0
  100. package/dist/provider-translator.d.ts +50 -0
  101. package/dist/provider-translator.d.ts.map +1 -0
  102. package/dist/provider-translator.js +403 -0
  103. package/dist/rate-limiter.d.ts +76 -0
  104. package/dist/rate-limiter.d.ts.map +1 -0
  105. package/dist/rate-limiter.js +179 -0
  106. package/dist/repair-tool-pairs.d.ts +38 -0
  107. package/dist/repair-tool-pairs.d.ts.map +1 -0
  108. package/dist/repair-tool-pairs.js +138 -0
  109. package/dist/retrieval-policy.d.ts +51 -0
  110. package/dist/retrieval-policy.d.ts.map +1 -0
  111. package/dist/retrieval-policy.js +77 -0
  112. package/dist/schema.d.ts +15 -0
  113. package/dist/schema.d.ts.map +1 -0
  114. package/dist/schema.js +229 -0
  115. package/dist/secret-scanner.d.ts +51 -0
  116. package/dist/secret-scanner.d.ts.map +1 -0
  117. package/dist/secret-scanner.js +248 -0
  118. package/dist/seed.d.ts +108 -0
  119. package/dist/seed.d.ts.map +1 -0
  120. package/dist/seed.js +177 -0
  121. package/dist/session-flusher.d.ts +53 -0
  122. package/dist/session-flusher.d.ts.map +1 -0
  123. package/dist/session-flusher.js +69 -0
  124. package/dist/session-topic-map.d.ts +41 -0
  125. package/dist/session-topic-map.d.ts.map +1 -0
  126. package/dist/session-topic-map.js +77 -0
  127. package/dist/spawn-context.d.ts +54 -0
  128. package/dist/spawn-context.d.ts.map +1 -0
  129. package/dist/spawn-context.js +159 -0
  130. package/dist/system-store.d.ts +73 -0
  131. package/dist/system-store.d.ts.map +1 -0
  132. package/dist/system-store.js +182 -0
  133. package/dist/temporal-store.d.ts +80 -0
  134. package/dist/temporal-store.d.ts.map +1 -0
  135. package/dist/temporal-store.js +149 -0
  136. package/dist/topic-detector.d.ts +35 -0
  137. package/dist/topic-detector.d.ts.map +1 -0
  138. package/dist/topic-detector.js +249 -0
  139. package/dist/topic-store.d.ts +45 -0
  140. package/dist/topic-store.d.ts.map +1 -0
  141. package/dist/topic-store.js +136 -0
  142. package/dist/topic-synthesizer.d.ts +51 -0
  143. package/dist/topic-synthesizer.d.ts.map +1 -0
  144. package/dist/topic-synthesizer.js +315 -0
  145. package/dist/trigger-registry.d.ts +63 -0
  146. package/dist/trigger-registry.d.ts.map +1 -0
  147. package/dist/trigger-registry.js +163 -0
  148. package/dist/types.d.ts +537 -0
  149. package/dist/types.d.ts.map +1 -0
  150. package/dist/types.js +9 -0
  151. package/dist/vector-store.d.ts +170 -0
  152. package/dist/vector-store.d.ts.map +1 -0
  153. package/dist/vector-store.js +677 -0
  154. package/dist/version.d.ts +34 -0
  155. package/dist/version.d.ts.map +1 -0
  156. package/dist/version.js +34 -0
  157. package/dist/wiki-page-emitter.d.ts +65 -0
  158. package/dist/wiki-page-emitter.d.ts.map +1 -0
  159. package/dist/wiki-page-emitter.js +258 -0
  160. package/dist/work-store.d.ts +112 -0
  161. package/dist/work-store.d.ts.map +1 -0
  162. package/dist/work-store.js +273 -0
  163. package/package.json +4 -1
@@ -0,0 +1,239 @@
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 { classifyContentType } from './content-type-classifier.js';
26
+ // ─── Internal helpers ────────────────────────────────────────────
27
+ /**
28
+ * Resolve the safe window to a finite positive integer.
29
+ * Mirrors the ClawText resolveSafeWindow() guard.
30
+ */
31
+ function resolveSafeWindow(recentWindowSize) {
32
+ if (Number.isFinite(recentWindowSize) && recentWindowSize > 0) {
33
+ return Math.floor(recentWindowSize);
34
+ }
35
+ return 20;
36
+ }
37
+ /**
38
+ * Get the maximum message_index for a conversation.
39
+ * Returns -1 if no messages exist.
40
+ */
41
+ function getMaxMessageIndex(db, conversationId) {
42
+ const row = db
43
+ .prepare('SELECT COALESCE(MAX(message_index), -1) AS max_index FROM messages WHERE conversation_id = ?')
44
+ .get(conversationId);
45
+ return typeof row.max_index === 'number' ? row.max_index : -1;
46
+ }
47
+ /**
48
+ * Decide if a message is noise based on content + is_heartbeat flag.
49
+ *
50
+ * A message is noise when:
51
+ * - is_heartbeat = 1 (explicit heartbeat marker), OR
52
+ * - text_content is NULL or empty (≤3 chars after trimming), OR
53
+ * - classifyContentType() returns 'noise' or 'ack'
54
+ *
55
+ * We call the classifier rather than duplicating its patterns here.
56
+ */
57
+ function isNoiseMessage(textContent, isHeartbeat) {
58
+ if (isHeartbeat === 1)
59
+ return true;
60
+ if (textContent === null || textContent.trim().length <= 3)
61
+ return true;
62
+ const { type } = classifyContentType(textContent);
63
+ return type === 'noise' || type === 'ack';
64
+ }
65
+ // ─── Noise Sweep ─────────────────────────────────────────────────
66
+ /**
67
+ * Delete noise and heartbeat messages outside the recent window.
68
+ *
69
+ * "Outside the recent window" means message_index < maxIndex - recentWindowSize.
70
+ * Messages inside the window are never deleted, even if they are noise —
71
+ * the model may still reference them in the current turn.
72
+ *
73
+ * Deletions are wrapped in a single transaction. The FTS5 trigger handles
74
+ * index cleanup automatically (msg_fts_ad fires on DELETE).
75
+ */
76
+ export function runNoiseSweep(db, conversationId, recentWindowSize = 20) {
77
+ const ZERO = { messagesDeleted: 0, passType: 'noise_sweep' };
78
+ try {
79
+ const safeWindow = resolveSafeWindow(recentWindowSize);
80
+ const maxIndex = getMaxMessageIndex(db, conversationId);
81
+ if (maxIndex < 0)
82
+ return ZERO;
83
+ // Messages with message_index strictly below this value are eligible.
84
+ const cutoff = maxIndex - safeWindow;
85
+ if (cutoff <= 0)
86
+ return ZERO; // Not enough history yet
87
+ // Fetch all candidate messages outside the recent window.
88
+ // Exclude messages whose content lives entirely in tool_results — those
89
+ // are tool result rows handled by runToolDecay(), not noise sweep.
90
+ // We deliberately avoid a content-based WHERE clause for the classifier
91
+ // because SQLite can't use the index for JS classification logic;
92
+ // it's cheaper to fetch a small batch and classify in JS.
93
+ const candidates = db
94
+ .prepare(`
95
+ SELECT id, text_content, is_heartbeat
96
+ FROM messages
97
+ WHERE conversation_id = ?
98
+ AND message_index < ?
99
+ AND (tool_results IS NULL OR tool_results = '')
100
+ `)
101
+ .all(conversationId, cutoff);
102
+ if (candidates.length === 0)
103
+ return ZERO;
104
+ // Filter to noise messages
105
+ const toDelete = candidates.filter(row => isNoiseMessage(row.text_content, row.is_heartbeat));
106
+ if (toDelete.length === 0)
107
+ return ZERO;
108
+ const ids = toDelete.map(r => r.id);
109
+ // Delete in a transaction; use chunked IN clauses to avoid
110
+ // SQLite's SQLITE_LIMIT_VARIABLE_NUMBER (default 999).
111
+ let totalDeleted = 0;
112
+ const CHUNK = 500;
113
+ db.prepare('BEGIN').run();
114
+ try {
115
+ for (let i = 0; i < ids.length; i += CHUNK) {
116
+ const chunk = ids.slice(i, i + CHUNK);
117
+ const placeholders = chunk.map(() => '?').join(', ');
118
+ const result = db
119
+ .prepare(`DELETE FROM messages WHERE id IN (${placeholders})`)
120
+ .run(...chunk);
121
+ totalDeleted += typeof result.changes === 'number' ? result.changes : chunk.length;
122
+ }
123
+ db.prepare('COMMIT').run();
124
+ }
125
+ catch (innerErr) {
126
+ db.prepare('ROLLBACK').run();
127
+ throw innerErr;
128
+ }
129
+ return { messagesDeleted: totalDeleted, passType: 'noise_sweep' };
130
+ }
131
+ catch (err) {
132
+ console.warn(`[proactive-pass] Noise sweep failed for conversation ${conversationId}: ${err instanceof Error ? err.message : String(err)}`);
133
+ return ZERO;
134
+ }
135
+ }
136
+ // ─── Tool Decay ──────────────────────────────────────────────────
137
+ /**
138
+ * Truncate oversized tool_results outside the recent window.
139
+ *
140
+ * Strategy:
141
+ * 1. Find messages whose tool_results JSON string is > 2000 chars total,
142
+ * outside the recent window.
143
+ * 2. Parse the JSON array.
144
+ * 3. For each result entry where the `content` field exceeds 500 chars,
145
+ * replace `content` with `[tool result truncated — N bytes]`.
146
+ * 4. Re-serialize and write back.
147
+ *
148
+ * The JSON structure is preserved (array of result objects). Only the
149
+ * oversized `content` values are collapsed.
150
+ *
151
+ * Mutations are committed in a single transaction.
152
+ */
153
+ export function runToolDecay(db, conversationId, recentWindowSize = 40) {
154
+ const ZERO = { messagesUpdated: 0, bytesFreed: 0, passType: 'tool_decay' };
155
+ try {
156
+ const safeWindow = resolveSafeWindow(recentWindowSize);
157
+ const maxIndex = getMaxMessageIndex(db, conversationId);
158
+ if (maxIndex < 0)
159
+ return ZERO;
160
+ const cutoff = maxIndex - safeWindow;
161
+ if (cutoff <= 0)
162
+ return ZERO;
163
+ // Fetch messages with large tool_results outside the recent window.
164
+ const candidates = db
165
+ .prepare(`
166
+ SELECT id, tool_results
167
+ FROM messages
168
+ WHERE conversation_id = ?
169
+ AND message_index < ?
170
+ AND tool_results IS NOT NULL
171
+ AND length(tool_results) > 2000
172
+ `)
173
+ .all(conversationId, cutoff);
174
+ if (candidates.length === 0)
175
+ return ZERO;
176
+ // Build the update list by processing each candidate.
177
+ const updates = [];
178
+ for (const row of candidates) {
179
+ let parsed;
180
+ try {
181
+ parsed = JSON.parse(row.tool_results);
182
+ }
183
+ catch {
184
+ // Corrupt JSON — skip this row
185
+ continue;
186
+ }
187
+ if (!Array.isArray(parsed))
188
+ continue;
189
+ let changed = false;
190
+ const newResults = parsed.map(entry => {
191
+ const content = entry.content;
192
+ if (typeof content === 'string' && content.length > 500) {
193
+ const originalBytes = Buffer.byteLength(content, 'utf8');
194
+ changed = true;
195
+ return {
196
+ ...entry,
197
+ content: `[tool result truncated — ${originalBytes} bytes]`,
198
+ };
199
+ }
200
+ return entry;
201
+ });
202
+ if (!changed)
203
+ continue;
204
+ const newJson = JSON.stringify(newResults);
205
+ const savedBytes = row.tool_results.length - newJson.length;
206
+ if (savedBytes > 0) {
207
+ updates.push({ id: row.id, newJson, savedBytes });
208
+ }
209
+ }
210
+ if (updates.length === 0)
211
+ return ZERO;
212
+ let totalUpdated = 0;
213
+ let totalBytesFreed = 0;
214
+ db.prepare('BEGIN').run();
215
+ try {
216
+ const stmt = db.prepare('UPDATE messages SET tool_results = ? WHERE id = ?');
217
+ for (const { id, newJson, savedBytes } of updates) {
218
+ stmt.run(newJson, id);
219
+ totalUpdated++;
220
+ totalBytesFreed += savedBytes;
221
+ }
222
+ db.prepare('COMMIT').run();
223
+ }
224
+ catch (innerErr) {
225
+ db.prepare('ROLLBACK').run();
226
+ throw innerErr;
227
+ }
228
+ return {
229
+ messagesUpdated: totalUpdated,
230
+ bytesFreed: totalBytesFreed,
231
+ passType: 'tool_decay',
232
+ };
233
+ }
234
+ catch (err) {
235
+ console.warn(`[proactive-pass] Tool decay failed for conversation ${conversationId}: ${err instanceof Error ? err.message : String(err)}`);
236
+ return ZERO;
237
+ }
238
+ }
239
+ //# sourceMappingURL=proactive-pass.js.map
@@ -0,0 +1,44 @@
1
+ /**
2
+ * hypermem configuration profiles
3
+ *
4
+ * Pre-built configs for common deployment patterns. Pass to createHyperMem()
5
+ * directly or use as a base for custom configs via mergeProfile().
6
+ *
7
+ * Profiles:
8
+ * minimal — 64k context, single agent, low resource usage
9
+ * standard — 128k context, fleet default, balanced
10
+ * rich — 200k+ context, multi-agent, full feature set
11
+ */
12
+ import type { HyperMemConfig } from './types.js';
13
+ export declare const lightProfile: HyperMemConfig;
14
+ export declare const standardProfile: HyperMemConfig;
15
+ export declare const fullProfile: HyperMemConfig;
16
+ export type ProfileName = 'light' | 'standard' | 'full';
17
+ export declare const minimalProfile: HyperMemConfig;
18
+ export declare const extendedProfile: HyperMemConfig;
19
+ export declare const richProfile: HyperMemConfig;
20
+ export declare const PROFILES: Record<ProfileName, HyperMemConfig>;
21
+ /**
22
+ * Load a named profile.
23
+ *
24
+ * @example
25
+ * const config = getProfile('light');
26
+ * const hm = createHyperMem(config);
27
+ */
28
+ export declare function getProfile(name: ProfileName | 'extended'): HyperMemConfig;
29
+ /**
30
+ * Merge a partial config on top of a named profile.
31
+ * Deep-merges compositor and indexer; top-level fields are replaced.
32
+ *
33
+ * @example
34
+ * const config = mergeProfile('light', {
35
+ * cache: { keyPrefix: 'myapp:' },
36
+ * compositor: { outputProfile: 'standard' }, // upgrade tier
37
+ * });
38
+ */
39
+ export declare function mergeProfile(name: ProfileName | 'extended', overrides: DeepPartial<HyperMemConfig>): HyperMemConfig;
40
+ type DeepPartial<T> = {
41
+ [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
42
+ };
43
+ export {};
44
+ //# sourceMappingURL=profiles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profiles.d.ts","sourceRoot":"","sources":["../src/profiles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAyE,MAAM,YAAY,CAAC;AAiExH,eAAO,MAAM,YAAY,EAAE,cAa1B,CAAC;AA6CF,eAAO,MAAM,eAAe,EAAE,cAO7B,CAAC;AA8CF,eAAO,MAAM,WAAW,EAAE,cAWzB,CAAC;AAMF,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,CAAC;AAGxD,eAAO,MAAM,cAAc,gBAAe,CAAC;AAC3C,eAAO,MAAM,eAAe,gBAAc,CAAC;AAC3C,eAAO,MAAM,WAAW,gBAAc,CAAC;AAEvC,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,WAAW,EAAE,cAAc,CAIxD,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,WAAW,GAAG,UAAU,GAAG,cAAc,CAIzE;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,WAAW,GAAG,UAAU,EAC9B,SAAS,EAAE,WAAW,CAAC,cAAc,CAAC,GACrC,cAAc,CAUhB;AAMD,KAAK,WAAW,CAAC,CAAC,IAAI;KACnB,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAChE,CAAC"}
@@ -0,0 +1,227 @@
1
+ /**
2
+ * hypermem configuration profiles
3
+ *
4
+ * Pre-built configs for common deployment patterns. Pass to createHyperMem()
5
+ * directly or use as a base for custom configs via mergeProfile().
6
+ *
7
+ * Profiles:
8
+ * minimal — 64k context, single agent, low resource usage
9
+ * standard — 128k context, fleet default, balanced
10
+ * rich — 200k+ context, multi-agent, full feature set
11
+ */
12
+ // ---------------------------------------------------------------------------
13
+ // Shared base (fields common across all profiles)
14
+ // ---------------------------------------------------------------------------
15
+ const BASE_CACHE = {
16
+ keyPrefix: 'hm:',
17
+ sessionTTL: 3600,
18
+ historyTTL: 86400,
19
+ };
20
+ const BASE_EMBEDDING = {
21
+ ollamaUrl: 'http://localhost:11434',
22
+ model: 'nomic-embed-text',
23
+ dimensions: 768,
24
+ timeout: 10000,
25
+ batchSize: 32,
26
+ };
27
+ // ---------------------------------------------------------------------------
28
+ // light — 64k context window, single agent, constrained resources
29
+ //
30
+ // Design intent:
31
+ // - Small local models (Mistral 7B, Phi-3, Llama 3 8B) at 64k context
32
+ // - Single agent — no cross-session context needed
33
+ // - Minimal ACA stack — no dreaming, no background indexing overhead
34
+ // - Low Redis churn — longer flush intervals, shorter history window
35
+ // - outputProfile: 'light' — ~100 token standalone directives, no fleet concepts
36
+ // - No parallel operations — sequential fact extraction only
37
+ // ---------------------------------------------------------------------------
38
+ const LIGHT_COMPOSITOR = {
39
+ defaultTokenBudget: 40000, // leaves ~24k for model output at 64k window
40
+ maxHistoryMessages: 200, // keep window tight — small models lose coherence past ~150 msgs
41
+ maxFacts: 15, // surface top facts only, don't swamp the window
42
+ maxCrossSessionContext: 0, // single agent — no cross-session
43
+ maxRecentToolPairs: 2, // minimal tool history
44
+ maxProseToolPairs: 4,
45
+ warmHistoryBudgetFraction: 0.35, // slightly less history, more room for context
46
+ contextWindowReserve: 0.35, // generous reserve — small models need output headroom
47
+ dynamicReserveEnabled: true,
48
+ dynamicReserveTurnHorizon: 3, // shorter horizon — small sessions
49
+ dynamicReserveMax: 0.50,
50
+ keystoneHistoryFraction: 0.1, // light keystone — history window is already small
51
+ keystoneMaxMessages: 5,
52
+ keystoneMinSignificance: 0.7, // higher bar — only high-signal keystone messages
53
+ targetBudgetFraction: 0.50, // Anvil spec: 0.50 for light
54
+ maxTotalTriggerTokens: 1500, // tight trigger ceiling
55
+ outputProfile: 'light', // standalone density directives only, no fleet concepts
56
+ wikiTokenCap: 300, // Anvil spec: 300 for light
57
+ };
58
+ const LIGHT_INDEXER = {
59
+ enabled: true,
60
+ factExtractionMode: 'pattern', // pattern only — tiered extraction is heavier
61
+ topicDormantAfter: '12h', // faster dormancy — small systems don't need long windows
62
+ topicClosedAfter: '48h',
63
+ factDecayRate: 0.05, // slightly faster decay — fewer facts, keep them fresh
64
+ episodeSignificanceThreshold: 0.6, // higher bar — only store meaningful episodes
65
+ periodicInterval: 120000, // 2min — less frequent background work on small systems
66
+ batchSize: 64,
67
+ maxMessagesPerTick: 200,
68
+ };
69
+ export const lightProfile = {
70
+ enabled: true,
71
+ dataDir: './hypermem-data',
72
+ cache: BASE_CACHE,
73
+ compositor: LIGHT_COMPOSITOR,
74
+ indexer: LIGHT_INDEXER,
75
+ embedding: {
76
+ ...BASE_EMBEDDING,
77
+ batchSize: 8, // smaller batches — don't spike memory on embed
78
+ timeout: 15000, // more generous timeout — local hardware can be slow
79
+ },
80
+ // dreaming: disabled (default) — don't run background promotion on small systems
81
+ // obsidian: disabled (default)
82
+ };
83
+ // ---------------------------------------------------------------------------
84
+ // standard — 128k context window, fleet default, balanced
85
+ //
86
+ // Design intent:
87
+ // - Mid-range models (Sonnet, GPT-5-mini, Gemini Flash) at 128k
88
+ // - Small multi-agent setups or single power-user agents
89
+ // - Full ACA stack — dreaming optional, background indexing on
90
+ // - outputProfile: 'standard' — full FOS, no MOD
91
+ // ---------------------------------------------------------------------------
92
+ const STANDARD_COMPOSITOR = {
93
+ defaultTokenBudget: 90000,
94
+ maxHistoryMessages: 500,
95
+ maxFacts: 30,
96
+ maxCrossSessionContext: 4000,
97
+ maxRecentToolPairs: 3,
98
+ maxProseToolPairs: 10,
99
+ warmHistoryBudgetFraction: 0.40,
100
+ contextWindowReserve: 0.25,
101
+ dynamicReserveEnabled: true,
102
+ dynamicReserveTurnHorizon: 5,
103
+ dynamicReserveMax: 0.50,
104
+ keystoneHistoryFraction: 0.20,
105
+ keystoneMaxMessages: 15,
106
+ keystoneMinSignificance: 0.5,
107
+ targetBudgetFraction: 0.65, // Anvil spec: 0.65 for standard
108
+ maxTotalTriggerTokens: 4000,
109
+ outputProfile: 'standard', // full FOS, MOD suppressed
110
+ wikiTokenCap: 600, // Anvil spec: 600 for standard
111
+ };
112
+ const STANDARD_INDEXER = {
113
+ enabled: true,
114
+ factExtractionMode: 'tiered',
115
+ topicDormantAfter: '24h',
116
+ topicClosedAfter: '72h',
117
+ factDecayRate: 0.02,
118
+ episodeSignificanceThreshold: 0.5,
119
+ periodicInterval: 60000, // 1min — standard background cadence
120
+ batchSize: 128,
121
+ maxMessagesPerTick: 500,
122
+ };
123
+ export const standardProfile = {
124
+ enabled: true,
125
+ dataDir: './hypermem-data',
126
+ cache: BASE_CACHE,
127
+ compositor: STANDARD_COMPOSITOR,
128
+ indexer: STANDARD_INDEXER,
129
+ embedding: BASE_EMBEDDING,
130
+ };
131
+ // ---------------------------------------------------------------------------
132
+ // extended — 200k+ context window, multi-agent, full feature set
133
+ //
134
+ // Design intent:
135
+ // - Large context models (Opus, GPT-5.4, Gemini Pro) at 200k+
136
+ // - Council / multi-agent fleet deployments
137
+ // - Full ACA stack including dreaming, background indexing, cross-session
138
+ // - outputProfile: 'full' — FOS + MOD, full spec
139
+ // - Higher keystone threshold — more historical context worth surfacing
140
+ // ---------------------------------------------------------------------------
141
+ const EXTENDED_COMPOSITOR = {
142
+ defaultTokenBudget: 160000,
143
+ maxHistoryMessages: 1000,
144
+ maxFacts: 60,
145
+ maxCrossSessionContext: 12000,
146
+ maxRecentToolPairs: 5,
147
+ maxProseToolPairs: 15,
148
+ warmHistoryBudgetFraction: 0.45,
149
+ contextWindowReserve: 0.20,
150
+ dynamicReserveEnabled: true,
151
+ dynamicReserveTurnHorizon: 7,
152
+ dynamicReserveMax: 0.45,
153
+ keystoneHistoryFraction: 0.25,
154
+ keystoneMaxMessages: 30,
155
+ keystoneMinSignificance: 0.4,
156
+ targetBudgetFraction: 0.55, // Anvil spec: 0.55 for extended (history is huge, budget carefully)
157
+ maxTotalTriggerTokens: 10000,
158
+ outputProfile: 'full', // FOS + MOD — full fleet spec
159
+ wikiTokenCap: 800, // Anvil spec: 800 for extended
160
+ };
161
+ const EXTENDED_INDEXER = {
162
+ enabled: true,
163
+ factExtractionMode: 'tiered',
164
+ topicDormantAfter: '48h',
165
+ topicClosedAfter: '168h', // 7 days — long-running council topics stay warm
166
+ factDecayRate: 0.01, // slow decay — preserve more institutional memory
167
+ episodeSignificanceThreshold: 0.4,
168
+ periodicInterval: 30000, // 30s — frequent background work for fleet throughput
169
+ batchSize: 256,
170
+ maxMessagesPerTick: 1000,
171
+ };
172
+ export const fullProfile = {
173
+ enabled: true,
174
+ dataDir: './hypermem-data',
175
+ cache: BASE_CACHE,
176
+ compositor: EXTENDED_COMPOSITOR,
177
+ indexer: EXTENDED_INDEXER,
178
+ embedding: {
179
+ ...BASE_EMBEDDING,
180
+ batchSize: 64, // larger batches — more throughput for fleet ingest
181
+ timeout: 8000, // tighter timeout — expect capable hardware
182
+ },
183
+ };
184
+ // Legacy aliases — kept for backward compat, removed in 1.0
185
+ export const minimalProfile = lightProfile;
186
+ export const extendedProfile = fullProfile;
187
+ export const richProfile = fullProfile;
188
+ export const PROFILES = {
189
+ light: lightProfile,
190
+ standard: standardProfile,
191
+ full: fullProfile,
192
+ };
193
+ /**
194
+ * Load a named profile.
195
+ *
196
+ * @example
197
+ * const config = getProfile('light');
198
+ * const hm = createHyperMem(config);
199
+ */
200
+ export function getProfile(name) {
201
+ // backward compat
202
+ if (name === 'extended')
203
+ return structuredClone(fullProfile);
204
+ return structuredClone(PROFILES[name]);
205
+ }
206
+ /**
207
+ * Merge a partial config on top of a named profile.
208
+ * Deep-merges compositor and indexer; top-level fields are replaced.
209
+ *
210
+ * @example
211
+ * const config = mergeProfile('light', {
212
+ * cache: { keyPrefix: 'myapp:' },
213
+ * compositor: { outputProfile: 'standard' }, // upgrade tier
214
+ * });
215
+ */
216
+ export function mergeProfile(name, overrides) {
217
+ const base = getProfile(name);
218
+ return {
219
+ ...base,
220
+ ...overrides,
221
+ compositor: { ...base.compositor, ...(overrides.compositor ?? {}) },
222
+ indexer: { ...base.indexer, ...(overrides.indexer ?? {}) },
223
+ embedding: { ...base.embedding, ...(overrides.embedding ?? {}) },
224
+ cache: { ...base.cache, ...(overrides.cache ?? {}) },
225
+ };
226
+ }
227
+ //# sourceMappingURL=profiles.js.map
@@ -0,0 +1,50 @@
1
+ /**
2
+ * hypermem Provider Translator
3
+ *
4
+ * Converts between provider-neutral (NeutralMessage) and provider-specific formats.
5
+ * This is the ONLY place where provider-specific formatting exists.
6
+ * Storage is always neutral. Translation happens at the send/receive boundary.
7
+ *
8
+ * This eliminates grafting/stripping entirely — tool calls are stored as structured
9
+ * data, and each provider gets the format it expects at send time.
10
+ */
11
+ import type { NeutralMessage, NeutralToolResult, ProviderMessage } from './types.js';
12
+ /**
13
+ * Final pair-integrity sweep before provider translation.
14
+ *
15
+ * Invariant: never emit a tool_result unless its matching tool_use/tool_call
16
+ * exists in the immediately prior assistant message with the same ID.
17
+ *
18
+ * If the pair is broken, degrade the orphan tool_result into plain user text
19
+ * so providers never see an invalid tool_result block.
20
+ */
21
+ export declare function repairToolCallPairs(messages: NeutralMessage[]): NeutralMessage[];
22
+ /**
23
+ * Generate a hypermem-native tool call ID.
24
+ * These are provider-neutral and deterministic within a session.
25
+ */
26
+ export declare function generateToolCallId(): string;
27
+ /**
28
+ * Convert a provider-specific tool call ID to a hypermem ID.
29
+ * Deterministic: same input always produces same output.
30
+ */
31
+ export declare function normalizeToolCallId(providerId: string): string;
32
+ export type ProviderType = 'anthropic' | 'openai' | 'openai-responses' | 'unknown';
33
+ export declare function detectProvider(providerString: string | null | undefined): ProviderType;
34
+ /**
35
+ * Convert neutral messages to provider-specific format.
36
+ */
37
+ export declare function toProviderFormat(messages: NeutralMessage[], provider: string | null | undefined): ProviderMessage[];
38
+ /**
39
+ * Convert a provider-specific response to neutral format.
40
+ */
41
+ export declare function fromProviderFormat(response: Record<string, unknown>, provider: string): NeutralMessage;
42
+ /**
43
+ * Convert a user message (from chat input) to neutral format.
44
+ */
45
+ export declare function userMessageToNeutral(content: string, metadata?: Record<string, unknown>): NeutralMessage;
46
+ /**
47
+ * Convert tool results to a neutral user message.
48
+ */
49
+ export declare function toolResultsToNeutral(results: NeutralToolResult[]): NeutralMessage;
50
+ //# sourceMappingURL=provider-translator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-translator.d.ts","sourceRoot":"","sources":["../src/provider-translator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EACV,cAAc,EAEd,iBAAiB,EACjB,eAAe,EAChB,MAAM,YAAY,CAAC;AAYpB;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,cAAc,EAAE,CA4ChF;AAOD;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAK3C;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAI9D;AAID,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG,QAAQ,GAAG,kBAAkB,GAAG,SAAS,CAAC;AAEnF,wBAAgB,cAAc,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,YAAY,CAOtF;AAgMD;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,cAAc,EAAE,EAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAClC,eAAe,EAAE,CAenB;AA8ED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,QAAQ,EAAE,MAAM,GACf,cAAc,CAYhB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,cAAc,CAQxG;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,iBAAiB,EAAE,GAAG,cAAc,CAOjF"}