audrey 0.17.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/README.md +129 -374
  2. package/dist/mcp-server/config.d.ts +20 -0
  3. package/dist/mcp-server/config.d.ts.map +1 -0
  4. package/dist/mcp-server/config.js +125 -0
  5. package/dist/mcp-server/config.js.map +1 -0
  6. package/dist/mcp-server/index.d.ts +100 -0
  7. package/dist/mcp-server/index.d.ts.map +1 -0
  8. package/dist/mcp-server/index.js +1113 -0
  9. package/dist/mcp-server/index.js.map +1 -0
  10. package/dist/src/adaptive.d.ts +7 -0
  11. package/dist/src/adaptive.d.ts.map +1 -0
  12. package/dist/src/adaptive.js +49 -0
  13. package/dist/src/adaptive.js.map +1 -0
  14. package/dist/src/affect.d.ts +19 -0
  15. package/dist/src/affect.d.ts.map +1 -0
  16. package/dist/src/affect.js +72 -0
  17. package/dist/src/affect.js.map +1 -0
  18. package/dist/src/audrey.d.ts +140 -0
  19. package/dist/src/audrey.d.ts.map +1 -0
  20. package/dist/src/audrey.js +564 -0
  21. package/dist/src/audrey.js.map +1 -0
  22. package/dist/src/capsule.d.ts +68 -0
  23. package/dist/src/capsule.d.ts.map +1 -0
  24. package/dist/src/capsule.js +311 -0
  25. package/dist/src/capsule.js.map +1 -0
  26. package/dist/src/causal.d.ts +28 -0
  27. package/dist/src/causal.d.ts.map +1 -0
  28. package/dist/src/causal.js +65 -0
  29. package/dist/src/causal.js.map +1 -0
  30. package/dist/src/confidence.d.ts +12 -0
  31. package/dist/src/confidence.d.ts.map +1 -0
  32. package/dist/src/confidence.js +63 -0
  33. package/dist/src/confidence.js.map +1 -0
  34. package/dist/src/consolidate.d.ts +8 -0
  35. package/dist/src/consolidate.d.ts.map +1 -0
  36. package/dist/src/consolidate.js +218 -0
  37. package/dist/src/consolidate.js.map +1 -0
  38. package/dist/src/context.d.ts +3 -0
  39. package/dist/src/context.d.ts.map +1 -0
  40. package/dist/src/context.js +19 -0
  41. package/dist/src/context.js.map +1 -0
  42. package/dist/src/db.d.ts +12 -0
  43. package/dist/src/db.d.ts.map +1 -0
  44. package/dist/src/db.js +380 -0
  45. package/dist/src/db.js.map +1 -0
  46. package/dist/src/decay.d.ts +7 -0
  47. package/dist/src/decay.d.ts.map +1 -0
  48. package/dist/src/decay.js +68 -0
  49. package/dist/src/decay.js.map +1 -0
  50. package/dist/src/embedding.d.ts +57 -0
  51. package/dist/src/embedding.d.ts.map +1 -0
  52. package/dist/src/embedding.js +254 -0
  53. package/dist/src/embedding.js.map +1 -0
  54. package/dist/src/encode.d.ts +15 -0
  55. package/dist/src/encode.d.ts.map +1 -0
  56. package/dist/src/encode.js +36 -0
  57. package/dist/src/encode.js.map +1 -0
  58. package/dist/src/events.d.ts +69 -0
  59. package/dist/src/events.d.ts.map +1 -0
  60. package/dist/src/events.js +149 -0
  61. package/dist/src/events.js.map +1 -0
  62. package/dist/src/export.d.ts +3 -0
  63. package/dist/src/export.d.ts.map +1 -0
  64. package/dist/src/export.js +46 -0
  65. package/dist/src/export.js.map +1 -0
  66. package/dist/src/forget.d.ts +11 -0
  67. package/dist/src/forget.d.ts.map +1 -0
  68. package/dist/src/forget.js +105 -0
  69. package/dist/src/forget.js.map +1 -0
  70. package/dist/src/fts.d.ts +34 -0
  71. package/dist/src/fts.d.ts.map +1 -0
  72. package/dist/src/fts.js +117 -0
  73. package/dist/src/fts.js.map +1 -0
  74. package/dist/src/hybrid-recall.d.ts +37 -0
  75. package/dist/src/hybrid-recall.d.ts.map +1 -0
  76. package/dist/src/hybrid-recall.js +213 -0
  77. package/dist/src/hybrid-recall.js.map +1 -0
  78. package/dist/src/import.d.ts +4 -0
  79. package/dist/src/import.d.ts.map +1 -0
  80. package/dist/src/import.js +127 -0
  81. package/dist/src/import.js.map +1 -0
  82. package/dist/src/index.d.ts +22 -0
  83. package/dist/src/index.d.ts.map +1 -0
  84. package/{src → dist/src}/index.js +5 -13
  85. package/dist/src/index.js.map +1 -0
  86. package/dist/src/interference.d.ts +13 -0
  87. package/dist/src/interference.d.ts.map +1 -0
  88. package/dist/src/interference.js +45 -0
  89. package/dist/src/interference.js.map +1 -0
  90. package/dist/src/introspect.d.ts +4 -0
  91. package/dist/src/introspect.d.ts.map +1 -0
  92. package/dist/src/introspect.js +40 -0
  93. package/dist/src/introspect.js.map +1 -0
  94. package/dist/src/llm.d.ts +38 -0
  95. package/dist/src/llm.d.ts.map +1 -0
  96. package/dist/src/llm.js +167 -0
  97. package/dist/src/llm.js.map +1 -0
  98. package/dist/src/migrate.d.ts +6 -0
  99. package/dist/src/migrate.d.ts.map +1 -0
  100. package/dist/src/migrate.js +51 -0
  101. package/dist/src/migrate.js.map +1 -0
  102. package/dist/src/promote.d.ts +40 -0
  103. package/dist/src/promote.d.ts.map +1 -0
  104. package/dist/src/promote.js +200 -0
  105. package/dist/src/promote.js.map +1 -0
  106. package/dist/src/prompts.d.ts +16 -0
  107. package/dist/src/prompts.d.ts.map +1 -0
  108. package/{src → dist/src}/prompts.js +172 -203
  109. package/dist/src/prompts.js.map +1 -0
  110. package/dist/src/recall.d.ts +9 -0
  111. package/dist/src/recall.d.ts.map +1 -0
  112. package/dist/src/recall.js +432 -0
  113. package/dist/src/recall.js.map +1 -0
  114. package/dist/src/redact.d.ts +27 -0
  115. package/dist/src/redact.d.ts.map +1 -0
  116. package/dist/src/redact.js +228 -0
  117. package/dist/src/redact.js.map +1 -0
  118. package/dist/src/rollback.d.ts +8 -0
  119. package/dist/src/rollback.d.ts.map +1 -0
  120. package/dist/src/rollback.js +33 -0
  121. package/dist/src/rollback.js.map +1 -0
  122. package/dist/src/routes.d.ts +7 -0
  123. package/dist/src/routes.d.ts.map +1 -0
  124. package/dist/src/routes.js +226 -0
  125. package/dist/src/routes.js.map +1 -0
  126. package/dist/src/rules-compiler.d.ts +20 -0
  127. package/dist/src/rules-compiler.d.ts.map +1 -0
  128. package/dist/src/rules-compiler.js +143 -0
  129. package/dist/src/rules-compiler.js.map +1 -0
  130. package/dist/src/server.d.ts +12 -0
  131. package/dist/src/server.d.ts.map +1 -0
  132. package/dist/src/server.js +22 -0
  133. package/dist/src/server.js.map +1 -0
  134. package/dist/src/tool-trace.d.ts +37 -0
  135. package/dist/src/tool-trace.d.ts.map +1 -0
  136. package/dist/src/tool-trace.js +142 -0
  137. package/dist/src/tool-trace.js.map +1 -0
  138. package/dist/src/types.d.ts +446 -0
  139. package/dist/src/types.d.ts.map +1 -0
  140. package/dist/src/types.js +6 -0
  141. package/dist/src/types.js.map +1 -0
  142. package/dist/src/ulid.d.ts +3 -0
  143. package/dist/src/ulid.d.ts.map +1 -0
  144. package/dist/src/ulid.js +11 -0
  145. package/dist/src/ulid.js.map +1 -0
  146. package/dist/src/utils.d.ts +10 -0
  147. package/dist/src/utils.d.ts.map +1 -0
  148. package/dist/src/utils.js +41 -0
  149. package/dist/src/utils.js.map +1 -0
  150. package/dist/src/validate.d.ts +22 -0
  151. package/dist/src/validate.d.ts.map +1 -0
  152. package/dist/src/validate.js +109 -0
  153. package/dist/src/validate.js.map +1 -0
  154. package/docs/production-readiness.md +28 -0
  155. package/examples/fintech-ops-demo.js +1 -1
  156. package/examples/healthcare-ops-demo.js +1 -1
  157. package/examples/stripe-demo.js +1 -1
  158. package/package.json +34 -13
  159. package/benchmarks/baselines.js +0 -169
  160. package/benchmarks/cases.js +0 -421
  161. package/benchmarks/reference-results.js +0 -70
  162. package/benchmarks/report.js +0 -255
  163. package/benchmarks/run.js +0 -514
  164. package/mcp-server/config.js +0 -133
  165. package/mcp-server/index.js +0 -1265
  166. package/mcp-server/serve.js +0 -482
  167. package/src/adaptive.js +0 -53
  168. package/src/affect.js +0 -64
  169. package/src/audrey.js +0 -642
  170. package/src/causal.js +0 -95
  171. package/src/confidence.js +0 -120
  172. package/src/consolidate.js +0 -281
  173. package/src/context.js +0 -15
  174. package/src/db.js +0 -391
  175. package/src/decay.js +0 -84
  176. package/src/embedding.js +0 -260
  177. package/src/encode.js +0 -69
  178. package/src/export.js +0 -67
  179. package/src/forget.js +0 -111
  180. package/src/fts.js +0 -134
  181. package/src/import.js +0 -273
  182. package/src/interference.js +0 -51
  183. package/src/introspect.js +0 -48
  184. package/src/llm.js +0 -249
  185. package/src/migrate.js +0 -58
  186. package/src/recall.js +0 -573
  187. package/src/rollback.js +0 -42
  188. package/src/ulid.js +0 -18
  189. package/src/utils.js +0 -63
  190. package/src/validate.js +0 -172
  191. package/types/index.d.ts +0 -434
package/src/recall.js DELETED
@@ -1,573 +0,0 @@
1
- import { computeConfidence, DEFAULT_HALF_LIVES, salienceModifier, sourceReliability } from './confidence.js';
2
- import { interferenceModifier } from './interference.js';
3
- import { contextMatchRatio, contextModifier } from './context.js';
4
- import { moodCongruenceModifier, affectSimilarity } from './affect.js';
5
- import { daysBetween, safeJsonParse } from './utils.js';
6
- import { hasFTSTables, searchFTSEpisodes, searchFTSSemantics, searchFTSProcedures, sanitizeFTSQuery } from './fts.js';
7
-
8
- const STOPWORDS = new Set([
9
- 'a', 'an', 'and', 'are', 'at', 'be', 'by', 'did', 'do', 'does', 'for', 'from', 'had', 'has', 'have',
10
- 'how', 'i', 'in', 'is', 'it', 'me', 'my', 'now', 'of', 'on', 'or', 'our', 's', 'sam', 'she', 'that',
11
- 'the', 'their', 'them', 'there', 'they', 'this', 'to', 'was', 'we', 'were', 'what', 'when', 'where',
12
- 'which', 'who', 'why', 'with', 'would', 'you', 'your',
13
- ]);
14
-
15
- const IDENTIFIER_TERMS = new Set(['account', 'api', 'credential', 'id', 'identifier', 'key', 'number', 'password', 'secret', 'ssn', 'token']);
16
-
17
- function tokenize(text) {
18
- return String(text || '')
19
- .toLowerCase()
20
- .replace(/[^a-z0-9]+/g, ' ')
21
- .trim()
22
- .split(/\s+/)
23
- .filter(Boolean);
24
- }
25
-
26
- function significantTokens(text) {
27
- return tokenize(text).filter(token => !STOPWORDS.has(token));
28
- }
29
-
30
- function lexicalCoverage(query, content) {
31
- const queryTokens = significantTokens(query);
32
- if (queryTokens.length === 0) return 1;
33
- const contentTokens = new Set(significantTokens(content));
34
- let matched = 0;
35
- for (const token of queryTokens) {
36
- if (contentTokens.has(token)) matched++;
37
- }
38
- return matched / queryTokens.length;
39
- }
40
-
41
- function hasIdentifierIntent(query) {
42
- const normalized = String(query || '').toLowerCase();
43
- const asksForValue = /\b(find|give|lookup|show|tell|what|which)\b/.test(normalized);
44
- const mentionsIdentifier = /\b(account number|api key|credential|id|identifier|key|number|passport number|password|secret|ssn|token)\b/.test(normalized);
45
- return asksForValue && mentionsIdentifier;
46
- }
47
-
48
- function hasIdentifierEvidence(content) {
49
- const tokens = significantTokens(content);
50
- if (tokens.some(token => IDENTIFIER_TERMS.has(token))) {
51
- return true;
52
- }
53
- return /(?:\b\d{4,}\b|sk-[a-z0-9_-]+)/i.test(content);
54
- }
55
-
56
- function adjustedScore(query, entry) {
57
- const coverage = lexicalCoverage(query, entry.content);
58
- let score = entry.score;
59
-
60
- if (hasIdentifierIntent(query) && !hasIdentifierEvidence(entry.content)) {
61
- score *= 0.02;
62
- }
63
-
64
- return { score, coverage };
65
- }
66
-
67
- function overlapRatio(contentA, contentB) {
68
- const tokensA = significantTokens(contentA);
69
- const tokensB = significantTokens(contentB);
70
- if (tokensA.length === 0 || tokensB.length === 0) return 0;
71
- const setB = new Set(tokensB);
72
- let matched = 0;
73
- for (const token of tokensA) {
74
- if (setB.has(token)) matched++;
75
- }
76
- return matched / Math.min(tokensA.length, tokensB.length);
77
- }
78
-
79
- function reliabilityForRecallSource(source) {
80
- if (source === 'consolidation') {
81
- return sourceReliability('tool-result');
82
- }
83
- return sourceReliability(source);
84
- }
85
-
86
- function shouldSuppressDuplicate(existing, candidate) {
87
- const overlap = overlapRatio(existing.content, candidate.content);
88
- if (overlap < 0.5) return false;
89
- if (existing.type !== candidate.type) return false;
90
- const existingReliability = reliabilityForRecallSource(existing.source);
91
- const candidateReliability = reliabilityForRecallSource(candidate.source);
92
- if (existingReliability < candidateReliability) return false;
93
- if (existingReliability - candidateReliability < 0.2) return false;
94
- return existing.score >= candidate.score * 0.95;
95
- }
96
-
97
- function applyResultGuards(query, results, limit) {
98
- const identifierIntent = hasIdentifierIntent(query);
99
- const rescored = results
100
- .map(entry => {
101
- const { score, coverage } = adjustedScore(query, entry);
102
- return { ...entry, score, lexicalCoverage: coverage };
103
- })
104
- .filter(entry => !identifierIntent || entry.score > 0.05)
105
- .sort((a, b) => b.score - a.score);
106
-
107
- const accepted = [];
108
- for (const candidate of rescored) {
109
- if (accepted.some(existing => shouldSuppressDuplicate(existing, candidate))) {
110
- continue;
111
- }
112
- accepted.push(candidate);
113
- if (accepted.length >= limit) break;
114
- }
115
-
116
- return accepted;
117
- }
118
-
119
- function computeEpisodicConfidence(ep, now, confidenceConfig = {}) {
120
- const ageDays = daysBetween(ep.created_at, now);
121
- const halfLives = confidenceConfig.halfLives || DEFAULT_HALF_LIVES;
122
- let confidence = computeConfidence({
123
- sourceType: ep.source,
124
- supportingCount: 1,
125
- contradictingCount: 0,
126
- ageDays,
127
- halfLifeDays: halfLives.episodic ?? DEFAULT_HALF_LIVES.episodic,
128
- retrievalCount: 0,
129
- daysSinceRetrieval: ageDays,
130
- weights: confidenceConfig.weights,
131
- customSourceReliability: confidenceConfig.sourceReliability,
132
- });
133
- confidence *= salienceModifier(ep.salience);
134
- return Math.max(0, Math.min(1, confidence));
135
- }
136
-
137
- function computeSemanticConfidence(sem, now, confidenceConfig = {}) {
138
- const ageDays = daysBetween(sem.created_at, now);
139
- const daysSinceRetrieval = sem.last_reinforced_at
140
- ? daysBetween(sem.last_reinforced_at, now)
141
- : ageDays;
142
- const halfLives = confidenceConfig.halfLives || DEFAULT_HALF_LIVES;
143
- let confidence = computeConfidence({
144
- sourceType: 'tool-result',
145
- supportingCount: sem.supporting_count || 0,
146
- contradictingCount: sem.contradicting_count || 0,
147
- ageDays,
148
- halfLifeDays: halfLives.semantic ?? DEFAULT_HALF_LIVES.semantic,
149
- retrievalCount: sem.retrieval_count || 0,
150
- daysSinceRetrieval,
151
- weights: confidenceConfig.weights,
152
- customSourceReliability: confidenceConfig.sourceReliability,
153
- });
154
- confidence *= interferenceModifier(sem.interference_count || 0, confidenceConfig.interferenceWeight);
155
- confidence *= salienceModifier(sem.salience);
156
- return Math.max(0, Math.min(1, confidence));
157
- }
158
-
159
- function computeProceduralConfidence(proc, now, confidenceConfig = {}) {
160
- const ageDays = daysBetween(proc.created_at, now);
161
- const daysSinceRetrieval = proc.last_reinforced_at
162
- ? daysBetween(proc.last_reinforced_at, now)
163
- : ageDays;
164
- const halfLives = confidenceConfig.halfLives || DEFAULT_HALF_LIVES;
165
- let confidence = computeConfidence({
166
- sourceType: 'tool-result',
167
- supportingCount: proc.success_count || 0,
168
- contradictingCount: proc.failure_count || 0,
169
- ageDays,
170
- halfLifeDays: halfLives.procedural ?? DEFAULT_HALF_LIVES.procedural,
171
- retrievalCount: proc.retrieval_count || 0,
172
- daysSinceRetrieval,
173
- weights: confidenceConfig.weights,
174
- customSourceReliability: confidenceConfig.sourceReliability,
175
- });
176
- confidence *= interferenceModifier(proc.interference_count || 0, confidenceConfig.interferenceWeight);
177
- confidence *= salienceModifier(proc.salience);
178
- return Math.max(0, Math.min(1, confidence));
179
- }
180
-
181
- function buildEpisodicEntry(ep, confidence, score, includeProvenance, contextMatch, moodCongruence) {
182
- const entry = {
183
- id: ep.id,
184
- content: ep.content,
185
- type: 'episodic',
186
- confidence,
187
- score,
188
- source: ep.source,
189
- createdAt: ep.created_at,
190
- agent: ep.agent || 'default',
191
- };
192
- if (contextMatch !== undefined) {
193
- entry.contextMatch = contextMatch;
194
- }
195
- if (moodCongruence !== undefined) {
196
- entry.moodCongruence = moodCongruence;
197
- }
198
- if (includeProvenance) {
199
- entry.provenance = {
200
- source: ep.source,
201
- sourceReliability: ep.source_reliability,
202
- createdAt: ep.created_at,
203
- supersedes: ep.supersedes || null,
204
- };
205
- }
206
- return entry;
207
- }
208
-
209
- function buildSemanticEntry(sem, confidence, score, includeProvenance) {
210
- const entry = {
211
- id: sem.id,
212
- content: sem.content,
213
- type: 'semantic',
214
- confidence,
215
- score,
216
- source: 'consolidation',
217
- state: sem.state,
218
- createdAt: sem.created_at,
219
- agent: sem.agent || 'default',
220
- };
221
- if (includeProvenance) {
222
- entry.provenance = {
223
- evidenceEpisodeIds: safeJsonParse(sem.evidence_episode_ids, []),
224
- evidenceCount: sem.evidence_count || 0,
225
- supportingCount: sem.supporting_count || 0,
226
- contradictingCount: sem.contradicting_count || 0,
227
- consolidationCheckpoint: sem.consolidation_checkpoint || null,
228
- };
229
- }
230
- return entry;
231
- }
232
-
233
- function buildProceduralEntry(proc, confidence, score, includeProvenance) {
234
- const entry = {
235
- id: proc.id,
236
- content: proc.content,
237
- type: 'procedural',
238
- confidence,
239
- score,
240
- source: 'consolidation',
241
- state: proc.state,
242
- createdAt: proc.created_at,
243
- agent: proc.agent || 'default',
244
- };
245
- if (includeProvenance) {
246
- entry.provenance = {
247
- evidenceEpisodeIds: safeJsonParse(proc.evidence_episode_ids, []),
248
- successCount: proc.success_count || 0,
249
- failureCount: proc.failure_count || 0,
250
- triggerConditions: proc.trigger_conditions || null,
251
- };
252
- }
253
- return entry;
254
- }
255
-
256
- function stateClause(includeDormant) {
257
- return includeDormant
258
- ? "AND (v.state = 'active' OR v.state = 'context_dependent' OR v.state = 'dormant')"
259
- : "AND (v.state = 'active' OR v.state = 'context_dependent')";
260
- }
261
-
262
- function matchesDateFilters(createdAt, filters) {
263
- if (filters.after && createdAt <= filters.after) return false;
264
- if (filters.before && createdAt >= filters.before) return false;
265
- return true;
266
- }
267
-
268
- function safeKForTable(db, table, candidateK) {
269
- const rowCount = db.prepare(`SELECT COUNT(*) AS c FROM ${table}`).get().c;
270
- return rowCount > 0 ? Math.min(candidateK, rowCount) : 0;
271
- }
272
-
273
- function knnEpisodic(db, queryBuffer, candidateK, now, minConfidence, includeProvenance, confidenceConfig, filters = {}, includePrivate = false, agentFilter = null) {
274
- const safeK = safeKForTable(db, 'vec_episodes', candidateK);
275
- if (safeK === 0) return [];
276
- const privateClause = includePrivate ? '' : 'AND e."private" = 0';
277
- const agentClause = agentFilter ? 'AND e.agent = ?' : '';
278
- const params = agentFilter ? [queryBuffer, safeK, agentFilter] : [queryBuffer, safeK];
279
- const rows = db.prepare(`
280
- SELECT e.*, (1.0 - v.distance) AS similarity
281
- FROM vec_episodes v
282
- JOIN episodes e ON e.id = v.id
283
- WHERE v.embedding MATCH ?
284
- AND k = ?
285
- AND e.superseded_by IS NULL
286
- ${privateClause}
287
- ${agentClause}
288
- `).all(...params);
289
-
290
- const results = [];
291
- for (const row of rows) {
292
- if (!matchesDateFilters(row.created_at, filters)) continue;
293
- if (filters.tags?.length) {
294
- const rowTags = safeJsonParse(row.tags, []);
295
- if (!filters.tags.some(t => rowTags.includes(t))) continue;
296
- }
297
- if (filters.sources?.length && !filters.sources.includes(row.source)) continue;
298
- let confidence = computeEpisodicConfidence(row, now, confidenceConfig);
299
-
300
- let ctxMatch;
301
- if (confidenceConfig?.retrievalContext) {
302
- const encodingCtx = safeJsonParse(row.context, {});
303
- ctxMatch = contextMatchRatio(encodingCtx, confidenceConfig.retrievalContext);
304
- confidence *= contextModifier(encodingCtx, confidenceConfig.retrievalContext, confidenceConfig.contextWeight);
305
- confidence = Math.max(0, Math.min(1, confidence));
306
- }
307
-
308
- let moodMatch;
309
- if (confidenceConfig?.retrievalMood) {
310
- const encodingAffect = safeJsonParse(row.affect, {});
311
- moodMatch = affectSimilarity(encodingAffect, confidenceConfig.retrievalMood);
312
- confidence *= moodCongruenceModifier(encodingAffect, confidenceConfig.retrievalMood, confidenceConfig.affectWeight);
313
- confidence = Math.max(0, Math.min(1, confidence));
314
- }
315
-
316
- if (confidence < minConfidence) continue;
317
- const score = row.similarity * confidence;
318
- results.push(buildEpisodicEntry(row, confidence, score, includeProvenance, ctxMatch, moodMatch));
319
- }
320
- return results;
321
- }
322
-
323
- function knnSemantic(db, queryBuffer, candidateK, now, minConfidence, includeProvenance, includeDormant, confidenceConfig, filters = {}, agentFilter = null) {
324
- const safeK = safeKForTable(db, 'vec_semantics', candidateK);
325
- if (safeK === 0) return { results: [], matchedIds: [] };
326
- const agentClause = agentFilter ? 'AND s.agent = ?' : '';
327
- const params = agentFilter ? [queryBuffer, safeK, agentFilter] : [queryBuffer, safeK];
328
- const rows = db.prepare(`
329
- SELECT s.*, (1.0 - v.distance) AS similarity
330
- FROM vec_semantics v
331
- JOIN semantics s ON s.id = v.id
332
- WHERE v.embedding MATCH ?
333
- AND k = ?
334
- ${stateClause(includeDormant)}
335
- ${agentClause}
336
- `).all(...params);
337
-
338
- const results = [];
339
- const matchedIds = [];
340
- for (const row of rows) {
341
- if (!matchesDateFilters(row.created_at, filters)) continue;
342
- const confidence = computeSemanticConfidence(row, now, confidenceConfig);
343
- if (confidence < minConfidence) continue;
344
- const score = row.similarity * confidence;
345
- matchedIds.push(row.id);
346
- results.push(buildSemanticEntry(row, confidence, score, includeProvenance));
347
- }
348
- return { results, matchedIds };
349
- }
350
-
351
- function knnProcedural(db, queryBuffer, candidateK, now, minConfidence, includeProvenance, includeDormant, confidenceConfig, filters = {}, agentFilter = null) {
352
- const safeK = safeKForTable(db, 'vec_procedures', candidateK);
353
- if (safeK === 0) return { results: [], matchedIds: [] };
354
- const agentClause = agentFilter ? 'AND p.agent = ?' : '';
355
- const params = agentFilter ? [queryBuffer, safeK, agentFilter] : [queryBuffer, safeK];
356
- const rows = db.prepare(`
357
- SELECT p.*, (1.0 - v.distance) AS similarity
358
- FROM vec_procedures v
359
- JOIN procedures p ON p.id = v.id
360
- WHERE v.embedding MATCH ?
361
- AND k = ?
362
- ${stateClause(includeDormant)}
363
- ${agentClause}
364
- `).all(...params);
365
-
366
- const results = [];
367
- const matchedIds = [];
368
- for (const row of rows) {
369
- if (!matchesDateFilters(row.created_at, filters)) continue;
370
- const confidence = computeProceduralConfidence(row, now, confidenceConfig);
371
- if (confidence < minConfidence) continue;
372
- const score = row.similarity * confidence;
373
- matchedIds.push(row.id);
374
- results.push(buildProceduralEntry(row, confidence, score, includeProvenance));
375
- }
376
- return { results, matchedIds };
377
- }
378
-
379
- async function runRecallQuery(db, embeddingProvider, query, options = {}) {
380
- const {
381
- minConfidence = 0,
382
- types,
383
- limit = 10,
384
- includeProvenance = false,
385
- includeDormant = false,
386
- confidenceConfig,
387
- tags,
388
- sources,
389
- after,
390
- before,
391
- includePrivate = false,
392
- scope = 'shared',
393
- agent,
394
- retrieval = 'hybrid',
395
- } = options;
396
-
397
- const searchTypes = types || ['episodic', 'semantic', 'procedural'];
398
- const now = new Date();
399
- const agentFilter = scope === 'agent' && agent ? agent : null;
400
-
401
- // Keyword-only mode: FTS5 search without vector embeddings
402
- if (retrieval === 'keyword') {
403
- const ftsAvailable = hasFTSTables(db);
404
- if (!ftsAvailable) {
405
- return { top: [], errors: [] };
406
- }
407
- const sanitized = sanitizeFTSQuery(query);
408
- if (!sanitized) return { top: [], errors: [] };
409
-
410
- const keywordResults = [];
411
- try {
412
- if (searchTypes.includes('episodic')) {
413
- for (const row of searchFTSEpisodes(db, sanitized, limit * 3, agentFilter)) {
414
- keywordResults.push({ id: row.id, content: row.content, type: 'episodic', score: -row.rank, agent: row.agent || 'default' });
415
- }
416
- }
417
- if (searchTypes.includes('semantic')) {
418
- for (const row of searchFTSSemantics(db, sanitized, limit * 3, agentFilter)) {
419
- keywordResults.push({ id: row.id, content: row.content, type: 'semantic', score: -row.rank, agent: row.agent || 'default' });
420
- }
421
- }
422
- if (searchTypes.includes('procedural')) {
423
- for (const row of searchFTSProcedures(db, sanitized, limit * 3, agentFilter)) {
424
- keywordResults.push({ id: row.id, content: row.content, type: 'procedural', score: -row.rank, agent: row.agent || 'default' });
425
- }
426
- }
427
- } catch {
428
- // FTS query syntax error — fall through with whatever we have
429
- }
430
- keywordResults.sort((a, b) => b.score - a.score);
431
- const top = keywordResults.slice(0, limit).map(entry => ({
432
- ...entry,
433
- confidence: 1,
434
- source: 'keyword',
435
- createdAt: now.toISOString(),
436
- }));
437
- return { top, errors: [] };
438
- }
439
-
440
- const queryVector = await embeddingProvider.embed(query);
441
- const queryBuffer = embeddingProvider.vectorToBuffer(queryVector);
442
- const hasFilters = tags?.length || sources?.length || after || before;
443
- const candidateK = hasFilters ? limit * 5 : limit * 3;
444
- const filters = { tags, sources, after, before };
445
-
446
- const allResults = [];
447
- const errors = [];
448
-
449
- if (searchTypes.includes('episodic')) {
450
- try {
451
- const episodic = knnEpisodic(db, queryBuffer, candidateK, now, minConfidence, includeProvenance, confidenceConfig, filters, includePrivate, agentFilter);
452
- allResults.push(...episodic);
453
- } catch (err) {
454
- errors.push({ type: 'episodic', message: err.message });
455
- }
456
- }
457
-
458
- if (searchTypes.includes('semantic')) {
459
- try {
460
- const { results: semResults, matchedIds: semIds } =
461
- knnSemantic(db, queryBuffer, candidateK, now, minConfidence, includeProvenance, includeDormant, confidenceConfig, filters, agentFilter);
462
- allResults.push(...semResults);
463
-
464
- if (semIds.length > 0) {
465
- const nowISO = now.toISOString();
466
- const placeholders = semIds.map(() => '?').join(',');
467
- db.prepare(
468
- `UPDATE semantics SET retrieval_count = retrieval_count + 1, last_reinforced_at = ? WHERE id IN (${placeholders})`
469
- ).run(nowISO, ...semIds);
470
- }
471
- } catch (err) {
472
- errors.push({ type: 'semantic', message: err.message });
473
- }
474
- }
475
-
476
- if (searchTypes.includes('procedural')) {
477
- try {
478
- const { results: procResults, matchedIds: procIds } =
479
- knnProcedural(db, queryBuffer, candidateK, now, minConfidence, includeProvenance, includeDormant, confidenceConfig, filters, agentFilter);
480
- allResults.push(...procResults);
481
-
482
- if (procIds.length > 0) {
483
- const nowISO = now.toISOString();
484
- const placeholders = procIds.map(() => '?').join(',');
485
- db.prepare(
486
- `UPDATE procedures SET retrieval_count = retrieval_count + 1, last_reinforced_at = ? WHERE id IN (${placeholders})`
487
- ).run(nowISO, ...procIds);
488
- }
489
- } catch (err) {
490
- errors.push({ type: 'procedural', message: err.message });
491
- }
492
- }
493
-
494
- // Hybrid mode: merge vector results with FTS5 keyword results via RRF
495
- if (retrieval === 'hybrid' && hasFTSTables(db)) {
496
- const sanitized = sanitizeFTSQuery(query);
497
- if (sanitized) {
498
- const keywordHits = new Map();
499
- try {
500
- if (searchTypes.includes('episodic')) {
501
- for (const row of searchFTSEpisodes(db, sanitized, limit * 3, agentFilter)) {
502
- keywordHits.set(row.id, (keywordHits.get(row.id) || 0) + 1);
503
- }
504
- }
505
- if (searchTypes.includes('semantic')) {
506
- for (const row of searchFTSSemantics(db, sanitized, limit * 3, agentFilter)) {
507
- keywordHits.set(row.id, (keywordHits.get(row.id) || 0) + 1);
508
- }
509
- }
510
- if (searchTypes.includes('procedural')) {
511
- for (const row of searchFTSProcedures(db, sanitized, limit * 3, agentFilter)) {
512
- keywordHits.set(row.id, (keywordHits.get(row.id) || 0) + 1);
513
- }
514
- }
515
- } catch {
516
- // FTS query error — continue with vector-only results
517
- }
518
-
519
- // RRF boost: memories found by both vector AND keyword get a score bonus
520
- const RRF_K = 60;
521
- if (keywordHits.size > 0) {
522
- // Rank keyword results by their BM25 order
523
- const keywordRanks = new Map();
524
- let rank = 1;
525
- for (const id of keywordHits.keys()) {
526
- keywordRanks.set(id, rank++);
527
- }
528
-
529
- for (const result of allResults) {
530
- if (keywordRanks.has(result.id)) {
531
- // Boost score for results found by both vector AND keyword search
532
- const kRank = keywordRanks.get(result.id);
533
- const rrfBoost = 1 / (RRF_K + kRank);
534
- result.score = result.score + rrfBoost;
535
- }
536
- }
537
- }
538
- }
539
- }
540
-
541
- const top = applyResultGuards(query, allResults, limit);
542
- return { top, errors };
543
- }
544
-
545
- /**
546
- * @param {import('better-sqlite3').Database} db
547
- * @param {import('./embedding.js').EmbeddingProvider} embeddingProvider
548
- * @param {string} query
549
- * @param {{ minConfidence?: number, types?: string[], limit?: number, includeProvenance?: boolean, includeDormant?: boolean, tags?: string[], sources?: string[], after?: string, before?: string }} [options]
550
- * @returns {AsyncGenerator<{ id: string, content: string, type: string, confidence: number, score: number, source: string, createdAt: string }>}
551
- */
552
- export async function* recallStream(db, embeddingProvider, query, options = {}) {
553
- const { top, errors } = await runRecallQuery(db, embeddingProvider, query, options);
554
- for (const entry of top) {
555
- if (errors.length > 0) entry._recallErrors = errors;
556
- yield entry;
557
- }
558
- }
559
-
560
- /**
561
- * @param {import('better-sqlite3').Database} db
562
- * @param {import('./embedding.js').EmbeddingProvider} embeddingProvider
563
- * @param {string} query
564
- * @param {{ minConfidence?: number, types?: string[], limit?: number, includeProvenance?: boolean, includeDormant?: boolean, tags?: string[], sources?: string[], after?: string, before?: string }} [options]
565
- * @returns {Promise<Array<{ id: string, content: string, type: string, confidence: number, score: number, source: string, createdAt: string }>>}
566
- */
567
- export async function recall(db, embeddingProvider, query, options = {}) {
568
- const { top, errors } = await runRecallQuery(db, embeddingProvider, query, options);
569
- const results = [...top];
570
- results.partialFailure = errors.length > 0;
571
- results.errors = errors;
572
- return results;
573
- }
package/src/rollback.js DELETED
@@ -1,42 +0,0 @@
1
- import { safeJsonParse } from './utils.js';
2
-
3
- /**
4
- * @param {import('better-sqlite3').Database} db
5
- * @returns {Array<{ id: string, checkpoint_cursor: string|null, input_episode_ids: string, output_memory_ids: string, started_at: string, completed_at: string|null, status: string }>}
6
- */
7
- export function getConsolidationHistory(db) {
8
- return db.prepare(`
9
- SELECT id, checkpoint_cursor, input_episode_ids, output_memory_ids,
10
- started_at, completed_at, status
11
- FROM consolidation_runs ORDER BY started_at DESC
12
- `).all();
13
- }
14
-
15
- /**
16
- * @param {import('better-sqlite3').Database} db
17
- * @param {string} runId
18
- * @returns {{ rolledBackMemories: number, restoredEpisodes: number }}
19
- */
20
- export function rollbackConsolidation(db, runId) {
21
- const run = db.prepare('SELECT * FROM consolidation_runs WHERE id = ?').get(runId);
22
- if (!run) throw new Error(`Consolidation run not found: ${runId}`);
23
- if (run.status === 'rolled_back') throw new Error(`Run already rolled back: ${runId}`);
24
-
25
- const outputIds = safeJsonParse(run.output_memory_ids, []);
26
- const inputIds = safeJsonParse(run.input_episode_ids, []);
27
-
28
- const doRollback = db.transaction(() => {
29
- const markSemantics = db.prepare('UPDATE semantics SET state = ? WHERE id = ?');
30
- const markProcedures = db.prepare('UPDATE procedures SET state = ? WHERE id = ?');
31
- for (const id of outputIds) {
32
- markSemantics.run('rolled_back', id);
33
- markProcedures.run('rolled_back', id);
34
- }
35
- const unmark = db.prepare('UPDATE episodes SET consolidated = 0 WHERE id = ?');
36
- for (const id of inputIds) { unmark.run(id); }
37
- db.prepare('UPDATE consolidation_runs SET status = ? WHERE id = ?').run('rolled_back', runId);
38
- });
39
-
40
- doRollback();
41
- return { rolledBackMemories: outputIds.length, restoredEpisodes: inputIds.length };
42
- }
package/src/ulid.js DELETED
@@ -1,18 +0,0 @@
1
- import { monotonicFactory } from 'ulid';
2
- import { createHash } from 'node:crypto';
3
-
4
- const monotonic = monotonicFactory();
5
-
6
- /** @returns {string} */
7
- export function generateId() {
8
- return monotonic();
9
- }
10
-
11
- /**
12
- * @param {...*} parts
13
- * @returns {string}
14
- */
15
- export function generateDeterministicId(...parts) {
16
- const input = JSON.stringify(parts);
17
- return createHash('sha256').update(input).digest('hex').slice(0, 26);
18
- }
package/src/utils.js DELETED
@@ -1,63 +0,0 @@
1
- /**
2
- * @param {Buffer} bufA
3
- * @param {Buffer} bufB
4
- * @param {import('./embedding.js').EmbeddingProvider} provider
5
- * @returns {number}
6
- */
7
- export function cosineSimilarity(bufA, bufB, provider) {
8
- const a = provider.bufferToVector(bufA);
9
- const b = provider.bufferToVector(bufB);
10
- let dot = 0, magA = 0, magB = 0;
11
- for (let i = 0; i < a.length; i++) {
12
- dot += a[i] * b[i];
13
- magA += a[i] * a[i];
14
- magB += b[i] * b[i];
15
- }
16
- const mag = Math.sqrt(magA) * Math.sqrt(magB);
17
- return mag === 0 ? 0 : dot / mag;
18
- }
19
-
20
- /**
21
- * @param {string} dateStr
22
- * @param {Date} now
23
- * @returns {number}
24
- */
25
- export function daysBetween(dateStr, now) {
26
- return Math.max(0, (now.getTime() - new Date(dateStr).getTime()) / (1000 * 60 * 60 * 24));
27
- }
28
-
29
- /**
30
- * @param {string | null | undefined} str
31
- * @param {*} [fallback=null]
32
- * @returns {*}
33
- */
34
- export function safeJsonParse(str, fallback = null) {
35
- if (!str) return fallback;
36
- try { return JSON.parse(str); }
37
- catch { return fallback; }
38
- }
39
-
40
- /**
41
- * @param {string | undefined | null} apiKey
42
- * @param {string} operation
43
- * @param {string} envVar
44
- * @returns {void}
45
- */
46
- export function requireApiKey(apiKey, operation, envVar) {
47
- if (typeof apiKey !== 'string' || apiKey.trim() === '') {
48
- throw new Error(`${operation} requires ${envVar}`);
49
- }
50
- }
51
-
52
- /**
53
- * @param {{ status: number, text: () => Promise<string> }} response
54
- * @returns {Promise<string>}
55
- */
56
- export async function describeHttpError(response) {
57
- if (typeof response.text !== 'function') {
58
- return `${response.status}`;
59
- }
60
- const body = await response.text().catch(() => '');
61
- const normalized = body.replace(/\s+/g, ' ').trim().slice(0, 300);
62
- return normalized ? `${response.status} ${normalized}` : `${response.status}`;
63
- }