@prmichaelsen/remember-mcp 3.0.0 → 3.13.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 (208) hide show
  1. package/AGENT.md +296 -250
  2. package/CHANGELOG.md +358 -0
  3. package/README.md +68 -45
  4. package/agent/commands/acp.clarification-create.md +382 -0
  5. package/agent/commands/acp.project-info.md +309 -0
  6. package/agent/commands/acp.project-remove.md +379 -0
  7. package/agent/commands/acp.project-update.md +296 -0
  8. package/agent/commands/acp.task-create.md +17 -9
  9. package/agent/commands/git.commit.md +13 -1
  10. package/agent/design/comment-memory-type.md +2 -2
  11. package/agent/design/local.collaborative-memory-sync.md +265 -0
  12. package/agent/design/local.content-flags.md +210 -0
  13. package/agent/design/local.ghost-persona-system.md +273 -0
  14. package/agent/design/local.group-acl-integration.md +338 -0
  15. package/agent/design/local.memory-acl-schema.md +352 -0
  16. package/agent/design/local.memory-collection-pattern-v2.md +348 -0
  17. package/agent/design/local.moderation-and-space-config.md +257 -0
  18. package/agent/design/local.v2-api-reference.md +621 -0
  19. package/agent/design/local.v2-migration-guide.md +191 -0
  20. package/agent/design/local.v2-usage-examples.md +265 -0
  21. package/agent/design/permissions-storage-architecture.md +11 -3
  22. package/agent/design/trust-escalation-prevention.md +9 -2
  23. package/agent/design/trust-system-implementation.md +12 -3
  24. package/agent/milestones/milestone-14-memory-collection-v2.md +182 -0
  25. package/agent/milestones/milestone-15-moderation-space-config.md +126 -0
  26. package/agent/progress.yaml +628 -49
  27. package/agent/scripts/acp.common.sh +2 -0
  28. package/agent/scripts/acp.install.sh +11 -1
  29. package/agent/scripts/acp.package-install-optimized.sh +454 -0
  30. package/agent/scripts/acp.package-install.sh +247 -300
  31. package/agent/scripts/acp.project-info.sh +218 -0
  32. package/agent/scripts/acp.project-remove.sh +302 -0
  33. package/agent/scripts/acp.project-update.sh +296 -0
  34. package/agent/scripts/acp.yaml-parser.sh +128 -10
  35. package/agent/tasks/milestone-14-memory-collection-v2/task-165-core-infrastructure-setup.md +171 -0
  36. package/agent/tasks/milestone-14-memory-collection-v2/task-166-update-remember-publish.md +191 -0
  37. package/agent/tasks/milestone-14-memory-collection-v2/task-167-update-remember-retract.md +186 -0
  38. package/agent/tasks/milestone-14-memory-collection-v2/task-168-implement-remember-revise.md +184 -0
  39. package/agent/tasks/milestone-14-memory-collection-v2/task-169-update-remember-search-space.md +179 -0
  40. package/agent/tasks/milestone-14-memory-collection-v2/task-170-update-remember-create-update.md +139 -0
  41. package/agent/tasks/milestone-14-memory-collection-v2/task-172-performance-testing-optimization.md +161 -0
  42. package/agent/tasks/milestone-14-memory-collection-v2/task-173-documentation-examples.md +258 -0
  43. package/agent/tasks/milestone-15-moderation-space-config/task-174-add-moderation-schema-fields.md +57 -0
  44. package/agent/tasks/milestone-15-moderation-space-config/task-175-create-space-config-service.md +64 -0
  45. package/agent/tasks/milestone-15-moderation-space-config/task-176-wire-moderation-publish-flow.md +45 -0
  46. package/agent/tasks/milestone-15-moderation-space-config/task-177-add-moderation-search-filters.md +70 -0
  47. package/agent/tasks/milestone-15-moderation-space-config/task-178-create-remember-moderate-tool.md +69 -0
  48. package/agent/tasks/milestone-15-moderation-space-config/task-179-documentation-integration-tests.md +58 -0
  49. package/agent/tasks/milestone-16-ghost-system/task-187-ghost-config-firestore.md +41 -0
  50. package/agent/tasks/milestone-16-ghost-system/task-188-trust-filter-integration.md +44 -0
  51. package/agent/tasks/milestone-16-ghost-system/task-189-ghost-memory-filtering.md +43 -0
  52. package/agent/tasks/milestone-16-ghost-system/task-190-ghost-config-tools.md +45 -0
  53. package/agent/tasks/milestone-16-ghost-system/task-191-escalation-firestore.md +38 -0
  54. package/agent/tasks/milestone-16-ghost-system/task-192-documentation-verification.md +39 -0
  55. package/agent/tasks/milestone-7-trust-permissions/task-180-access-result-permission-types.md +69 -0
  56. package/agent/tasks/milestone-7-trust-permissions/task-181-firestore-permissions-access-logs.md +56 -0
  57. package/agent/tasks/milestone-7-trust-permissions/task-182-trust-enforcement-service.md +68 -0
  58. package/agent/tasks/milestone-7-trust-permissions/task-183-access-control-service.md +70 -0
  59. package/agent/tasks/milestone-7-trust-permissions/task-184-permission-tools.md +79 -0
  60. package/agent/tasks/milestone-7-trust-permissions/task-185-wire-trust-into-search-query.md +55 -0
  61. package/agent/tasks/milestone-7-trust-permissions/task-186-documentation-verification.md +56 -0
  62. package/agent/tasks/task-76-fix-indexnullstate-schema-bug.md +197 -0
  63. package/dist/collections/composite-ids.d.ts +106 -0
  64. package/dist/collections/core-infrastructure.spec.d.ts +11 -0
  65. package/dist/collections/dot-notation.d.ts +106 -0
  66. package/dist/collections/tracking-arrays.d.ts +176 -0
  67. package/dist/constants/content-types.d.ts +1 -0
  68. package/dist/schema/v2-collections-comments.spec.d.ts +8 -0
  69. package/dist/schema/v2-collections.d.ts +210 -0
  70. package/dist/server-factory.d.ts +15 -0
  71. package/dist/server-factory.js +2798 -1029
  72. package/dist/server.js +2526 -1012
  73. package/dist/services/access-control.d.ts +103 -0
  74. package/dist/services/access-control.spec.d.ts +2 -0
  75. package/dist/services/credentials-provider.d.ts +24 -0
  76. package/dist/services/credentials-provider.spec.d.ts +2 -0
  77. package/dist/services/escalation.service.d.ts +22 -0
  78. package/dist/services/escalation.service.spec.d.ts +2 -0
  79. package/dist/services/ghost-config.service.d.ts +55 -0
  80. package/dist/services/ghost-config.service.spec.d.ts +2 -0
  81. package/dist/services/space-config.service.d.ts +23 -0
  82. package/dist/services/space-config.service.spec.d.ts +2 -0
  83. package/dist/services/trust-enforcement.d.ts +83 -0
  84. package/dist/services/trust-enforcement.spec.d.ts +2 -0
  85. package/dist/services/trust-validator.d.ts +43 -0
  86. package/dist/services/trust-validator.spec.d.ts +2 -0
  87. package/dist/tools/confirm-publish-moderation.spec.d.ts +8 -0
  88. package/dist/tools/confirm.d.ts +8 -1
  89. package/dist/tools/create-memory.d.ts +2 -1
  90. package/dist/tools/create-memory.spec.d.ts +10 -0
  91. package/dist/tools/create-relationship.d.ts +2 -1
  92. package/dist/tools/delete-memory.d.ts +2 -1
  93. package/dist/tools/delete-relationship.d.ts +2 -1
  94. package/dist/tools/deny.d.ts +2 -1
  95. package/dist/tools/find-similar.d.ts +2 -1
  96. package/dist/tools/get-preferences.d.ts +2 -1
  97. package/dist/tools/ghost-config.d.ts +27 -0
  98. package/dist/tools/ghost-config.spec.d.ts +2 -0
  99. package/dist/tools/moderate.d.ts +20 -0
  100. package/dist/tools/moderate.spec.d.ts +5 -0
  101. package/dist/tools/publish.d.ts +11 -3
  102. package/dist/tools/query-memory.d.ts +3 -1
  103. package/dist/tools/query-space.d.ts +4 -1
  104. package/dist/tools/retract.d.ts +29 -0
  105. package/dist/tools/revise.d.ts +45 -0
  106. package/dist/tools/revise.spec.d.ts +8 -0
  107. package/dist/tools/search-memory.d.ts +2 -1
  108. package/dist/tools/search-relationship.d.ts +2 -1
  109. package/dist/tools/search-space.d.ts +25 -5
  110. package/dist/tools/search-space.spec.d.ts +9 -0
  111. package/dist/tools/set-preference.d.ts +2 -1
  112. package/dist/tools/update-memory.d.ts +2 -1
  113. package/dist/tools/update-relationship.d.ts +2 -1
  114. package/dist/types/access-result.d.ts +48 -0
  115. package/dist/types/access-result.spec.d.ts +2 -0
  116. package/dist/types/auth.d.ts +46 -0
  117. package/dist/types/ghost-config.d.ts +36 -0
  118. package/dist/types/memory.d.ts +3 -1
  119. package/dist/types/preferences.d.ts +1 -1
  120. package/dist/utils/auth-helpers.d.ts +14 -0
  121. package/dist/utils/auth-helpers.spec.d.ts +2 -0
  122. package/dist/utils/test-data-generator.d.ts +124 -0
  123. package/dist/utils/test-data-generator.spec.d.ts +12 -0
  124. package/dist/v2-performance.e2e.d.ts +17 -0
  125. package/dist/v2-smoke.e2e.d.ts +14 -0
  126. package/dist/weaviate/client.d.ts +5 -8
  127. package/dist/weaviate/space-schema.d.ts +2 -2
  128. package/docs/performance/v2-benchmarks.md +80 -0
  129. package/jest.e2e.config.js +14 -3
  130. package/package.json +1 -1
  131. package/scripts/.collection-recreation-state.yaml +16 -0
  132. package/scripts/.gitkeep +5 -0
  133. package/scripts/README-collection-recreation.md +224 -0
  134. package/scripts/README.md +51 -0
  135. package/scripts/backup-collections.ts +543 -0
  136. package/scripts/delete-collection.ts +137 -0
  137. package/scripts/migrate-recreate-collections.ts +578 -0
  138. package/scripts/migrate-v1-to-v2.ts +1094 -0
  139. package/scripts/package-lock.json +1113 -0
  140. package/scripts/package.json +27 -0
  141. package/src/collections/composite-ids.ts +193 -0
  142. package/src/collections/core-infrastructure.spec.ts +353 -0
  143. package/src/collections/dot-notation.ts +212 -0
  144. package/src/collections/tracking-arrays.ts +298 -0
  145. package/src/constants/content-types.ts +20 -0
  146. package/src/schema/v2-collections-comments.spec.ts +141 -0
  147. package/src/schema/v2-collections.ts +433 -0
  148. package/src/server-factory.ts +89 -20
  149. package/src/server.ts +45 -17
  150. package/src/services/access-control.spec.ts +383 -0
  151. package/src/services/access-control.ts +291 -0
  152. package/src/services/credentials-provider.spec.ts +22 -0
  153. package/src/services/credentials-provider.ts +34 -0
  154. package/src/services/escalation.service.spec.ts +183 -0
  155. package/src/services/escalation.service.ts +150 -0
  156. package/src/services/ghost-config.service.spec.ts +339 -0
  157. package/src/services/ghost-config.service.ts +219 -0
  158. package/src/services/space-config.service.spec.ts +102 -0
  159. package/src/services/space-config.service.ts +79 -0
  160. package/src/services/trust-enforcement.spec.ts +309 -0
  161. package/src/services/trust-enforcement.ts +197 -0
  162. package/src/services/trust-validator.spec.ts +108 -0
  163. package/src/services/trust-validator.ts +105 -0
  164. package/src/tools/confirm-publish-moderation.spec.ts +240 -0
  165. package/src/tools/confirm.ts +869 -135
  166. package/src/tools/create-memory.spec.ts +126 -0
  167. package/src/tools/create-memory.ts +20 -27
  168. package/src/tools/create-relationship.ts +17 -8
  169. package/src/tools/delete-memory.ts +13 -6
  170. package/src/tools/delete-relationship.ts +15 -6
  171. package/src/tools/deny.ts +8 -1
  172. package/src/tools/find-similar.ts +21 -8
  173. package/src/tools/get-preferences.ts +10 -1
  174. package/src/tools/ghost-config.spec.ts +180 -0
  175. package/src/tools/ghost-config.ts +230 -0
  176. package/src/tools/moderate.spec.ts +277 -0
  177. package/src/tools/moderate.ts +219 -0
  178. package/src/tools/publish.ts +99 -41
  179. package/src/tools/query-memory.ts +28 -6
  180. package/src/tools/query-space.ts +39 -4
  181. package/src/tools/retract.ts +292 -0
  182. package/src/tools/revise.spec.ts +146 -0
  183. package/src/tools/revise.ts +283 -0
  184. package/src/tools/search-memory.ts +30 -7
  185. package/src/tools/search-relationship.ts +11 -2
  186. package/src/tools/search-space.spec.ts +341 -0
  187. package/src/tools/search-space.ts +323 -99
  188. package/src/tools/set-preference.ts +10 -1
  189. package/src/tools/update-memory.ts +16 -5
  190. package/src/tools/update-relationship.ts +10 -1
  191. package/src/types/access-result.spec.ts +193 -0
  192. package/src/types/access-result.ts +62 -0
  193. package/src/types/auth.ts +52 -0
  194. package/src/types/ghost-config.ts +46 -0
  195. package/src/types/memory.ts +9 -1
  196. package/src/types/preferences.ts +2 -2
  197. package/src/utils/auth-helpers.spec.ts +75 -0
  198. package/src/utils/auth-helpers.ts +25 -0
  199. package/src/utils/test-data-generator.spec.ts +317 -0
  200. package/src/utils/test-data-generator.ts +292 -0
  201. package/src/utils/weaviate-filters.ts +4 -4
  202. package/src/v2-performance.e2e.ts +173 -0
  203. package/src/v2-smoke.e2e.ts +401 -0
  204. package/src/weaviate/client.spec.ts +5 -5
  205. package/src/weaviate/client.ts +51 -36
  206. package/src/weaviate/schema.ts +11 -256
  207. package/src/weaviate/space-schema.spec.ts +24 -24
  208. package/src/weaviate/space-schema.ts +18 -6
@@ -3,15 +3,27 @@
3
3
  *
4
4
  * Generic confirmation tool that executes any pending action.
5
5
  * This is the second phase of the confirmation workflow.
6
+ *
7
+ * Memory Collection Pattern v2:
8
+ * - Multi-space publication to Memory_spaces_public
9
+ * - Multi-group publication to Memory_groups_{groupId}
10
+ * - Composite IDs ({userId}.{memoryId}) for published memories
11
+ * - Tracking arrays (space_ids, group_ids) on source and published memories
6
12
  */
7
13
 
8
14
  import type { Tool } from '@modelcontextprotocol/sdk/types.js';
9
15
  import { confirmationTokenService, type ConfirmationRequest } from '../services/confirmation-token.service.js';
10
- import { getWeaviateClient, getMemoryCollectionName, fetchMemoryWithAllProperties, sanitizeUserId } from '../weaviate/client.js';
16
+ import { getWeaviateClient, getMemoryCollectionName, fetchMemoryWithAllProperties } from '../weaviate/client.js';
11
17
  import { ensurePublicCollection } from '../weaviate/space-schema.js';
12
18
  import { handleToolError } from '../utils/error-handler.js';
13
19
  import { logger } from '../utils/logger.js';
14
20
  import { createDebugLogger } from '../utils/debug.js';
21
+ import { CollectionType, getCollectionName } from '../collections/dot-notation.js';
22
+ import { generateCompositeId, parseCompositeId } from '../collections/composite-ids.js';
23
+ import { addToSpaceIds, addToGroupIds, removeFromSpaceIds, removeFromGroupIds, getPublishedLocations } from '../collections/tracking-arrays.js';
24
+ import { parseRevisionHistory, buildRevisionHistory, type RevisionResult } from './revise.js';
25
+ import type { AuthContext } from '../types/auth.js';
26
+ import { getSpaceConfig } from '../services/space-config.service.js';
15
27
 
16
28
  /**
17
29
  * Tool definition for remember_confirm
@@ -62,7 +74,8 @@ interface ConfirmArgs {
62
74
  */
63
75
  export async function handleConfirm(
64
76
  args: ConfirmArgs,
65
- userId: string
77
+ userId: string,
78
+ authContext?: AuthContext
66
79
  ): Promise<string> {
67
80
  const debug = createDebugLogger({
68
81
  tool: 'remember_confirm',
@@ -125,10 +138,15 @@ export async function handleConfirm(
125
138
  return await executeDeleteMemory(request, userId);
126
139
  }
127
140
 
128
- // Add other action types here as needed
129
- // if (request.action === 'retract_memory') {
130
- // return await executeRetractMemory(request, userId);
131
- // }
141
+ // Handle retract_memory action
142
+ if (request.action === 'retract_memory') {
143
+ return await executeRetractMemory(request, userId);
144
+ }
145
+
146
+ // Handle revise_memory action
147
+ if (request.action === 'revise_memory') {
148
+ return await executeReviseMemory(request, userId);
149
+ }
132
150
 
133
151
  throw new Error(`Unknown action type: ${request.action}`);
134
152
  } catch (error) {
@@ -147,10 +165,17 @@ export async function handleConfirm(
147
165
 
148
166
  /**
149
167
  * Execute publish memory action
168
+ *
169
+ * Memory Collection Pattern v2:
170
+ * - Publishes to Memory_spaces_public with composite ID
171
+ * - Publishes to Memory_groups_{groupId} for each group
172
+ * - Updates tracking arrays on source memory
173
+ * - Supports rollback on partial failure
150
174
  */
151
175
  async function executePublishMemory(
152
176
  request: ConfirmationRequest & { request_id: string },
153
- userId: string
177
+ userId: string,
178
+ authContext?: AuthContext
154
179
  ): Promise<string> {
155
180
  const debug = createDebugLogger({
156
181
  tool: 'remember_confirm',
@@ -159,28 +184,47 @@ async function executePublishMemory(
159
184
  });
160
185
 
161
186
  try {
187
+ // Normalize arrays (handle undefined)
188
+ const spaces = request.payload.spaces || [];
189
+ const groups = request.payload.groups || [];
190
+
162
191
  debug.debug('Executing publish memory action', {
163
192
  memoryId: request.payload.memory_id,
164
- spaces: request.payload.spaces,
193
+ spaces,
194
+ groups,
165
195
  });
166
196
 
167
197
  logger.info('Executing publish memory action', {
168
198
  function: 'executePublishMemory',
169
199
  userId,
170
200
  memoryId: request.payload.memory_id,
171
- spaces: request.payload.spaces,
172
- spaceCount: request.payload.spaces?.length || 0,
201
+ spaces,
202
+ groups,
203
+ spaceCount: spaces.length,
204
+ groupCount: groups.length,
173
205
  });
174
206
 
207
+ // Validate that at least one destination is provided
208
+ if (spaces.length === 0 && groups.length === 0) {
209
+ return JSON.stringify(
210
+ {
211
+ success: false,
212
+ error: 'No destinations',
213
+ message: 'Must specify at least one space or group to publish to',
214
+ },
215
+ null,
216
+ 2
217
+ );
218
+ }
219
+
175
220
  // Fetch the memory NOW (during confirmation, not from stored payload)
176
221
  const weaviateClient = getWeaviateClient();
177
- const userCollection = weaviateClient.collections.get(
178
- getMemoryCollectionName(userId)
179
- );
222
+ const userCollectionName = getMemoryCollectionName(userId);
223
+ const userCollection = weaviateClient.collections.get(userCollectionName);
180
224
 
181
225
  logger.debug('Fetching original memory', {
182
226
  function: 'executePublishMemory',
183
- collectionName: getMemoryCollectionName(userId),
227
+ collectionName: userCollectionName,
184
228
  memoryId: request.payload.memory_id,
185
229
  });
186
230
 
@@ -197,14 +241,6 @@ async function executePublishMemory(
197
241
  memoryId: request.payload.memory_id,
198
242
  hasProperties: !!originalMemory?.properties,
199
243
  propertyCount: originalMemory?.properties ? Object.keys(originalMemory.properties).length : 0,
200
- propertyKeys: originalMemory?.properties ? Object.keys(originalMemory.properties) : [],
201
- hasTitle: !!originalMemory?.properties?.title,
202
- hasContent: !!originalMemory?.properties?.content,
203
- hasUserId: !!originalMemory?.properties?.user_id,
204
- hasTags: !!originalMemory?.properties?.tags,
205
- hasWeight: !!originalMemory?.properties?.weight,
206
- contentLength: originalMemory?.properties?.content?.length || 0,
207
- titleValue: originalMemory?.properties?.title || 'NO_TITLE',
208
244
  });
209
245
 
210
246
  if (!originalMemory) {
@@ -223,28 +259,6 @@ async function executePublishMemory(
223
259
  );
224
260
  }
225
261
 
226
- // Check if memory has already been published
227
- if (originalMemory.properties.space_memory_id) {
228
- const requestedSpaces = request.payload.spaces?.join(', ') || 'unknown';
229
- logger.warn('Memory already published', {
230
- function: 'executePublishMemory',
231
- memoryId: request.payload.memory_id,
232
- existingSpaceMemoryId: originalMemory.properties.space_memory_id,
233
- requestedSpaces: request.payload.spaces,
234
- });
235
- return JSON.stringify(
236
- {
237
- success: false,
238
- error: 'Already published',
239
- message: `This memory has already been published to this space. Space memory ID: ${originalMemory.properties.space_memory_id}`,
240
- space_memory_id: originalMemory.properties.space_memory_id,
241
- requested_spaces: request.payload.spaces,
242
- },
243
- null,
244
- 2
245
- );
246
- }
247
-
248
262
  // Verify ownership again
249
263
  if (originalMemory.properties.user_id !== userId) {
250
264
  logger.warn('Permission denied - wrong owner', {
@@ -263,115 +277,300 @@ async function executePublishMemory(
263
277
  2
264
278
  );
265
279
  }
266
-
267
- logger.debug('Ensuring public collection exists', {
268
- function: 'executePublishMemory',
269
- });
270
280
 
271
- // Get unified public collection
272
- const publicCollection = await ensurePublicCollection(weaviateClient);
281
+ // Generate composite ID for published memories
282
+ const compositeId = generateCompositeId(userId, request.payload.memory_id);
273
283
 
274
- logger.debug('Public collection ready', {
284
+ logger.debug('Generated composite ID', {
275
285
  function: 'executePublishMemory',
276
- collectionName: 'Memory_public',
286
+ compositeId,
287
+ userId,
288
+ memoryId: request.payload.memory_id,
277
289
  });
278
-
279
- // Create published memory (copy with modifications)
290
+
291
+ // Get existing tracking arrays from source memory
292
+ const existingSpaceIds: string[] = Array.isArray(originalMemory.properties.space_ids)
293
+ ? originalMemory.properties.space_ids
294
+ : [];
295
+ const existingGroupIds: string[] = Array.isArray(originalMemory.properties.group_ids)
296
+ ? originalMemory.properties.group_ids
297
+ : [];
298
+
299
+ // Prepare tags
280
300
  const originalTags = Array.isArray(originalMemory.properties.tags)
281
301
  ? originalMemory.properties.tags
282
302
  : [];
283
303
  const additionalTags = Array.isArray(request.payload.additional_tags)
284
304
  ? request.payload.additional_tags
285
305
  : [];
286
-
287
- // Validate payload has required fields
288
- if (!request.payload.spaces || !Array.isArray(request.payload.spaces) || request.payload.spaces.length === 0) {
289
- throw new Error('Payload missing required field: spaces');
290
- }
306
+ const mergedTags = [...originalTags, ...additionalTags];
291
307
 
292
- // Create published memory - preserve ALL original properties
293
- const publishedMemory = {
294
- ...originalMemory.properties,
295
- // Add space-specific fields (don't overwrite existing properties)
296
- spaces: request.payload.spaces, // Required field (validated above)
297
- author_id: userId, // Track original author
298
- published_at: new Date().toISOString(),
299
- discovery_count: 0,
300
- attribution: 'user' as const,
301
- // Merge additional tags with original tags
302
- tags: [...originalTags, ...additionalTags],
303
- // Keep doc_type as 'memory' (space_memory concept was removed)
304
- // Keep original created_at, updated_at, version (don't overwrite)
305
- };
306
-
307
- logger.info('Inserting memory into Memory_public', {
308
- function: 'executePublishMemory',
309
- spaces: request.payload.spaces,
310
- spaceCount: request.payload.spaces?.length || 0,
311
- memoryId: request.payload.memory_id,
312
- hasUserId: !!(publishedMemory as any).user_id,
313
- hasAuthorId: !!publishedMemory.author_id,
314
- publishedMemoryKeys: Object.keys(publishedMemory),
315
- publishedMemoryKeyCount: Object.keys(publishedMemory).length,
316
- hasContent: !!publishedMemory.content,
317
- hasTitle: !!publishedMemory.title,
318
- contentLength: publishedMemory.content?.length || 0,
319
- titleValue: publishedMemory.title || 'NO_TITLE',
320
- });
308
+ // Track publication results for rollback
309
+ const publicationResults: {
310
+ spaces?: { success: boolean; id?: string; error?: string };
311
+ groups: Array<{ groupId: string; success: boolean; id?: string; error?: string }>;
312
+ } = { groups: [] };
321
313
 
322
- // Insert directly into unified public collection
323
- // CRITICAL: Weaviate insert API expects {properties: {...}}, not the properties directly!
324
- const result = await debug.time('Insert into Memory_public', async () => {
325
- return await publicCollection.data.insert({
326
- properties: publishedMemory,
314
+ // STEP 1: Publish to spaces (Memory_spaces_public)
315
+ if (spaces.length > 0) {
316
+ logger.debug('Ensuring public spaces collection exists', {
317
+ function: 'executePublishMemory',
327
318
  });
328
- });
329
-
330
- logger.info('Memory published successfully', {
331
- function: 'executePublishMemory',
332
- spaceMemoryId: result,
333
- spaces: request.payload.spaces,
334
- });
335
-
336
- debug.info('Memory published successfully', {
337
- spaceMemoryId: result,
338
- spaces: request.payload.spaces,
339
- });
340
319
 
341
- // Update original memory with space_memory_id for bidirectional linking
342
- try {
343
- await userCollection.data.update({
344
- id: request.payload.memory_id,
345
- properties: {
346
- space_memory_id: result,
347
- },
348
- });
320
+ const publicCollection = await ensurePublicCollection(weaviateClient);
321
+
322
+ // Check if memory already exists in spaces collection with this composite ID
323
+ let existingSpaceMemory = null;
324
+ try {
325
+ existingSpaceMemory = await fetchMemoryWithAllProperties(publicCollection, compositeId);
326
+ } catch (e) {
327
+ // Memory doesn't exist, which is fine
328
+ }
329
+
330
+ // Calculate new space_ids array
331
+ const newSpaceIds = [...new Set([...existingSpaceIds, ...spaces])];
332
+
333
+ // Determine moderation status: pending if ANY target space requires moderation
334
+ let spaceModerationStatus = 'approved';
335
+ for (const spaceId of spaces) {
336
+ const spaceConfig = await getSpaceConfig(spaceId, 'space');
337
+ if (spaceConfig.require_moderation) {
338
+ spaceModerationStatus = 'pending';
339
+ break;
340
+ }
341
+ }
342
+
343
+ // Create published memory with tracking arrays
344
+ const publishedMemory: Record<string, any> = {
345
+ ...originalMemory.properties,
346
+ // Use composite ID as the document ID
347
+ id: compositeId,
348
+ // Tracking arrays (v2 feature)
349
+ space_ids: newSpaceIds,
350
+ group_ids: existingGroupIds,
351
+ // Legacy compatibility
352
+ spaces: spaces,
353
+ // Publication metadata
354
+ author_id: userId,
355
+ published_at: new Date().toISOString(),
356
+ discovery_count: 0,
357
+ attribution: 'user' as const,
358
+ // Moderation status
359
+ moderation_status: spaceModerationStatus,
360
+ // Merge tags
361
+ tags: mergedTags,
362
+ };
363
+
364
+ // Remove internal Weaviate properties
365
+ delete publishedMemory._additional;
349
366
 
350
- logger.info('Updated original memory with space_memory_id', {
367
+ logger.info('Publishing memory to Memory_spaces_public', {
351
368
  function: 'executePublishMemory',
352
- memoryId: request.payload.memory_id,
353
- spaceMemoryId: result,
369
+ compositeId,
370
+ spaces,
371
+ spaceIds: newSpaceIds,
354
372
  });
355
- } catch (updateError) {
356
- logger.warn('Failed to update original memory with space_memory_id', {
373
+
374
+ try {
375
+ if (existingSpaceMemory) {
376
+ // Update existing memory
377
+ await publicCollection.data.update({
378
+ id: compositeId,
379
+ properties: publishedMemory,
380
+ });
381
+ publicationResults.spaces = { success: true, id: compositeId };
382
+ } else {
383
+ // Insert new memory with specific ID
384
+ await publicCollection.data.insert({
385
+ id: compositeId,
386
+ properties: publishedMemory,
387
+ });
388
+ publicationResults.spaces = { success: true, id: compositeId };
389
+ }
390
+
391
+ logger.info('Memory published to spaces successfully', {
392
+ function: 'executePublishMemory',
393
+ compositeId,
394
+ spaces,
395
+ });
396
+ } catch (spaceError) {
397
+ logger.error('Failed to publish to spaces', {
398
+ function: 'executePublishMemory',
399
+ error: spaceError instanceof Error ? spaceError.message : String(spaceError),
400
+ });
401
+ publicationResults.spaces = {
402
+ success: false,
403
+ error: spaceError instanceof Error ? spaceError.message : String(spaceError)
404
+ };
405
+ }
406
+ }
407
+
408
+ // STEP 2: Publish to groups (Memory_groups_{groupId})
409
+ for (const groupId of groups) {
410
+ const groupCollectionName = getCollectionName(CollectionType.GROUPS, groupId);
411
+
412
+ logger.debug('Publishing to group collection', {
357
413
  function: 'executePublishMemory',
358
- memoryId: request.payload.memory_id,
359
- spaceMemoryId: result,
360
- error: updateError instanceof Error ? updateError.message : String(updateError),
414
+ groupId,
415
+ collectionName: groupCollectionName,
361
416
  });
362
- // Don't fail the publish if this update fails - it's not critical
363
- }
364
417
 
365
- // Return minimal response with spaces array
366
- return JSON.stringify(
367
- {
368
- success: true,
369
- space_memory_id: result,
370
- spaces: request.payload.spaces || ['the_void'],
371
- },
372
- null,
373
- 2
374
- );
418
+ try {
419
+ const groupCollection = weaviateClient.collections.get(groupCollectionName);
420
+
421
+ // Check if memory already exists in this group
422
+ let existingGroupMemory = null;
423
+ try {
424
+ existingGroupMemory = await fetchMemoryWithAllProperties(groupCollection, compositeId);
425
+ } catch (e) {
426
+ // Memory doesn't exist in this group
427
+ }
428
+
429
+ // Calculate new group_ids array (for this group publication)
430
+ const newGroupIds = [...new Set([...existingGroupIds, groupId])];
431
+
432
+ // Determine moderation status for this group
433
+ const groupConfig = await getSpaceConfig(groupId, 'group');
434
+ const groupModerationStatus = groupConfig.require_moderation ? 'pending' : 'approved';
435
+
436
+ // Create published memory for group
437
+ const groupMemory: Record<string, any> = {
438
+ ...originalMemory.properties,
439
+ // Use composite ID
440
+ id: compositeId,
441
+ // Tracking arrays
442
+ space_ids: existingSpaceIds,
443
+ group_ids: newGroupIds,
444
+ // Publication metadata
445
+ author_id: userId,
446
+ published_at: new Date().toISOString(),
447
+ discovery_count: 0,
448
+ attribution: 'user' as const,
449
+ // Moderation status
450
+ moderation_status: groupModerationStatus,
451
+ // Merge tags
452
+ tags: mergedTags,
453
+ };
454
+
455
+ // Remove internal Weaviate properties
456
+ delete groupMemory._additional;
457
+
458
+ if (existingGroupMemory) {
459
+ await groupCollection.data.update({
460
+ id: compositeId,
461
+ properties: groupMemory,
462
+ });
463
+ } else {
464
+ await groupCollection.data.insert({
465
+ id: compositeId,
466
+ properties: groupMemory,
467
+ });
468
+ }
469
+
470
+ publicationResults.groups.push({ groupId, success: true, id: compositeId });
471
+
472
+ logger.info('Memory published to group successfully', {
473
+ function: 'executePublishMemory',
474
+ compositeId,
475
+ groupId,
476
+ });
477
+ } catch (groupError) {
478
+ logger.error('Failed to publish to group', {
479
+ function: 'executePublishMemory',
480
+ groupId,
481
+ error: groupError instanceof Error ? groupError.message : String(groupError),
482
+ });
483
+ publicationResults.groups.push({
484
+ groupId,
485
+ success: false,
486
+ error: groupError instanceof Error ? groupError.message : String(groupError)
487
+ });
488
+ }
489
+ }
490
+
491
+ // STEP 3: Update source memory with tracking arrays
492
+ const finalSpaceIds = publicationResults.spaces?.success
493
+ ? [...new Set([...existingSpaceIds, ...spaces])]
494
+ : existingSpaceIds;
495
+
496
+ const successfulGroups = publicationResults.groups
497
+ .filter(g => g.success)
498
+ .map(g => g.groupId);
499
+ const finalGroupIds = [...new Set([...existingGroupIds, ...successfulGroups])];
500
+
501
+ // Only update if there are changes
502
+ if (finalSpaceIds.length > existingSpaceIds.length || finalGroupIds.length > existingGroupIds.length) {
503
+ try {
504
+ await userCollection.data.update({
505
+ id: request.payload.memory_id,
506
+ properties: {
507
+ space_ids: finalSpaceIds,
508
+ group_ids: finalGroupIds,
509
+ },
510
+ });
511
+
512
+ logger.info('Updated source memory with tracking arrays', {
513
+ function: 'executePublishMemory',
514
+ memoryId: request.payload.memory_id,
515
+ spaceIds: finalSpaceIds,
516
+ groupIds: finalGroupIds,
517
+ });
518
+ } catch (updateError) {
519
+ logger.warn('Failed to update source memory tracking arrays', {
520
+ function: 'executePublishMemory',
521
+ memoryId: request.payload.memory_id,
522
+ error: updateError instanceof Error ? updateError.message : String(updateError),
523
+ });
524
+ // Don't fail the publish if this update fails
525
+ }
526
+ }
527
+
528
+ // Build response
529
+ const successfulPublications: string[] = [];
530
+ const failedPublications: string[] = [];
531
+
532
+ if (spaces.length > 0) {
533
+ if (publicationResults.spaces?.success) {
534
+ successfulPublications.push(`spaces: ${spaces.join(', ')}`);
535
+ } else {
536
+ failedPublications.push(`spaces: ${publicationResults.spaces?.error || 'unknown error'}`);
537
+ }
538
+ }
539
+
540
+ for (const groupResult of publicationResults.groups) {
541
+ if (groupResult.success) {
542
+ successfulPublications.push(`group: ${groupResult.groupId}`);
543
+ } else {
544
+ failedPublications.push(`group ${groupResult.groupId}: ${groupResult.error || 'unknown error'}`);
545
+ }
546
+ }
547
+
548
+ // Return result
549
+ if (successfulPublications.length > 0) {
550
+ return JSON.stringify(
551
+ {
552
+ success: true,
553
+ composite_id: compositeId,
554
+ published_to: successfulPublications,
555
+ failed: failedPublications.length > 0 ? failedPublications : undefined,
556
+ space_ids: finalSpaceIds,
557
+ group_ids: finalGroupIds,
558
+ },
559
+ null,
560
+ 2
561
+ );
562
+ } else {
563
+ return JSON.stringify(
564
+ {
565
+ success: false,
566
+ error: 'Publication failed',
567
+ message: 'Failed to publish to any destination',
568
+ details: failedPublications,
569
+ },
570
+ null,
571
+ 2
572
+ );
573
+ }
375
574
  } catch (error) {
376
575
  debug.error('Execute publish failed', {
377
576
  error: error instanceof Error ? error.message : String(error),
@@ -391,7 +590,8 @@ async function executePublishMemory(
391
590
  */
392
591
  async function executeDeleteMemory(
393
592
  request: ConfirmationRequest & { request_id: string },
394
- userId: string
593
+ userId: string,
594
+ authContext?: AuthContext
395
595
  ): Promise<string> {
396
596
  try {
397
597
  logger.info('Executing delete memory action', {
@@ -405,7 +605,7 @@ async function executeDeleteMemory(
405
605
 
406
606
  // Soft delete the memory
407
607
  const client = getWeaviateClient();
408
- const collectionName = `Memory_${sanitizeUserId(userId)}`;
608
+ const collectionName = getMemoryCollectionName(userId);
409
609
  const collection = client.collections.get(collectionName);
410
610
 
411
611
  await collection.data.update({
@@ -444,3 +644,537 @@ async function executeDeleteMemory(
444
644
  throw error;
445
645
  }
446
646
  }
647
+
648
+ /**
649
+ * Execute retract memory action
650
+ *
651
+ * Memory Collection Pattern v2:
652
+ * - Selective retraction from specific spaces/groups
653
+ * - Space memories are orphaned (remain in Memory_spaces_public with empty tracking arrays)
654
+ * - Group memories are deleted from group collections
655
+ * - Tracking arrays updated on source memory
656
+ */
657
+ async function executeRetractMemory(
658
+ request: ConfirmationRequest & { request_id: string },
659
+ userId: string,
660
+ authContext?: AuthContext
661
+ ): Promise<string> {
662
+ const debug = createDebugLogger({
663
+ tool: 'remember_confirm',
664
+ userId,
665
+ operation: 'execute_retract',
666
+ });
667
+
668
+ try {
669
+ // Normalize arrays (handle undefined)
670
+ const spaces = request.payload.spaces || [];
671
+ const groups = request.payload.groups || [];
672
+
673
+ debug.debug('Executing retract memory action', {
674
+ memoryId: request.payload.memory_id,
675
+ spaces,
676
+ groups,
677
+ });
678
+
679
+ logger.info('Executing retract memory action', {
680
+ function: 'executeRetractMemory',
681
+ userId,
682
+ memoryId: request.payload.memory_id,
683
+ spaces,
684
+ groups,
685
+ spaceCount: spaces.length,
686
+ groupCount: groups.length,
687
+ });
688
+
689
+ // Fetch the source memory
690
+ const weaviateClient = getWeaviateClient();
691
+ const userCollectionName = getMemoryCollectionName(userId);
692
+ const userCollection = weaviateClient.collections.get(userCollectionName);
693
+
694
+ const sourceMemory = await fetchMemoryWithAllProperties(
695
+ userCollection,
696
+ request.payload.memory_id
697
+ );
698
+
699
+ if (!sourceMemory) {
700
+ logger.info('Source memory not found for retraction', {
701
+ function: 'executeRetractMemory',
702
+ memoryId: request.payload.memory_id,
703
+ });
704
+ return JSON.stringify(
705
+ {
706
+ success: false,
707
+ error: 'Memory not found',
708
+ message: `Source memory ${request.payload.memory_id} no longer exists`,
709
+ },
710
+ null,
711
+ 2
712
+ );
713
+ }
714
+
715
+ // Get current tracking arrays
716
+ const currentSpaceIds: string[] = Array.isArray(sourceMemory.properties.space_ids)
717
+ ? sourceMemory.properties.space_ids
718
+ : [];
719
+ const currentGroupIds: string[] = Array.isArray(sourceMemory.properties.group_ids)
720
+ ? sourceMemory.properties.group_ids
721
+ : [];
722
+
723
+ // Generate composite ID for published memories
724
+ const compositeId = generateCompositeId(userId, request.payload.memory_id);
725
+
726
+ // Track retraction results
727
+ const retractionResults: {
728
+ spaces?: { success: boolean; error?: string };
729
+ groups: Array<{ groupId: string; success: boolean; error?: string }>;
730
+ } = { groups: [] };
731
+
732
+ // STEP 1: Retract from spaces (Memory_spaces_public)
733
+ // Space memories are ORPHANED, not deleted, for historical reference
734
+ if (spaces.length > 0) {
735
+ try {
736
+ const publicCollection = weaviateClient.collections.get(
737
+ getCollectionName(CollectionType.SPACES)
738
+ );
739
+
740
+ // Fetch the published memory from spaces
741
+ const publishedMemory = await fetchMemoryWithAllProperties(publicCollection, compositeId);
742
+
743
+ if (publishedMemory) {
744
+ // Calculate new space_ids (remove retracted spaces)
745
+ const newSpaceIds = currentSpaceIds.filter(id => !spaces.includes(id));
746
+
747
+ // Update the published memory with new tracking arrays
748
+ // Memory remains even if space_ids becomes empty (orphaned)
749
+ await publicCollection.data.update({
750
+ id: compositeId,
751
+ properties: {
752
+ space_ids: newSpaceIds,
753
+ retracted_at: new Date().toISOString(),
754
+ },
755
+ });
756
+
757
+ retractionResults.spaces = { success: true };
758
+
759
+ logger.info('Memory retracted from spaces (orphaned)', {
760
+ function: 'executeRetractMemory',
761
+ compositeId,
762
+ retractedSpaces: spaces,
763
+ remainingSpaces: newSpaceIds,
764
+ isOrphaned: newSpaceIds.length === 0 && currentGroupIds.length === 0,
765
+ });
766
+ } else {
767
+ // Memory not found in spaces collection
768
+ retractionResults.spaces = {
769
+ success: false,
770
+ error: 'Memory not found in spaces collection',
771
+ };
772
+ logger.warn('Memory not found in spaces collection', {
773
+ function: 'executeRetractMemory',
774
+ compositeId,
775
+ });
776
+ }
777
+ } catch (spaceError) {
778
+ logger.error('Failed to retract from spaces', {
779
+ function: 'executeRetractMemory',
780
+ error: spaceError instanceof Error ? spaceError.message : String(spaceError),
781
+ });
782
+ retractionResults.spaces = {
783
+ success: false,
784
+ error: spaceError instanceof Error ? spaceError.message : String(spaceError),
785
+ };
786
+ }
787
+ }
788
+
789
+ // STEP 2: Retract from groups (Memory_groups_{groupId})
790
+ // Group memories are ORPHANED (same as spaces), not deleted
791
+ // This preserves historical data while making it unsearchable by default
792
+ for (const groupId of groups) {
793
+ const groupCollectionName = getCollectionName(CollectionType.GROUPS, groupId);
794
+
795
+ try {
796
+ const groupCollection = weaviateClient.collections.get(groupCollectionName);
797
+
798
+ // Check if memory exists in this group
799
+ const groupMemory = await fetchMemoryWithAllProperties(groupCollection, compositeId);
800
+
801
+ if (groupMemory) {
802
+ // Get current group_ids from the group memory
803
+ const groupMemoryGroupIds: string[] = Array.isArray(groupMemory.properties.group_ids)
804
+ ? groupMemory.properties.group_ids
805
+ : [];
806
+
807
+ // Remove this group from the group_ids array
808
+ const newGroupIds = groupMemoryGroupIds.filter(id => id !== groupId);
809
+
810
+ // Update the memory with new tracking arrays (orphan if empty)
811
+ await groupCollection.data.update({
812
+ id: compositeId,
813
+ properties: {
814
+ group_ids: newGroupIds,
815
+ retracted_at: new Date().toISOString(),
816
+ },
817
+ });
818
+
819
+ retractionResults.groups.push({ groupId, success: true });
820
+
821
+ logger.info('Memory retracted from group (orphaned)', {
822
+ function: 'executeRetractMemory',
823
+ compositeId,
824
+ groupId,
825
+ remainingGroups: newGroupIds,
826
+ isOrphaned: newGroupIds.length === 0,
827
+ });
828
+ } else {
829
+ // Memory not found in this group
830
+ retractionResults.groups.push({
831
+ groupId,
832
+ success: false,
833
+ error: 'Memory not found in group',
834
+ });
835
+ logger.warn('Memory not found in group collection', {
836
+ function: 'executeRetractMemory',
837
+ compositeId,
838
+ groupId,
839
+ });
840
+ }
841
+ } catch (groupError) {
842
+ logger.error('Failed to retract from group', {
843
+ function: 'executeRetractMemory',
844
+ groupId,
845
+ error: groupError instanceof Error ? groupError.message : String(groupError),
846
+ });
847
+ retractionResults.groups.push({
848
+ groupId,
849
+ success: false,
850
+ error: groupError instanceof Error ? groupError.message : String(groupError),
851
+ });
852
+ }
853
+ }
854
+
855
+ // STEP 3: Update source memory tracking arrays
856
+ const finalSpaceIds = retractionResults.spaces?.success
857
+ ? currentSpaceIds.filter(id => !spaces.includes(id))
858
+ : currentSpaceIds;
859
+
860
+ const successfulGroupRetractions = retractionResults.groups
861
+ .filter(g => g.success)
862
+ .map(g => g.groupId);
863
+ const finalGroupIds = currentGroupIds.filter(id => !successfulGroupRetractions.includes(id));
864
+
865
+ // Update source memory
866
+ try {
867
+ await userCollection.data.update({
868
+ id: request.payload.memory_id,
869
+ properties: {
870
+ space_ids: finalSpaceIds,
871
+ group_ids: finalGroupIds,
872
+ },
873
+ });
874
+
875
+ logger.info('Updated source memory tracking arrays after retraction', {
876
+ function: 'executeRetractMemory',
877
+ memoryId: request.payload.memory_id,
878
+ spaceIds: finalSpaceIds,
879
+ groupIds: finalGroupIds,
880
+ });
881
+ } catch (updateError) {
882
+ logger.warn('Failed to update source memory tracking arrays after retraction', {
883
+ function: 'executeRetractMemory',
884
+ memoryId: request.payload.memory_id,
885
+ error: updateError instanceof Error ? updateError.message : String(updateError),
886
+ });
887
+ // Don't fail the retraction if this update fails
888
+ }
889
+
890
+ // Build response
891
+ const successfulRetractions: string[] = [];
892
+ const failedRetractions: string[] = [];
893
+
894
+ if (spaces.length > 0) {
895
+ if (retractionResults.spaces?.success) {
896
+ successfulRetractions.push(`spaces: ${spaces.join(', ')}`);
897
+ } else {
898
+ failedRetractions.push(`spaces: ${retractionResults.spaces?.error || 'unknown error'}`);
899
+ }
900
+ }
901
+
902
+ for (const groupResult of retractionResults.groups) {
903
+ if (groupResult.success) {
904
+ successfulRetractions.push(`group: ${groupResult.groupId}`);
905
+ } else {
906
+ failedRetractions.push(`group ${groupResult.groupId}: ${groupResult.error || 'unknown error'}`);
907
+ }
908
+ }
909
+
910
+ // Return result
911
+ if (successfulRetractions.length > 0) {
912
+ return JSON.stringify(
913
+ {
914
+ success: true,
915
+ composite_id: compositeId,
916
+ retracted_from: successfulRetractions,
917
+ failed: failedRetractions.length > 0 ? failedRetractions : undefined,
918
+ space_ids: finalSpaceIds,
919
+ group_ids: finalGroupIds,
920
+ is_orphaned: finalSpaceIds.length === 0 && finalGroupIds.length === 0,
921
+ },
922
+ null,
923
+ 2
924
+ );
925
+ } else {
926
+ return JSON.stringify(
927
+ {
928
+ success: false,
929
+ error: 'Retraction failed',
930
+ message: 'Failed to retract from any destination',
931
+ details: failedRetractions,
932
+ },
933
+ null,
934
+ 2
935
+ );
936
+ }
937
+ } catch (error) {
938
+ debug.error('Execute retract failed', {
939
+ error: error instanceof Error ? error.message : String(error),
940
+ stack: error instanceof Error ? error.stack : undefined,
941
+ });
942
+ handleToolError(error, {
943
+ toolName: 'remember_confirm',
944
+ userId,
945
+ operation: 'execute retract_memory',
946
+ action: 'retract_memory',
947
+ });
948
+ }
949
+ }
950
+
951
+ /**
952
+ * Execute revise memory action
953
+ *
954
+ * Memory Collection Pattern v2:
955
+ * - Syncs content from source memory to all published copies
956
+ * - Preserves old content in revision_history (max 10 entries)
957
+ * - Updates revised_at and revision_count on each copy
958
+ * - Supports partial success (some locations may fail)
959
+ */
960
+ async function executeReviseMemory(
961
+ request: ConfirmationRequest & { request_id: string },
962
+ userId: string,
963
+ authContext?: AuthContext
964
+ ): Promise<string> {
965
+ const debug = createDebugLogger({
966
+ tool: 'remember_confirm',
967
+ userId,
968
+ operation: 'execute_revise',
969
+ });
970
+
971
+ try {
972
+ const { memory_id, space_ids = [], group_ids = [] } = request.payload;
973
+
974
+ debug.debug('Executing revise memory action', {
975
+ memoryId: memory_id,
976
+ spaceIds: space_ids,
977
+ groupIds: group_ids,
978
+ });
979
+
980
+ logger.info('Executing revise memory action', {
981
+ function: 'executeReviseMemory',
982
+ userId,
983
+ memoryId: memory_id,
984
+ spaceCount: space_ids.length,
985
+ groupCount: group_ids.length,
986
+ });
987
+
988
+ // Fetch source memory
989
+ const weaviateClient = getWeaviateClient();
990
+ const userCollectionName = getMemoryCollectionName(userId);
991
+ const userCollection = weaviateClient.collections.get(userCollectionName);
992
+
993
+ const sourceMemory = await fetchMemoryWithAllProperties(
994
+ userCollection,
995
+ memory_id
996
+ );
997
+
998
+ if (!sourceMemory) {
999
+ return JSON.stringify(
1000
+ {
1001
+ success: false,
1002
+ error: 'Memory not found',
1003
+ message: `Source memory ${memory_id} no longer exists`,
1004
+ },
1005
+ null,
1006
+ 2
1007
+ );
1008
+ }
1009
+
1010
+ // Verify ownership again
1011
+ if (sourceMemory.properties.user_id !== userId) {
1012
+ return JSON.stringify(
1013
+ {
1014
+ success: false,
1015
+ error: 'Permission denied',
1016
+ message: 'You can only revise your own memories',
1017
+ },
1018
+ null,
1019
+ 2
1020
+ );
1021
+ }
1022
+
1023
+ const newContent = String(sourceMemory.properties.content ?? '');
1024
+ const revisedAt = new Date().toISOString();
1025
+ const compositeId = generateCompositeId(userId, memory_id);
1026
+ const results: RevisionResult[] = [];
1027
+
1028
+ logger.info('Revising published copies', {
1029
+ function: 'executeReviseMemory',
1030
+ compositeId,
1031
+ spaceCount: space_ids.length > 0 ? 1 : 0,
1032
+ groupCount: group_ids.length,
1033
+ });
1034
+
1035
+ /**
1036
+ * Update content + revision tracking in a single collection.
1037
+ */
1038
+ async function reviseInCollection(
1039
+ collectionName: string,
1040
+ locationLabel: string
1041
+ ): Promise<void> {
1042
+ try {
1043
+ const collection = weaviateClient.collections.get(collectionName);
1044
+ const publishedMemory = await fetchMemoryWithAllProperties(
1045
+ collection,
1046
+ compositeId
1047
+ );
1048
+
1049
+ if (!publishedMemory) {
1050
+ results.push({
1051
+ location: locationLabel,
1052
+ status: 'skipped',
1053
+ error: 'Published copy not found (may have been deleted)',
1054
+ });
1055
+ logger.warn('Published copy not found in collection', {
1056
+ function: 'executeReviseMemory',
1057
+ collectionName,
1058
+ compositeId,
1059
+ });
1060
+ return;
1061
+ }
1062
+
1063
+ const oldContent = String(publishedMemory.properties.content ?? '');
1064
+
1065
+ // Build updated revision history (only if content actually changed)
1066
+ let revisionHistory = parseRevisionHistory(
1067
+ publishedMemory.properties.revision_history
1068
+ );
1069
+ if (oldContent !== newContent) {
1070
+ revisionHistory = buildRevisionHistory(
1071
+ revisionHistory,
1072
+ oldContent,
1073
+ revisedAt
1074
+ );
1075
+ }
1076
+
1077
+ const currentRevisionCount =
1078
+ typeof publishedMemory.properties.revision_count === 'number'
1079
+ ? publishedMemory.properties.revision_count
1080
+ : 0;
1081
+
1082
+ await collection.data.update({
1083
+ id: compositeId,
1084
+ properties: {
1085
+ content: newContent,
1086
+ revised_at: revisedAt,
1087
+ revision_count: currentRevisionCount + 1,
1088
+ revision_history: JSON.stringify(revisionHistory),
1089
+ },
1090
+ });
1091
+
1092
+ results.push({ location: locationLabel, status: 'success' });
1093
+
1094
+ logger.info('Revised published memory in collection', {
1095
+ function: 'executeReviseMemory',
1096
+ collectionName,
1097
+ compositeId,
1098
+ revisionCount: currentRevisionCount + 1,
1099
+ contentChanged: oldContent !== newContent,
1100
+ });
1101
+ } catch (err) {
1102
+ results.push({
1103
+ location: locationLabel,
1104
+ status: 'failed',
1105
+ error: err instanceof Error ? err.message : String(err),
1106
+ });
1107
+ logger.error('Failed to revise in collection', {
1108
+ function: 'executeReviseMemory',
1109
+ collectionName,
1110
+ compositeId,
1111
+ error: err instanceof Error ? err.message : String(err),
1112
+ });
1113
+ }
1114
+ }
1115
+
1116
+ // Revise in Memory_spaces_public (single collection for all spaces)
1117
+ if (space_ids.length > 0) {
1118
+ await reviseInCollection(
1119
+ getCollectionName(CollectionType.SPACES),
1120
+ 'Memory_spaces_public'
1121
+ );
1122
+ }
1123
+
1124
+ // Revise in each group's collection
1125
+ for (const groupId of group_ids) {
1126
+ await reviseInCollection(
1127
+ getCollectionName(CollectionType.GROUPS, groupId),
1128
+ `Memory_groups_${groupId}`
1129
+ );
1130
+ }
1131
+
1132
+ const successCount = results.filter(r => r.status === 'success').length;
1133
+ const failedCount = results.filter(r => r.status === 'failed').length;
1134
+ const skippedCount = results.filter(r => r.status === 'skipped').length;
1135
+
1136
+ logger.info('Revise execution complete', {
1137
+ function: 'executeReviseMemory',
1138
+ userId,
1139
+ memoryId: memory_id,
1140
+ successCount,
1141
+ failedCount,
1142
+ skippedCount,
1143
+ });
1144
+
1145
+ return JSON.stringify(
1146
+ {
1147
+ success: successCount > 0,
1148
+ composite_id: compositeId,
1149
+ revised_at: revisedAt,
1150
+ summary: {
1151
+ total: results.length,
1152
+ success: successCount,
1153
+ failed: failedCount,
1154
+ skipped: skippedCount,
1155
+ },
1156
+ results,
1157
+ ...(failedCount > 0
1158
+ ? {
1159
+ warnings: [
1160
+ `Failed to revise ${failedCount} of ${results.length} location(s)`,
1161
+ ],
1162
+ }
1163
+ : {}),
1164
+ },
1165
+ null,
1166
+ 2
1167
+ );
1168
+ } catch (error) {
1169
+ debug.error('Execute revise failed', {
1170
+ error: error instanceof Error ? error.message : String(error),
1171
+ stack: error instanceof Error ? error.stack : undefined,
1172
+ });
1173
+ handleToolError(error, {
1174
+ toolName: 'remember_confirm',
1175
+ userId,
1176
+ operation: 'execute revise_memory',
1177
+ action: 'revise_memory',
1178
+ });
1179
+ }
1180
+ }