@prmichaelsen/remember-mcp 2.0.2 → 2.0.4

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.
@@ -0,0 +1,116 @@
1
+ # Task 25: Fix Update Memory Errors
2
+
3
+ **Milestone**: M8 - Testing & Quality
4
+ **Estimated Time**: 2 hours
5
+ **Dependencies**: None
6
+ **Status**: Completed
7
+ **Priority**: Medium
8
+ **Completed**: 2026-02-15
9
+
10
+ ---
11
+
12
+ ## Objective
13
+
14
+ Investigate and fix intermittent `remember_update_memory` errors occurring in production. While less frequent than relationship errors, these failures prevent users from updating their memories.
15
+
16
+ ## Problem Statement
17
+
18
+ Production logs show occasional errors:
19
+ ```
20
+ Tool execution failed for remember_update_memory:
21
+ ```
22
+
23
+ **Frequency**: Less common than relationship errors
24
+ **Impact**: Users cannot update existing memories
25
+ **Severity**: Medium (not blocking all operations)
26
+
27
+ ## Steps
28
+
29
+ 1. **Gather Error Details**
30
+ - Review full error messages in Cloud Run logs
31
+ - Identify which update operations are failing
32
+ - Check if specific memory IDs or update types fail
33
+ - Determine if errors correlate with specific fields being updated
34
+
35
+ 2. **Review Update Memory Code**
36
+ - Examine [`src/tools/update-memory.ts`](../../src/tools/update-memory.ts)
37
+ - Check memory fetch logic (lines ~67-76 in error logs)
38
+ - Review partial update logic
39
+ - Verify version increment logic
40
+ - Check ownership verification
41
+
42
+ 3. **Analyze Error Context**
43
+ - Review error handler implementation (added in v2.0.1)
44
+ - Check if error context includes all necessary information
45
+ - Verify stack traces are captured
46
+ - Ensure userId and memoryId are logged
47
+
48
+ 4. **Identify Common Patterns**
49
+ - Check if errors occur with specific content types
50
+ - Verify if large updates fail more often
51
+ - Test with various field combinations
52
+ - Check for race conditions with concurrent updates
53
+
54
+ 5. **Implement Enhanced Validation**
55
+ - Add pre-update validation for all fields
56
+ - Verify memory exists before attempting update
57
+ - Check ownership before any modifications
58
+ - Validate content type if being updated
59
+ - Ensure weight/trust bounds (0-1)
60
+
61
+ 6. **Add Better Error Messages**
62
+ - Specify which validation failed
63
+ - Include attempted update values in error
64
+ - Add suggestions for fixing invalid inputs
65
+ - Log full update payload on error
66
+
67
+ 7. **Add Tests**
68
+ - Unit test for non-existent memory update
69
+ - Unit test for invalid field values
70
+ - Unit test for cross-user update attempts
71
+ - Unit test for concurrent updates
72
+ - Integration test with real Weaviate
73
+
74
+ 8. **Deploy and Monitor**
75
+ - Deploy fix to production
76
+ - Monitor error logs for 24 hours
77
+ - Verify error rate decreases
78
+ - Check error messages are helpful
79
+
80
+ ## Verification
81
+
82
+ - [ ] Update memory errors have clear, actionable messages
83
+ - [ ] Valid updates succeed consistently
84
+ - [ ] Invalid updates return helpful error messages
85
+ - [ ] Error rate in production drops significantly
86
+ - [ ] Unit tests cover all error scenarios
87
+ - [ ] Integration tests pass with real Weaviate
88
+ - [ ] Error context includes userId, memoryId, and update fields
89
+
90
+ ## Files to Modify
91
+
92
+ - `src/tools/update-memory.ts` - Enhance validation and error handling
93
+ - `src/utils/error-handler.ts` - Add update-specific error context
94
+ - `src/tools/update-memory.spec.ts` - Add error case tests (create if needed)
95
+
96
+ ## Potential Root Causes
97
+
98
+ 1. **Memory Not Found**: Attempting to update deleted/non-existent memory
99
+ 2. **Ownership Mismatch**: User trying to update another user's memory
100
+ 3. **Invalid Field Values**: Out-of-bounds weight/trust, invalid content type
101
+ 4. **Weaviate Connection**: Transient connection issues
102
+ 5. **Race Conditions**: Concurrent updates to same memory
103
+ 6. **Large Payloads**: Updates with very large content
104
+
105
+ ## Expected Outcome
106
+
107
+ - Clear error messages for all update failure scenarios
108
+ - Reduced error rate in production logs
109
+ - Better user experience with actionable feedback
110
+ - Comprehensive test coverage for error cases
111
+ - Improved debugging with detailed error context
112
+
113
+ ---
114
+
115
+ **Next Task**: Task 26 - Add Production Error Monitoring
116
+ **Related**: Task 22 (Comprehensive Error Handling)
@@ -1568,9 +1568,13 @@ async function handleSearchMemory(args, userId) {
1568
1568
  });
1569
1569
  return JSON.stringify(searchResult, null, 2);
1570
1570
  } catch (error) {
1571
- const errorMessage = error instanceof Error ? error.message : String(error);
1572
- logger.error("Failed to search memories:", { error: errorMessage, userId, query: args.query });
1573
- throw new Error(`Failed to search memories: ${errorMessage}`);
1571
+ handleToolError(error, {
1572
+ toolName: "remember_search_memory",
1573
+ operation: "search memories",
1574
+ userId,
1575
+ query: args.query,
1576
+ includeRelationships: args.include_relationships
1577
+ });
1574
1578
  }
1575
1579
  }
1576
1580
 
@@ -1644,8 +1648,13 @@ async function handleDeleteMemory(args, userId) {
1644
1648
  };
1645
1649
  return JSON.stringify(result, null, 2);
1646
1650
  } catch (error) {
1647
- logger.error("Failed to delete memory:", error);
1648
- throw new Error(`Failed to delete memory: ${error instanceof Error ? error.message : String(error)}`);
1651
+ handleToolError(error, {
1652
+ toolName: "remember_delete_memory",
1653
+ operation: "delete memory",
1654
+ userId,
1655
+ memoryId: args.memory_id,
1656
+ deleteRelationships: args.delete_relationships
1657
+ });
1649
1658
  }
1650
1659
  }
1651
1660
 
@@ -1823,23 +1832,14 @@ async function handleUpdateMemory(args, userId) {
1823
1832
  };
1824
1833
  return JSON.stringify(result, null, 2);
1825
1834
  } catch (error) {
1826
- const errorMessage = error instanceof Error ? error.message : String(error);
1827
- const errorStack = error instanceof Error ? error.stack : void 0;
1828
- logger.error("Failed to update memory:", {
1829
- error: errorMessage,
1830
- stack: errorStack,
1835
+ handleToolError(error, {
1836
+ toolName: "remember_update_memory",
1837
+ operation: "update memory",
1831
1838
  userId,
1832
1839
  memoryId: args.memory_id,
1833
- providedFields: Object.keys(args).filter((k) => k !== "memory_id")
1840
+ providedFields: Object.keys(args).filter((k) => k !== "memory_id").join(", "),
1841
+ updateCount: Object.keys(args).filter((k) => k !== "memory_id").length
1834
1842
  });
1835
- throw new Error(
1836
- `Failed to update memory: ${errorMessage}` + (errorStack ? `
1837
-
1838
- Stack trace:
1839
- ${errorStack}` : "") + `
1840
-
1841
- Context: userId=${userId}, memoryId=${args.memory_id}`
1842
- );
1843
1843
  }
1844
1844
  }
1845
1845
 
@@ -1963,8 +1963,14 @@ async function handleFindSimilar(args, userId) {
1963
1963
  };
1964
1964
  return JSON.stringify(result, null, 2);
1965
1965
  } catch (error) {
1966
- logger.error("Failed to find similar memories:", error);
1967
- throw new Error(`Failed to find similar memories: ${error instanceof Error ? error.message : String(error)}`);
1966
+ handleToolError(error, {
1967
+ toolName: "remember_find_similar",
1968
+ operation: "find similar memories",
1969
+ userId,
1970
+ memoryId: args.memory_id,
1971
+ searchText: args.text,
1972
+ limit: args.limit
1973
+ });
1968
1974
  }
1969
1975
  }
1970
1976
 
@@ -2126,8 +2132,13 @@ ${content}${tags}`;
2126
2132
  };
2127
2133
  return JSON.stringify(result, null, 2);
2128
2134
  } catch (error) {
2129
- logger.error("Failed to query memories:", error);
2130
- throw new Error(`Failed to query memories: ${error instanceof Error ? error.message : String(error)}`);
2135
+ handleToolError(error, {
2136
+ toolName: "remember_query_memory",
2137
+ operation: "query memories",
2138
+ userId,
2139
+ query: args.query,
2140
+ format: args.format
2141
+ });
2131
2142
  }
2132
2143
  }
2133
2144
 
@@ -2195,6 +2206,7 @@ async function handleCreateRelationship(args, userId, context) {
2195
2206
  }
2196
2207
  await ensureMemoryCollection(userId);
2197
2208
  const collection = getMemoryCollection(userId);
2209
+ logger.info("Validating memories", { userId, memoryIds: args.memory_ids });
2198
2210
  const memoryChecks = await Promise.all(
2199
2211
  args.memory_ids.map(async (memoryId) => {
2200
2212
  try {
@@ -2202,12 +2214,23 @@ async function handleCreateRelationship(args, userId, context) {
2202
2214
  returnProperties: ["user_id", "doc_type", "relationships"]
2203
2215
  });
2204
2216
  if (!memory) {
2217
+ logger.warn("Memory not found", { userId, memoryId });
2205
2218
  return { memoryId, error: "Memory not found" };
2206
2219
  }
2207
2220
  if (memory.properties.user_id !== userId) {
2221
+ logger.warn("Unauthorized memory access attempt", {
2222
+ userId,
2223
+ memoryId,
2224
+ actualUserId: memory.properties.user_id
2225
+ });
2208
2226
  return { memoryId, error: "Unauthorized: Memory belongs to another user" };
2209
2227
  }
2210
2228
  if (memory.properties.doc_type !== "memory") {
2229
+ logger.warn("Invalid doc_type for relationship", {
2230
+ userId,
2231
+ memoryId,
2232
+ docType: memory.properties.doc_type
2233
+ });
2211
2234
  return { memoryId, error: "Cannot create relationship with non-memory document" };
2212
2235
  }
2213
2236
  return {
@@ -2216,15 +2239,30 @@ async function handleCreateRelationship(args, userId, context) {
2216
2239
  relationships: memory.properties.relationships || []
2217
2240
  };
2218
2241
  } catch (error) {
2219
- return { memoryId, error: `Failed to fetch memory: ${error}` };
2242
+ const errorMsg = error instanceof Error ? error.message : String(error);
2243
+ logger.error("Failed to fetch memory for relationship", {
2244
+ userId,
2245
+ memoryId,
2246
+ error: errorMsg
2247
+ });
2248
+ return { memoryId, error: `Failed to fetch memory: ${errorMsg}` };
2220
2249
  }
2221
2250
  })
2222
2251
  );
2223
2252
  const errors = memoryChecks.filter((check) => check.error);
2224
2253
  if (errors.length > 0) {
2225
2254
  const errorMessages = errors.map((e) => `${e.memoryId}: ${e.error}`).join("; ");
2255
+ logger.error("Memory validation failed", {
2256
+ userId,
2257
+ errorCount: errors.length,
2258
+ errors: errorMessages
2259
+ });
2226
2260
  throw new Error(`Memory validation failed: ${errorMessages}`);
2227
2261
  }
2262
+ logger.info("All memories validated successfully", {
2263
+ userId,
2264
+ validatedCount: memoryChecks.length
2265
+ });
2228
2266
  const now = (/* @__PURE__ */ new Date()).toISOString();
2229
2267
  const relationship = {
2230
2268
  // Core identity
@@ -2297,8 +2335,16 @@ async function handleCreateRelationship(args, userId, context) {
2297
2335
  };
2298
2336
  return JSON.stringify(response, null, 2);
2299
2337
  } catch (error) {
2300
- logger.error("Failed to create relationship:", error);
2301
- throw new Error(`Failed to create relationship: ${error instanceof Error ? error.message : String(error)}`);
2338
+ handleToolError(error, {
2339
+ toolName: "remember_create_relationship",
2340
+ operation: "create relationship",
2341
+ userId,
2342
+ memoryIds: args.memory_ids.join(", "),
2343
+ memoryCount: args.memory_ids.length,
2344
+ relationshipType: args.relationship_type,
2345
+ observation: args.observation?.substring(0, 100)
2346
+ // First 100 chars
2347
+ });
2302
2348
  }
2303
2349
  }
2304
2350
 
@@ -2421,12 +2467,18 @@ async function handleUpdateRelationship(args, userId) {
2421
2467
  };
2422
2468
  return JSON.stringify(result, null, 2);
2423
2469
  } catch (error) {
2424
- logger.error("Failed to update relationship:", error);
2425
- throw new Error(`Failed to update relationship: ${error instanceof Error ? error.message : String(error)}`);
2470
+ handleToolError(error, {
2471
+ toolName: "remember_update_relationship",
2472
+ operation: "update relationship",
2473
+ userId,
2474
+ relationshipId: args.relationship_id,
2475
+ updatedFields: Object.keys(args).filter((k) => k !== "relationship_id")
2476
+ });
2426
2477
  }
2427
2478
  }
2428
2479
 
2429
2480
  // src/tools/search-relationship.ts
2481
+ import { Filters as Filters2 } from "weaviate-client";
2430
2482
  var searchRelationshipTool = {
2431
2483
  name: "remember_search_relationship",
2432
2484
  description: `Search relationships by observation text or relationship type.
@@ -2494,63 +2546,46 @@ async function handleSearchRelationship(args, userId) {
2494
2546
  const collection = getMemoryCollection(userId);
2495
2547
  const limit = args.limit ?? 10;
2496
2548
  const offset = args.offset ?? 0;
2497
- const whereFilters = [
2498
- {
2499
- path: "doc_type",
2500
- operator: "Equal",
2501
- valueText: "relationship"
2502
- }
2503
- ];
2549
+ const filterList = [];
2550
+ filterList.push(
2551
+ collection.filter.byProperty("doc_type").equal("relationship")
2552
+ );
2504
2553
  if (args.relationship_types && args.relationship_types.length > 0) {
2505
2554
  if (args.relationship_types.length === 1) {
2506
- whereFilters.push({
2507
- path: "relationship_type",
2508
- operator: "Equal",
2509
- valueText: args.relationship_types[0]
2510
- });
2555
+ filterList.push(
2556
+ collection.filter.byProperty("relationship_type").equal(args.relationship_types[0])
2557
+ );
2511
2558
  } else {
2512
- whereFilters.push({
2513
- operator: "Or",
2514
- operands: args.relationship_types.map((type) => ({
2515
- path: "relationship_type",
2516
- operator: "Equal",
2517
- valueText: type
2518
- }))
2519
- });
2559
+ const typeFilters = args.relationship_types.map(
2560
+ (type) => collection.filter.byProperty("relationship_type").equal(type)
2561
+ );
2562
+ filterList.push(Filters2.or(...typeFilters));
2520
2563
  }
2521
2564
  }
2522
2565
  if (args.strength_min !== void 0) {
2523
- whereFilters.push({
2524
- path: "strength",
2525
- operator: "GreaterThanEqual",
2526
- valueNumber: args.strength_min
2527
- });
2566
+ filterList.push(
2567
+ collection.filter.byProperty("strength").greaterOrEqual(args.strength_min)
2568
+ );
2528
2569
  }
2529
2570
  if (args.confidence_min !== void 0) {
2530
- whereFilters.push({
2531
- path: "confidence",
2532
- operator: "GreaterThanEqual",
2533
- valueNumber: args.confidence_min
2534
- });
2571
+ filterList.push(
2572
+ collection.filter.byProperty("confidence").greaterOrEqual(args.confidence_min)
2573
+ );
2535
2574
  }
2536
2575
  if (args.tags && args.tags.length > 0) {
2537
- whereFilters.push({
2538
- path: "tags",
2539
- operator: "ContainsAny",
2540
- valueTextArray: args.tags
2541
- });
2576
+ filterList.push(
2577
+ collection.filter.byProperty("tags").containsAny(args.tags)
2578
+ );
2542
2579
  }
2580
+ const combinedFilters = filterList.length > 1 ? Filters2.and(...filterList) : filterList[0];
2543
2581
  const searchOptions = {
2544
2582
  alpha: 1,
2545
2583
  // Pure semantic search for relationships
2546
2584
  limit: limit + offset
2547
2585
  // Get extra for offset
2548
2586
  };
2549
- if (whereFilters.length > 0) {
2550
- searchOptions.filters = whereFilters.length > 1 ? {
2551
- operator: "And",
2552
- operands: whereFilters
2553
- } : whereFilters[0];
2587
+ if (combinedFilters) {
2588
+ searchOptions.filters = combinedFilters;
2554
2589
  }
2555
2590
  const results = await collection.query.hybrid(args.query, searchOptions);
2556
2591
  const paginatedResults = results.objects.slice(offset, offset + limit);
@@ -2586,8 +2621,13 @@ async function handleSearchRelationship(args, userId) {
2586
2621
  };
2587
2622
  return JSON.stringify(result, null, 2);
2588
2623
  } catch (error) {
2589
- logger.error("Failed to search relationships:", error);
2590
- throw new Error(`Failed to search relationships: ${error instanceof Error ? error.message : String(error)}`);
2624
+ handleToolError(error, {
2625
+ toolName: "remember_search_relationship",
2626
+ operation: "search relationships",
2627
+ userId,
2628
+ query: args.query,
2629
+ limit: args.limit
2630
+ });
2591
2631
  }
2592
2632
  }
2593
2633
 
@@ -2688,8 +2728,12 @@ async function handleDeleteRelationship(args, userId) {
2688
2728
  };
2689
2729
  return JSON.stringify(result, null, 2);
2690
2730
  } catch (error) {
2691
- logger.error("Failed to delete relationship:", error);
2692
- throw new Error(`Failed to delete relationship: ${error instanceof Error ? error.message : String(error)}`);
2731
+ handleToolError(error, {
2732
+ toolName: "remember_delete_relationship",
2733
+ operation: "delete relationship",
2734
+ userId,
2735
+ relationshipId: args.relationship_id
2736
+ });
2693
2737
  }
2694
2738
  }
2695
2739
 
@@ -3059,8 +3103,12 @@ async function handleSetPreference(args, userId) {
3059
3103
  logger.info("Preferences set successfully", { userId });
3060
3104
  return JSON.stringify(result, null, 2);
3061
3105
  } catch (error) {
3062
- logger.error("Failed to set preferences:", error);
3063
- throw new Error(`Failed to set preferences: ${error instanceof Error ? error.message : String(error)}`);
3106
+ handleToolError(error, {
3107
+ toolName: "remember_set_preference",
3108
+ operation: "set preference",
3109
+ userId,
3110
+ preferencesProvided: Object.keys(args.preferences || {}).length
3111
+ });
3064
3112
  }
3065
3113
  }
3066
3114
 
@@ -3116,8 +3164,12 @@ async function handleGetPreferences(args, userId) {
3116
3164
  logger.info("Preferences retrieved successfully", { userId, category, isDefault });
3117
3165
  return JSON.stringify(response, null, 2);
3118
3166
  } catch (error) {
3119
- logger.error("Failed to get preferences:", error);
3120
- throw new Error(`Failed to get preferences: ${error instanceof Error ? error.message : String(error)}`);
3167
+ handleToolError(error, {
3168
+ toolName: "remember_get_preferences",
3169
+ operation: "get preferences",
3170
+ userId,
3171
+ category: args.category
3172
+ });
3121
3173
  }
3122
3174
  }
3123
3175