@waynesutton/agent-memory 0.0.1-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/.claude/settings.json +9 -0
  2. package/.claude/settings.local.json +7 -0
  3. package/AGENTS.md +113 -0
  4. package/CLAUDE.md +79 -0
  5. package/README.md +1003 -0
  6. package/dist/cli/index.d.ts +3 -0
  7. package/dist/cli/index.d.ts.map +1 -0
  8. package/dist/cli/index.js +192 -0
  9. package/dist/cli/index.js.map +1 -0
  10. package/dist/cli/parsers/claude-code.d.ts +3 -0
  11. package/dist/cli/parsers/claude-code.d.ts.map +1 -0
  12. package/dist/cli/parsers/claude-code.js +75 -0
  13. package/dist/cli/parsers/claude-code.js.map +1 -0
  14. package/dist/cli/parsers/codex.d.ts +3 -0
  15. package/dist/cli/parsers/codex.d.ts.map +1 -0
  16. package/dist/cli/parsers/codex.js +42 -0
  17. package/dist/cli/parsers/codex.js.map +1 -0
  18. package/dist/cli/parsers/conductor.d.ts +3 -0
  19. package/dist/cli/parsers/conductor.d.ts.map +1 -0
  20. package/dist/cli/parsers/conductor.js +43 -0
  21. package/dist/cli/parsers/conductor.js.map +1 -0
  22. package/dist/cli/parsers/cursor.d.ts +3 -0
  23. package/dist/cli/parsers/cursor.d.ts.map +1 -0
  24. package/dist/cli/parsers/cursor.js +50 -0
  25. package/dist/cli/parsers/cursor.js.map +1 -0
  26. package/dist/cli/parsers/index.d.ts +12 -0
  27. package/dist/cli/parsers/index.d.ts.map +1 -0
  28. package/dist/cli/parsers/index.js +27 -0
  29. package/dist/cli/parsers/index.js.map +1 -0
  30. package/dist/cli/parsers/opencode.d.ts +3 -0
  31. package/dist/cli/parsers/opencode.d.ts.map +1 -0
  32. package/dist/cli/parsers/opencode.js +72 -0
  33. package/dist/cli/parsers/opencode.js.map +1 -0
  34. package/dist/cli/parsers/parsers.test.d.ts +2 -0
  35. package/dist/cli/parsers/parsers.test.d.ts.map +1 -0
  36. package/dist/cli/parsers/parsers.test.js +151 -0
  37. package/dist/cli/parsers/parsers.test.js.map +1 -0
  38. package/dist/cli/parsers/pi.d.ts +3 -0
  39. package/dist/cli/parsers/pi.d.ts.map +1 -0
  40. package/dist/cli/parsers/pi.js +43 -0
  41. package/dist/cli/parsers/pi.js.map +1 -0
  42. package/dist/cli/parsers/types.d.ts +25 -0
  43. package/dist/cli/parsers/types.d.ts.map +1 -0
  44. package/dist/cli/parsers/types.js +2 -0
  45. package/dist/cli/parsers/types.js.map +1 -0
  46. package/dist/cli/parsers/vscode-copilot.d.ts +3 -0
  47. package/dist/cli/parsers/vscode-copilot.d.ts.map +1 -0
  48. package/dist/cli/parsers/vscode-copilot.js +69 -0
  49. package/dist/cli/parsers/vscode-copilot.js.map +1 -0
  50. package/dist/cli/parsers/zed.d.ts +3 -0
  51. package/dist/cli/parsers/zed.d.ts.map +1 -0
  52. package/dist/cli/parsers/zed.js +43 -0
  53. package/dist/cli/parsers/zed.js.map +1 -0
  54. package/dist/cli/sync.d.ts +21 -0
  55. package/dist/cli/sync.d.ts.map +1 -0
  56. package/dist/cli/sync.js +78 -0
  57. package/dist/cli/sync.js.map +1 -0
  58. package/dist/cli/type-extractor.d.ts +25 -0
  59. package/dist/cli/type-extractor.d.ts.map +1 -0
  60. package/dist/cli/type-extractor.js +254 -0
  61. package/dist/cli/type-extractor.js.map +1 -0
  62. package/dist/cli/type-extractor.test.d.ts +2 -0
  63. package/dist/cli/type-extractor.test.d.ts.map +1 -0
  64. package/dist/cli/type-extractor.test.js +173 -0
  65. package/dist/cli/type-extractor.test.js.map +1 -0
  66. package/dist/client/http.d.ts +44 -0
  67. package/dist/client/http.d.ts.map +1 -0
  68. package/dist/client/http.js +311 -0
  69. package/dist/client/http.js.map +1 -0
  70. package/dist/client/index.d.ts +158 -0
  71. package/dist/client/index.d.ts.map +1 -0
  72. package/dist/client/index.js +256 -0
  73. package/dist/client/index.js.map +1 -0
  74. package/dist/component/_generated/api.d.ts +12 -0
  75. package/dist/component/_generated/api.d.ts.map +1 -0
  76. package/dist/component/_generated/api.js +13 -0
  77. package/dist/component/_generated/api.js.map +1 -0
  78. package/dist/component/_generated/dataModel.d.ts +18 -0
  79. package/dist/component/_generated/dataModel.d.ts.map +1 -0
  80. package/dist/component/_generated/dataModel.js +11 -0
  81. package/dist/component/_generated/dataModel.js.map +1 -0
  82. package/dist/component/_generated/server.d.ts +42 -0
  83. package/dist/component/_generated/server.d.ts.map +1 -0
  84. package/dist/component/_generated/server.js +39 -0
  85. package/dist/component/_generated/server.js.map +1 -0
  86. package/dist/component/actions.d.ts +42 -0
  87. package/dist/component/actions.d.ts.map +1 -0
  88. package/dist/component/actions.js +405 -0
  89. package/dist/component/actions.js.map +1 -0
  90. package/dist/component/apiKeyMutations.d.ts +29 -0
  91. package/dist/component/apiKeyMutations.d.ts.map +1 -0
  92. package/dist/component/apiKeyMutations.js +149 -0
  93. package/dist/component/apiKeyMutations.js.map +1 -0
  94. package/dist/component/apiKeyQueries.d.ts +37 -0
  95. package/dist/component/apiKeyQueries.d.ts.map +1 -0
  96. package/dist/component/apiKeyQueries.js +127 -0
  97. package/dist/component/apiKeyQueries.js.map +1 -0
  98. package/dist/component/checksum.d.ts +6 -0
  99. package/dist/component/checksum.d.ts.map +1 -0
  100. package/dist/component/checksum.js +14 -0
  101. package/dist/component/checksum.js.map +1 -0
  102. package/dist/component/checksum.test.d.ts +2 -0
  103. package/dist/component/checksum.test.d.ts.map +1 -0
  104. package/dist/component/checksum.test.js +27 -0
  105. package/dist/component/checksum.test.js.map +1 -0
  106. package/dist/component/convex.config.d.ts +3 -0
  107. package/dist/component/convex.config.d.ts.map +1 -0
  108. package/dist/component/convex.config.js +4 -0
  109. package/dist/component/convex.config.js.map +1 -0
  110. package/dist/component/cronActions.d.ts +3 -0
  111. package/dist/component/cronActions.d.ts.map +1 -0
  112. package/dist/component/cronActions.js +38 -0
  113. package/dist/component/cronActions.js.map +1 -0
  114. package/dist/component/cronQueries.d.ts +6 -0
  115. package/dist/component/cronQueries.d.ts.map +1 -0
  116. package/dist/component/cronQueries.js +38 -0
  117. package/dist/component/cronQueries.js.map +1 -0
  118. package/dist/component/crons.d.ts +3 -0
  119. package/dist/component/crons.d.ts.map +1 -0
  120. package/dist/component/crons.js +18 -0
  121. package/dist/component/crons.js.map +1 -0
  122. package/dist/component/format.d.ts +11 -0
  123. package/dist/component/format.d.ts.map +1 -0
  124. package/dist/component/format.js +175 -0
  125. package/dist/component/format.js.map +1 -0
  126. package/dist/component/format.test.d.ts +2 -0
  127. package/dist/component/format.test.d.ts.map +1 -0
  128. package/dist/component/format.test.js +118 -0
  129. package/dist/component/format.test.js.map +1 -0
  130. package/dist/component/mutations.d.ts +158 -0
  131. package/dist/component/mutations.d.ts.map +1 -0
  132. package/dist/component/mutations.js +745 -0
  133. package/dist/component/mutations.js.map +1 -0
  134. package/dist/component/queries.d.ts +94 -0
  135. package/dist/component/queries.d.ts.map +1 -0
  136. package/dist/component/queries.js +574 -0
  137. package/dist/component/queries.js.map +1 -0
  138. package/dist/component/schema.d.ts +278 -0
  139. package/dist/component/schema.d.ts.map +1 -0
  140. package/dist/component/schema.js +161 -0
  141. package/dist/component/schema.js.map +1 -0
  142. package/dist/mcp/server.d.ts +11 -0
  143. package/dist/mcp/server.d.ts.map +1 -0
  144. package/dist/mcp/server.js +571 -0
  145. package/dist/mcp/server.js.map +1 -0
  146. package/dist/shared.d.ts +126 -0
  147. package/dist/shared.d.ts.map +1 -0
  148. package/dist/shared.js +67 -0
  149. package/dist/shared.js.map +1 -0
  150. package/dist/test.d.ts +23 -0
  151. package/dist/test.d.ts.map +1 -0
  152. package/dist/test.js +21 -0
  153. package/dist/test.js.map +1 -0
  154. package/eslint.config.js +15 -0
  155. package/example/convex/convex.config.ts +7 -0
  156. package/example/convex/memory.ts +129 -0
  157. package/llms.md +175 -0
  158. package/llms.txt +126 -0
  159. package/package.json +80 -0
  160. package/prds/API-REFERENCE.md +935 -0
  161. package/prds/SETUP.md +682 -0
  162. package/src/cli/index.ts +254 -0
  163. package/src/cli/parsers/claude-code.ts +80 -0
  164. package/src/cli/parsers/codex.ts +45 -0
  165. package/src/cli/parsers/conductor.ts +47 -0
  166. package/src/cli/parsers/cursor.ts +55 -0
  167. package/src/cli/parsers/index.ts +30 -0
  168. package/src/cli/parsers/opencode.ts +84 -0
  169. package/src/cli/parsers/parsers.test.ts +201 -0
  170. package/src/cli/parsers/pi.ts +47 -0
  171. package/src/cli/parsers/types.ts +26 -0
  172. package/src/cli/parsers/vscode-copilot.ts +78 -0
  173. package/src/cli/parsers/zed.ts +47 -0
  174. package/src/cli/sync.ts +110 -0
  175. package/src/cli/type-extractor.test.ts +241 -0
  176. package/src/cli/type-extractor.ts +331 -0
  177. package/src/client/http.ts +415 -0
  178. package/src/client/index.ts +519 -0
  179. package/src/component/_generated/api.ts +14 -0
  180. package/src/component/_generated/dataModel.ts +20 -0
  181. package/src/component/_generated/server.ts +64 -0
  182. package/src/component/actions.ts +558 -0
  183. package/src/component/apiKeyMutations.ts +175 -0
  184. package/src/component/apiKeyQueries.ts +156 -0
  185. package/src/component/checksum.test.ts +31 -0
  186. package/src/component/checksum.ts +13 -0
  187. package/src/component/convex.config.ts +5 -0
  188. package/src/component/cronActions.ts +52 -0
  189. package/src/component/cronQueries.ts +42 -0
  190. package/src/component/crons.ts +34 -0
  191. package/src/component/format.test.ts +133 -0
  192. package/src/component/format.ts +232 -0
  193. package/src/component/mutations.ts +824 -0
  194. package/src/component/queries.ts +684 -0
  195. package/src/component/schema.ts +207 -0
  196. package/src/mcp/server.ts +695 -0
  197. package/src/shared.ts +251 -0
  198. package/src/test.ts +32 -0
  199. package/tsconfig.json +21 -0
  200. package/vitest.config.ts +8 -0
@@ -0,0 +1,824 @@
1
+ import { mutation, internalMutation } from "./_generated/server.js";
2
+ import { v } from "convex/values";
3
+ import {
4
+ memoryTypeValidator,
5
+ scopeValidator,
6
+ syncDirectionValidator,
7
+ historyEventValidator,
8
+ feedbackSentimentValidator,
9
+ } from "./schema.js";
10
+ import { computeChecksum } from "./checksum.js";
11
+
12
+ // ── create ──────────────────────────────────────────────────────────
13
+
14
+ export const create = mutation({
15
+ args: {
16
+ projectId: v.string(),
17
+ scope: scopeValidator,
18
+ userId: v.optional(v.string()),
19
+ agentId: v.optional(v.string()),
20
+ sessionId: v.optional(v.string()),
21
+ title: v.string(),
22
+ content: v.string(),
23
+ memoryType: memoryTypeValidator,
24
+ tags: v.optional(v.array(v.string())),
25
+ paths: v.optional(v.array(v.string())),
26
+ priority: v.optional(v.float64()),
27
+ source: v.optional(v.string()),
28
+ },
29
+ returns: v.string(),
30
+ handler: async (ctx, args) => {
31
+ const checksum = computeChecksum(args.content);
32
+ const now = Date.now();
33
+ const id = await ctx.db.insert("memories", {
34
+ projectId: args.projectId,
35
+ scope: args.scope,
36
+ userId: args.userId,
37
+ agentId: args.agentId,
38
+ sessionId: args.sessionId,
39
+ title: args.title,
40
+ content: args.content,
41
+ memoryType: args.memoryType,
42
+ tags: args.tags ?? [],
43
+ paths: args.paths,
44
+ priority: args.priority,
45
+ source: args.source,
46
+ checksum,
47
+ archived: false,
48
+ accessCount: 0,
49
+ lastAccessedAt: now,
50
+ positiveCount: 0,
51
+ negativeCount: 0,
52
+ });
53
+
54
+ // Record history
55
+ await ctx.db.insert("memoryHistory", {
56
+ memoryId: id,
57
+ projectId: args.projectId,
58
+ newContent: args.content,
59
+ newTitle: args.title,
60
+ event: "created",
61
+ actor: args.source ?? args.agentId ?? args.userId ?? "unknown",
62
+ timestamp: now,
63
+ });
64
+
65
+ return id;
66
+ },
67
+ });
68
+
69
+ // ── update ──────────────────────────────────────────────────────────
70
+
71
+ export const update = mutation({
72
+ args: {
73
+ memoryId: v.string(),
74
+ content: v.optional(v.string()),
75
+ title: v.optional(v.string()),
76
+ tags: v.optional(v.array(v.string())),
77
+ paths: v.optional(v.array(v.string())),
78
+ priority: v.optional(v.float64()),
79
+ memoryType: v.optional(memoryTypeValidator),
80
+ actor: v.optional(v.string()),
81
+ },
82
+ returns: v.null(),
83
+ handler: async (ctx, args) => {
84
+ const id = ctx.db.normalizeId("memories", args.memoryId);
85
+ if (!id) throw new Error(`Invalid memory ID: ${args.memoryId}`);
86
+
87
+ const existing = await ctx.db.get(id);
88
+ if (!existing) throw new Error(`Memory not found: ${args.memoryId}`);
89
+
90
+ const patch: Record<string, unknown> = {};
91
+ if (args.content !== undefined) {
92
+ patch.content = args.content;
93
+ patch.checksum = computeChecksum(args.content);
94
+ }
95
+ if (args.title !== undefined) patch.title = args.title;
96
+ if (args.tags !== undefined) patch.tags = args.tags;
97
+ if (args.paths !== undefined) patch.paths = args.paths;
98
+ if (args.priority !== undefined) patch.priority = args.priority;
99
+ if (args.memoryType !== undefined) patch.memoryType = args.memoryType;
100
+
101
+ await ctx.db.patch(id, patch);
102
+
103
+ // Record history if content or title changed
104
+ if (args.content !== undefined || args.title !== undefined) {
105
+ await ctx.db.insert("memoryHistory", {
106
+ memoryId: id,
107
+ projectId: existing.projectId,
108
+ previousContent: args.content !== undefined ? existing.content : undefined,
109
+ newContent: args.content,
110
+ previousTitle: args.title !== undefined ? existing.title : undefined,
111
+ newTitle: args.title,
112
+ event: "updated",
113
+ actor: args.actor ?? "unknown",
114
+ timestamp: Date.now(),
115
+ });
116
+ }
117
+
118
+ return null;
119
+ },
120
+ });
121
+
122
+ // ── archive (soft-delete) ───────────────────────────────────────────
123
+
124
+ export const archive = mutation({
125
+ args: {
126
+ memoryId: v.string(),
127
+ actor: v.optional(v.string()),
128
+ },
129
+ returns: v.null(),
130
+ handler: async (ctx, args) => {
131
+ const id = ctx.db.normalizeId("memories", args.memoryId);
132
+ if (!id) throw new Error(`Invalid memory ID: ${args.memoryId}`);
133
+
134
+ const existing = await ctx.db.get(id);
135
+ if (!existing) throw new Error(`Memory not found: ${args.memoryId}`);
136
+
137
+ await ctx.db.patch(id, { archived: true });
138
+
139
+ await ctx.db.insert("memoryHistory", {
140
+ memoryId: id,
141
+ projectId: existing.projectId,
142
+ previousContent: existing.content,
143
+ previousTitle: existing.title,
144
+ event: "archived",
145
+ actor: args.actor ?? "unknown",
146
+ timestamp: Date.now(),
147
+ });
148
+
149
+ return null;
150
+ },
151
+ });
152
+
153
+ // ── restore (un-archive) ───────────────────────────────────────────
154
+
155
+ export const restore = mutation({
156
+ args: {
157
+ memoryId: v.string(),
158
+ actor: v.optional(v.string()),
159
+ },
160
+ returns: v.null(),
161
+ handler: async (ctx, args) => {
162
+ const id = ctx.db.normalizeId("memories", args.memoryId);
163
+ if (!id) throw new Error(`Invalid memory ID: ${args.memoryId}`);
164
+
165
+ const existing = await ctx.db.get(id);
166
+ if (!existing) throw new Error(`Memory not found: ${args.memoryId}`);
167
+
168
+ await ctx.db.patch(id, { archived: false });
169
+
170
+ await ctx.db.insert("memoryHistory", {
171
+ memoryId: id,
172
+ projectId: existing.projectId,
173
+ event: "restored",
174
+ actor: args.actor ?? "unknown",
175
+ timestamp: Date.now(),
176
+ });
177
+
178
+ return null;
179
+ },
180
+ });
181
+
182
+ // ── batchArchive ────────────────────────────────────────────────────
183
+
184
+ export const batchArchive = mutation({
185
+ args: {
186
+ memoryIds: v.array(v.string()),
187
+ actor: v.optional(v.string()),
188
+ },
189
+ returns: v.object({
190
+ archived: v.float64(),
191
+ failed: v.float64(),
192
+ }),
193
+ handler: async (ctx, args) => {
194
+ let archived = 0;
195
+ let failed = 0;
196
+ const now = Date.now();
197
+
198
+ for (const memoryId of args.memoryIds) {
199
+ const id = ctx.db.normalizeId("memories", memoryId);
200
+ if (!id) { failed++; continue; }
201
+
202
+ const existing = await ctx.db.get(id);
203
+ if (!existing) { failed++; continue; }
204
+
205
+ await ctx.db.patch(id, { archived: true });
206
+ await ctx.db.insert("memoryHistory", {
207
+ memoryId: id,
208
+ projectId: existing.projectId,
209
+ previousContent: existing.content,
210
+ previousTitle: existing.title,
211
+ event: "archived",
212
+ actor: args.actor ?? "unknown",
213
+ timestamp: now,
214
+ });
215
+ archived++;
216
+ }
217
+
218
+ return { archived, failed };
219
+ },
220
+ });
221
+
222
+ // ── batchUpdate ─────────────────────────────────────────────────────
223
+
224
+ export const batchUpdate = mutation({
225
+ args: {
226
+ updates: v.array(
227
+ v.object({
228
+ memoryId: v.string(),
229
+ content: v.optional(v.string()),
230
+ title: v.optional(v.string()),
231
+ tags: v.optional(v.array(v.string())),
232
+ paths: v.optional(v.array(v.string())),
233
+ priority: v.optional(v.float64()),
234
+ memoryType: v.optional(memoryTypeValidator),
235
+ }),
236
+ ),
237
+ actor: v.optional(v.string()),
238
+ },
239
+ returns: v.object({
240
+ updated: v.float64(),
241
+ failed: v.float64(),
242
+ }),
243
+ handler: async (ctx, args) => {
244
+ let updated = 0;
245
+ let failed = 0;
246
+ const now = Date.now();
247
+
248
+ for (const upd of args.updates) {
249
+ const id = ctx.db.normalizeId("memories", upd.memoryId);
250
+ if (!id) { failed++; continue; }
251
+
252
+ const existing = await ctx.db.get(id);
253
+ if (!existing) { failed++; continue; }
254
+
255
+ const patch: Record<string, unknown> = {};
256
+ if (upd.content !== undefined) {
257
+ patch.content = upd.content;
258
+ patch.checksum = computeChecksum(upd.content);
259
+ }
260
+ if (upd.title !== undefined) patch.title = upd.title;
261
+ if (upd.tags !== undefined) patch.tags = upd.tags;
262
+ if (upd.paths !== undefined) patch.paths = upd.paths;
263
+ if (upd.priority !== undefined) patch.priority = upd.priority;
264
+ if (upd.memoryType !== undefined) patch.memoryType = upd.memoryType;
265
+
266
+ await ctx.db.patch(id, patch);
267
+
268
+ if (upd.content !== undefined || upd.title !== undefined) {
269
+ await ctx.db.insert("memoryHistory", {
270
+ memoryId: id,
271
+ projectId: existing.projectId,
272
+ previousContent: upd.content !== undefined ? existing.content : undefined,
273
+ newContent: upd.content,
274
+ previousTitle: upd.title !== undefined ? existing.title : undefined,
275
+ newTitle: upd.title,
276
+ event: "updated",
277
+ actor: args.actor ?? "unknown",
278
+ timestamp: now,
279
+ });
280
+ }
281
+
282
+ updated++;
283
+ }
284
+
285
+ return { updated, failed };
286
+ },
287
+ });
288
+
289
+ // ── recordAccess (track memory reads for relevance) ─────────────────
290
+
291
+ export const recordAccess = mutation({
292
+ args: {
293
+ memoryIds: v.array(v.string()),
294
+ },
295
+ returns: v.null(),
296
+ handler: async (ctx, args) => {
297
+ const now = Date.now();
298
+ for (const memoryId of args.memoryIds) {
299
+ const id = ctx.db.normalizeId("memories", memoryId);
300
+ if (!id) continue;
301
+
302
+ const existing = await ctx.db.get(id);
303
+ if (!existing) continue;
304
+
305
+ await ctx.db.patch(id, {
306
+ accessCount: (existing.accessCount ?? 0) + 1,
307
+ lastAccessedAt: now,
308
+ });
309
+ }
310
+ return null;
311
+ },
312
+ });
313
+
314
+ // ── addFeedback ─────────────────────────────────────────────────────
315
+
316
+ export const addFeedback = mutation({
317
+ args: {
318
+ memoryId: v.string(),
319
+ sentiment: feedbackSentimentValidator,
320
+ comment: v.optional(v.string()),
321
+ actor: v.string(),
322
+ },
323
+ returns: v.null(),
324
+ handler: async (ctx, args) => {
325
+ const id = ctx.db.normalizeId("memories", args.memoryId);
326
+ if (!id) throw new Error(`Invalid memory ID: ${args.memoryId}`);
327
+
328
+ const existing = await ctx.db.get(id);
329
+ if (!existing) throw new Error(`Memory not found: ${args.memoryId}`);
330
+
331
+ // Record the feedback entry
332
+ await ctx.db.insert("memoryFeedback", {
333
+ memoryId: id,
334
+ projectId: existing.projectId,
335
+ sentiment: args.sentiment,
336
+ comment: args.comment,
337
+ actor: args.actor,
338
+ timestamp: Date.now(),
339
+ });
340
+
341
+ // Update aggregated counts on the memory
342
+ if (args.sentiment === "positive") {
343
+ await ctx.db.patch(id, {
344
+ positiveCount: (existing.positiveCount ?? 0) + 1,
345
+ });
346
+ } else {
347
+ await ctx.db.patch(id, {
348
+ negativeCount: (existing.negativeCount ?? 0) + 1,
349
+ });
350
+ }
351
+
352
+ return null;
353
+ },
354
+ });
355
+
356
+ // ── addRelation ─────────────────────────────────────────────────────
357
+
358
+ export const addRelation = mutation({
359
+ args: {
360
+ projectId: v.string(),
361
+ fromMemoryId: v.string(),
362
+ toMemoryId: v.string(),
363
+ relationship: v.string(),
364
+ metadata: v.optional(
365
+ v.object({
366
+ confidence: v.optional(v.float64()),
367
+ createdBy: v.optional(v.string()),
368
+ }),
369
+ ),
370
+ },
371
+ returns: v.string(),
372
+ handler: async (ctx, args) => {
373
+ const fromId = ctx.db.normalizeId("memories", args.fromMemoryId);
374
+ const toId = ctx.db.normalizeId("memories", args.toMemoryId);
375
+ if (!fromId) throw new Error(`Invalid from memory ID: ${args.fromMemoryId}`);
376
+ if (!toId) throw new Error(`Invalid to memory ID: ${args.toMemoryId}`);
377
+
378
+ const id = await ctx.db.insert("memoryRelations", {
379
+ projectId: args.projectId,
380
+ fromMemoryId: fromId,
381
+ toMemoryId: toId,
382
+ relationship: args.relationship,
383
+ metadata: args.metadata,
384
+ timestamp: Date.now(),
385
+ });
386
+
387
+ return id;
388
+ },
389
+ });
390
+
391
+ // ── removeRelation ──────────────────────────────────────────────────
392
+
393
+ export const removeRelation = mutation({
394
+ args: {
395
+ relationId: v.string(),
396
+ },
397
+ returns: v.null(),
398
+ handler: async (ctx, args) => {
399
+ const id = ctx.db.normalizeId("memoryRelations", args.relationId);
400
+ if (!id) throw new Error(`Invalid relation ID: ${args.relationId}`);
401
+
402
+ await ctx.db.delete(id);
403
+ return null;
404
+ },
405
+ });
406
+
407
+ // ── importFromLocal (bulk upsert) ───────────────────────────────────
408
+
409
+ const importMemoryValidator = v.object({
410
+ title: v.string(),
411
+ content: v.string(),
412
+ memoryType: memoryTypeValidator,
413
+ scope: scopeValidator,
414
+ tags: v.array(v.string()),
415
+ paths: v.optional(v.array(v.string())),
416
+ priority: v.optional(v.float64()),
417
+ source: v.string(),
418
+ checksum: v.string(),
419
+ });
420
+
421
+ export const importFromLocal = mutation({
422
+ args: {
423
+ projectId: v.string(),
424
+ userId: v.optional(v.string()),
425
+ memories: v.array(importMemoryValidator),
426
+ },
427
+ returns: v.object({
428
+ created: v.float64(),
429
+ updated: v.float64(),
430
+ unchanged: v.float64(),
431
+ }),
432
+ handler: async (ctx, args) => {
433
+ let created = 0;
434
+ let updated = 0;
435
+ let unchanged = 0;
436
+ const now = Date.now();
437
+
438
+ for (const mem of args.memories) {
439
+ const existing = await ctx.db
440
+ .query("memories")
441
+ .withIndex("by_project_title", (q: any) =>
442
+ q.eq("projectId", args.projectId).eq("title", mem.title),
443
+ )
444
+ .first();
445
+
446
+ if (!existing) {
447
+ const id = await ctx.db.insert("memories", {
448
+ projectId: args.projectId,
449
+ userId: args.userId,
450
+ title: mem.title,
451
+ content: mem.content,
452
+ memoryType: mem.memoryType,
453
+ scope: mem.scope,
454
+ tags: mem.tags,
455
+ paths: mem.paths,
456
+ priority: mem.priority,
457
+ source: mem.source,
458
+ checksum: mem.checksum,
459
+ archived: false,
460
+ accessCount: 0,
461
+ lastAccessedAt: now,
462
+ positiveCount: 0,
463
+ negativeCount: 0,
464
+ });
465
+
466
+ await ctx.db.insert("memoryHistory", {
467
+ memoryId: id,
468
+ projectId: args.projectId,
469
+ newContent: mem.content,
470
+ newTitle: mem.title,
471
+ event: "created",
472
+ actor: mem.source,
473
+ timestamp: now,
474
+ });
475
+
476
+ created++;
477
+ } else if (existing.checksum !== mem.checksum) {
478
+ await ctx.db.patch(existing._id, {
479
+ content: mem.content,
480
+ memoryType: mem.memoryType,
481
+ tags: mem.tags,
482
+ paths: mem.paths,
483
+ priority: mem.priority,
484
+ source: mem.source,
485
+ checksum: mem.checksum,
486
+ });
487
+
488
+ await ctx.db.insert("memoryHistory", {
489
+ memoryId: existing._id,
490
+ projectId: args.projectId,
491
+ previousContent: existing.content,
492
+ newContent: mem.content,
493
+ previousTitle: existing.title,
494
+ newTitle: mem.title,
495
+ event: "updated",
496
+ actor: mem.source,
497
+ timestamp: now,
498
+ });
499
+
500
+ updated++;
501
+ } else {
502
+ unchanged++;
503
+ }
504
+ }
505
+
506
+ return { created, updated, unchanged };
507
+ },
508
+ });
509
+
510
+ // ── upsertProject ───────────────────────────────────────────────────
511
+
512
+ export const upsertProject = mutation({
513
+ args: {
514
+ projectId: v.string(),
515
+ name: v.string(),
516
+ description: v.optional(v.string()),
517
+ settings: v.optional(
518
+ v.object({
519
+ autoSync: v.boolean(),
520
+ syncFormats: v.array(v.string()),
521
+ embeddingModel: v.optional(v.string()),
522
+ embeddingDimensions: v.optional(v.float64()),
523
+ factExtractionPrompt: v.optional(v.string()),
524
+ updateDecisionPrompt: v.optional(v.string()),
525
+ decayEnabled: v.optional(v.boolean()),
526
+ decayHalfLifeDays: v.optional(v.float64()),
527
+ }),
528
+ ),
529
+ },
530
+ returns: v.string(),
531
+ handler: async (ctx, args) => {
532
+ const existing = await ctx.db
533
+ .query("projects")
534
+ .withIndex("by_projectId", (q: any) => q.eq("projectId", args.projectId))
535
+ .first();
536
+
537
+ const settings = args.settings ?? {
538
+ autoSync: false,
539
+ syncFormats: [],
540
+ };
541
+
542
+ if (existing) {
543
+ await ctx.db.patch(existing._id, {
544
+ name: args.name,
545
+ description: args.description,
546
+ settings,
547
+ });
548
+ return existing._id;
549
+ }
550
+
551
+ const id = await ctx.db.insert("projects", {
552
+ projectId: args.projectId,
553
+ name: args.name,
554
+ description: args.description,
555
+ settings,
556
+ });
557
+ return id;
558
+ },
559
+ });
560
+
561
+ // ── recordSync ──────────────────────────────────────────────────────
562
+
563
+ export const recordSync = mutation({
564
+ args: {
565
+ projectId: v.string(),
566
+ userId: v.optional(v.string()),
567
+ memoryId: v.string(),
568
+ targetFormat: v.string(),
569
+ targetPath: v.string(),
570
+ checksum: v.string(),
571
+ direction: syncDirectionValidator,
572
+ },
573
+ returns: v.null(),
574
+ handler: async (ctx, args) => {
575
+ const memId = ctx.db.normalizeId("memories", args.memoryId);
576
+ if (!memId) throw new Error(`Invalid memory ID: ${args.memoryId}`);
577
+
578
+ const now = Date.now();
579
+
580
+ await ctx.db.insert("syncLog", {
581
+ projectId: args.projectId,
582
+ userId: args.userId,
583
+ memoryId: memId,
584
+ targetFormat: args.targetFormat,
585
+ targetPath: args.targetPath,
586
+ syncedAt: now,
587
+ checksum: args.checksum,
588
+ direction: args.direction,
589
+ });
590
+
591
+ await ctx.db.patch(memId, { lastSyncedAt: now });
592
+
593
+ return null;
594
+ },
595
+ });
596
+
597
+ // ── storeEmbedding ──────────────────────────────────────────────────
598
+
599
+ export const storeEmbedding = mutation({
600
+ args: {
601
+ memoryId: v.string(),
602
+ embedding: v.array(v.float64()),
603
+ model: v.string(),
604
+ dimensions: v.float64(),
605
+ },
606
+ returns: v.null(),
607
+ handler: async (ctx, args) => {
608
+ const memId = ctx.db.normalizeId("memories", args.memoryId);
609
+ if (!memId) throw new Error(`Invalid memory ID: ${args.memoryId}`);
610
+
611
+ const existing = await ctx.db
612
+ .query("embeddings")
613
+ .withIndex("by_memory", (q: any) => q.eq("memoryId", memId))
614
+ .first();
615
+
616
+ if (existing) {
617
+ await ctx.db.patch(existing._id, {
618
+ embedding: args.embedding,
619
+ model: args.model,
620
+ dimensions: args.dimensions,
621
+ });
622
+ await ctx.db.patch(memId, { embeddingId: existing._id });
623
+ } else {
624
+ const embeddingId = await ctx.db.insert("embeddings", {
625
+ memoryId: memId,
626
+ embedding: args.embedding,
627
+ model: args.model,
628
+ dimensions: args.dimensions,
629
+ });
630
+ await ctx.db.patch(memId, { embeddingId });
631
+ }
632
+
633
+ return null;
634
+ },
635
+ });
636
+
637
+ // ── Internal: applyDecay (called by cron) ───────────────────────────
638
+
639
+ export const applyDecay = internalMutation({
640
+ args: {
641
+ projectId: v.string(),
642
+ halfLifeDays: v.float64(),
643
+ },
644
+ returns: v.object({
645
+ processed: v.float64(),
646
+ decayed: v.float64(),
647
+ }),
648
+ handler: async (ctx, args) => {
649
+ const now = Date.now();
650
+ const halfLifeMs = args.halfLifeDays * 24 * 60 * 60 * 1000;
651
+
652
+ const memories = await ctx.db
653
+ .query("memories")
654
+ .withIndex("by_project", (q: any) =>
655
+ q.eq("projectId", args.projectId).eq("archived", false),
656
+ )
657
+ .take(500);
658
+
659
+ let processed = 0;
660
+ let decayed = 0;
661
+
662
+ for (const m of memories) {
663
+ if ((m.priority ?? 0) >= 0.8) continue; // don't decay pinned memories
664
+ processed++;
665
+
666
+ const lastAccess = m.lastAccessedAt ?? m._creationTime;
667
+ const timeSinceAccess = now - lastAccess;
668
+
669
+ // Exponential decay: score = base * 0.5^(t/halfLife)
670
+ const decayFactor = Math.pow(0.5, timeSinceAccess / halfLifeMs);
671
+
672
+ // If very low access and old, reduce priority
673
+ if (decayFactor < 0.1 && (m.accessCount ?? 0) < 3) {
674
+ const currentPriority = m.priority ?? 0.5;
675
+ const newPriority = Math.max(0.01, currentPriority * decayFactor);
676
+ if (Math.abs(newPriority - currentPriority) > 0.01) {
677
+ await ctx.db.patch(m._id, { priority: newPriority });
678
+ decayed++;
679
+ }
680
+ }
681
+ }
682
+
683
+ return { processed, decayed };
684
+ },
685
+ });
686
+
687
+ // ── Internal: cleanupOldHistory ─────────────────────────────────────
688
+
689
+ export const cleanupOldHistory = internalMutation({
690
+ args: {
691
+ projectId: v.string(),
692
+ olderThanMs: v.float64(),
693
+ },
694
+ returns: v.object({ deleted: v.float64() }),
695
+ handler: async (ctx, args) => {
696
+ const cutoff = Date.now() - args.olderThanMs;
697
+ const old = await ctx.db
698
+ .query("memoryHistory")
699
+ .withIndex("by_project", (q: any) =>
700
+ q.eq("projectId", args.projectId),
701
+ )
702
+ .take(500);
703
+
704
+ let deleted = 0;
705
+ for (const entry of old) {
706
+ if (entry.timestamp < cutoff) {
707
+ await ctx.db.delete(entry._id);
708
+ deleted++;
709
+ }
710
+ }
711
+
712
+ return { deleted };
713
+ },
714
+ });
715
+
716
+ // ── Internal: store ingest result (used by ingest action) ──────────
717
+
718
+ export const ingestCreateMemory = internalMutation({
719
+ args: {
720
+ projectId: v.string(),
721
+ scope: scopeValidator,
722
+ userId: v.optional(v.string()),
723
+ agentId: v.optional(v.string()),
724
+ sessionId: v.optional(v.string()),
725
+ title: v.string(),
726
+ content: v.string(),
727
+ memoryType: memoryTypeValidator,
728
+ tags: v.array(v.string()),
729
+ source: v.string(),
730
+ },
731
+ returns: v.string(),
732
+ handler: async (ctx, args) => {
733
+ const checksum = computeChecksum(args.content);
734
+ const now = Date.now();
735
+ const id = await ctx.db.insert("memories", {
736
+ projectId: args.projectId,
737
+ scope: args.scope,
738
+ userId: args.userId,
739
+ agentId: args.agentId,
740
+ sessionId: args.sessionId,
741
+ title: args.title,
742
+ content: args.content,
743
+ memoryType: args.memoryType,
744
+ tags: args.tags,
745
+ checksum,
746
+ archived: false,
747
+ accessCount: 0,
748
+ lastAccessedAt: now,
749
+ positiveCount: 0,
750
+ negativeCount: 0,
751
+ source: args.source,
752
+ });
753
+
754
+ await ctx.db.insert("memoryHistory", {
755
+ memoryId: id,
756
+ projectId: args.projectId,
757
+ newContent: args.content,
758
+ newTitle: args.title,
759
+ event: "created",
760
+ actor: "ingest",
761
+ timestamp: now,
762
+ });
763
+
764
+ return id;
765
+ },
766
+ });
767
+
768
+ export const ingestUpdateMemory = internalMutation({
769
+ args: {
770
+ memoryId: v.string(),
771
+ content: v.string(),
772
+ },
773
+ returns: v.null(),
774
+ handler: async (ctx, args) => {
775
+ const id = ctx.db.normalizeId("memories", args.memoryId);
776
+ if (!id) throw new Error(`Invalid memory ID: ${args.memoryId}`);
777
+
778
+ const existing = await ctx.db.get(id);
779
+ if (!existing) throw new Error(`Memory not found: ${args.memoryId}`);
780
+
781
+ const checksum = computeChecksum(args.content);
782
+ await ctx.db.patch(id, { content: args.content, checksum });
783
+
784
+ await ctx.db.insert("memoryHistory", {
785
+ memoryId: id,
786
+ projectId: existing.projectId,
787
+ previousContent: existing.content,
788
+ newContent: args.content,
789
+ event: "merged",
790
+ actor: "ingest",
791
+ timestamp: Date.now(),
792
+ });
793
+
794
+ return null;
795
+ },
796
+ });
797
+
798
+ export const ingestDeleteMemory = internalMutation({
799
+ args: {
800
+ memoryId: v.string(),
801
+ },
802
+ returns: v.null(),
803
+ handler: async (ctx, args) => {
804
+ const id = ctx.db.normalizeId("memories", args.memoryId);
805
+ if (!id) throw new Error(`Invalid memory ID: ${args.memoryId}`);
806
+
807
+ const existing = await ctx.db.get(id);
808
+ if (!existing) throw new Error(`Memory not found: ${args.memoryId}`);
809
+
810
+ await ctx.db.patch(id, { archived: true });
811
+
812
+ await ctx.db.insert("memoryHistory", {
813
+ memoryId: id,
814
+ projectId: existing.projectId,
815
+ previousContent: existing.content,
816
+ previousTitle: existing.title,
817
+ event: "archived",
818
+ actor: "ingest",
819
+ timestamp: Date.now(),
820
+ });
821
+
822
+ return null;
823
+ },
824
+ });