@bip-krishna/agentmind 0.1.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 (169) hide show
  1. package/.agentmind/memory.db +0 -0
  2. package/.context-memory/memory.db +0 -0
  3. package/README.md +176 -0
  4. package/apps/dashboard/src/server.ts +369 -0
  5. package/cli/src/index.ts +163 -0
  6. package/dist/apps/dashboard/src/server.d.ts +6 -0
  7. package/dist/apps/dashboard/src/server.d.ts.map +1 -0
  8. package/dist/apps/dashboard/src/server.js +360 -0
  9. package/dist/apps/dashboard/src/server.js.map +1 -0
  10. package/dist/cli/src/index.d.ts +3 -0
  11. package/dist/cli/src/index.d.ts.map +1 -0
  12. package/dist/cli/src/index.js +162 -0
  13. package/dist/cli/src/index.js.map +1 -0
  14. package/dist/examples/advanced.d.ts +2 -0
  15. package/dist/examples/advanced.d.ts.map +1 -0
  16. package/dist/examples/advanced.js +41 -0
  17. package/dist/examples/advanced.js.map +1 -0
  18. package/dist/examples/basic.d.ts +2 -0
  19. package/dist/examples/basic.d.ts.map +1 -0
  20. package/dist/examples/basic.js +22 -0
  21. package/dist/examples/basic.js.map +1 -0
  22. package/dist/packages/core/src/consolidation.d.ts +27 -0
  23. package/dist/packages/core/src/consolidation.d.ts.map +1 -0
  24. package/dist/packages/core/src/consolidation.js +97 -0
  25. package/dist/packages/core/src/consolidation.js.map +1 -0
  26. package/dist/packages/core/src/events.d.ts +18 -0
  27. package/dist/packages/core/src/events.d.ts.map +1 -0
  28. package/dist/packages/core/src/events.js +13 -0
  29. package/dist/packages/core/src/events.js.map +1 -0
  30. package/dist/packages/core/src/extraction.d.ts +32 -0
  31. package/dist/packages/core/src/extraction.d.ts.map +1 -0
  32. package/dist/packages/core/src/extraction.js +65 -0
  33. package/dist/packages/core/src/extraction.js.map +1 -0
  34. package/dist/packages/core/src/index.d.ts +10 -0
  35. package/dist/packages/core/src/index.d.ts.map +1 -0
  36. package/dist/packages/core/src/index.js +7 -0
  37. package/dist/packages/core/src/index.js.map +1 -0
  38. package/dist/packages/core/src/memory-store.d.ts +36 -0
  39. package/dist/packages/core/src/memory-store.d.ts.map +1 -0
  40. package/dist/packages/core/src/memory-store.js +2 -0
  41. package/dist/packages/core/src/memory-store.js.map +1 -0
  42. package/dist/packages/core/src/memory.d.ts +75 -0
  43. package/dist/packages/core/src/memory.d.ts.map +1 -0
  44. package/dist/packages/core/src/memory.js +402 -0
  45. package/dist/packages/core/src/memory.js.map +1 -0
  46. package/dist/packages/core/src/policy.d.ts +17 -0
  47. package/dist/packages/core/src/policy.d.ts.map +1 -0
  48. package/dist/packages/core/src/policy.js +22 -0
  49. package/dist/packages/core/src/policy.js.map +1 -0
  50. package/dist/packages/core/src/types.d.ts +102 -0
  51. package/dist/packages/core/src/types.d.ts.map +1 -0
  52. package/dist/packages/core/src/types.js +23 -0
  53. package/dist/packages/core/src/types.js.map +1 -0
  54. package/dist/packages/embeddings/src/index.d.ts +4 -0
  55. package/dist/packages/embeddings/src/index.d.ts.map +1 -0
  56. package/dist/packages/embeddings/src/index.js +4 -0
  57. package/dist/packages/embeddings/src/index.js.map +1 -0
  58. package/dist/packages/embeddings/src/local.d.ts +15 -0
  59. package/dist/packages/embeddings/src/local.d.ts.map +1 -0
  60. package/dist/packages/embeddings/src/local.js +45 -0
  61. package/dist/packages/embeddings/src/local.js.map +1 -0
  62. package/dist/packages/embeddings/src/openai.d.ts +17 -0
  63. package/dist/packages/embeddings/src/openai.d.ts.map +1 -0
  64. package/dist/packages/embeddings/src/openai.js +41 -0
  65. package/dist/packages/embeddings/src/openai.js.map +1 -0
  66. package/dist/packages/embeddings/src/provider.d.ts +8 -0
  67. package/dist/packages/embeddings/src/provider.d.ts.map +1 -0
  68. package/dist/packages/embeddings/src/provider.js +12 -0
  69. package/dist/packages/embeddings/src/provider.js.map +1 -0
  70. package/dist/packages/integrations/src/anthropic.d.ts +15 -0
  71. package/dist/packages/integrations/src/anthropic.d.ts.map +1 -0
  72. package/dist/packages/integrations/src/anthropic.js +25 -0
  73. package/dist/packages/integrations/src/anthropic.js.map +1 -0
  74. package/dist/packages/integrations/src/index.d.ts +4 -0
  75. package/dist/packages/integrations/src/index.d.ts.map +1 -0
  76. package/dist/packages/integrations/src/index.js +4 -0
  77. package/dist/packages/integrations/src/index.js.map +1 -0
  78. package/dist/packages/integrations/src/openai.d.ts +27 -0
  79. package/dist/packages/integrations/src/openai.d.ts.map +1 -0
  80. package/dist/packages/integrations/src/openai.js +26 -0
  81. package/dist/packages/integrations/src/openai.js.map +1 -0
  82. package/dist/packages/integrations/src/vercel.d.ts +24 -0
  83. package/dist/packages/integrations/src/vercel.d.ts.map +1 -0
  84. package/dist/packages/integrations/src/vercel.js +20 -0
  85. package/dist/packages/integrations/src/vercel.js.map +1 -0
  86. package/dist/packages/retrieval/src/index.d.ts +3 -0
  87. package/dist/packages/retrieval/src/index.d.ts.map +1 -0
  88. package/dist/packages/retrieval/src/index.js +3 -0
  89. package/dist/packages/retrieval/src/index.js.map +1 -0
  90. package/dist/packages/retrieval/src/ranker.d.ts +21 -0
  91. package/dist/packages/retrieval/src/ranker.d.ts.map +1 -0
  92. package/dist/packages/retrieval/src/ranker.js +54 -0
  93. package/dist/packages/retrieval/src/ranker.js.map +1 -0
  94. package/dist/packages/retrieval/src/token.d.ts +3 -0
  95. package/dist/packages/retrieval/src/token.d.ts.map +1 -0
  96. package/dist/packages/retrieval/src/token.js +7 -0
  97. package/dist/packages/retrieval/src/token.js.map +1 -0
  98. package/dist/packages/storage/src/config.d.ts +14 -0
  99. package/dist/packages/storage/src/config.d.ts.map +1 -0
  100. package/dist/packages/storage/src/config.js +41 -0
  101. package/dist/packages/storage/src/config.js.map +1 -0
  102. package/dist/packages/storage/src/index.d.ts +4 -0
  103. package/dist/packages/storage/src/index.d.ts.map +1 -0
  104. package/dist/packages/storage/src/index.js +4 -0
  105. package/dist/packages/storage/src/index.js.map +1 -0
  106. package/dist/packages/storage/src/memory-store.d.ts +37 -0
  107. package/dist/packages/storage/src/memory-store.d.ts.map +1 -0
  108. package/dist/packages/storage/src/memory-store.js +107 -0
  109. package/dist/packages/storage/src/memory-store.js.map +1 -0
  110. package/dist/packages/storage/src/migrations.d.ts +12 -0
  111. package/dist/packages/storage/src/migrations.d.ts.map +1 -0
  112. package/dist/packages/storage/src/migrations.js +89 -0
  113. package/dist/packages/storage/src/migrations.js.map +1 -0
  114. package/dist/packages/storage/src/postgres-store.d.ts +45 -0
  115. package/dist/packages/storage/src/postgres-store.d.ts.map +1 -0
  116. package/dist/packages/storage/src/postgres-store.js +282 -0
  117. package/dist/packages/storage/src/postgres-store.js.map +1 -0
  118. package/dist/packages/storage/src/sqlite-store.d.ts +36 -0
  119. package/dist/packages/storage/src/sqlite-store.d.ts.map +1 -0
  120. package/dist/packages/storage/src/sqlite-store.js +243 -0
  121. package/dist/packages/storage/src/sqlite-store.js.map +1 -0
  122. package/examples/advanced.ts +46 -0
  123. package/examples/basic.ts +25 -0
  124. package/package.json +52 -0
  125. package/packages/core/package.json +13 -0
  126. package/packages/core/src/consolidation.ts +104 -0
  127. package/packages/core/src/events.ts +25 -0
  128. package/packages/core/src/extraction.ts +81 -0
  129. package/packages/core/src/index.ts +22 -0
  130. package/packages/core/src/memory-store.ts +50 -0
  131. package/packages/core/src/memory.ts +447 -0
  132. package/packages/core/src/policy.ts +36 -0
  133. package/packages/core/src/types.ts +119 -0
  134. package/packages/core/tsconfig.json +6 -0
  135. package/packages/embeddings/package.json +9 -0
  136. package/packages/embeddings/src/index.ts +3 -0
  137. package/packages/embeddings/src/local.ts +47 -0
  138. package/packages/embeddings/src/openai.ts +50 -0
  139. package/packages/embeddings/src/provider.d.ts.map +1 -0
  140. package/packages/embeddings/src/provider.js.map +1 -0
  141. package/packages/embeddings/src/provider.ts +17 -0
  142. package/packages/embeddings/tsconfig.json +6 -0
  143. package/packages/integrations/package.json +7 -0
  144. package/packages/integrations/src/anthropic.ts +34 -0
  145. package/packages/integrations/src/index.ts +3 -0
  146. package/packages/integrations/src/openai.ts +52 -0
  147. package/packages/integrations/src/vercel.ts +26 -0
  148. package/packages/retrieval/package.json +8 -0
  149. package/packages/retrieval/src/index.ts +2 -0
  150. package/packages/retrieval/src/ranker.d.ts.map +1 -0
  151. package/packages/retrieval/src/ranker.js.map +1 -0
  152. package/packages/retrieval/src/ranker.ts +77 -0
  153. package/packages/retrieval/src/token.d.ts.map +1 -0
  154. package/packages/retrieval/src/token.js.map +1 -0
  155. package/packages/retrieval/src/token.ts +5 -0
  156. package/packages/retrieval/tsconfig.json +6 -0
  157. package/packages/storage/package.json +11 -0
  158. package/packages/storage/src/config.ts +44 -0
  159. package/packages/storage/src/index.ts +3 -0
  160. package/packages/storage/src/memory-store.ts +100 -0
  161. package/packages/storage/src/migrations.ts +96 -0
  162. package/packages/storage/src/postgres-store.ts +299 -0
  163. package/packages/storage/src/sqlite-store.ts +298 -0
  164. package/packages/storage/tsconfig.json +6 -0
  165. package/test-mvp.mjs +82 -0
  166. package/tests/consolidation.test.ts +45 -0
  167. package/tests/eval.test.ts +102 -0
  168. package/tsconfig.json +21 -0
  169. package/vitest.config.ts +8 -0
@@ -0,0 +1,447 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import {
3
+ CreateMemoryInputSchema,
4
+ type CreateMemoryInput,
5
+ type ContextOptions,
6
+ type ContextResult,
7
+ type Memory as MemoryRecord,
8
+ type MemoryType,
9
+ type SearchResult,
10
+ } from "./types.js";
11
+ import type { MemoryStore } from "./memory-store.js";
12
+ import type { EmbeddingProvider } from "../../embeddings/src/provider.js";
13
+ import { HybridRanker } from "../../retrieval/src/ranker.js";
14
+ import { estimateTokens } from "../../retrieval/src/token.js";
15
+ import { MemoryEvents } from "./events.js";
16
+ import { resolvePolicy, isSensitive, type MemoryPolicy } from "./policy.js";
17
+ import { Consolidator } from "./consolidation.js";
18
+ import type { ExtractorProvider } from "./extraction.js";
19
+ import { cosineSimilarity } from "../../embeddings/src/provider.js";
20
+
21
+ export interface MemoryCreateOptions {
22
+ project: string; // name or id
23
+ store?: MemoryStore;
24
+ embeddingProvider?: EmbeddingProvider;
25
+ extractor?: ExtractorProvider;
26
+ policy?: MemoryPolicy;
27
+ }
28
+
29
+ export type MemoryLifecycle = "extracted" | "candidate" | "validated" | "stored" | "retrieved" | "updated" | "archived";
30
+
31
+ export class MemoryEngine {
32
+ private store: MemoryStore;
33
+ private projectId: string;
34
+ private projectName: string;
35
+ private embeddingProvider?: EmbeddingProvider;
36
+ private extractor?: ExtractorProvider;
37
+ private policy: ReturnType<typeof resolvePolicy>;
38
+ public readonly events = new MemoryEvents();
39
+ private ranker: HybridRanker;
40
+ private consolidator: Consolidator;
41
+
42
+ private constructor(opts: { store: MemoryStore; projectId: string; projectName: string; embeddingProvider?: EmbeddingProvider; extractor?: ExtractorProvider; policy?: MemoryPolicy; ranker?: HybridRanker }) {
43
+ this.store = opts.store;
44
+ this.projectId = opts.projectId;
45
+ this.projectName = opts.projectName;
46
+ this.embeddingProvider = opts.embeddingProvider;
47
+ this.extractor = opts.extractor;
48
+ this.policy = resolvePolicy(opts.policy);
49
+ this.ranker = opts.ranker ?? new HybridRanker();
50
+ this.consolidator = new Consolidator(this.store, this.embeddingProvider);
51
+ }
52
+
53
+ static async create(opts: MemoryCreateOptions): Promise<MemoryEngine> {
54
+ const store: MemoryStore = (opts.store as MemoryStore) ?? (await import("../../storage/src/config.js").then(m => m.createStoreFromConfig(m.resolveStorageConfig())));
55
+ let project = await store.getProjectByName(opts.project);
56
+ if (!project) {
57
+ project = await store.getProject(opts.project);
58
+ }
59
+ if (!project) {
60
+ const now = new Date();
61
+ const p = { id: randomUUID(), name: opts.project, createdAt: now, updatedAt: now };
62
+ await store.createProject(p);
63
+ project = p;
64
+ }
65
+ return new MemoryEngine({
66
+ store,
67
+ projectId: project.id,
68
+ projectName: project.name,
69
+ embeddingProvider: opts.embeddingProvider,
70
+ extractor: opts.extractor,
71
+ policy: opts.policy,
72
+ });
73
+ }
74
+
75
+ get project() {
76
+ return { id: this.projectId, name: this.projectName };
77
+ }
78
+
79
+ // ---------- Ingest / Remember ----------
80
+
81
+ on<K extends Parameters<MemoryEvents["on"]>[0]>(event: K, listener: Parameters<MemoryEvents["on"]>[1]): this {
82
+ this.events.on(event as any, listener as any);
83
+ return this;
84
+ }
85
+
86
+ async remember(input: Partial<CreateMemoryInput> & { content: string; type?: MemoryType }): Promise<MemoryRecord> {
87
+ const parsed = CreateMemoryInputSchema.parse(input);
88
+ // Policy checks — minimumImportance applies only to auto-extracted memories; manual remember always allowed
89
+ if (!this.policy.storeSensitiveData && isSensitive(parsed.content)) {
90
+ throw new Error("Refusing to store sensitive data (policy.storeSensitiveData=false)");
91
+ }
92
+ if (this.policy.shouldRemember) {
93
+ const allowed = await this.policy.shouldRemember({
94
+ projectId: this.projectId,
95
+ type: parsed.type,
96
+ content: parsed.content,
97
+ importance: parsed.importance,
98
+ confidence: parsed.confidence,
99
+ status: "active",
100
+ createdAt: new Date(),
101
+ updatedAt: new Date(),
102
+ accessCount: 0,
103
+ metadata: parsed.metadata,
104
+ archived: false,
105
+ } as any);
106
+ if (!allowed) throw new Error("Policy shouldRemember returned false");
107
+ }
108
+ const count = (await this.store.listMemories(this.projectId)).filter((m) => !m.archived).length;
109
+ if (count >= this.policy.maxMemoriesPerProject) {
110
+ throw new Error(`maxMemoriesPerProject ${this.policy.maxMemoriesPerProject} reached`);
111
+ }
112
+ const now = new Date();
113
+ const mem: MemoryRecord = {
114
+ id: randomUUID(),
115
+ projectId: this.projectId,
116
+ type: parsed.type,
117
+ content: parsed.content,
118
+ importance: parsed.importance,
119
+ confidence: parsed.confidence,
120
+ status: "active",
121
+ createdAt: now,
122
+ updatedAt: now,
123
+ accessCount: 0,
124
+ sourceId: parsed.sourceId,
125
+ metadata: parsed.metadata,
126
+ archived: false,
127
+ };
128
+
129
+ // Deduplication: keyword similarity check against existing memories
130
+ const existing = await this.store.listMemories(this.projectId);
131
+ for (const e of existing) {
132
+ if (e.archived || e.status === "superseded") continue;
133
+ if (this.isNearDuplicate(e.content, mem.content)) {
134
+ // update existing instead of creating new
135
+ await this.store.updateMemory(e.id, {
136
+ content: mem.content,
137
+ importance: Math.max(e.importance, mem.importance),
138
+ updatedAt: now,
139
+ accessCount: e.accessCount,
140
+ });
141
+ if (this.embeddingProvider) {
142
+ const vec = await this.embeddingProvider.embed(mem.content);
143
+ await this.store.upsertEmbedding(e.id, vec, this.embeddingProvider.model);
144
+ }
145
+ const updated = await this.store.getMemory(e.id);
146
+ return updated!;
147
+ }
148
+ }
149
+
150
+ // Conflict detection (simple): if embedding similarity high but content contradicts via keyword overlap low? fallback to string includes
151
+ // For MVP: if we detect "switched to" / "migrated" / "instead of", supersede previous conflicting memory
152
+ await this.maybeSupersedeConflicting(mem);
153
+
154
+ await this.store.createMemory(mem);
155
+
156
+ if (this.embeddingProvider) {
157
+ const vec = await this.embeddingProvider.embed(mem.content);
158
+ await this.store.upsertEmbedding(mem.id, vec, this.embeddingProvider.model);
159
+ mem.embedding = vec;
160
+ }
161
+
162
+ this.events.emit("memory.created", mem);
163
+ return mem;
164
+ }
165
+
166
+ /** ingest raw conversation — stores source + auto-extracts if policy/extractor configured */
167
+ async ingest(input: { role: string; content: string; sourceType?: "conversation" | "document" | "tool" | "manual" }): Promise<{ sourceId: string; extracted?: MemoryRecord[] }> {
168
+ const sourceId = randomUUID();
169
+ await this.store.createSource({
170
+ id: sourceId,
171
+ projectId: this.projectId,
172
+ type: input.sourceType ?? "conversation",
173
+ content: `${input.role}: ${input.content}`,
174
+ createdAt: new Date(),
175
+ metadata: { role: input.role },
176
+ });
177
+ if (this.policy.autoExtract && this.extractor) {
178
+ const extracted = await this.extractor.extract(input.content);
179
+ const memories: MemoryRecord[] = [];
180
+ for (const e of extracted) {
181
+ if (e.importance < this.policy.minimumImportance) continue;
182
+ try {
183
+ const m = await this.remember({ type: e.type, content: e.content, importance: e.importance, confidence: e.confidence, sourceId, metadata: { autoExtracted: true } });
184
+ memories.push(m);
185
+ } catch {}
186
+ }
187
+ if (!this.policy.retainConversations) {
188
+ // optionally delete source after extraction — keep for traceability if needed
189
+ }
190
+ return { sourceId, extracted: memories };
191
+ }
192
+ return { sourceId };
193
+ }
194
+
195
+ // ---------- Search ----------
196
+
197
+ async search(query: string, opts?: { limit?: number; types?: MemoryType[]; minImportance?: number }): Promise<SearchResult[]> {
198
+ const limit = opts?.limit ?? 5;
199
+
200
+ // If we have embeddings, do semantic + keyword hybrid; else keyword fallback via store.search
201
+ if (this.embeddingProvider) {
202
+ const qVec = await this.embeddingProvider.embed(query);
203
+
204
+ // Prefer native pgvector search if available (faster, uses HNSW index)
205
+ if (this.store.vectorSearch) {
206
+ try {
207
+ const vectorResults = await this.store.vectorSearch(this.projectId, qVec, limit * 3);
208
+ const withVectors = vectorResults
209
+ .filter(({ memory }) => !memory.archived && memory.status !== "archived")
210
+ .filter(({ memory }) => !opts?.types || opts.types.includes(memory.type))
211
+ .filter(({ memory }) => opts?.minImportance === undefined || memory.importance >= opts.minImportance)
212
+ .map(({ memory, distance }) => ({ ...memory, _vec: undefined as any, _distance: distance }));
213
+ // Hybrid rank still applies keyword+importance+recency on top of vector distance
214
+ const candidates = this.ranker.rank(withVectors as any, query, qVec);
215
+ const top = candidates.slice(0, limit);
216
+ for (const c of top) {
217
+ await this.store.updateMemory(c.memory.id, { lastAccessedAt: new Date(), accessCount: c.memory.accessCount + 1 });
218
+ }
219
+ const eventId = randomUUID();
220
+ const scores: Record<string, number> = {};
221
+ top.forEach((r) => (scores[r.memory.id] = r.score));
222
+ const retrievalEvent = { id: eventId, projectId: this.projectId, query, retrievedMemoryIds: top.map((r) => r.memory.id), scores, finalContextTokens: estimateTokens(top.map((r) => r.memory.content).join("\n")), createdAt: new Date() };
223
+ await this.store.logRetrieval(retrievalEvent);
224
+ this.events.emit("retrieval.completed", retrievalEvent);
225
+ this.events.emit("memory.retrieved", top.map((r) => r.memory) as any, query);
226
+ return top.map((r) => ({ memory: r.memory, score: r.score, semanticScore: r.semanticScore, keywordScore: r.keywordScore }));
227
+ } catch {
228
+ // fallback to JS ranking
229
+ }
230
+ }
231
+
232
+ const all = await this.store.listMemories(this.projectId);
233
+ const withEmbeddings: Array<MemoryRecord & { _vec?: number[] }> = [];
234
+ for (const m of all) {
235
+ if (m.archived || m.status === "archived") continue;
236
+ if (opts?.types && !opts.types.includes(m.type)) continue;
237
+ if (opts?.minImportance !== undefined && m.importance < opts.minImportance) continue;
238
+ const emb = await this.store.getEmbedding(m.id);
239
+ withEmbeddings.push({ ...m, _vec: emb?.vector });
240
+ }
241
+
242
+ // rank via HybridRanker (semantic cosine + keyword + importance + recency)
243
+ const candidates = this.ranker.rank(withEmbeddings as any, query, qVec);
244
+ const top = candidates.slice(0, limit);
245
+
246
+ // update access counts
247
+ for (const c of top) {
248
+ await this.store.updateMemory(c.memory.id, {
249
+ lastAccessedAt: new Date(),
250
+ accessCount: c.memory.accessCount + 1,
251
+ });
252
+ }
253
+
254
+ // log retrieval
255
+ const eventId = randomUUID();
256
+ const scores: Record<string, number> = {};
257
+ top.forEach((r) => (scores[r.memory.id] = r.score));
258
+ const retrievalEvent = {
259
+ id: eventId,
260
+ projectId: this.projectId,
261
+ query,
262
+ retrievedMemoryIds: top.map((r) => r.memory.id),
263
+ scores,
264
+ finalContextTokens: estimateTokens(top.map((r) => r.memory.content).join("\n")),
265
+ createdAt: new Date(),
266
+ };
267
+ await this.store.logRetrieval(retrievalEvent);
268
+ this.events.emit("retrieval.completed", retrievalEvent);
269
+ this.events.emit("memory.retrieved", top.map((r) => r.memory) as any, query);
270
+
271
+ return top.map((r) => ({
272
+ memory: r.memory,
273
+ score: r.score,
274
+ semanticScore: r.semanticScore,
275
+ keywordScore: r.keywordScore,
276
+ }));
277
+ }
278
+
279
+ // fallback: store's keyword search
280
+ const results = await this.store.search({
281
+ projectId: this.projectId,
282
+ query,
283
+ types: opts?.types,
284
+ limit,
285
+ minImportance: opts?.minImportance,
286
+ });
287
+ for (const r of results) {
288
+ await this.store.updateMemory(r.memory.id, {
289
+ lastAccessedAt: new Date(),
290
+ accessCount: r.memory.accessCount + 1,
291
+ });
292
+ }
293
+ return results;
294
+ }
295
+
296
+ // ---------- Context ----------
297
+
298
+ async context(opts: ContextOptions): Promise<ContextResult>;
299
+ async context(query: string): Promise<ContextResult>;
300
+ async context(opts: string | ContextOptions): Promise<ContextResult> {
301
+ const q = typeof opts === "string" ? opts : opts.query;
302
+ const maxTokens = typeof opts === "string" ? 6000 : (opts.maxTokens ?? 6000);
303
+ const limit = typeof opts === "string" ? 5 : (opts.limit ?? 10);
304
+ const types = typeof opts === "string" ? undefined : opts.types;
305
+
306
+ const results = await this.search(q, { limit, types });
307
+ // token budgeting: greedily add until maxTokens
308
+ const memories: MemoryRecord[] = [];
309
+ let tokenCount = 0;
310
+ const header = `Relevant project memory (${this.projectName}):\n`;
311
+ tokenCount += estimateTokens(header);
312
+ let text = header;
313
+
314
+ for (const r of results) {
315
+ const chunk = `- ${r.memory.content}\n`;
316
+ const chunkTokens = estimateTokens(chunk);
317
+ if (tokenCount + chunkTokens > maxTokens) break;
318
+ memories.push(r.memory as any);
319
+ text += chunk;
320
+ tokenCount += chunkTokens;
321
+ }
322
+
323
+ if (memories.length === 0) {
324
+ text = "";
325
+ tokenCount = 0;
326
+ }
327
+
328
+ return {
329
+ memories,
330
+ text,
331
+ tokenCount,
332
+ sources: [...new Set(memories.map((m: any) => m.sourceId).filter(Boolean))] as string[],
333
+ };
334
+ }
335
+
336
+ // alias
337
+ buildContext(opts: ContextOptions) {
338
+ return this.context(opts);
339
+ }
340
+
341
+ // ---------- Lifecycle ----------
342
+
343
+ async forget(id: string): Promise<void> {
344
+ const mem = await this.store.getMemory(id);
345
+ await this.store.deleteMemory(id);
346
+ // Embeddings contain derived data — ensure deleted (store cascade handles it, but explicit for InMemory)
347
+ try { await (this.store as any).deleteEmbedding?.(id); } catch {}
348
+ this.events.emit("memory.deleted", id);
349
+ if (mem) this.events.emit("memory.archived", mem);
350
+ }
351
+
352
+ async forgetProject(projectId?: string): Promise<void> {
353
+ const pid = projectId ?? this.projectId;
354
+ const memories = await this.store.listMemories(pid);
355
+ for (const m of memories) await this.store.deleteMemory(m.id);
356
+ await this.store.deleteProject(pid);
357
+ }
358
+
359
+ async archive(id: string): Promise<void> {
360
+ await this.store.archiveMemory(id);
361
+ const mem = await this.store.getMemory(id);
362
+ if (mem) this.events.emit("memory.archived", mem);
363
+ }
364
+
365
+ async deleteProject(): Promise<void> {
366
+ await this.forgetProject(this.projectId);
367
+ }
368
+
369
+ async export(filter?: { includeArchived?: boolean }): Promise<{ project: { id: string; name: string }; memories: MemoryRecord[] }> {
370
+ let memories = await this.store.listMemories(this.projectId);
371
+ if (!filter?.includeArchived) memories = memories.filter((m) => !m.archived);
372
+ return { project: { id: this.projectId, name: this.projectName }, memories };
373
+ }
374
+
375
+ async consolidate(opts?: { threshold?: number; minClusterSize?: number }) {
376
+ const results = await this.consolidator.consolidate(this.projectId, opts);
377
+ for (const r of results) this.events.emit("consolidation.completed", r.summary, r.sourceIds);
378
+ return results;
379
+ }
380
+
381
+ async updateMemory(id: string, update: Partial<MemoryRecord>): Promise<void> {
382
+ await this.store.updateMemory(id, update);
383
+ const mem = await this.store.getMemory(id);
384
+ if (mem) this.events.emit("memory.updated", mem);
385
+ if (update.content && this.embeddingProvider && mem) {
386
+ const vec = await this.embeddingProvider.embed(mem.content);
387
+ await this.store.upsertEmbedding(id, vec, this.embeddingProvider.model);
388
+ }
389
+ }
390
+
391
+ // ---------- Helpers ----------
392
+
393
+ private isNearDuplicate(a: string, b: string): boolean {
394
+ const norm = (s: string) => s.toLowerCase().replace(/\W+/g, " ").trim();
395
+ const na = norm(a), nb = norm(b);
396
+ if (na === nb) return true;
397
+ // Jaccard over tokens threshold 0.85
398
+ const sa = new Set(na.split(" "));
399
+ const sb = new Set(nb.split(" "));
400
+ const inter = [...sa].filter((x) => sb.has(x)).length;
401
+ const union = new Set([...sa, ...sb]).size;
402
+ return union > 0 && inter / union > 0.85;
403
+ }
404
+
405
+ private async maybeSupersedeConflicting(newMem: MemoryRecord): Promise<void> {
406
+ const triggers = ["switched to", "migrated to", "changed to", "instead of", "no longer", "replaced", "now using", "moved to"];
407
+ const lower = newMem.content.toLowerCase();
408
+ const triggered = triggers.some((t) => lower.includes(t));
409
+ const existing = await this.store.listMemories(this.projectId);
410
+ const newTokens = new Set(lower.split(/\W+/).filter(Boolean));
411
+
412
+ // Embedding-aware conflict: if semantic similarity high but content contradicts
413
+ let newVec: number[] | null = null;
414
+ if (this.embeddingProvider) {
415
+ try { newVec = await this.embeddingProvider.embed(newMem.content); } catch {}
416
+ }
417
+
418
+ for (const e of existing) {
419
+ if (e.archived || e.status !== "active") continue;
420
+ const overlap = e.content.toLowerCase().split(/\W+/).filter((w) => newTokens.has(w)).length;
421
+ let shouldSupersede = false;
422
+
423
+ if (triggered && overlap >= 2 && !this.isNearDuplicate(e.content, newMem.content)) {
424
+ const nouns = ["database", "postgres", "mongodb", "sqlite", "storage", "backend", "auth", "payment", "api", "framework"];
425
+ const sharesNoun = nouns.some((n) => e.content.toLowerCase().includes(n) && lower.includes(n));
426
+ if (sharesNoun) shouldSupersede = true;
427
+ }
428
+
429
+ // Embedding similarity conflict: high similarity (>0.82) but not duplicate -> likely same topic updated
430
+ if (!shouldSupersede && newVec && triggered) {
431
+ const emb = await this.store.getEmbedding(e.id);
432
+ if (emb) {
433
+ const sim = cosineSimilarity(newVec, emb.vector);
434
+ if (sim > 0.82 && !this.isNearDuplicate(e.content, newMem.content)) shouldSupersede = true;
435
+ }
436
+ }
437
+
438
+ if (shouldSupersede) {
439
+ await this.store.updateMemory(e.id, { status: "superseded", archived: false, updatedAt: new Date() });
440
+ this.events.emit("memory.superseded", e, newMem);
441
+ }
442
+ }
443
+ }
444
+ }
445
+
446
+ // Convenience alias per spec: `Memory` class
447
+ export const Memory = MemoryEngine;
@@ -0,0 +1,36 @@
1
+ import type { Memory } from "./types.js";
2
+
3
+ export interface MemoryPolicy {
4
+ autoExtract?: boolean;
5
+ minimumImportance?: number; // default 0.65
6
+ maxMemoriesPerProject?: number; // default 10000
7
+ retainConversations?: boolean; // default false
8
+ storeSensitiveData?: boolean; // default false
9
+ shouldRemember?: (memory: Omit<Memory, "id" | "createdAt" | "updatedAt">) => boolean | Promise<boolean>;
10
+ maxTokensPerContext?: number; // default 6000
11
+ }
12
+
13
+ export const DEFAULT_POLICY: Required<Omit<MemoryPolicy, "shouldRemember">> & { shouldRemember?: MemoryPolicy["shouldRemember"] } = {
14
+ autoExtract: false,
15
+ minimumImportance: 0.65,
16
+ maxMemoriesPerProject: 10000,
17
+ retainConversations: false,
18
+ storeSensitiveData: false,
19
+ maxTokensPerContext: 6000,
20
+ };
21
+
22
+ export function resolvePolicy(overrides?: MemoryPolicy): typeof DEFAULT_POLICY {
23
+ return { ...DEFAULT_POLICY, ...overrides };
24
+ }
25
+
26
+ export const SENSITIVE_PATTERNS = [
27
+ /password/i,
28
+ /api[_-]?key/i,
29
+ /secret/i,
30
+ /\bssn\b/i,
31
+ /credit[_-]?card/i,
32
+ ];
33
+
34
+ export function isSensitive(content: string): boolean {
35
+ return SENSITIVE_PATTERNS.some((re) => re.test(content));
36
+ }
@@ -0,0 +1,119 @@
1
+ import { z } from "zod";
2
+
3
+ // ---------- Memory types ----------
4
+
5
+ export const MemoryTypeSchema = z.enum([
6
+ "fact",
7
+ "preference",
8
+ "decision",
9
+ "event",
10
+ "knowledge",
11
+ "procedure",
12
+ "task",
13
+ "summary",
14
+ ]);
15
+
16
+ export type MemoryType = z.infer<typeof MemoryTypeSchema>;
17
+
18
+ export const MemoryStatusSchema = z.enum(["active", "superseded", "archived"]);
19
+ export type MemoryStatus = z.infer<typeof MemoryStatusSchema>;
20
+
21
+ export interface Memory {
22
+ id: string;
23
+ projectId: string;
24
+ type: MemoryType;
25
+ content: string;
26
+ embedding?: number[];
27
+ importance: number; // 0-1
28
+ confidence: number; // 0-1
29
+ status: MemoryStatus;
30
+ createdAt: Date;
31
+ updatedAt: Date;
32
+ lastAccessedAt?: Date;
33
+ accessCount: number;
34
+ sourceId?: string;
35
+ metadata: Record<string, unknown>;
36
+ archived: boolean;
37
+ }
38
+
39
+ export interface Project {
40
+ id: string;
41
+ name: string;
42
+ description?: string;
43
+ createdAt: Date;
44
+ updatedAt: Date;
45
+ }
46
+
47
+ export type SourceType = "conversation" | "document" | "tool" | "manual";
48
+
49
+ export interface Source {
50
+ id: string;
51
+ projectId: string;
52
+ type: SourceType;
53
+ content?: string;
54
+ createdAt: Date;
55
+ metadata: Record<string, unknown>;
56
+ }
57
+
58
+ export interface RetrievalEvent {
59
+ id: string;
60
+ projectId: string;
61
+ query: string;
62
+ retrievedMemoryIds: string[];
63
+ scores: Record<string, number>;
64
+ finalContextTokens: number;
65
+ createdAt: Date;
66
+ }
67
+
68
+ // ---------- Search ----------
69
+
70
+ export interface SearchQuery {
71
+ projectId: string;
72
+ query: string;
73
+ types?: MemoryType[];
74
+ limit?: number;
75
+ minImportance?: number;
76
+ includeArchived?: boolean;
77
+ }
78
+
79
+ export interface CandidateMemory extends Memory {
80
+ semanticScore: number;
81
+ keywordScore: number;
82
+ finalScore: number;
83
+ }
84
+
85
+ export interface SearchResult {
86
+ memory: Memory;
87
+ score: number;
88
+ semanticScore: number;
89
+ keywordScore: number;
90
+ }
91
+
92
+ // ---------- Context ----------
93
+
94
+ export interface ContextOptions {
95
+ query: string;
96
+ maxTokens?: number; // default 6000
97
+ limit?: number;
98
+ types?: MemoryType[];
99
+ }
100
+
101
+ export interface ContextResult {
102
+ memories: Memory[];
103
+ text: string;
104
+ tokenCount: number;
105
+ sources: string[];
106
+ }
107
+
108
+ // ---------- Validation schemas ----------
109
+
110
+ export const CreateMemoryInputSchema = z.object({
111
+ type: MemoryTypeSchema.default("fact"),
112
+ content: z.string().min(1).max(10000),
113
+ importance: z.number().min(0).max(1).default(0.5),
114
+ confidence: z.number().min(0).max(1).default(1),
115
+ sourceId: z.string().optional(),
116
+ metadata: z.record(z.unknown()).default({}),
117
+ });
118
+
119
+ export type CreateMemoryInput = z.infer<typeof CreateMemoryInputSchema>;
@@ -0,0 +1,6 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "composite": false
5
+ }
6
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "@agentmind/embeddings",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "./dist/src/index.js",
6
+ "types": "./dist/src/index.d.ts",
7
+ "scripts": { "build": "tsc -b" },
8
+ "dependencies": { "zod": "^3.23.0" }
9
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./provider.js";
2
+ export * from "./openai.js";
3
+ export * from "./local.js";
@@ -0,0 +1,47 @@
1
+ import type { EmbeddingProvider } from "./provider.js";
2
+
3
+ /**
4
+ * Deterministic, zero-dependency local embedding for tests / offline dev.
5
+ * Hashing trick: char n-grams -> fixed dimensions, L2-normalized.
6
+ * NOT suitable for production quality but ensures storage contract works without API keys.
7
+ */
8
+ export class LocalEmbeddingProvider implements EmbeddingProvider {
9
+ readonly model = "local-hash-v1";
10
+ readonly dimensions: number;
11
+
12
+ constructor(dimensions = 256) {
13
+ this.dimensions = dimensions;
14
+ }
15
+
16
+ async embed(text: string): Promise<number[]> {
17
+ return this.hashEmbed(text);
18
+ }
19
+
20
+ async embedBatch(texts: string[]): Promise<number[][]> {
21
+ return texts.map((t) => this.hashEmbed(t));
22
+ }
23
+
24
+ private hashEmbed(text: string): number[] {
25
+ const vec = new Array(this.dimensions).fill(0);
26
+ const normalized = text.toLowerCase();
27
+ // char trigrams
28
+ for (let i = 0; i < normalized.length; i++) {
29
+ const trigram = normalized.slice(i, i + 3);
30
+ if (!trigram) continue;
31
+ let hash = 0;
32
+ for (let j = 0; j < trigram.length; j++) hash = (hash * 31 + trigram.charCodeAt(j)) >>> 0;
33
+ const idx = hash % this.dimensions;
34
+ vec[idx] += 1;
35
+ }
36
+ // token-level boost
37
+ for (const tok of normalized.split(/\W+/).filter(Boolean)) {
38
+ let hash = 0;
39
+ for (let j = 0; j < tok.length; j++) hash = (hash * 131 + tok.charCodeAt(j)) >>> 0;
40
+ const idx = (hash * 7) % this.dimensions;
41
+ vec[idx] += 2;
42
+ }
43
+ // L2 normalize
44
+ const norm = Math.sqrt(vec.reduce((s, v) => s + v * v, 0)) || 1;
45
+ return vec.map((v) => v / norm);
46
+ }
47
+ }