@danielsimonjr/memory-mcp 11.1.1 → 12.5.2

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 (161) hide show
  1. package/README.md +302 -107
  2. package/dist/index.d.ts +2 -2
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +6 -0
  5. package/dist/server/toolDefinitions.d.ts +1 -1
  6. package/dist/server/toolDefinitions.d.ts.map +1 -1
  7. package/dist/server/toolDefinitions.js +2323 -6
  8. package/dist/server/toolHandlers.d.ts +2 -10
  9. package/dist/server/toolHandlers.d.ts.map +1 -1
  10. package/dist/server/toolHandlers.js +2097 -7
  11. package/package.json +9 -6
  12. package/dist/core/EntityManager.d.ts +0 -263
  13. package/dist/core/EntityManager.d.ts.map +0 -1
  14. package/dist/core/EntityManager.js +0 -478
  15. package/dist/core/GraphStorage.d.ts +0 -344
  16. package/dist/core/GraphStorage.d.ts.map +0 -1
  17. package/dist/core/GraphStorage.js +0 -708
  18. package/dist/core/GraphTraversal.d.ts +0 -136
  19. package/dist/core/GraphTraversal.d.ts.map +0 -1
  20. package/dist/core/GraphTraversal.js +0 -556
  21. package/dist/core/HierarchyManager.d.ts +0 -111
  22. package/dist/core/HierarchyManager.d.ts.map +0 -1
  23. package/dist/core/HierarchyManager.js +0 -224
  24. package/dist/core/ManagerContext.d.ts +0 -72
  25. package/dist/core/ManagerContext.d.ts.map +0 -1
  26. package/dist/core/ManagerContext.js +0 -118
  27. package/dist/core/ObservationManager.d.ts +0 -85
  28. package/dist/core/ObservationManager.d.ts.map +0 -1
  29. package/dist/core/ObservationManager.js +0 -123
  30. package/dist/core/RelationManager.d.ts +0 -131
  31. package/dist/core/RelationManager.d.ts.map +0 -1
  32. package/dist/core/RelationManager.js +0 -210
  33. package/dist/core/SQLiteStorage.d.ts +0 -354
  34. package/dist/core/SQLiteStorage.d.ts.map +0 -1
  35. package/dist/core/SQLiteStorage.js +0 -917
  36. package/dist/core/StorageFactory.d.ts +0 -45
  37. package/dist/core/StorageFactory.d.ts.map +0 -1
  38. package/dist/core/StorageFactory.js +0 -64
  39. package/dist/core/TransactionManager.d.ts +0 -259
  40. package/dist/core/TransactionManager.d.ts.map +0 -1
  41. package/dist/core/TransactionManager.js +0 -392
  42. package/dist/core/index.d.ts +0 -16
  43. package/dist/core/index.d.ts.map +0 -1
  44. package/dist/core/index.js +0 -17
  45. package/dist/features/AnalyticsManager.d.ts +0 -44
  46. package/dist/features/AnalyticsManager.d.ts.map +0 -1
  47. package/dist/features/AnalyticsManager.js +0 -223
  48. package/dist/features/ArchiveManager.d.ts +0 -121
  49. package/dist/features/ArchiveManager.d.ts.map +0 -1
  50. package/dist/features/ArchiveManager.js +0 -240
  51. package/dist/features/CompressionManager.d.ts +0 -109
  52. package/dist/features/CompressionManager.d.ts.map +0 -1
  53. package/dist/features/CompressionManager.js +0 -404
  54. package/dist/features/IOManager.d.ts +0 -221
  55. package/dist/features/IOManager.d.ts.map +0 -1
  56. package/dist/features/IOManager.js +0 -997
  57. package/dist/features/StreamingExporter.d.ts +0 -105
  58. package/dist/features/StreamingExporter.d.ts.map +0 -1
  59. package/dist/features/StreamingExporter.js +0 -150
  60. package/dist/features/TagManager.d.ts +0 -147
  61. package/dist/features/TagManager.d.ts.map +0 -1
  62. package/dist/features/TagManager.js +0 -210
  63. package/dist/features/index.d.ts +0 -12
  64. package/dist/features/index.d.ts.map +0 -1
  65. package/dist/features/index.js +0 -11
  66. package/dist/search/BasicSearch.d.ts +0 -51
  67. package/dist/search/BasicSearch.d.ts.map +0 -1
  68. package/dist/search/BasicSearch.js +0 -137
  69. package/dist/search/BooleanSearch.d.ts +0 -98
  70. package/dist/search/BooleanSearch.d.ts.map +0 -1
  71. package/dist/search/BooleanSearch.js +0 -430
  72. package/dist/search/EmbeddingService.d.ts +0 -178
  73. package/dist/search/EmbeddingService.d.ts.map +0 -1
  74. package/dist/search/EmbeddingService.js +0 -358
  75. package/dist/search/FuzzySearch.d.ts +0 -118
  76. package/dist/search/FuzzySearch.d.ts.map +0 -1
  77. package/dist/search/FuzzySearch.js +0 -312
  78. package/dist/search/RankedSearch.d.ts +0 -71
  79. package/dist/search/RankedSearch.d.ts.map +0 -1
  80. package/dist/search/RankedSearch.js +0 -238
  81. package/dist/search/SavedSearchManager.d.ts +0 -79
  82. package/dist/search/SavedSearchManager.d.ts.map +0 -1
  83. package/dist/search/SavedSearchManager.js +0 -145
  84. package/dist/search/SearchFilterChain.d.ts +0 -120
  85. package/dist/search/SearchFilterChain.d.ts.map +0 -1
  86. package/dist/search/SearchFilterChain.js +0 -185
  87. package/dist/search/SearchManager.d.ts +0 -285
  88. package/dist/search/SearchManager.d.ts.map +0 -1
  89. package/dist/search/SearchManager.js +0 -338
  90. package/dist/search/SearchSuggestions.d.ts +0 -27
  91. package/dist/search/SearchSuggestions.d.ts.map +0 -1
  92. package/dist/search/SearchSuggestions.js +0 -57
  93. package/dist/search/SemanticSearch.d.ts +0 -146
  94. package/dist/search/SemanticSearch.d.ts.map +0 -1
  95. package/dist/search/SemanticSearch.js +0 -313
  96. package/dist/search/TFIDFIndexManager.d.ts +0 -63
  97. package/dist/search/TFIDFIndexManager.d.ts.map +0 -1
  98. package/dist/search/TFIDFIndexManager.js +0 -215
  99. package/dist/search/VectorStore.d.ts +0 -235
  100. package/dist/search/VectorStore.d.ts.map +0 -1
  101. package/dist/search/VectorStore.js +0 -311
  102. package/dist/search/index.d.ts +0 -18
  103. package/dist/search/index.d.ts.map +0 -1
  104. package/dist/search/index.js +0 -21
  105. package/dist/types/index.d.ts +0 -13
  106. package/dist/types/index.d.ts.map +0 -1
  107. package/dist/types/index.js +0 -12
  108. package/dist/types/types.d.ts +0 -1234
  109. package/dist/types/types.d.ts.map +0 -1
  110. package/dist/types/types.js +0 -9
  111. package/dist/utils/compressedCache.d.ts +0 -192
  112. package/dist/utils/compressedCache.d.ts.map +0 -1
  113. package/dist/utils/compressedCache.js +0 -309
  114. package/dist/utils/compressionUtil.d.ts +0 -214
  115. package/dist/utils/compressionUtil.d.ts.map +0 -1
  116. package/dist/utils/compressionUtil.js +0 -247
  117. package/dist/utils/constants.d.ts +0 -245
  118. package/dist/utils/constants.d.ts.map +0 -1
  119. package/dist/utils/constants.js +0 -252
  120. package/dist/utils/entityUtils.d.ts +0 -321
  121. package/dist/utils/entityUtils.d.ts.map +0 -1
  122. package/dist/utils/entityUtils.js +0 -538
  123. package/dist/utils/errors.d.ts +0 -77
  124. package/dist/utils/errors.d.ts.map +0 -1
  125. package/dist/utils/errors.js +0 -121
  126. package/dist/utils/formatters.d.ts +0 -145
  127. package/dist/utils/formatters.d.ts.map +0 -1
  128. package/dist/utils/formatters.js +0 -132
  129. package/dist/utils/index.d.ts +0 -22
  130. package/dist/utils/index.d.ts.map +0 -1
  131. package/dist/utils/index.js +0 -75
  132. package/dist/utils/indexes.d.ts +0 -270
  133. package/dist/utils/indexes.d.ts.map +0 -1
  134. package/dist/utils/indexes.js +0 -526
  135. package/dist/utils/logger.d.ts +0 -24
  136. package/dist/utils/logger.d.ts.map +0 -1
  137. package/dist/utils/logger.js +0 -33
  138. package/dist/utils/parallelUtils.d.ts +0 -72
  139. package/dist/utils/parallelUtils.d.ts.map +0 -1
  140. package/dist/utils/parallelUtils.js +0 -169
  141. package/dist/utils/schemas.d.ts +0 -374
  142. package/dist/utils/schemas.d.ts.map +0 -1
  143. package/dist/utils/schemas.js +0 -484
  144. package/dist/utils/searchAlgorithms.d.ts +0 -99
  145. package/dist/utils/searchAlgorithms.d.ts.map +0 -1
  146. package/dist/utils/searchAlgorithms.js +0 -167
  147. package/dist/utils/searchCache.d.ts +0 -108
  148. package/dist/utils/searchCache.d.ts.map +0 -1
  149. package/dist/utils/searchCache.js +0 -209
  150. package/dist/utils/taskScheduler.d.ts +0 -290
  151. package/dist/utils/taskScheduler.d.ts.map +0 -1
  152. package/dist/utils/taskScheduler.js +0 -466
  153. package/dist/workers/WorkerPool.d.ts +0 -81
  154. package/dist/workers/WorkerPool.d.ts.map +0 -1
  155. package/dist/workers/WorkerPool.js +0 -121
  156. package/dist/workers/index.d.ts +0 -12
  157. package/dist/workers/index.d.ts.map +0 -1
  158. package/dist/workers/index.js +0 -9
  159. package/dist/workers/levenshteinWorker.d.ts +0 -60
  160. package/dist/workers/levenshteinWorker.d.ts.map +0 -1
  161. package/dist/workers/levenshteinWorker.js +0 -98
@@ -1,15 +1,103 @@
1
1
  /**
2
2
  * MCP Tool Handlers
3
3
  *
4
- * Contains handler functions for all 59 Knowledge Graph tools.
4
+ * Contains handler functions for all 137 Knowledge Graph tools.
5
5
  * Handlers call managers directly via ManagerContext.
6
6
  * All core functionality is imported from @danielsimonjr/memoryjs.
7
7
  *
8
8
  * @module server/toolHandlers
9
9
  */
10
- import { formatToolResponse, formatTextResponse, formatRawResponse, validateWithSchema, validateFilePath, BatchCreateEntitiesSchema, BatchCreateRelationsSchema, EntityNamesSchema, DeleteRelationsSchema, AddObservationsInputSchema, DeleteObservationsInputSchema, ArchiveCriteriaSchema, SavedSearchInputSchema, SavedSearchUpdateSchema, ImportFormatSchema, ExtendedExportFormatSchema, MergeStrategySchema, ExportFilterSchema, SearchQuerySchema, HybridSearchManager, QueryAnalyzer, QueryPlanner, ReflectionManager, ObservationNormalizer, } from '@danielsimonjr/memoryjs';
10
+ import path from 'node:path';
11
+ import { formatToolResponse, formatTextResponse, formatRawResponse, validateWithSchema, validateFilePath, BatchCreateEntitiesSchema, BatchCreateRelationsSchema, EntityNamesSchema, DeleteRelationsSchema, AddObservationsInputSchema, DeleteObservationsInputSchema, ArchiveCriteriaSchema, SavedSearchInputSchema, SavedSearchUpdateSchema, ImportFormatSchema, ExtendedExportFormatSchema, MergeStrategySchema, ExportFilterSchema, SearchQuerySchema, HybridSearchManager, QueryAnalyzer, QueryPlanner, ReflectionManager, ObservationNormalizer, RefIndex, AuditLog, GovernanceManager, FreshnessManager, ArtifactManager, CollaborativeSynthesis, FailureDistillation, CognitiveLoadAnalyzer, ConsolidationScheduler, DreamEngine, DistillationPipeline, DefaultDistillationPolicy, NoOpDistillationPolicy, computeEntropy, passesEntropyFilter, EntropyFilterStage, getRoleProfile, listRoleProfiles, QueryCostEstimator, ContradictionDetector, PiiRedactor, DecisionManager, RankedSearch, clearAllSearchCaches, getAllCacheStats, } from '@danielsimonjr/memoryjs';
12
+ import { promises as fs } from 'node:fs';
13
+ import { performance } from 'node:perf_hooks';
11
14
  import { z } from 'zod';
12
15
  import { maybeCompressResponse } from './responseCompressor.js';
16
+ // ==================== SINGLETON INFRASTRUCTURE ====================
17
+ // WeakMap-based singletons keyed on ManagerContext to avoid re-instantiation per request.
18
+ // These managers are not on ManagerContext directly, so we wire them up once per ctx.
19
+ const refIndexMap = new WeakMap();
20
+ const auditLogMap = new WeakMap();
21
+ const governanceMap = new WeakMap();
22
+ const freshnessMap = new WeakMap();
23
+ const artifactManagerMap = new WeakMap();
24
+ const distillationPipelineMap = new WeakMap();
25
+ const failureDistillationMap = new WeakMap();
26
+ const consolidationSchedulerMap = new WeakMap();
27
+ const dreamEngineMap = new WeakMap();
28
+ const queryCostEstimatorMap = new WeakMap();
29
+ // Active project scope (Phase 13 deferred item; ctx.defaultProjectId is readonly,
30
+ // so we keep mutable scope state in a WeakMap keyed on the context. Set via the
31
+ // `set_project_scope` tool; handlers may consult `getActiveProjectScope(ctx)`
32
+ // when they want to auto-apply the scope.
33
+ const projectScopeMap = new WeakMap();
34
+ export function getActiveProjectScope(ctx) {
35
+ return projectScopeMap.get(ctx);
36
+ }
37
+ function getStorageFilePath(ctx) {
38
+ // GraphStorage stores the path on `memoryFilePath` (private but reachable via
39
+ // type-cast). Older versions of this helper looked for `filePath` and
40
+ // silently fell back to 'memory.jsonl' — which made diag / size / reindex
41
+ // reports point at the wrong file. Try both field names defensively.
42
+ const storage = ctx.storage;
43
+ return storage.memoryFilePath ?? storage.filePath ?? 'memory.jsonl';
44
+ }
45
+ function getRefIndex(ctx) {
46
+ if (!refIndexMap.has(ctx)) {
47
+ const storagePath = getStorageFilePath(ctx);
48
+ const dir = path.dirname(storagePath);
49
+ refIndexMap.set(ctx, new RefIndex(path.join(dir, 'memory-ref-index.jsonl')));
50
+ }
51
+ return refIndexMap.get(ctx);
52
+ }
53
+ function getAuditLog(ctx) {
54
+ if (!auditLogMap.has(ctx)) {
55
+ const storagePath = getStorageFilePath(ctx);
56
+ const dir = path.dirname(storagePath);
57
+ auditLogMap.set(ctx, new AuditLog(path.join(dir, 'memory-audit.jsonl')));
58
+ }
59
+ return auditLogMap.get(ctx);
60
+ }
61
+ function getGovernanceManager(ctx) {
62
+ if (!governanceMap.has(ctx)) {
63
+ // GovernanceManager constructor accepts GraphStorage; ctx.storage is GraphStorage
64
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
65
+ governanceMap.set(ctx, new GovernanceManager(ctx.storage, getAuditLog(ctx)));
66
+ }
67
+ return governanceMap.get(ctx);
68
+ }
69
+ function getFreshnessManager(ctx) {
70
+ if (!freshnessMap.has(ctx)) {
71
+ freshnessMap.set(ctx, new FreshnessManager(ctx.storage));
72
+ }
73
+ return freshnessMap.get(ctx);
74
+ }
75
+ function getArtifactManager(ctx) {
76
+ if (!artifactManagerMap.has(ctx)) {
77
+ const refIndex = getRefIndex(ctx);
78
+ // EntityManager.registerRef() requires setRefIndex to be called first.
79
+ ctx.entityManager.setRefIndex(refIndex);
80
+ artifactManagerMap.set(ctx, new ArtifactManager(ctx.storage, ctx.entityManager, refIndex));
81
+ }
82
+ return artifactManagerMap.get(ctx);
83
+ }
84
+ function getDistillationPipeline(ctx) {
85
+ if (!distillationPipelineMap.has(ctx)) {
86
+ distillationPipelineMap.set(ctx, new DistillationPipeline());
87
+ }
88
+ return distillationPipelineMap.get(ctx);
89
+ }
90
+ function getFailureDistillation(ctx) {
91
+ if (!failureDistillationMap.has(ctx)) {
92
+ failureDistillationMap.set(ctx, new FailureDistillation(ctx.storage));
93
+ }
94
+ return failureDistillationMap.get(ctx);
95
+ }
96
+ /** Simple token estimator: ~4 chars per token */
97
+ function estimateTokens(entity) {
98
+ const text = [entity.name, entity.entityType, ...entity.observations].join(' ');
99
+ return Math.ceil(text.length / 4);
100
+ }
13
101
  /**
14
102
  * Wrapper to apply automatic response compression for large tool responses.
15
103
  *
@@ -51,11 +139,43 @@ async function withCompression(handler) {
51
139
  * Filtered/limited search tools (search_nodes_ranked, fuzzy_search, etc.) are not
52
140
  * wrapped because their results are bounded by query specificity or limit params.
53
141
  */
142
+ /**
143
+ * v2.1.0 — Parse `find_duplicate_observations` / `find_jaccard_duplicate_observations`
144
+ * filter args into the manager's `ObservationDedupFilter` shape. Validates each
145
+ * field independently so a stray garbage value falls back to the manager default
146
+ * rather than throwing — REST-like leniency since these are diagnostic tools.
147
+ */
148
+ function parseObservationDedupFilter(args) {
149
+ const out = {};
150
+ if (typeof args.entityType === 'string') {
151
+ out.entityType = args.entityType;
152
+ }
153
+ else if (Array.isArray(args.entityType) && args.entityType.every((s) => typeof s === 'string')) {
154
+ out.entityType = args.entityType;
155
+ }
156
+ if (typeof args.projectId === 'string')
157
+ out.projectId = args.projectId;
158
+ if (typeof args.sessionId === 'string')
159
+ out.sessionId = args.sessionId;
160
+ if (typeof args.minOccurrences === 'number' && Number.isFinite(args.minOccurrences) && args.minOccurrences >= 2) {
161
+ out.minOccurrences = args.minOccurrences;
162
+ }
163
+ if (typeof args.maxGroups === 'number' && Number.isFinite(args.maxGroups) && args.maxGroups >= 1) {
164
+ out.maxGroups = args.maxGroups;
165
+ }
166
+ return out;
167
+ }
54
168
  export const toolHandlers = {
55
169
  // ==================== ENTITY HANDLERS ====================
56
170
  create_entities: async (ctx, args) => {
57
171
  const entities = validateWithSchema(args.entities, BatchCreateEntitiesSchema, 'Invalid entities data');
58
- return formatToolResponse(await ctx.entityManager.createEntities(entities));
172
+ // Phase 13 (v12.4.0): auto-stamp active project scope on entities lacking
173
+ // an explicit projectId. Set via set_project_scope; explicit projectId wins.
174
+ const activeScope = getActiveProjectScope(ctx);
175
+ const scoped = activeScope
176
+ ? entities.map((e) => (e.projectId === undefined ? { ...e, projectId: activeScope } : e))
177
+ : entities;
178
+ return formatToolResponse(await ctx.entityManager.createEntities(scoped));
59
179
  },
60
180
  delete_entities: async (ctx, args) => {
61
181
  const entityNames = validateWithSchema(args.entityNames, EntityNamesSchema, 'Invalid entity names');
@@ -69,6 +189,50 @@ export const toolHandlers = {
69
189
  : [];
70
190
  return withCompression(async () => formatToolResponse(await ctx.searchManager.openNodes(names)));
71
191
  },
192
+ // Phase 13: Project scoping + memory versioning
193
+ list_projects: async (ctx, _args) => {
194
+ const projects = await ctx.entityManager.listProjects();
195
+ return formatToolResponse({ projects, count: projects.length });
196
+ },
197
+ set_project_scope: async (ctx, args) => {
198
+ const projectId = validateWithSchema(args.projectId, z.string(), 'Invalid projectId');
199
+ if (projectId === '') {
200
+ projectScopeMap.delete(ctx);
201
+ return formatToolResponse({ projectId: null });
202
+ }
203
+ projectScopeMap.set(ctx, projectId);
204
+ return formatToolResponse({ projectId });
205
+ },
206
+ get_project_scope: async (ctx, _args) => {
207
+ const projectId = projectScopeMap.get(ctx) ?? null;
208
+ return formatToolResponse({ projectId });
209
+ },
210
+ get_entity_versions: async (ctx, args) => {
211
+ const entityName = validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName');
212
+ const latest = await ctx.entityManager.getLatestVersion(entityName);
213
+ if (!latest) {
214
+ return { content: [{ type: 'text', text: `Entity '${entityName}' not found` }], isError: true };
215
+ }
216
+ return formatToolResponse(latest);
217
+ },
218
+ get_version_chain: async (ctx, args) => {
219
+ const entityName = validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName');
220
+ const chain = await ctx.entityManager.getVersionChain(entityName);
221
+ if (chain.length === 0) {
222
+ return { content: [{ type: 'text', text: `Entity '${entityName}' not found` }], isError: true };
223
+ }
224
+ return formatToolResponse({
225
+ rootEntity: chain[0]?.name ?? null,
226
+ latestEntity: chain.find(e => e.isLatest !== false)?.name ?? null,
227
+ versions: chain.map(e => ({
228
+ name: e.name,
229
+ version: e.version ?? 1,
230
+ isLatest: e.isLatest !== false,
231
+ observations: e.observations,
232
+ })),
233
+ count: chain.length,
234
+ });
235
+ },
72
236
  // ==================== RELATION HANDLERS ====================
73
237
  create_relations: async (ctx, args) => {
74
238
  const relations = validateWithSchema(args.relations, BatchCreateRelationsSchema, 'Invalid relations data');
@@ -79,6 +243,49 @@ export const toolHandlers = {
79
243
  await ctx.relationManager.deleteRelations(relations);
80
244
  return formatTextResponse(`Deleted ${relations.length} relations`);
81
245
  },
246
+ // Phase 13: Temporal knowledge graph
247
+ invalidate_relation: async (ctx, args) => {
248
+ try {
249
+ const from = validateWithSchema(args.from, z.string().min(1), 'Invalid from');
250
+ const relationType = validateWithSchema(args.relationType, z.string().min(1), 'Invalid relationType');
251
+ const to = validateWithSchema(args.to, z.string().min(1), 'Invalid to');
252
+ const ended = args.ended !== undefined ? validateWithSchema(args.ended, z.string(), 'Invalid ended') : undefined;
253
+ await ctx.relationManager.invalidateRelation(from, relationType, to, ended);
254
+ return formatTextResponse(`Invalidated: ${from} -[${relationType}]-> ${to} (ended: ${ended ?? 'now'})`);
255
+ }
256
+ catch (err) {
257
+ const msg = err instanceof Error ? err.message : String(err);
258
+ return { content: [{ type: 'text', text: msg }], isError: true };
259
+ }
260
+ },
261
+ query_as_of: async (ctx, args) => {
262
+ const entityName = validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName');
263
+ const asOf = validateWithSchema(args.asOf, z.string().min(1), 'Invalid asOf');
264
+ const direction = args.direction !== undefined
265
+ ? validateWithSchema(args.direction, z.enum(['outgoing', 'incoming', 'both']), 'Invalid direction')
266
+ : undefined;
267
+ const relations = await ctx.relationManager.queryAsOf(entityName, asOf, { direction });
268
+ return formatToolResponse({ entity: entityName, asOf, relations, count: relations.length });
269
+ },
270
+ timeline: async (ctx, args) => {
271
+ const entityName = validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName');
272
+ const direction = args.direction !== undefined
273
+ ? validateWithSchema(args.direction, z.enum(['outgoing', 'incoming', 'both']), 'Invalid direction')
274
+ : undefined;
275
+ const relations = await ctx.relationManager.timeline(entityName, { direction });
276
+ return formatToolResponse({
277
+ entity: entityName,
278
+ timeline: relations.map(r => ({
279
+ from: r.from,
280
+ relationType: r.relationType,
281
+ to: r.to,
282
+ validFrom: r.properties?.validFrom ?? null,
283
+ validUntil: r.properties?.validUntil ?? null,
284
+ current: r.properties?.validUntil === undefined,
285
+ })),
286
+ count: relations.length,
287
+ });
288
+ },
82
289
  // ==================== OBSERVATION HANDLERS ====================
83
290
  add_observations: async (ctx, args) => {
84
291
  const observations = validateWithSchema(args.observations, AddObservationsInputSchema, 'Invalid observations data');
@@ -94,10 +301,20 @@ export const toolHandlers = {
94
301
  const entityName = args.entityName !== undefined
95
302
  ? validateWithSchema(args.entityName, z.string().min(1), 'Invalid entity name')
96
303
  : undefined;
97
- const options = args.options ?? {};
304
+ const options = args.options !== undefined
305
+ ? validateWithSchema(args.options, z.object({
306
+ resolveCoreferences: z.boolean().optional(),
307
+ anchorTimestamps: z.boolean().optional(),
308
+ extractKeywords: z.boolean().optional(),
309
+ }).strict(), 'Invalid options')
310
+ : {};
98
311
  const persist = args.persist === true;
99
312
  const normalizer = new ObservationNormalizer();
100
- const graph = await ctx.storage.loadGraph();
313
+ // Use getGraphForMutation() to acquire the storage write lock — prevents
314
+ // concurrent calls from overwriting each other's persisted normalizations.
315
+ const graph = persist
316
+ ? await ctx.storage.getGraphForMutation()
317
+ : await ctx.storage.loadGraph();
101
318
  const entities = entityName
102
319
  ? graph.entities.filter(e => e.name === entityName)
103
320
  : graph.entities;
@@ -187,11 +404,37 @@ export const toolHandlers = {
187
404
  const limit = args.limit !== undefined ? validateWithSchema(args.limit, z.number().int().positive().max(200), 'Invalid limit') : undefined;
188
405
  return formatToolResponse(await ctx.searchManager.autoSearch(query, limit));
189
406
  },
407
+ // Phase 13: Semantic forget
408
+ forget_memory: async (ctx, args) => {
409
+ const content = validateWithSchema(args.content, z.string().min(1), 'Invalid content');
410
+ const threshold = args.threshold !== undefined ? validateWithSchema(args.threshold, z.number().min(0).max(1), 'Invalid threshold') : undefined;
411
+ const projectId = args.projectId !== undefined ? validateWithSchema(args.projectId, z.string(), 'Invalid projectId') : undefined;
412
+ const dryRun = args.dryRun === true;
413
+ const result = await ctx.semanticForget.forgetByContent(content, { threshold, projectId, dryRun });
414
+ if (result.method === 'not_found') {
415
+ return formatTextResponse(`No matching memory found for: "${content}". Try a different search term.`);
416
+ }
417
+ return formatToolResponse(result);
418
+ },
190
419
  // Phase 11 Sprint 2: Hybrid search
191
420
  hybrid_search: async (ctx, args) => {
192
421
  const query = validateWithSchema(args.query, SearchQuerySchema, 'Invalid search query');
193
- const weights = args.weights;
194
- const filters = args.filters;
422
+ const weights = args.weights !== undefined
423
+ ? validateWithSchema(args.weights, z.object({
424
+ semantic: z.number().optional(),
425
+ lexical: z.number().optional(),
426
+ symbolic: z.number().optional(),
427
+ }).strict(), 'Invalid weights')
428
+ : undefined;
429
+ const filters = args.filters !== undefined
430
+ ? validateWithSchema(args.filters, z.object({
431
+ tags: z.array(z.string()).optional(),
432
+ entityTypes: z.array(z.string()).optional(),
433
+ dateRange: z.object({ start: z.string(), end: z.string() }).strict().optional(),
434
+ minImportance: z.number().optional(),
435
+ maxImportance: z.number().optional(),
436
+ }).strict(), 'Invalid filters')
437
+ : undefined;
195
438
  const limit = args.limit !== undefined
196
439
  ? validateWithSchema(args.limit, z.number().int().positive().max(200), 'Invalid limit')
197
440
  : 10;
@@ -532,6 +775,22 @@ export const toolHandlers = {
532
775
  const dryRun = args.dryRun !== undefined ? validateWithSchema(args.dryRun, z.boolean(), 'Invalid dryRun value') : undefined;
533
776
  return formatToolResponse(await ctx.ioManager.importGraph(format, data, mergeStrategy, dryRun));
534
777
  },
778
+ // Phase 13: Conversation ingestion
779
+ ingest: async (ctx, args) => {
780
+ if (!args.messages || args.messages.length === 0) {
781
+ return formatTextResponse('No messages provided. At least one message is required.');
782
+ }
783
+ const messages = validateWithSchema(args.messages, z.array(z.object({ role: z.enum(['user', 'assistant', 'system']), content: z.string(), timestamp: z.string().optional() })), 'Invalid messages');
784
+ const source = args.source !== undefined ? validateWithSchema(args.source, z.string(), 'Invalid source') : undefined;
785
+ const projectId = args.projectId !== undefined ? validateWithSchema(args.projectId, z.string(), 'Invalid projectId') : undefined;
786
+ const tags = args.tags !== undefined ? validateWithSchema(args.tags, z.array(z.string()), 'Invalid tags') : undefined;
787
+ const chunkBy = args.chunkBy !== undefined
788
+ ? validateWithSchema(args.chunkBy, z.enum(['exchange', 'paragraph', 'fixed']), 'Invalid chunkBy')
789
+ : undefined;
790
+ const dryRun = args.dryRun === true;
791
+ const result = await ctx.ioManager.ingest({ messages, source }, { projectId, tags, chunkBy, dryRun });
792
+ return formatToolResponse(result);
793
+ },
535
794
  export_graph: async (ctx, args) => {
536
795
  const format = validateWithSchema(args.format, ExtendedExportFormatSchema, 'Invalid export format');
537
796
  const filter = args.filter !== undefined ? validateWithSchema(args.filter, ExportFilterSchema, 'Invalid export filter') : undefined;
@@ -540,6 +799,7 @@ export const toolHandlers = {
540
799
  ? validateWithSchema(args.compressionQuality, z.number().int().min(0).max(11), 'Invalid compression quality (must be 0-11)')
541
800
  : undefined;
542
801
  const streaming = args.streaming !== undefined ? validateWithSchema(args.streaming, z.boolean(), 'Invalid streaming value') : undefined;
802
+ const redactPii = args.redactPii !== undefined ? validateWithSchema(args.redactPii, z.boolean(), 'Invalid redactPii value') : false;
543
803
  const rawOutputPath = args.outputPath !== undefined ? validateWithSchema(args.outputPath, z.string(), 'Invalid outputPath value') : undefined;
544
804
  // Validate outputPath to prevent path traversal attacks
545
805
  const outputPath = rawOutputPath !== undefined ? validateFilePath(rawOutputPath) : undefined;
@@ -551,6 +811,10 @@ export const toolHandlers = {
551
811
  else {
552
812
  graph = await ctx.storage.loadGraph();
553
813
  }
814
+ // η.6.3 — apply PII redactor on export when requested
815
+ if (redactPii) {
816
+ graph = new PiiRedactor().redactGraph(graph);
817
+ }
554
818
  // Export with optional compression and streaming
555
819
  const result = await ctx.ioManager.exportGraphWithCompression(graph, format, {
556
820
  filter,
@@ -655,6 +919,1832 @@ export const toolHandlers = {
655
919
  stats: semanticSearch.getStats(),
656
920
  });
657
921
  },
922
+ // ==================== REF INDEX HANDLERS ====================
923
+ register_ref: async (ctx, args) => {
924
+ const ref = validateWithSchema(args.ref, z.string().min(1), 'Invalid ref');
925
+ const entityName = validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName');
926
+ const description = args.description !== undefined
927
+ ? validateWithSchema(args.description, z.string(), 'Invalid description')
928
+ : undefined;
929
+ const entry = await getRefIndex(ctx).register(ref, entityName, description);
930
+ return formatToolResponse(entry);
931
+ },
932
+ resolve_ref: async (ctx, args) => {
933
+ const ref = validateWithSchema(args.ref, z.string().min(1), 'Invalid ref');
934
+ const entityName = await getRefIndex(ctx).resolve(ref);
935
+ if (entityName === null) {
936
+ return formatTextResponse(`Ref "${ref}" is not registered`);
937
+ }
938
+ return formatToolResponse({ ref, entityName });
939
+ },
940
+ deregister_ref: async (ctx, args) => {
941
+ const ref = validateWithSchema(args.ref, z.string().min(1), 'Invalid ref');
942
+ await getRefIndex(ctx).deregister(ref);
943
+ return formatTextResponse(`Ref "${ref}" deregistered`);
944
+ },
945
+ list_refs: async (ctx, args) => {
946
+ const entityName = args.entityName !== undefined
947
+ ? validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName')
948
+ : undefined;
949
+ const refs = await getRefIndex(ctx).listRefs(entityName);
950
+ return formatToolResponse({ refs, count: refs.length });
951
+ },
952
+ // ==================== ARTIFACT HANDLERS ====================
953
+ create_artifact: async (ctx, args) => {
954
+ const content = validateWithSchema(args.content, z.string().min(1), 'Invalid content');
955
+ const toolName = validateWithSchema(args.toolName, z.string().min(1), 'Invalid toolName');
956
+ const artifactType = validateWithSchema(args.artifactType, z.enum(['tool_output', 'code_snippet', 'api_response', 'search_result', 'file_content', 'user_input']), 'Invalid artifactType');
957
+ const description = args.description !== undefined
958
+ ? validateWithSchema(args.description, z.string(), 'Invalid description')
959
+ : undefined;
960
+ const sessionId = args.sessionId !== undefined
961
+ ? validateWithSchema(args.sessionId, z.string(), 'Invalid sessionId')
962
+ : undefined;
963
+ const artifact = await getArtifactManager(ctx).createArtifact({
964
+ content,
965
+ toolName,
966
+ artifactType,
967
+ description,
968
+ sessionId,
969
+ });
970
+ return formatToolResponse(artifact);
971
+ },
972
+ get_artifact: async (ctx, args) => {
973
+ const ref = validateWithSchema(args.ref, z.string().min(1), 'Invalid ref');
974
+ const artifact = await getArtifactManager(ctx).getArtifact(ref);
975
+ if (!artifact) {
976
+ return formatTextResponse(`Artifact "${ref}" not found`);
977
+ }
978
+ return formatToolResponse(artifact);
979
+ },
980
+ list_artifacts: async (ctx, args) => {
981
+ const filter = {};
982
+ if (args.toolName !== undefined) {
983
+ filter.toolName = validateWithSchema(args.toolName, z.string(), 'Invalid toolName');
984
+ }
985
+ if (args.artifactType !== undefined) {
986
+ filter.artifactType = validateWithSchema(args.artifactType, z.enum(['tool_output', 'code_snippet', 'api_response', 'search_result', 'file_content', 'user_input']), 'Invalid artifactType');
987
+ }
988
+ if (args.since !== undefined) {
989
+ const sinceStr = validateWithSchema(args.since, z.string().regex(/^\d{4}-\d{2}-\d{2}(T[\d:.Z+-]+)?$/, 'since must be an ISO 8601 date string'), 'Invalid since');
990
+ const sinceDate = new Date(sinceStr);
991
+ if (isNaN(sinceDate.getTime())) {
992
+ throw new Error(`Invalid since date: "${sinceStr}" is not a valid date`);
993
+ }
994
+ filter.since = sinceDate;
995
+ }
996
+ const artifacts = await getArtifactManager(ctx).listArtifacts(Object.keys(filter).length > 0 ? filter : undefined);
997
+ return formatToolResponse({ artifacts, count: artifacts.length });
998
+ },
999
+ // ==================== TEMPORAL SEARCH HANDLER ====================
1000
+ search_by_time: async (ctx, args) => {
1001
+ const query = validateWithSchema(args.query, z.string().min(1), 'Invalid query');
1002
+ const options = {};
1003
+ if (args.field !== undefined) {
1004
+ options.field = validateWithSchema(args.field, z.enum(['createdAt', 'lastModified', 'any']), 'Invalid field');
1005
+ }
1006
+ if (args.includeUndated !== undefined) {
1007
+ options.includeUndated = validateWithSchema(args.includeUndated, z.boolean(), 'Invalid includeUndated');
1008
+ }
1009
+ const entities = await ctx.searchManager.searchByTime(query, options);
1010
+ return formatToolResponse({ query, entities, count: entities.length });
1011
+ },
1012
+ // ==================== DISTILLATION HANDLER ====================
1013
+ configure_distillation: async (ctx, args) => {
1014
+ const policy = validateWithSchema(args.policy, z.enum(['default', 'noop', 'none']), 'Invalid policy');
1015
+ const pipeline = getDistillationPipeline(ctx);
1016
+ pipeline.clearPolicies();
1017
+ if (policy === 'default') {
1018
+ pipeline.addPolicy(new DefaultDistillationPolicy(), 'default');
1019
+ }
1020
+ else if (policy === 'noop') {
1021
+ pipeline.addPolicy(new NoOpDistillationPolicy(), 'noop');
1022
+ }
1023
+ // 'none' clears all policies — passthrough behavior
1024
+ return formatTextResponse(`Distillation pipeline configured with policy: "${policy}" (${pipeline.policyCount} policies active)`);
1025
+ },
1026
+ // ==================== FRESHNESS HANDLERS ====================
1027
+ check_freshness: async (ctx, args) => {
1028
+ const entityName = validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName');
1029
+ const graph = await ctx.storage.loadGraph();
1030
+ const entity = graph.entities.find(e => e.name === entityName);
1031
+ if (!entity) {
1032
+ return formatTextResponse(`Entity "${entityName}" not found`);
1033
+ }
1034
+ const fm = getFreshnessManager(ctx);
1035
+ const annotated = fm.annotateEntity(entity);
1036
+ return formatToolResponse({
1037
+ entityName,
1038
+ freshnessScore: fm.calculateFreshness(entity),
1039
+ expiresAt: fm.computeExpiresAt(entity),
1040
+ isExpired: fm.isExpired(entity),
1041
+ annotated,
1042
+ });
1043
+ },
1044
+ get_stale_entities: async (ctx, args) => {
1045
+ const threshold = args.threshold !== undefined
1046
+ ? validateWithSchema(args.threshold, z.number().min(0).max(1), 'Invalid threshold (0-1)')
1047
+ : undefined;
1048
+ const fm = getFreshnessManager(ctx);
1049
+ const stale = await fm.getStaleEntities(ctx.storage, threshold);
1050
+ return formatToolResponse({ entities: stale, count: stale.length });
1051
+ },
1052
+ get_expired_entities: async (ctx) => {
1053
+ const fm = getFreshnessManager(ctx);
1054
+ const expired = await fm.getExpiredEntities(ctx.storage);
1055
+ return formatToolResponse({ entities: expired, count: expired.length });
1056
+ },
1057
+ refresh_entity: async (ctx, args) => {
1058
+ const entityName = validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName');
1059
+ const fm = getFreshnessManager(ctx);
1060
+ const updated = await fm.refreshEntity(entityName, ctx.storage);
1061
+ return formatToolResponse({ updated, freshnessScore: fm.calculateFreshness(updated) });
1062
+ },
1063
+ freshness_report: async (ctx, args) => {
1064
+ const threshold = args.threshold !== undefined
1065
+ ? validateWithSchema(args.threshold, z.number().min(0).max(1), 'Invalid threshold (0-1)')
1066
+ : undefined;
1067
+ const fm = getFreshnessManager(ctx);
1068
+ const report = await fm.generateReport(ctx.storage, threshold);
1069
+ return formatToolResponse(report);
1070
+ },
1071
+ // ==================== LLM QUERY PLANNER HANDLER ====================
1072
+ query_natural_language: async (ctx, args) => {
1073
+ const query = validateWithSchema(args.query, z.string().min(1), 'Invalid query');
1074
+ const entities = await ctx.queryNaturalLanguage(query);
1075
+ return formatToolResponse({ query, entities, count: entities.length });
1076
+ },
1077
+ // ==================== GOVERNANCE HANDLERS ====================
1078
+ set_governance_policy: async (ctx, args) => {
1079
+ const gm = getGovernanceManager(ctx);
1080
+ // GovernancePolicy uses function callbacks; we translate boolean args into allow/deny functions
1081
+ const allowCreate = args.canCreate !== undefined
1082
+ ? validateWithSchema(args.canCreate, z.boolean(), 'Invalid canCreate')
1083
+ : true;
1084
+ const allowUpdate = args.canUpdate !== undefined
1085
+ ? validateWithSchema(args.canUpdate, z.boolean(), 'Invalid canUpdate')
1086
+ : true;
1087
+ const allowDelete = args.canDelete !== undefined
1088
+ ? validateWithSchema(args.canDelete, z.boolean(), 'Invalid canDelete')
1089
+ : true;
1090
+ gm.setPolicy({
1091
+ canCreate: allowCreate ? undefined : () => false,
1092
+ canUpdate: allowUpdate ? undefined : () => false,
1093
+ canDelete: allowDelete ? undefined : () => false,
1094
+ });
1095
+ return formatTextResponse(`Governance policy set: canCreate=${allowCreate}, canUpdate=${allowUpdate}, canDelete=${allowDelete}`);
1096
+ },
1097
+ audit_query: async (ctx, args) => {
1098
+ const filter = {};
1099
+ if (args.operation !== undefined) {
1100
+ filter.operation = validateWithSchema(args.operation, z.enum(['create', 'update', 'delete', 'merge', 'archive']), 'Invalid operation');
1101
+ }
1102
+ if (args.agentId !== undefined) {
1103
+ filter.agentId = validateWithSchema(args.agentId, z.string(), 'Invalid agentId');
1104
+ }
1105
+ if (args.entityName !== undefined) {
1106
+ filter.entityName = validateWithSchema(args.entityName, z.string(), 'Invalid entityName');
1107
+ }
1108
+ // AuditFilter uses fromTime/toTime (not since/until)
1109
+ if (args.since !== undefined) {
1110
+ filter.fromTime = validateWithSchema(args.since, z.string(), 'Invalid since');
1111
+ }
1112
+ if (args.until !== undefined) {
1113
+ filter.toTime = validateWithSchema(args.until, z.string(), 'Invalid until');
1114
+ }
1115
+ const limit = args.limit !== undefined
1116
+ ? validateWithSchema(args.limit, z.number().int().min(1).max(1000), 'Invalid limit')
1117
+ : 50;
1118
+ const al = getAuditLog(ctx);
1119
+ let entries = await al.query(filter);
1120
+ entries = entries.slice(0, limit);
1121
+ return formatToolResponse({ entries, count: entries.length });
1122
+ },
1123
+ audit_history: async (ctx, args) => {
1124
+ const entityName = validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName');
1125
+ const al = getAuditLog(ctx);
1126
+ const entries = await al.getHistory(entityName);
1127
+ return formatToolResponse({ entityName, entries, count: entries.length });
1128
+ },
1129
+ rollback_operation: async (ctx, args) => {
1130
+ const auditEntryId = validateWithSchema(args.auditEntryId, z.string().min(1), 'Invalid auditEntryId');
1131
+ const gm = getGovernanceManager(ctx);
1132
+ await gm.rollback(auditEntryId);
1133
+ return formatTextResponse(`Operation "${auditEntryId}" rolled back successfully`);
1134
+ },
1135
+ // ==================== ROLE PROFILE HANDLERS ====================
1136
+ set_agent_role: async (_ctx, args) => {
1137
+ const role = validateWithSchema(args.role, z.enum(['researcher', 'planner', 'executor', 'reviewer', 'default']), 'Invalid role');
1138
+ const profile = getRoleProfile(role);
1139
+ return formatToolResponse({
1140
+ role,
1141
+ label: profile.label,
1142
+ salienceConfig: profile.salienceConfig,
1143
+ contextConfig: profile.contextConfig,
1144
+ message: `Role profile "${role}" retrieved. Apply salienceConfig and contextConfig to your agent memory system.`,
1145
+ });
1146
+ },
1147
+ list_role_profiles: async (_ctx) => {
1148
+ const profiles = listRoleProfiles();
1149
+ return formatToolResponse({ profiles, count: profiles.length });
1150
+ },
1151
+ // ==================== ENTROPY FILTER HANDLERS ====================
1152
+ enable_entropy_filter: async (ctx, args) => {
1153
+ const enabled = validateWithSchema(args.enabled, z.boolean(), 'Invalid enabled');
1154
+ const minEntropy = args.minEntropy !== undefined
1155
+ ? validateWithSchema(args.minEntropy, z.number().min(0), 'Invalid minEntropy')
1156
+ : 1.5;
1157
+ const minLength = args.minLength !== undefined
1158
+ ? validateWithSchema(args.minLength, z.number().int().min(0), 'Invalid minLength')
1159
+ : 10;
1160
+ // Register (or clear) the entropy filter stage on the agent memory facade's pipeline
1161
+ const agentMem = ctx.agentMemory();
1162
+ const pipeline = agentMem.consolidationPipeline;
1163
+ if (enabled) {
1164
+ const stage = new EntropyFilterStage({ minEntropy, minLength });
1165
+ pipeline.registerStage(stage);
1166
+ return formatTextResponse(`Entropy filter enabled (minEntropy=${minEntropy}, minLength=${minLength}). Stage registered on consolidation pipeline.`);
1167
+ }
1168
+ else {
1169
+ return formatTextResponse('Entropy filter disabled. No new entropy-filter stage will be registered on the consolidation pipeline.');
1170
+ }
1171
+ },
1172
+ compute_entropy: async (_ctx, args) => {
1173
+ const text = validateWithSchema(args.text, z.string(), 'Invalid text');
1174
+ const entropy = computeEntropy(text);
1175
+ const result = { text: text.length > 100 ? text.slice(0, 100) + '...' : text, entropy };
1176
+ if (args.minEntropy !== undefined) {
1177
+ const minEntropy = validateWithSchema(args.minEntropy, z.number().min(0), 'Invalid minEntropy');
1178
+ result.minEntropy = minEntropy;
1179
+ result.passes = passesEntropyFilter(text, minEntropy);
1180
+ }
1181
+ return formatToolResponse(result);
1182
+ },
1183
+ // ==================== CONSOLIDATION HANDLERS ====================
1184
+ start_consolidation: async (ctx, args) => {
1185
+ const intervalMs = args.intervalMs !== undefined
1186
+ ? validateWithSchema(args.intervalMs, z.number().int().min(1000), 'Invalid intervalMs')
1187
+ : undefined;
1188
+ const autoMergeDuplicates = args.autoMergeDuplicates !== undefined
1189
+ ? validateWithSchema(args.autoMergeDuplicates, z.boolean(), 'Invalid autoMergeDuplicates')
1190
+ : undefined;
1191
+ // Prefer ctx.consolidationScheduler (set via MEMORY_AUTO_CONSOLIDATION env var).
1192
+ // Otherwise use the per-ctx singleton so stop/run_now can retrieve the same instance.
1193
+ let scheduler = ctx.consolidationScheduler ?? consolidationSchedulerMap.get(ctx);
1194
+ if (!scheduler) {
1195
+ const agentMem = ctx.agentMemory();
1196
+ scheduler = new ConsolidationScheduler(agentMem.consolidationPipeline, ctx.compressionManager, {
1197
+ consolidationIntervalMs: intervalMs,
1198
+ autoMergeDuplicates: autoMergeDuplicates,
1199
+ });
1200
+ consolidationSchedulerMap.set(ctx, scheduler);
1201
+ }
1202
+ scheduler.start();
1203
+ return formatTextResponse(`Consolidation scheduler started (interval: ${scheduler.getInterval()}ms, autoMerge: ${scheduler.getConfig().autoMergeDuplicates})`);
1204
+ },
1205
+ stop_consolidation: async (ctx) => {
1206
+ const scheduler = ctx.consolidationScheduler ?? consolidationSchedulerMap.get(ctx);
1207
+ if (!scheduler) {
1208
+ return formatTextResponse('No active consolidation scheduler found. Start one first with start_consolidation.');
1209
+ }
1210
+ scheduler.stop();
1211
+ return formatTextResponse('Consolidation scheduler stopped');
1212
+ },
1213
+ run_consolidation_now: async (ctx) => {
1214
+ // Use ctx.consolidationScheduler or the per-ctx singleton if available,
1215
+ // otherwise create a temporary scheduler just for this one run.
1216
+ let scheduler = ctx.consolidationScheduler ?? consolidationSchedulerMap.get(ctx);
1217
+ if (!scheduler) {
1218
+ const agentMem = ctx.agentMemory();
1219
+ scheduler = new ConsolidationScheduler(agentMem.consolidationPipeline, ctx.compressionManager);
1220
+ }
1221
+ const result = await scheduler.runNow();
1222
+ return formatToolResponse(result);
1223
+ },
1224
+ // ==================== DREAM ENGINE HANDLERS ====================
1225
+ dream_start: async (ctx, args) => {
1226
+ const intervalMs = args.intervalMs !== undefined
1227
+ ? validateWithSchema(args.intervalMs, z.number().int().min(1000), 'Invalid intervalMs')
1228
+ : undefined;
1229
+ const runOnSessionEnd = args.runOnSessionEnd !== undefined
1230
+ ? validateWithSchema(args.runOnSessionEnd, z.boolean(), 'Invalid runOnSessionEnd')
1231
+ : undefined;
1232
+ const maxDurationMs = args.maxDurationMs !== undefined
1233
+ ? validateWithSchema(args.maxDurationMs, z.number().int().min(1000), 'Invalid maxDurationMs')
1234
+ : undefined;
1235
+ const phases = args.phases !== undefined
1236
+ ? validateWithSchema(args.phases, z.object({
1237
+ temporalAnchoring: z.boolean().optional(),
1238
+ freshnessSweep: z.boolean().optional(),
1239
+ entropyPruning: z.boolean().optional(),
1240
+ consolidation: z.boolean().optional(),
1241
+ compression: z.boolean().optional(),
1242
+ entityEnrichment: z.boolean().optional(),
1243
+ patternPromotion: z.boolean().optional(),
1244
+ graphHygiene: z.boolean().optional(),
1245
+ }), 'Invalid phases')
1246
+ : undefined;
1247
+ let engine = dreamEngineMap.get(ctx);
1248
+ if (!engine) {
1249
+ const agentMem = ctx.agentMemory();
1250
+ const config = {
1251
+ ...(intervalMs !== undefined && { intervalMs }),
1252
+ ...(runOnSessionEnd !== undefined && { runOnSessionEnd }),
1253
+ ...(maxDurationMs !== undefined && { maxDurationMs }),
1254
+ ...(phases !== undefined && { phases }),
1255
+ };
1256
+ engine = new DreamEngine(ctx.storage, agentMem.consolidationPipeline, config);
1257
+ dreamEngineMap.set(ctx, engine);
1258
+ }
1259
+ engine.start();
1260
+ return formatTextResponse('DreamEngine started — background memory maintenance active');
1261
+ },
1262
+ dream_stop: async (ctx) => {
1263
+ const engine = dreamEngineMap.get(ctx);
1264
+ if (!engine) {
1265
+ return formatTextResponse('No active DreamEngine found. Start one first with dream_start.');
1266
+ }
1267
+ engine.stop();
1268
+ return formatTextResponse('DreamEngine stopped');
1269
+ },
1270
+ dream_run_now: async (ctx, args) => {
1271
+ const phases = args.phases !== undefined
1272
+ ? validateWithSchema(args.phases, z.object({
1273
+ temporalAnchoring: z.boolean().optional(),
1274
+ freshnessSweep: z.boolean().optional(),
1275
+ entropyPruning: z.boolean().optional(),
1276
+ consolidation: z.boolean().optional(),
1277
+ compression: z.boolean().optional(),
1278
+ entityEnrichment: z.boolean().optional(),
1279
+ patternPromotion: z.boolean().optional(),
1280
+ graphHygiene: z.boolean().optional(),
1281
+ }), 'Invalid phases')
1282
+ : undefined;
1283
+ // Use the per-ctx singleton if available, otherwise create a one-off engine.
1284
+ let engine = dreamEngineMap.get(ctx);
1285
+ if (!engine) {
1286
+ const agentMem = ctx.agentMemory();
1287
+ const config = {
1288
+ ...(phases !== undefined && { phases }),
1289
+ };
1290
+ engine = new DreamEngine(ctx.storage, agentMem.consolidationPipeline, config);
1291
+ }
1292
+ const result = await engine.runDreamCycle();
1293
+ return formatToolResponse(result);
1294
+ },
1295
+ // ==================== MEMORY FORMATTER HANDLER ====================
1296
+ format_with_salience_budget: async (ctx, args) => {
1297
+ const entityNames = validateWithSchema(args.entityNames, z.array(z.string().min(1)), 'Invalid entityNames');
1298
+ const salienceScoresRaw = validateWithSchema(args.salienceScores, z.record(z.string(), z.number()), 'Invalid salienceScores');
1299
+ const totalTokenBudget = validateWithSchema(args.totalTokenBudget, z.number().int().min(1), 'Invalid totalTokenBudget');
1300
+ const header = args.header !== undefined
1301
+ ? validateWithSchema(args.header, z.string(), 'Invalid header')
1302
+ : undefined;
1303
+ const separator = args.separator !== undefined
1304
+ ? validateWithSchema(args.separator, z.string(), 'Invalid separator')
1305
+ : undefined;
1306
+ const graph = await ctx.storage.loadGraph();
1307
+ const memories = graph.entities.filter(e => entityNames.includes(e.name));
1308
+ const salienceScores = new Map(Object.entries(salienceScoresRaw));
1309
+ const formatted = ctx.memoryFormatter.formatWithSalienceBudget(memories, salienceScores, totalTokenBudget, { header, separator });
1310
+ return formatTextResponse(formatted);
1311
+ },
1312
+ // ==================== COLLABORATIVE SYNTHESIS HANDLER ====================
1313
+ synthesize_collaborative_context: async (ctx, args) => {
1314
+ const seedEntityName = validateWithSchema(args.seedEntityName, z.string().min(1), 'Invalid seedEntityName');
1315
+ const config = {};
1316
+ if (args.maxDepth !== undefined) {
1317
+ config.maxDepth = validateWithSchema(args.maxDepth, z.number().int().min(1).max(10), 'Invalid maxDepth');
1318
+ }
1319
+ if (args.minNeighborSalience !== undefined) {
1320
+ config.minNeighborSalience = validateWithSchema(args.minNeighborSalience, z.number().min(0).max(1), 'Invalid minNeighborSalience');
1321
+ }
1322
+ if (args.maxNeighbors !== undefined) {
1323
+ config.maxNeighbors = validateWithSchema(args.maxNeighbors, z.number().int().min(1).max(100), 'Invalid maxNeighbors');
1324
+ }
1325
+ const salienceContext = {};
1326
+ if (args.queryText !== undefined) {
1327
+ salienceContext.queryText = validateWithSchema(args.queryText, z.string(), 'Invalid queryText');
1328
+ }
1329
+ if (args.currentTask !== undefined) {
1330
+ salienceContext.currentTask = validateWithSchema(args.currentTask, z.string(), 'Invalid currentTask');
1331
+ }
1332
+ const synthesis = new CollaborativeSynthesis(ctx.storage, ctx.graphTraversal, ctx.salienceEngine, config);
1333
+ return withCompression(async () => {
1334
+ const result = await synthesis.synthesize(seedEntityName, Object.keys(salienceContext).length > 0 ? salienceContext : undefined);
1335
+ return formatToolResponse(result);
1336
+ });
1337
+ },
1338
+ // ==================== FAILURE DISTILLATION HANDLERS ====================
1339
+ distill_failure: async (ctx, args) => {
1340
+ const sessionId = validateWithSchema(args.sessionId, z.string().min(1), 'Invalid sessionId');
1341
+ const config = {};
1342
+ if (args.minLessonConfidence !== undefined) {
1343
+ config.minLessonConfidence = validateWithSchema(args.minLessonConfidence, z.number().min(0).max(1), 'Invalid minLessonConfidence');
1344
+ }
1345
+ if (args.maxCauseChainLength !== undefined) {
1346
+ config.maxCauseChainLength = validateWithSchema(args.maxCauseChainLength, z.number().int().min(1).max(20), 'Invalid maxCauseChainLength');
1347
+ }
1348
+ const fd = Object.keys(config).length > 0
1349
+ ? new FailureDistillation(ctx.storage, config)
1350
+ : getFailureDistillation(ctx);
1351
+ const result = await fd.distillFromSession(sessionId);
1352
+ return formatToolResponse(result);
1353
+ },
1354
+ end_session: async (ctx, args) => {
1355
+ const sessionId = validateWithSchema(args.sessionId, z.string().min(1), 'Invalid sessionId');
1356
+ const outcome = validateWithSchema(args.outcome, z.enum(['success', 'failure', 'partial']), 'Invalid outcome');
1357
+ const distillFailures = args.distillFailures !== undefined
1358
+ ? validateWithSchema(args.distillFailures, z.boolean(), 'Invalid distillFailures')
1359
+ : true;
1360
+ // Use getGraphForMutation() to acquire the storage write lock — prevents
1361
+ // concurrent end_session calls from clobbering each other's outcome writes.
1362
+ const graph = await ctx.storage.getGraphForMutation();
1363
+ const sessionEntity = graph.entities.find(e => e.name === sessionId);
1364
+ if (!sessionEntity) {
1365
+ return formatTextResponse(`Session "${sessionId}" not found`);
1366
+ }
1367
+ // Update session outcome
1368
+ const updatedEntities = graph.entities.map(e => e.name === sessionId
1369
+ ? { ...e, observations: [...e.observations, `outcome: ${outcome}`], lastModified: new Date().toISOString() }
1370
+ : e);
1371
+ await ctx.storage.saveGraph({ entities: updatedEntities, relations: [...graph.relations] });
1372
+ let distillationResult = null;
1373
+ if (outcome === 'failure' && distillFailures) {
1374
+ const fd = getFailureDistillation(ctx);
1375
+ distillationResult = await fd.distillFromSession(sessionId);
1376
+ }
1377
+ return formatToolResponse({
1378
+ sessionId,
1379
+ outcome,
1380
+ distillationResult,
1381
+ message: `Session "${sessionId}" ended with outcome: ${outcome}`,
1382
+ });
1383
+ },
1384
+ // ==================== COGNITIVE LOAD HANDLERS ====================
1385
+ analyze_cognitive_load: async (ctx, args) => {
1386
+ const entityNames = validateWithSchema(args.entityNames, z.array(z.string().min(1)), 'Invalid entityNames');
1387
+ const loadThreshold = args.loadThreshold !== undefined
1388
+ ? validateWithSchema(args.loadThreshold, z.number().min(0).max(1), 'Invalid loadThreshold')
1389
+ : undefined;
1390
+ const graph = await ctx.storage.loadGraph();
1391
+ const memories = graph.entities.filter((e) => entityNames.includes(e.name));
1392
+ if (memories.length === 0) {
1393
+ return formatTextResponse('No entities found matching the provided names');
1394
+ }
1395
+ const analyzer = new CognitiveLoadAnalyzer(loadThreshold ? { loadThreshold } : undefined);
1396
+ const metrics = analyzer.computeMetrics(memories, estimateTokens);
1397
+ return formatToolResponse({ entityCount: memories.length, metrics });
1398
+ },
1399
+ adaptive_reduce_memories: async (ctx, args) => {
1400
+ const entityNames = validateWithSchema(args.entityNames, z.array(z.string().min(1)), 'Invalid entityNames');
1401
+ const salienceScoresRaw = validateWithSchema(args.salienceScores, z.record(z.string(), z.number()), 'Invalid salienceScores');
1402
+ const loadThreshold = args.loadThreshold !== undefined
1403
+ ? validateWithSchema(args.loadThreshold, z.number().min(0).max(1), 'Invalid loadThreshold')
1404
+ : undefined;
1405
+ const graph = await ctx.storage.loadGraph();
1406
+ const memories = graph.entities.filter((e) => entityNames.includes(e.name));
1407
+ if (memories.length === 0) {
1408
+ return formatTextResponse('No entities found matching the provided names');
1409
+ }
1410
+ const salienceScores = new Map(Object.entries(salienceScoresRaw));
1411
+ const analyzer = new CognitiveLoadAnalyzer(loadThreshold ? { loadThreshold } : undefined);
1412
+ const result = analyzer.adaptiveReduce(memories, salienceScores, estimateTokens);
1413
+ return formatToolResponse({
1414
+ retained: result.retained.map(e => e.name),
1415
+ removed: result.removed.map(e => e.name),
1416
+ retainedCount: result.retained.length,
1417
+ removedCount: result.removed.length,
1418
+ beforeMetrics: result.beforeMetrics,
1419
+ afterMetrics: result.afterMetrics,
1420
+ });
1421
+ },
1422
+ // Phase 13: User profile + agent diary
1423
+ get_profile: async (ctx, args) => {
1424
+ const projectId = args.projectId !== undefined ? validateWithSchema(args.projectId, z.string(), 'Invalid projectId') : undefined;
1425
+ const amm = ctx.agentMemory();
1426
+ const profile = await amm.profileManager.getProfile({ projectId });
1427
+ return formatToolResponse(profile);
1428
+ },
1429
+ update_profile: async (ctx, args) => {
1430
+ const content = validateWithSchema(args.content, z.string().min(1), 'Invalid content');
1431
+ const type = validateWithSchema(args.type, z.enum(['static', 'dynamic']), 'Invalid type');
1432
+ const projectId = args.projectId !== undefined ? validateWithSchema(args.projectId, z.string(), 'Invalid projectId') : undefined;
1433
+ const amm = ctx.agentMemory();
1434
+ await amm.profileManager.addFact(content, type, { projectId });
1435
+ return formatTextResponse(`Added ${type} profile fact: "${content}"`);
1436
+ },
1437
+ diary_write: async (ctx, args) => {
1438
+ const agentId = validateWithSchema(args.agentId, z.string().min(1), 'Invalid agentId');
1439
+ const entry = validateWithSchema(args.entry, z.string().min(1), 'Invalid entry');
1440
+ const topic = args.topic !== undefined ? validateWithSchema(args.topic, z.string(), 'Invalid topic') : undefined;
1441
+ const amm = ctx.agentMemory();
1442
+ await amm.writeDiary(agentId, entry, { topic });
1443
+ return formatTextResponse(`Diary entry written for agent '${agentId}'`);
1444
+ },
1445
+ diary_read: async (ctx, args) => {
1446
+ const agentId = validateWithSchema(args.agentId, z.string().min(1), 'Invalid agentId');
1447
+ const lastN = args.lastN !== undefined ? validateWithSchema(args.lastN, z.number().int().positive(), 'Invalid lastN') : undefined;
1448
+ const topic = args.topic !== undefined ? validateWithSchema(args.topic, z.string(), 'Invalid topic') : undefined;
1449
+ const amm = ctx.agentMemory();
1450
+ const entries = await amm.readDiary(agentId, { lastN, topic });
1451
+ return formatToolResponse({ agentId, entries, count: entries.length });
1452
+ },
1453
+ // ==================== SESSION & WORKING MEMORY HANDLERS ====================
1454
+ session_start: async (ctx, args) => {
1455
+ const goalDescription = args.taskDescription !== undefined ? validateWithSchema(args.taskDescription, z.string(), 'Invalid taskDescription') : undefined;
1456
+ const previousSessionId = args.parentSessionId !== undefined ? validateWithSchema(args.parentSessionId, z.string(), 'Invalid parentSessionId') : undefined;
1457
+ const amm = ctx.agentMemory();
1458
+ const session = await amm.startSession({ goalDescription, previousSessionId });
1459
+ return formatToolResponse(session);
1460
+ },
1461
+ session_end: async (ctx, args) => {
1462
+ const sessionId = validateWithSchema(args.sessionId, z.string().min(1), 'Invalid sessionId');
1463
+ const status = args.status !== undefined
1464
+ ? validateWithSchema(args.status, z.enum(['completed', 'abandoned']), 'Invalid status')
1465
+ : undefined;
1466
+ const amm = ctx.agentMemory();
1467
+ const result = await amm.endSession(sessionId, status);
1468
+ return formatToolResponse(result);
1469
+ },
1470
+ session_checkpoint: async (ctx, args) => {
1471
+ const sessionId = validateWithSchema(args.sessionId, z.string().min(1), 'Invalid sessionId');
1472
+ const name = args.name !== undefined ? validateWithSchema(args.name, z.string(), 'Invalid name') : undefined;
1473
+ const amm = ctx.agentMemory();
1474
+ const checkpoint = await amm.checkpointSession(sessionId, name);
1475
+ return formatToolResponse(checkpoint);
1476
+ },
1477
+ session_restore: async (ctx, args) => {
1478
+ const checkpointId = validateWithSchema(args.checkpointId, z.string().min(1), 'Invalid checkpointId');
1479
+ const amm = ctx.agentMemory();
1480
+ await amm.restoreSession(checkpointId);
1481
+ return formatTextResponse(`Session restored from checkpoint '${checkpointId}'`);
1482
+ },
1483
+ add_working_memory: async (ctx, args) => {
1484
+ const sessionId = validateWithSchema(args.sessionId, z.string().min(1), 'Invalid sessionId');
1485
+ const content = validateWithSchema(args.content, z.string().min(1), 'Invalid content');
1486
+ const taskId = args.taskId !== undefined ? validateWithSchema(args.taskId, z.string(), 'Invalid taskId') : undefined;
1487
+ const importance = args.importance !== undefined ? validateWithSchema(args.importance, z.number().min(0).max(10), 'Invalid importance') : undefined;
1488
+ const ttlHours = args.ttlHours !== undefined ? validateWithSchema(args.ttlHours, z.number().positive(), 'Invalid ttlHours') : undefined;
1489
+ const amm = ctx.agentMemory();
1490
+ const memory = await amm.addWorkingMemory({ sessionId, content, taskId, importance, ttlHours });
1491
+ return formatToolResponse(memory);
1492
+ },
1493
+ promote_working_memory: async (ctx, args) => {
1494
+ const memoryName = validateWithSchema(args.memoryName, z.string().min(1), 'Invalid memoryName');
1495
+ const targetType = args.targetType !== undefined
1496
+ ? validateWithSchema(args.targetType, z.enum(['episodic', 'semantic']), 'Invalid targetType')
1497
+ : undefined;
1498
+ const amm = ctx.agentMemory();
1499
+ const result = await amm.promoteMemory(memoryName, targetType);
1500
+ return formatToolResponse(result);
1501
+ },
1502
+ confirm_memory: async (ctx, args) => {
1503
+ const memoryName = validateWithSchema(args.memoryName, z.string().min(1), 'Invalid memoryName');
1504
+ const confidenceBoost = args.confidenceBoost !== undefined ? validateWithSchema(args.confidenceBoost, z.number(), 'Invalid confidenceBoost') : undefined;
1505
+ const amm = ctx.agentMemory();
1506
+ const result = await amm.confirmMemory(memoryName, confidenceBoost);
1507
+ return formatToolResponse(result);
1508
+ },
1509
+ clear_expired_memories: async (ctx) => {
1510
+ const amm = ctx.agentMemory();
1511
+ const count = await amm.clearExpiredMemories();
1512
+ return formatTextResponse(`Cleared ${count} expired working memories`);
1513
+ },
1514
+ wake_up: async (ctx, args) => {
1515
+ const compress = args.compress !== undefined ? validateWithSchema(args.compress, z.boolean(), 'Invalid compress') : undefined;
1516
+ const result = await ctx.contextWindowManager.wakeUp({ compress });
1517
+ return formatToolResponse(result);
1518
+ },
1519
+ // ==================== AUTO-ENHANCEMENT HANDLERS ====================
1520
+ auto_link_observations: async (ctx, args) => {
1521
+ const text = validateWithSchema(args.text, z.string().min(1), 'Invalid text');
1522
+ const graph = await ctx.storage.loadGraph();
1523
+ const mentions = ctx.autoLinker.detectMentions(text, graph.entities);
1524
+ return formatToolResponse({ mentions, count: mentions.length });
1525
+ },
1526
+ extract_facts: async (ctx, args) => {
1527
+ const text = validateWithSchema(args.text, z.string().min(1), 'Invalid text');
1528
+ const facts = ctx.factExtractor.extract(text);
1529
+ return formatToolResponse({ facts, count: facts.length });
1530
+ },
1531
+ detect_contradictions: async (ctx, args) => {
1532
+ const entityName = validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName');
1533
+ const threshold = args.threshold !== undefined ? validateWithSchema(args.threshold, z.number().min(0).max(1), 'Invalid threshold') : undefined;
1534
+ const graph = await ctx.storage.loadGraph();
1535
+ const entity = graph.entities.find(e => e.name === entityName);
1536
+ if (!entity) {
1537
+ return { content: [{ type: 'text', text: `Entity '${entityName}' not found` }], isError: true };
1538
+ }
1539
+ if (!ctx.semanticSearch) {
1540
+ return formatTextResponse('Contradiction detection requires semantic search (set MEMORY_EMBEDDING_PROVIDER)');
1541
+ }
1542
+ const detector = new ContradictionDetector(ctx.semanticSearch, threshold ?? 0.85);
1543
+ const contradictions = await detector.detect(entity, entity.observations);
1544
+ return formatToolResponse({ entityName, contradictions, count: contradictions.length });
1545
+ },
1546
+ consolidate_session: async (ctx, args) => {
1547
+ const sessionId = validateWithSchema(args.sessionId, z.string().min(1), 'Invalid sessionId');
1548
+ const amm = ctx.agentMemory();
1549
+ const result = await amm.consolidateSession(sessionId);
1550
+ return formatToolResponse(result);
1551
+ },
1552
+ detect_patterns: async (ctx, args) => {
1553
+ const entityType = validateWithSchema(args.entityType, z.string().min(1), 'Invalid entityType');
1554
+ const minOccurrences = args.minOccurrences !== undefined ? validateWithSchema(args.minOccurrences, z.number().int().min(2), 'Invalid minOccurrences') : undefined;
1555
+ const amm = ctx.agentMemory();
1556
+ const patterns = await amm.consolidationPipeline.extractPatterns(entityType, minOccurrences);
1557
+ return formatToolResponse({ patterns, count: patterns.length });
1558
+ },
1559
+ summarize_entity: async (ctx, args) => {
1560
+ const entityName = validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName');
1561
+ const threshold = args.threshold !== undefined ? validateWithSchema(args.threshold, z.number().min(0).max(1), 'Invalid threshold') : undefined;
1562
+ const amm = ctx.agentMemory();
1563
+ const result = await amm.consolidationPipeline.applySummarizationToEntity(entityName, threshold);
1564
+ return formatToolResponse(result);
1565
+ },
1566
+ priority_dedup: async (ctx) => {
1567
+ const result = await ctx.compressionManager.priorityDedup();
1568
+ return formatToolResponse(result);
1569
+ },
1570
+ // ==================== CONTEXT COMPRESSION HANDLERS ====================
1571
+ compress_context: async (ctx, args) => {
1572
+ const text = validateWithSchema(args.text, z.string().min(1), 'Invalid text');
1573
+ const level = args.level !== undefined
1574
+ ? validateWithSchema(args.level, z.enum(['light', 'medium', 'aggressive']), 'Invalid level')
1575
+ : undefined;
1576
+ const result = ctx.contextWindowManager.compressForContext(text, { level });
1577
+ return formatToolResponse(result);
1578
+ },
1579
+ // ==================== DECAY & SALIENCE HANDLERS ====================
1580
+ run_decay_cycle: async (ctx) => {
1581
+ const amm = ctx.agentMemory();
1582
+ const result = await amm.runDecayCycle();
1583
+ return formatToolResponse(result);
1584
+ },
1585
+ get_decayed_memories: async (ctx, args) => {
1586
+ const threshold = args.threshold !== undefined ? validateWithSchema(args.threshold, z.number(), 'Invalid threshold') : undefined;
1587
+ const amm = ctx.agentMemory();
1588
+ const memories = await amm.getDecayedMemories(threshold);
1589
+ return formatToolResponse({ memories, count: memories.length });
1590
+ },
1591
+ forget_weak_memories: async (ctx, args) => {
1592
+ const effectiveImportanceThreshold = args.threshold !== undefined
1593
+ ? validateWithSchema(args.threshold, z.number(), 'Invalid threshold')
1594
+ : 0.1;
1595
+ const dryRun = args.dryRun !== undefined ? validateWithSchema(args.dryRun, z.boolean(), 'Invalid dryRun') : undefined;
1596
+ const olderThanHours = args.maxCount !== undefined ? validateWithSchema(args.maxCount, z.number().int().positive(), 'Invalid olderThanHours') : undefined;
1597
+ const options = { effectiveImportanceThreshold, dryRun, olderThanHours };
1598
+ const amm = ctx.agentMemory();
1599
+ const result = await amm.forgetWeakMemories(options);
1600
+ return formatToolResponse(result);
1601
+ },
1602
+ reinforce_memory: async (ctx, args) => {
1603
+ const memoryName = validateWithSchema(args.memoryName, z.string().min(1), 'Invalid memoryName');
1604
+ const confirmationBoost = args.confirmationBoost !== undefined ? validateWithSchema(args.confirmationBoost, z.number(), 'Invalid confirmationBoost') : undefined;
1605
+ const confidenceBoost = args.confidenceBoost !== undefined ? validateWithSchema(args.confidenceBoost, z.number(), 'Invalid confidenceBoost') : undefined;
1606
+ const amm = ctx.agentMemory();
1607
+ await amm.reinforceMemory(memoryName, { confirmationBoost, confidenceBoost });
1608
+ return formatTextResponse(`Memory '${memoryName}' reinforced`);
1609
+ },
1610
+ score_salience: async (ctx, args) => {
1611
+ const entityName = validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName');
1612
+ const queryText = args.queryText !== undefined ? validateWithSchema(args.queryText, z.string(), 'Invalid queryText') : undefined;
1613
+ const taskDescription = args.taskDescription !== undefined ? validateWithSchema(args.taskDescription, z.string(), 'Invalid taskDescription') : undefined;
1614
+ const sessionId = args.sessionId !== undefined ? validateWithSchema(args.sessionId, z.string(), 'Invalid sessionId') : undefined;
1615
+ const graph = await ctx.storage.loadGraph();
1616
+ const entity = graph.entities.find(e => e.name === entityName);
1617
+ if (!entity) {
1618
+ return { content: [{ type: 'text', text: `Entity '${entityName}' not found` }], isError: true };
1619
+ }
1620
+ const context = { queryText, currentTask: taskDescription, currentSession: sessionId };
1621
+ const scored = await ctx.salienceEngine.calculateSalience(entity, context);
1622
+ return formatToolResponse(scored);
1623
+ },
1624
+ // ==================== MULTI-AGENT HANDLERS ====================
1625
+ register_agent: async (ctx, args) => {
1626
+ const agentId = validateWithSchema(args.agentId, z.string().min(1), 'Invalid agentId');
1627
+ const type = args.type !== undefined ? validateWithSchema(args.type, z.string(), 'Invalid type') : 'default';
1628
+ const trustLevel = args.trustLevel !== undefined ? validateWithSchema(args.trustLevel, z.number().min(0).max(1), 'Invalid trustLevel') : 0.5;
1629
+ const capabilities = args.capabilities !== undefined ? validateWithSchema(args.capabilities, z.array(z.string()), 'Invalid capabilities') : [];
1630
+ const amm = ctx.agentMemory();
1631
+ amm.registerAgent(agentId, { name: agentId, type, trustLevel, capabilities });
1632
+ return formatTextResponse(`Agent '${agentId}' registered (type: ${type}, trust: ${trustLevel})`);
1633
+ },
1634
+ search_cross_agent: async (ctx, args) => {
1635
+ const requestingAgentId = validateWithSchema(args.requestingAgentId, z.string().min(1), 'Invalid requestingAgentId');
1636
+ const query = validateWithSchema(args.query, z.string().min(1), 'Invalid query');
1637
+ const agentIds = args.agentIds !== undefined ? validateWithSchema(args.agentIds, z.array(z.string()), 'Invalid agentIds') : undefined;
1638
+ const amm = ctx.agentMemory();
1639
+ const results = await amm.searchCrossAgent(requestingAgentId, query, { agentIds });
1640
+ return withCompression(async () => formatToolResponse({ results, count: results.length }));
1641
+ },
1642
+ set_memory_visibility: async (ctx, args) => {
1643
+ const memoryName = validateWithSchema(args.memoryName, z.string().min(1), 'Invalid memoryName');
1644
+ const agentId = validateWithSchema(args.agentId, z.string().min(1), 'Invalid agentId');
1645
+ const visibility = validateWithSchema(args.visibility, z.enum(['private', 'team', 'org', 'shared', 'public']), 'Invalid visibility');
1646
+ // η.5.5.b extensions
1647
+ const allowedRoles = args.allowedRoles !== undefined
1648
+ ? validateWithSchema(args.allowedRoles, z.array(z.string()), 'Invalid allowedRoles')
1649
+ : undefined;
1650
+ const visibleFrom = args.visibleFrom !== undefined ? validateWithSchema(args.visibleFrom, z.string(), 'Invalid visibleFrom') : undefined;
1651
+ const visibleUntil = args.visibleUntil !== undefined ? validateWithSchema(args.visibleUntil, z.string(), 'Invalid visibleUntil') : undefined;
1652
+ const amm = ctx.agentMemory();
1653
+ const result = await amm.multiAgentManager.setMemoryVisibility(memoryName, agentId, visibility);
1654
+ // Bug 3 fix: previously returned null silently when entity wasn't an
1655
+ // AgentEntity. Auto-promote: stamp agentId + memoryType + role/window
1656
+ // fields so the visibility takes effect. Error if the entity doesn't
1657
+ // exist at all.
1658
+ if (result === null) {
1659
+ const graph = await ctx.storage.getGraphForMutation();
1660
+ const entity = graph.entities.find(e => e.name === memoryName);
1661
+ if (!entity) {
1662
+ return {
1663
+ content: [{ type: 'text', text: `Entity '${memoryName}' not found — cannot set visibility` }],
1664
+ isError: true,
1665
+ };
1666
+ }
1667
+ // Promote plain Entity → AgentEntity by stamping multi-agent fields
1668
+ entity.agentId = agentId;
1669
+ entity.visibility = visibility;
1670
+ if (entity.memoryType === undefined)
1671
+ entity.memoryType = 'semantic';
1672
+ if (entity.confidence === undefined)
1673
+ entity.confidence = 0.8;
1674
+ if (entity.confirmationCount === undefined)
1675
+ entity.confirmationCount = 0;
1676
+ if (entity.accessCount === undefined)
1677
+ entity.accessCount = 0;
1678
+ // η.5.5.b extension fields
1679
+ if (allowedRoles !== undefined)
1680
+ entity.allowedRoles = allowedRoles;
1681
+ if (visibleFrom !== undefined)
1682
+ entity.visibleFrom = visibleFrom;
1683
+ if (visibleUntil !== undefined)
1684
+ entity.visibleUntil = visibleUntil;
1685
+ await ctx.storage.saveGraph(graph);
1686
+ return formatToolResponse({
1687
+ memoryName,
1688
+ agentId,
1689
+ visibility,
1690
+ allowedRoles,
1691
+ visibleFrom,
1692
+ visibleUntil,
1693
+ promoted: true,
1694
+ message: 'Plain entity auto-promoted to AgentEntity with visibility set',
1695
+ });
1696
+ }
1697
+ // Already an AgentEntity — apply η.5.5.b extension fields if provided
1698
+ if (allowedRoles !== undefined || visibleFrom !== undefined || visibleUntil !== undefined) {
1699
+ const graph = await ctx.storage.getGraphForMutation();
1700
+ const entity = graph.entities.find(e => e.name === memoryName);
1701
+ if (entity) {
1702
+ if (allowedRoles !== undefined)
1703
+ entity.allowedRoles = allowedRoles;
1704
+ if (visibleFrom !== undefined)
1705
+ entity.visibleFrom = visibleFrom;
1706
+ if (visibleUntil !== undefined)
1707
+ entity.visibleUntil = visibleUntil;
1708
+ await ctx.storage.saveGraph(graph);
1709
+ }
1710
+ }
1711
+ return formatToolResponse(result);
1712
+ },
1713
+ get_visible_memories: async (ctx, args) => {
1714
+ const agentId = validateWithSchema(args.agentId, z.string().min(1), 'Invalid agentId');
1715
+ const amm = ctx.agentMemory();
1716
+ const memories = await amm.multiAgentManager.getVisibleMemories(agentId);
1717
+ return withCompression(async () => formatToolResponse({ memories, count: memories.length }));
1718
+ },
1719
+ resolve_agent_conflict: async (ctx, args) => {
1720
+ const primaryMemory = validateWithSchema(args.primaryMemory, z.string().min(1), 'Invalid primaryMemory');
1721
+ const conflictingMemory = validateWithSchema(args.conflictingMemory, z.string().min(1), 'Invalid conflictingMemory');
1722
+ const strategy = args.strategy !== undefined
1723
+ ? validateWithSchema(args.strategy, z.enum(['most_recent', 'highest_confidence', 'most_confirmations', 'trusted_agent']), 'Invalid strategy')
1724
+ : undefined;
1725
+ const amm = ctx.agentMemory();
1726
+ // Build a ConflictInfo structure for the two memories
1727
+ const conflictInfo = {
1728
+ primaryMemory,
1729
+ conflictingMemories: [conflictingMemory],
1730
+ detectionMethod: 'manual',
1731
+ suggestedStrategy: strategy ?? 'most_recent',
1732
+ detectedAt: new Date().toISOString(),
1733
+ };
1734
+ const result = await amm.resolveConflict(conflictInfo, strategy);
1735
+ return formatToolResponse(result);
1736
+ },
1737
+ // ==================== OBSERVABILITY HANDLERS ====================
1738
+ visualize_graph: async (ctx, args) => {
1739
+ const maxEntities = args.maxEntities !== undefined ? validateWithSchema(args.maxEntities, z.number().int().positive(), 'Invalid maxEntities') : undefined;
1740
+ const title = args.title !== undefined ? validateWithSchema(args.title, z.string(), 'Invalid title') : undefined;
1741
+ const html = await ctx.ioManager.visualizeGraph({ maxEntities, title });
1742
+ return formatRawResponse(html);
1743
+ },
1744
+ split_transcript: async (ctx, args) => {
1745
+ const text = validateWithSchema(args.text, z.string().min(1), 'Invalid text');
1746
+ const result = ctx.ioManager.splitTranscript(text);
1747
+ return formatToolResponse(result);
1748
+ },
1749
+ estimate_query_cost: async (ctx, args) => {
1750
+ const query = validateWithSchema(args.query, z.string().min(1), 'Invalid query');
1751
+ let estimator = queryCostEstimatorMap.get(ctx);
1752
+ if (!estimator) {
1753
+ estimator = new QueryCostEstimator();
1754
+ queryCostEstimatorMap.set(ctx, estimator);
1755
+ }
1756
+ const graph = await ctx.storage.loadGraph();
1757
+ const estimates = estimator.estimateAllMethods(query, graph.entities.length);
1758
+ return formatToolResponse({ query, entityCount: graph.entities.length, estimates });
1759
+ },
1760
+ get_context_profile: async (ctx, args) => {
1761
+ const name = validateWithSchema(args.name, z.string().min(1), 'Invalid name');
1762
+ const profileManager = ctx.contextWindowManager.getContextProfileManager();
1763
+ const profile = profileManager.getProfile(name);
1764
+ return formatToolResponse(profile);
1765
+ },
1766
+ // ==================== η.4.4 BITEMPORAL ENTITY HANDLERS ====================
1767
+ invalidate_entity: async (ctx, args) => {
1768
+ const name = validateWithSchema(args.name, z.string().min(1), 'Invalid name');
1769
+ const ended = args.ended !== undefined
1770
+ ? validateWithSchema(args.ended, z.string(), 'Invalid ended')
1771
+ : undefined;
1772
+ await ctx.entityManager.invalidateEntity(name, ended);
1773
+ return formatTextResponse(`Invalidated entity '${name}' (ended: ${ended ?? 'now'})`);
1774
+ },
1775
+ entity_as_of: async (ctx, args) => {
1776
+ const name = validateWithSchema(args.name, z.string().min(1), 'Invalid name');
1777
+ const asOf = validateWithSchema(args.asOf, z.string().min(1), 'Invalid asOf');
1778
+ const entity = await ctx.entityManager.entityAsOf(name, asOf);
1779
+ if (!entity)
1780
+ return formatToolResponse({ entity: null, valid: false, asOf });
1781
+ return formatToolResponse({ entity, valid: true, asOf });
1782
+ },
1783
+ entity_timeline: async (ctx, args) => {
1784
+ const name = validateWithSchema(args.name, z.string().min(1), 'Invalid name');
1785
+ const versions = await ctx.entityManager.entityTimeline(name);
1786
+ return formatToolResponse({ name, versions, count: versions.length });
1787
+ },
1788
+ invalidate_observation: async (ctx, args) => {
1789
+ const entityName = validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName');
1790
+ const content = validateWithSchema(args.content, z.string().min(1), 'Invalid content');
1791
+ const ended = args.ended !== undefined
1792
+ ? validateWithSchema(args.ended, z.string(), 'Invalid ended')
1793
+ : undefined;
1794
+ await ctx.observationManager.invalidateObservation(entityName, content, ended);
1795
+ return formatTextResponse(`Invalidated observation on '${entityName}' (ended: ${ended ?? 'now'})`);
1796
+ },
1797
+ observations_as_of: async (ctx, args) => {
1798
+ const entityName = validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName');
1799
+ const asOf = validateWithSchema(args.asOf, z.string().min(1), 'Invalid asOf');
1800
+ const observations = await ctx.observationManager.observationsAsOf(entityName, asOf);
1801
+ return formatToolResponse({ entityName, asOf, observations, count: observations.length });
1802
+ },
1803
+ // ==================== η.5.5.c OCC HANDLER ====================
1804
+ update_entity: async (ctx, args) => {
1805
+ const name = validateWithSchema(args.name, z.string().min(1), 'Invalid name');
1806
+ const updates = validateWithSchema(args.updates, z.record(z.string(), z.unknown()), 'Invalid updates');
1807
+ const expectedVersion = args.expectedVersion !== undefined
1808
+ ? validateWithSchema(args.expectedVersion, z.number().int().positive(), 'Invalid expectedVersion')
1809
+ : undefined;
1810
+ const updated = await ctx.entityManager.updateEntity(name, updates, expectedVersion !== undefined ? { expectedVersion } : undefined);
1811
+ return formatToolResponse(updated);
1812
+ },
1813
+ // ==================== η.6.1 RBAC HANDLERS ====================
1814
+ rbac_assign_role: async (ctx, args) => {
1815
+ const agentId = validateWithSchema(args.agentId, z.string().min(1), 'Invalid agentId');
1816
+ const role = validateWithSchema(args.role, z.string().min(1), 'Invalid role');
1817
+ const resourceType = args.resourceType !== undefined
1818
+ ? validateWithSchema(args.resourceType, z.enum(['entity', 'relation', 'observation', 'session', 'artifact']), 'Invalid resourceType')
1819
+ : undefined;
1820
+ const scope = args.scope !== undefined ? validateWithSchema(args.scope, z.string(), 'Invalid scope') : undefined;
1821
+ const validFrom = args.validFrom !== undefined ? validateWithSchema(args.validFrom, z.string(), 'Invalid validFrom') : undefined;
1822
+ const validUntil = args.validUntil !== undefined ? validateWithSchema(args.validUntil, z.string(), 'Invalid validUntil') : undefined;
1823
+ const notes = args.notes !== undefined ? validateWithSchema(args.notes, z.string(), 'Invalid notes') : undefined;
1824
+ await ctx.roleAssignmentStore.assign({ agentId, role, resourceType, scope, validFrom, validUntil, notes });
1825
+ return formatTextResponse(`Assigned role '${role}' to agent '${agentId}'${resourceType ? ` (resourceType=${resourceType})` : ''}${scope ? ` (scope=${scope})` : ''}`);
1826
+ },
1827
+ rbac_revoke_role: async (ctx, args) => {
1828
+ const agentId = validateWithSchema(args.agentId, z.string().min(1), 'Invalid agentId');
1829
+ const role = validateWithSchema(args.role, z.string().min(1), 'Invalid role');
1830
+ const resourceType = args.resourceType !== undefined
1831
+ ? validateWithSchema(args.resourceType, z.enum(['entity', 'relation', 'observation', 'session', 'artifact']), 'Invalid resourceType')
1832
+ : undefined;
1833
+ await ctx.roleAssignmentStore.revoke(agentId, role, resourceType);
1834
+ return formatTextResponse(`Revoked role '${role}' from agent '${agentId}'`);
1835
+ },
1836
+ rbac_check_permission: async (ctx, args) => {
1837
+ const agentId = validateWithSchema(args.agentId, z.string().min(1), 'Invalid agentId');
1838
+ const action = validateWithSchema(args.action, z.enum(['read', 'write', 'delete', 'manage']), 'Invalid action');
1839
+ const resourceType = validateWithSchema(args.resourceType, z.enum(['entity', 'relation', 'observation', 'session', 'artifact']), 'Invalid resourceType');
1840
+ const resourceName = args.resourceName !== undefined ? validateWithSchema(args.resourceName, z.string(), 'Invalid resourceName') : undefined;
1841
+ const now = args.now !== undefined ? validateWithSchema(args.now, z.string(), 'Invalid now') : undefined;
1842
+ const allowed = ctx.rbacMiddleware.checkPermission(agentId, action, resourceType, resourceName, now);
1843
+ return formatToolResponse({ agentId, action, resourceType, resourceName, allowed });
1844
+ },
1845
+ rbac_list_assignments: async (ctx, args) => {
1846
+ const agentId = validateWithSchema(args.agentId, z.string().min(1), 'Invalid agentId');
1847
+ const activeOnly = args.activeOnly !== undefined ? validateWithSchema(args.activeOnly, z.boolean(), 'Invalid activeOnly') : false;
1848
+ const now = args.now !== undefined ? validateWithSchema(args.now, z.string(), 'Invalid now') : undefined;
1849
+ const assignments = activeOnly
1850
+ ? ctx.roleAssignmentStore.listActive(agentId, now)
1851
+ : ctx.roleAssignmentStore.list(agentId);
1852
+ return formatToolResponse({ agentId, activeOnly, assignments, count: assignments.length });
1853
+ },
1854
+ // ==================== 3B.4 PROCEDURAL MEMORY HANDLERS ====================
1855
+ add_procedure: async (ctx, args) => {
1856
+ const procedure = await ctx.procedureManager.addProcedure(args);
1857
+ return formatToolResponse(procedure);
1858
+ },
1859
+ get_procedure: async (ctx, args) => {
1860
+ const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
1861
+ const procedure = await ctx.procedureManager.getProcedure(id);
1862
+ if (!procedure) {
1863
+ return { content: [{ type: 'text', text: `Procedure '${id}' not found` }], isError: true };
1864
+ }
1865
+ return formatToolResponse(procedure);
1866
+ },
1867
+ match_procedure: async (ctx, args) => {
1868
+ const context = validateWithSchema(args.context, z.string().min(1), 'Invalid context');
1869
+ const threshold = args.threshold !== undefined ? validateWithSchema(args.threshold, z.number().min(0).max(1), 'Invalid threshold') : 0;
1870
+ const candidateIds = args.candidateIds !== undefined ? validateWithSchema(args.candidateIds, z.array(z.string()), 'Invalid candidateIds') : undefined;
1871
+ // If specific candidates passed, load each; otherwise scan all 'procedure' entities.
1872
+ const graph = await ctx.storage.loadGraph();
1873
+ const allProcedureEntities = graph.entities.filter(e => e.entityType === 'procedure');
1874
+ const candidates = await Promise.all((candidateIds ?? allProcedureEntities.map(e => e.name)).map(id => ctx.procedureManager.getProcedure(id)));
1875
+ const valid = candidates.filter((p) => p !== null);
1876
+ const matches = await ctx.procedureManager.matchProcedure(context, valid, threshold);
1877
+ return formatToolResponse({ context, threshold, matches, count: matches.length });
1878
+ },
1879
+ refine_procedure: async (ctx, args) => {
1880
+ const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
1881
+ const succeeded = validateWithSchema(args.succeeded, z.boolean(), 'Invalid succeeded');
1882
+ const notes = args.notes !== undefined ? validateWithSchema(args.notes, z.string(), 'Invalid notes') : undefined;
1883
+ const recordedAt = args.recordedAt !== undefined ? validateWithSchema(args.recordedAt, z.string(), 'Invalid recordedAt') : undefined;
1884
+ const updated = await ctx.procedureManager.refineProcedure(id, { succeeded, notes, recordedAt });
1885
+ return formatToolResponse(updated);
1886
+ },
1887
+ get_procedure_step: async (ctx, args) => {
1888
+ const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
1889
+ const order = validateWithSchema(args.order, z.number().int().positive(), 'Invalid order');
1890
+ const next = args.next !== undefined ? validateWithSchema(args.next, z.boolean(), 'Invalid next') : false;
1891
+ const step = next
1892
+ ? await ctx.procedureManager.getNextStep(id, order)
1893
+ : await ctx.procedureManager.getStep(id, order);
1894
+ if (!step) {
1895
+ return { content: [{ type: 'text', text: `Step ${order} not found in procedure '${id}'` }], isError: true };
1896
+ }
1897
+ return formatToolResponse(step);
1898
+ },
1899
+ // ==================== 3B.5 ACTIVE RETRIEVAL HANDLER ====================
1900
+ adaptive_retrieve: async (ctx, args) => {
1901
+ const query = validateWithSchema(args.query, z.string().min(1), 'Invalid query');
1902
+ const budgetTokens = args.budgetTokens !== undefined ? validateWithSchema(args.budgetTokens, z.number().int().positive(), 'Invalid budgetTokens') : undefined;
1903
+ const result = await ctx.activeRetrieval.adaptiveRetrieve({ query, budgetTokens });
1904
+ return formatToolResponse(result);
1905
+ },
1906
+ // ==================== 3B.6 CAUSAL REASONING HANDLERS ====================
1907
+ find_causes: async (ctx, args) => {
1908
+ const effect = validateWithSchema(args.effect, z.string().min(1), 'Invalid effect');
1909
+ const candidates = validateWithSchema(args.candidates, z.array(z.string()).min(1), 'Invalid candidates');
1910
+ const maxDepth = args.maxDepth !== undefined ? validateWithSchema(args.maxDepth, z.number().int().positive(), 'Invalid maxDepth') : undefined;
1911
+ const chains = await ctx.causalReasoner.findCauses(effect, candidates, maxDepth);
1912
+ return formatToolResponse({ effect, candidates, chains, count: chains.length });
1913
+ },
1914
+ find_effects: async (ctx, args) => {
1915
+ const cause = validateWithSchema(args.cause, z.string().min(1), 'Invalid cause');
1916
+ const candidates = validateWithSchema(args.candidates, z.array(z.string()).min(1), 'Invalid candidates');
1917
+ const maxDepth = args.maxDepth !== undefined ? validateWithSchema(args.maxDepth, z.number().int().positive(), 'Invalid maxDepth') : undefined;
1918
+ const chains = await ctx.causalReasoner.findEffects(cause, candidates, maxDepth);
1919
+ return formatToolResponse({ cause, candidates, chains, count: chains.length });
1920
+ },
1921
+ counterfactual_query: async (ctx, args) => {
1922
+ const seed = validateWithSchema(args.seed, z.string().min(1), 'Invalid seed');
1923
+ const removeFrom = validateWithSchema(args.removeFrom, z.string().min(1), 'Invalid removeFrom');
1924
+ const removeTo = validateWithSchema(args.removeTo, z.string().min(1), 'Invalid removeTo');
1925
+ const predict = validateWithSchema(args.predict, z.string().min(1), 'Invalid predict');
1926
+ const maxDepth = args.maxDepth !== undefined ? validateWithSchema(args.maxDepth, z.number().int().positive(), 'Invalid maxDepth') : undefined;
1927
+ const chains = await ctx.causalReasoner.counterfactual({ seed, removeFrom, removeTo, predict, maxDepth });
1928
+ return formatToolResponse({ scenario: { seed, removeFrom, removeTo, predict }, chains, count: chains.length });
1929
+ },
1930
+ detect_causal_cycles: async (ctx, args) => {
1931
+ const seed = validateWithSchema(args.seed, z.string().min(1), 'Invalid seed');
1932
+ const maxDepth = args.maxDepth !== undefined ? validateWithSchema(args.maxDepth, z.number().int().positive(), 'Invalid maxDepth') : undefined;
1933
+ const cycles = ctx.causalReasoner.detectCycles(seed, maxDepth);
1934
+ return formatToolResponse({ seed, cycles, count: cycles.length });
1935
+ },
1936
+ // ==================== 3B.7 WORLD MODEL HANDLERS ====================
1937
+ get_world_state: async (ctx) => {
1938
+ const snapshot = await ctx.worldModelManager.getCurrentState();
1939
+ return formatToolResponse(snapshot.toJSON());
1940
+ },
1941
+ validate_fact_against_world: async (ctx, args) => {
1942
+ const observation = validateWithSchema(args.observation, z.string().min(1), 'Invalid observation');
1943
+ const entityName = validateWithSchema(args.entityName, z.string().min(1), 'Invalid entityName');
1944
+ try {
1945
+ const result = await ctx.worldModelManager.validateFact(observation, entityName);
1946
+ return formatToolResponse({ observation, entityName, result });
1947
+ }
1948
+ catch (err) {
1949
+ // Graceful path for the documented "Returns null if no validator
1950
+ // is wired" semantics: when memoryjs's local embedding provider is
1951
+ // selected but `@xenova/transformers` isn't installed, the
1952
+ // underlying EmbeddingService throws a raw Node module-resolution
1953
+ // error. Translating to a structured null result keeps the MCP
1954
+ // surface coherent and surfaces the configuration issue instead
1955
+ // of leaking Node internals.
1956
+ const msg = err instanceof Error ? err.message : String(err);
1957
+ // Anchored on memoryjs EmbeddingService.ts:368 prefix and the
1958
+ // package-name signal — narrow enough to avoid swallowing
1959
+ // legitimate "Failed to initialize world model — entity has no
1960
+ // embedding" or similar downstream errors.
1961
+ const isProviderUnavailable = /^Failed to initialize local embedding service:/.test(msg) ||
1962
+ /Cannot find package ['"]@xenova\/transformers['"]/.test(msg);
1963
+ if (isProviderUnavailable) {
1964
+ // Log to stderr (stdout is the MCP JSON-RPC stream — must not
1965
+ // be polluted). Operators piping stderr to a file see this once
1966
+ // per process; LLM clients see only the structured null result.
1967
+ console.warn('[validate_fact_against_world] embedding_provider_unavailable: ' + msg);
1968
+ return formatToolResponse({
1969
+ observation,
1970
+ entityName,
1971
+ result: null,
1972
+ reason: 'embedding_provider_unavailable',
1973
+ detail: msg,
1974
+ });
1975
+ }
1976
+ throw err;
1977
+ }
1978
+ },
1979
+ predict_outcome: async (ctx, args) => {
1980
+ const action = validateWithSchema(args.action, z.string().min(1), 'Invalid action');
1981
+ const candidates = validateWithSchema(args.candidates, z.array(z.string()).min(1), 'Invalid candidates');
1982
+ const chains = await ctx.worldModelManager.predictOutcome(action, candidates);
1983
+ return formatToolResponse({ action, candidates, chains, count: chains.length });
1984
+ },
1985
+ // ==================== v2.1.0 TOOL AFFORDANCE ====================
1986
+ record_tool_outcome: async (ctx, args) => {
1987
+ const toolName = validateWithSchema(args.toolName, z.string().min(1), 'Invalid toolName');
1988
+ const outcome = validateWithSchema(args.outcome, z.enum(['success', 'failure', 'partial']), 'Invalid outcome');
1989
+ const errorMessage = args.errorMessage === undefined
1990
+ ? undefined
1991
+ : validateWithSchema(args.errorMessage, z.string(), 'Invalid errorMessage');
1992
+ const durationMs = args.durationMs === undefined
1993
+ ? undefined
1994
+ : validateWithSchema(args.durationMs, z.number().min(0), 'Invalid durationMs');
1995
+ const record = await ctx.toolAffordanceManager.recordOutcome(toolName, {
1996
+ outcome,
1997
+ errorMessage,
1998
+ durationMs,
1999
+ });
2000
+ return formatToolResponse({ record });
2001
+ },
2002
+ get_tool_affordance_stats: async (ctx, args) => {
2003
+ const toolName = validateWithSchema(args.toolName, z.string().min(1), 'Invalid toolName');
2004
+ const stats = ctx.toolAffordanceManager.rollingStats(toolName);
2005
+ return formatToolResponse({ toolName, stats: stats ?? null });
2006
+ },
2007
+ suggest_tool: async (ctx, args) => {
2008
+ const taskHint = validateWithSchema(args.taskHint, z.string().min(1), 'Invalid taskHint');
2009
+ const limit = args.limit === undefined
2010
+ ? undefined
2011
+ : validateWithSchema(args.limit, z.number().int().min(1), 'Invalid limit');
2012
+ const minScore = args.minScore === undefined
2013
+ ? undefined
2014
+ : validateWithSchema(args.minScore, z.number().min(0).max(1), 'Invalid minScore');
2015
+ const suggestions = await ctx.toolAffordanceManager.suggestTool(taskHint, { limit, minScore });
2016
+ return formatToolResponse({ taskHint, suggestions, count: suggestions.length });
2017
+ },
2018
+ list_tool_affordances: async (ctx) => {
2019
+ const records = await ctx.toolAffordanceManager.list();
2020
+ return formatToolResponse({ records, count: records.length });
2021
+ },
2022
+ remove_tool_affordance: async (ctx, args) => {
2023
+ const toolName = validateWithSchema(args.toolName, z.string().min(1), 'Invalid toolName');
2024
+ const removed = await ctx.toolAffordanceManager.remove(toolName);
2025
+ return formatToolResponse({ toolName, removed });
2026
+ },
2027
+ observe_tool_start: (ctx, args) => {
2028
+ const toolName = validateWithSchema(args.toolName, z.string().min(1), 'Invalid toolName');
2029
+ const argsField = args.args === undefined
2030
+ ? undefined
2031
+ : validateWithSchema(args.args, z.record(z.string(), z.unknown()), 'Invalid args (must be an object)');
2032
+ const callId = ctx.toolCallObserver.observeStart(toolName, argsField);
2033
+ return Promise.resolve(formatToolResponse({ callId, toolName }));
2034
+ },
2035
+ observe_tool_complete: async (ctx, args) => {
2036
+ const callId = validateWithSchema(args.callId, z.string().min(1), 'Invalid callId');
2037
+ const result = args.result === undefined
2038
+ ? undefined
2039
+ : validateWithSchema(args.result, z.string(), 'Invalid result');
2040
+ await ctx.toolCallObserver.observeComplete(callId, result === undefined ? undefined : { result });
2041
+ return formatToolResponse({ callId, recorded: 'success' });
2042
+ },
2043
+ observe_tool_error: async (ctx, args) => {
2044
+ const callId = validateWithSchema(args.callId, z.string().min(1), 'Invalid callId');
2045
+ const errorMessage = validateWithSchema(args.errorMessage, z.string().min(1), 'Invalid errorMessage');
2046
+ await ctx.toolCallObserver.observeError(callId, errorMessage);
2047
+ return formatToolResponse({ callId, recorded: 'failure', errorMessage });
2048
+ },
2049
+ observe_tool_partial: async (ctx, args) => {
2050
+ const callId = validateWithSchema(args.callId, z.string().min(1), 'Invalid callId');
2051
+ const reason = validateWithSchema(args.reason, z.string().min(1), 'Invalid reason');
2052
+ await ctx.toolCallObserver.observePartial(callId, reason);
2053
+ return formatToolResponse({ callId, recorded: 'partial', reason });
2054
+ },
2055
+ observe_tool_cancel: (ctx, args) => {
2056
+ const callId = validateWithSchema(args.callId, z.string().min(1), 'Invalid callId');
2057
+ ctx.toolCallObserver.cancel(callId);
2058
+ return Promise.resolve(formatToolResponse({ callId, cancelled: true }));
2059
+ },
2060
+ tool_observer_in_flight_count: (ctx) => {
2061
+ const count = ctx.toolCallObserver.inFlightCount();
2062
+ return Promise.resolve(formatToolResponse({ inFlightCount: count }));
2063
+ },
2064
+ // ==================== v2.1.0 HEURISTIC GUIDELINES ====================
2065
+ add_heuristic: async (ctx, args) => {
2066
+ const condition = validateWithSchema(args.condition, z.string().min(1), 'Invalid condition');
2067
+ const action = validateWithSchema(args.action, z.string().min(1), 'Invalid action');
2068
+ const priority = args.priority === undefined
2069
+ ? undefined
2070
+ : validateWithSchema(args.priority, z.number(), 'Invalid priority');
2071
+ const initialConfidence = args.initialConfidence === undefined
2072
+ ? undefined
2073
+ : validateWithSchema(args.initialConfidence, z.number().min(0).max(1), 'Invalid initialConfidence');
2074
+ const importance = args.importance === undefined
2075
+ ? undefined
2076
+ : validateWithSchema(args.importance, z.number().min(0).max(10), 'Invalid importance');
2077
+ const agentId = args.agentId === undefined
2078
+ ? undefined
2079
+ : validateWithSchema(args.agentId, z.string().min(1), 'Invalid agentId');
2080
+ const id = args.id === undefined
2081
+ ? undefined
2082
+ : validateWithSchema(args.id, z.string().min(1), 'Invalid id');
2083
+ const heuristicId = await ctx.heuristicManager.add({
2084
+ condition,
2085
+ action,
2086
+ priority,
2087
+ initialConfidence,
2088
+ importance,
2089
+ agentId,
2090
+ id: id,
2091
+ });
2092
+ return formatToolResponse({ id: heuristicId });
2093
+ },
2094
+ get_heuristic: async (ctx, args) => {
2095
+ const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
2096
+ const heuristic = ctx.heuristicManager.get(id);
2097
+ return formatToolResponse({ id, heuristic: heuristic ?? null });
2098
+ },
2099
+ list_heuristics: async (ctx) => {
2100
+ const heuristics = await ctx.heuristicManager.list();
2101
+ return formatToolResponse({ heuristics, count: heuristics.length });
2102
+ },
2103
+ heuristic_count: async (ctx) => {
2104
+ const size = await ctx.heuristicManager.size();
2105
+ return formatToolResponse({ count: size });
2106
+ },
2107
+ match_heuristics: async (ctx, args) => {
2108
+ const input = validateWithSchema(args.input, z.string().min(1), 'Invalid input');
2109
+ const limit = args.limit === undefined
2110
+ ? undefined
2111
+ : validateWithSchema(args.limit, z.number().int().min(1), 'Invalid limit');
2112
+ const minScore = args.minScore === undefined
2113
+ ? undefined
2114
+ : validateWithSchema(args.minScore, z.number().min(0).max(1), 'Invalid minScore');
2115
+ const matches = await ctx.heuristicManager.match(input, { limit, minScore });
2116
+ return formatToolResponse({ input, matches, count: matches.length });
2117
+ },
2118
+ reinforce_heuristic: async (ctx, args) => {
2119
+ const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
2120
+ const result = await ctx.heuristicManager.reinforce(id);
2121
+ return formatToolResponse({ id, result });
2122
+ },
2123
+ record_heuristic_contradiction: async (ctx, args) => {
2124
+ const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
2125
+ const result = await ctx.heuristicManager.recordContradiction(id);
2126
+ return formatToolResponse({ id, result });
2127
+ },
2128
+ detect_heuristic_conflicts: async (ctx) => {
2129
+ const conflicts = await ctx.heuristicManager.detectConflicts();
2130
+ return formatToolResponse({ conflicts, count: conflicts.length });
2131
+ },
2132
+ remove_heuristic: async (ctx, args) => {
2133
+ const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
2134
+ const removed = await ctx.heuristicManager.remove(id);
2135
+ return formatToolResponse({ id, removed });
2136
+ },
2137
+ clear_heuristics: async (ctx) => {
2138
+ await ctx.heuristicManager.clear();
2139
+ return formatToolResponse({ cleared: true });
2140
+ },
2141
+ // ==================== v2.1.0 PROJECT CONTEXT ====================
2142
+ upsert_project_context: async (ctx, args) => {
2143
+ const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
2144
+ const facts = args.facts === undefined
2145
+ ? undefined
2146
+ : validateWithSchema(args.facts, z.array(z.string()), 'Invalid facts');
2147
+ const conventions = args.conventions === undefined
2148
+ ? undefined
2149
+ : validateWithSchema(args.conventions, z.array(z.string()), 'Invalid conventions');
2150
+ const commands = args.commands === undefined
2151
+ ? undefined
2152
+ : validateWithSchema(args.commands, z.array(z.object({
2153
+ name: z.string().min(1),
2154
+ command: z.string().min(1),
2155
+ purpose: z.string().min(1),
2156
+ })), 'Invalid commands');
2157
+ const glossary = args.glossary === undefined
2158
+ ? undefined
2159
+ : validateWithSchema(args.glossary, z.array(z.object({
2160
+ term: z.string().min(1),
2161
+ definition: z.string().min(1),
2162
+ })), 'Invalid glossary');
2163
+ const record = await ctx.projectContextManager.upsert(projectId, {
2164
+ facts, conventions, commands, glossary,
2165
+ });
2166
+ return formatToolResponse({ record });
2167
+ },
2168
+ get_project_context: async (ctx, args) => {
2169
+ const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
2170
+ const record = ctx.projectContextManager.get(projectId);
2171
+ return formatToolResponse({ projectId, record: record ?? null });
2172
+ },
2173
+ append_project_fact: async (ctx, args) => {
2174
+ const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
2175
+ const fact = validateWithSchema(args.fact, z.string().min(1), 'Invalid fact');
2176
+ const record = await ctx.projectContextManager.appendFact(projectId, fact);
2177
+ return formatToolResponse({ record });
2178
+ },
2179
+ append_project_convention: async (ctx, args) => {
2180
+ const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
2181
+ const convention = validateWithSchema(args.convention, z.string().min(1), 'Invalid convention');
2182
+ const record = await ctx.projectContextManager.appendConvention(projectId, convention);
2183
+ return formatToolResponse({ record });
2184
+ },
2185
+ append_project_command: async (ctx, args) => {
2186
+ const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
2187
+ const name = validateWithSchema(args.name, z.string().min(1), 'Invalid name');
2188
+ const command = validateWithSchema(args.command, z.string().min(1), 'Invalid command');
2189
+ const purpose = validateWithSchema(args.purpose, z.string().min(1), 'Invalid purpose');
2190
+ const record = await ctx.projectContextManager.appendCommand(projectId, { name, command, purpose });
2191
+ return formatToolResponse({ record });
2192
+ },
2193
+ append_project_glossary_term: async (ctx, args) => {
2194
+ const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
2195
+ const term = validateWithSchema(args.term, z.string().min(1), 'Invalid term');
2196
+ const definition = validateWithSchema(args.definition, z.string().min(1), 'Invalid definition');
2197
+ const record = await ctx.projectContextManager.appendGlossaryTerm(projectId, { term, definition });
2198
+ return formatToolResponse({ record });
2199
+ },
2200
+ remove_project_fact: async (ctx, args) => {
2201
+ const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
2202
+ const fact = validateWithSchema(args.fact, z.string().min(1), 'Invalid fact');
2203
+ const removed = await ctx.projectContextManager.removeFact(projectId, fact);
2204
+ return formatToolResponse({ projectId, fact, removed });
2205
+ },
2206
+ remove_project_convention: async (ctx, args) => {
2207
+ const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
2208
+ const convention = validateWithSchema(args.convention, z.string().min(1), 'Invalid convention');
2209
+ const removed = await ctx.projectContextManager.removeConvention(projectId, convention);
2210
+ return formatToolResponse({ projectId, convention, removed });
2211
+ },
2212
+ remove_project_command: async (ctx, args) => {
2213
+ const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
2214
+ const commandName = validateWithSchema(args.commandName, z.string().min(1), 'Invalid commandName');
2215
+ const removed = await ctx.projectContextManager.removeCommand(projectId, commandName);
2216
+ return formatToolResponse({ projectId, commandName, removed });
2217
+ },
2218
+ remove_project_glossary_term: async (ctx, args) => {
2219
+ const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
2220
+ const term = validateWithSchema(args.term, z.string().min(1), 'Invalid term');
2221
+ const removed = await ctx.projectContextManager.removeGlossaryTerm(projectId, term);
2222
+ return formatToolResponse({ projectId, term, removed });
2223
+ },
2224
+ clear_project_context: async (ctx, args) => {
2225
+ const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
2226
+ const cleared = await ctx.projectContextManager.clear(projectId);
2227
+ return formatToolResponse({ projectId, cleared });
2228
+ },
2229
+ format_project_context_for_llm: async (ctx, args) => {
2230
+ const projectId = validateWithSchema(args.projectId, z.string().min(1), 'Invalid projectId');
2231
+ const budgetChars = args.budgetChars === undefined
2232
+ ? undefined
2233
+ : validateWithSchema(args.budgetChars, z.number().int().min(1), 'Invalid budgetChars');
2234
+ const prose = await ctx.projectContextManager.forContext(projectId, { budgetChars });
2235
+ return formatTextResponse(prose);
2236
+ },
2237
+ // ==================== v2.1.0 DECISION RATIONALE ====================
2238
+ propose_decision: async (ctx, args) => {
2239
+ const context = validateWithSchema(args.context, z.string().min(1), 'Invalid context');
2240
+ const decision = validateWithSchema(args.decision, z.string().min(1), 'Invalid decision');
2241
+ const alternatives = args.alternatives === undefined
2242
+ ? []
2243
+ : validateWithSchema(args.alternatives, z.array(z.string()), 'Invalid alternatives');
2244
+ const consequences = args.consequences === undefined
2245
+ ? []
2246
+ : validateWithSchema(args.consequences, z.array(z.string()), 'Invalid consequences');
2247
+ const relatedFiles = args.relatedFiles === undefined
2248
+ ? undefined
2249
+ : validateWithSchema(args.relatedFiles, z.array(z.string()), 'Invalid relatedFiles');
2250
+ const supersedes = args.supersedes === undefined
2251
+ ? undefined
2252
+ : validateWithSchema(args.supersedes, z.string().min(1), 'Invalid supersedes');
2253
+ const sourceSessionId = args.sourceSessionId === undefined
2254
+ ? undefined
2255
+ : validateWithSchema(args.sourceSessionId, z.string().min(1), 'Invalid sourceSessionId');
2256
+ const sourceProjectId = args.sourceProjectId === undefined
2257
+ ? undefined
2258
+ : validateWithSchema(args.sourceProjectId, z.string().min(1), 'Invalid sourceProjectId');
2259
+ const importance = args.importance === undefined
2260
+ ? undefined
2261
+ : validateWithSchema(args.importance, z.number().min(0).max(10), 'Invalid importance');
2262
+ const agentId = args.agentId === undefined
2263
+ ? undefined
2264
+ : validateWithSchema(args.agentId, z.string().min(1), 'Invalid agentId');
2265
+ // `supersedes` is a branded DecisionId at the type level; the
2266
+ // manager casts internally so a string is fine here.
2267
+ const record = await ctx.decisionManager.propose({
2268
+ context,
2269
+ decision,
2270
+ alternatives,
2271
+ consequences,
2272
+ relatedFiles,
2273
+ supersedes: supersedes,
2274
+ sourceSessionId,
2275
+ sourceProjectId,
2276
+ }, { importance, agentId });
2277
+ return formatToolResponse({ record });
2278
+ },
2279
+ accept_decision: async (ctx, args) => {
2280
+ const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
2281
+ const result = await ctx.decisionManager.accept(id);
2282
+ return formatToolResponse({ id, result });
2283
+ },
2284
+ reject_decision: async (ctx, args) => {
2285
+ const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
2286
+ const reason = validateWithSchema(args.reason, z.string().min(1), 'Invalid reason');
2287
+ const result = await ctx.decisionManager.reject(id, reason);
2288
+ return formatToolResponse({ id, reason, result });
2289
+ },
2290
+ supersede_decision: async (ctx, args) => {
2291
+ const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
2292
+ const by = validateWithSchema(args.by, z.string().min(1), 'Invalid by');
2293
+ const result = await ctx.decisionManager.supersede(id, by);
2294
+ return formatToolResponse({ id, by, result });
2295
+ },
2296
+ find_decisions_by_context: async (ctx, args) => {
2297
+ const query = validateWithSchema(args.query, z.string().min(1), 'Invalid query');
2298
+ const records = await ctx.decisionManager.findByContext(query);
2299
+ return formatToolResponse({ query, records, count: records.length });
2300
+ },
2301
+ get_decision_chain: async (ctx, args) => {
2302
+ const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
2303
+ const chain = await ctx.decisionManager.getChain(id);
2304
+ return formatToolResponse({ id, chain, length: chain.length });
2305
+ },
2306
+ list_decisions: async (ctx, args) => {
2307
+ const status = args.status === undefined
2308
+ ? undefined
2309
+ : validateWithSchema(args.status, z.enum(['proposed', 'accepted', 'superseded', 'rejected']), 'Invalid status');
2310
+ const sourceSessionId = args.sourceSessionId === undefined
2311
+ ? undefined
2312
+ : validateWithSchema(args.sourceSessionId, z.string().min(1), 'Invalid sourceSessionId');
2313
+ const sourceProjectId = args.sourceProjectId === undefined
2314
+ ? undefined
2315
+ : validateWithSchema(args.sourceProjectId, z.string().min(1), 'Invalid sourceProjectId');
2316
+ const limit = args.limit === undefined
2317
+ ? undefined
2318
+ : validateWithSchema(args.limit, z.number().int().min(1), 'Invalid limit');
2319
+ const records = await ctx.decisionManager.list({ status, sourceSessionId, sourceProjectId, limit });
2320
+ return formatToolResponse({ records, count: records.length });
2321
+ },
2322
+ get_decision: async (ctx, args) => {
2323
+ const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
2324
+ const record = ctx.decisionManager.get(id);
2325
+ return formatToolResponse({ id, record: record ?? null });
2326
+ },
2327
+ export_decision_as_adr_markdown: async (ctx, args) => {
2328
+ const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
2329
+ const markdown = ctx.decisionManager.exportAsAdrMarkdown(id);
2330
+ return formatTextResponse(markdown);
2331
+ },
2332
+ parse_adr_markdown: async (_ctx, args) => {
2333
+ const text = validateWithSchema(args.text, z.string().min(1), 'Invalid text');
2334
+ const parsed = DecisionManager.parseAdrMarkdown(text);
2335
+ return formatToolResponse({ parsed });
2336
+ },
2337
+ // ==================== v2.1.0 do_not_remember (Exclusion) ====================
2338
+ add_exclusion_rule: async (ctx, args) => {
2339
+ const pattern = validateWithSchema(args.pattern, z.string().min(1), 'Invalid pattern');
2340
+ const scope = args.scope === undefined
2341
+ ? undefined
2342
+ : validateWithSchema(args.scope, z.enum(['future-only', 'past-only', 'both']), 'Invalid scope');
2343
+ const entityType = args.entityType === undefined
2344
+ ? undefined
2345
+ : validateWithSchema(args.entityType, z.string().min(1), 'Invalid entityType');
2346
+ const reason = args.reason === undefined
2347
+ ? undefined
2348
+ : validateWithSchema(args.reason, z.string().min(1), 'Invalid reason');
2349
+ const rule = await ctx.exclusionManager.add({ pattern, scope, entityType, reason });
2350
+ return formatToolResponse({ rule });
2351
+ },
2352
+ list_exclusion_rules: async (ctx) => {
2353
+ const rules = await ctx.exclusionManager.list();
2354
+ return formatToolResponse({ rules, count: rules.length });
2355
+ },
2356
+ remove_exclusion_rule: async (ctx, args) => {
2357
+ const id = validateWithSchema(args.id, z.string().min(1), 'Invalid id');
2358
+ const removed = await ctx.exclusionManager.remove(id);
2359
+ return formatToolResponse({ id, removed });
2360
+ },
2361
+ check_exclusion: async (ctx, args) => {
2362
+ const content = validateWithSchema(args.content, z.string(), 'Invalid content');
2363
+ const entityType = args.entityType === undefined
2364
+ ? undefined
2365
+ : validateWithSchema(args.entityType, z.string().min(1), 'Invalid entityType');
2366
+ const verdict = await ctx.exclusionManager.check(content, entityType);
2367
+ return formatToolResponse(verdict);
2368
+ },
2369
+ find_matching_memories_for_rule: async (ctx, args) => {
2370
+ const pattern = validateWithSchema(args.pattern, z.string().min(1), 'Invalid pattern');
2371
+ const entityType = args.entityType === undefined
2372
+ ? undefined
2373
+ : validateWithSchema(args.entityType, z.string().min(1), 'Invalid entityType');
2374
+ const matches = await ctx.exclusionManager.findMatchingMemories({ pattern, entityType });
2375
+ return formatToolResponse({
2376
+ pattern,
2377
+ entityType,
2378
+ matches: matches.map((m) => ({ name: m.name, entityType: m.entityType })),
2379
+ count: matches.length,
2380
+ });
2381
+ },
2382
+ // ==================== v2.1.0 OBSERVATION DEDUP ====================
2383
+ find_duplicate_observations: async (ctx, args) => {
2384
+ const filter = parseObservationDedupFilter(args);
2385
+ const groups = await ctx.observationDedupManager.findDuplicateObservations(filter);
2386
+ return formatToolResponse({ filter, groups, count: groups.length });
2387
+ },
2388
+ find_jaccard_duplicate_observations: async (ctx, args) => {
2389
+ const filter = parseObservationDedupFilter(args);
2390
+ const groups = await ctx.observationDedupManager.findJaccardDuplicates(filter);
2391
+ return formatToolResponse({ filter, groups, count: groups.length });
2392
+ },
2393
+ // ==================== v2.1.0 SPELL CORRECTION ====================
2394
+ spell_suggest: async (ctx, args) => {
2395
+ const query = validateWithSchema(args.query, z.string().min(1), 'Invalid query');
2396
+ const limit = args.limit === undefined
2397
+ ? undefined
2398
+ : validateWithSchema(args.limit, z.number().int().min(1), 'Invalid limit');
2399
+ const minScore = args.minScore === undefined
2400
+ ? undefined
2401
+ : validateWithSchema(args.minScore, z.number().min(0).max(1), 'Invalid minScore');
2402
+ const maxDistance = args.maxDistance === undefined
2403
+ ? undefined
2404
+ : validateWithSchema(args.maxDistance, z.number().min(0), 'Invalid maxDistance');
2405
+ const suggestions = await ctx.spellChecker.suggest(query, { limit, minScore, maxDistance });
2406
+ return formatToolResponse({ query, suggestions, count: suggestions.length });
2407
+ },
2408
+ spell_rebuild_vocabulary: async (ctx) => {
2409
+ await ctx.spellChecker.rebuild();
2410
+ return formatToolResponse({ rebuilt: true, vocabularySize: ctx.spellChecker.vocabularySize() });
2411
+ },
2412
+ spell_vocabulary_size: async (ctx) => {
2413
+ const size = ctx.spellChecker.vocabularySize();
2414
+ return formatToolResponse({ vocabularySize: size });
2415
+ },
2416
+ // ==================== ENGINEERING / DIAGNOSTIC TOOLS (v12.5.0) ====================
2417
+ // Parallel to the `memory diag` / `memory check` / etc. CLI surface in memoryjs v2.2+.
2418
+ // Useful when the MCP server is up but the graph state is suspect.
2419
+ diag: async (ctx) => {
2420
+ const storagePath = getStorageFilePath(ctx);
2421
+ let exists = false;
2422
+ let sizeBytes = 0;
2423
+ try {
2424
+ const stat = await fs.stat(storagePath);
2425
+ exists = true;
2426
+ sizeBytes = stat.size;
2427
+ }
2428
+ catch { /* file may not exist yet */ }
2429
+ const stats = await ctx.analyticsManager.getGraphStats();
2430
+ return formatToolResponse({
2431
+ runtime: { node: process.version, platform: process.platform, arch: process.arch, pid: process.pid },
2432
+ storage: {
2433
+ path: storagePath,
2434
+ type: process.env.MEMORY_STORAGE_TYPE ?? 'jsonl',
2435
+ exists,
2436
+ sizeBytes,
2437
+ entities: stats.totalEntities,
2438
+ relations: stats.totalRelations,
2439
+ },
2440
+ loadedAt: new Date().toISOString(),
2441
+ });
2442
+ },
2443
+ health: async (ctx) => {
2444
+ const checks = [];
2445
+ const t1 = performance.now();
2446
+ let graph;
2447
+ try {
2448
+ graph = await ctx.storage.loadGraph();
2449
+ checks.push({ name: 'storage:loadGraph', ok: true, durationMs: performance.now() - t1 });
2450
+ }
2451
+ catch (e) {
2452
+ checks.push({
2453
+ name: 'storage:loadGraph',
2454
+ ok: false,
2455
+ durationMs: performance.now() - t1,
2456
+ detail: e instanceof Error ? e.message : String(e),
2457
+ });
2458
+ return formatToolResponse({ ok: false, failed: 1, totalChecks: 1, totalMs: performance.now() - t1, checks });
2459
+ }
2460
+ const t2 = performance.now();
2461
+ const names = new Set();
2462
+ const dupes = [];
2463
+ for (const e of graph.entities) {
2464
+ if (names.has(e.name))
2465
+ dupes.push(e.name);
2466
+ else
2467
+ names.add(e.name);
2468
+ }
2469
+ checks.push({
2470
+ name: 'entities:distinct-names',
2471
+ ok: dupes.length === 0,
2472
+ durationMs: performance.now() - t2,
2473
+ detail: dupes.length === 0 ? undefined : `${dupes.length} duplicate(s)`,
2474
+ });
2475
+ const t3 = performance.now();
2476
+ const orphans = [];
2477
+ for (const r of graph.relations) {
2478
+ if (!names.has(r.from) || !names.has(r.to)) {
2479
+ orphans.push(`${r.from} → ${r.to}`);
2480
+ }
2481
+ }
2482
+ checks.push({
2483
+ name: 'relations:no-orphans',
2484
+ ok: orphans.length === 0,
2485
+ durationMs: performance.now() - t3,
2486
+ detail: orphans.length === 0 ? undefined : `${orphans.length} orphan(s)`,
2487
+ });
2488
+ const t4 = performance.now();
2489
+ const byName = new Map();
2490
+ for (const e of graph.entities)
2491
+ byName.set(e.name, e);
2492
+ const cycleIssues = [];
2493
+ for (const e of graph.entities) {
2494
+ if (!e.parentId)
2495
+ continue;
2496
+ if (!byName.has(e.parentId)) {
2497
+ cycleIssues.push(`${e.name}.parentId='${e.parentId}' missing`);
2498
+ continue;
2499
+ }
2500
+ const visited = new Set();
2501
+ let cur = byName.get(e.parentId);
2502
+ while (cur && cur.parentId) {
2503
+ if (visited.has(cur.name)) {
2504
+ cycleIssues.push(`cycle through ${cur.name}`);
2505
+ break;
2506
+ }
2507
+ visited.add(cur.name);
2508
+ cur = byName.get(cur.parentId);
2509
+ }
2510
+ }
2511
+ checks.push({
2512
+ name: 'hierarchy:no-cycles-no-missing-parents',
2513
+ ok: cycleIssues.length === 0,
2514
+ durationMs: performance.now() - t4,
2515
+ detail: cycleIssues.length === 0 ? undefined : cycleIssues.slice(0, 3).join('; '),
2516
+ });
2517
+ const failed = checks.filter((c) => !c.ok).length;
2518
+ return formatToolResponse({
2519
+ ok: failed === 0,
2520
+ failed,
2521
+ totalChecks: checks.length,
2522
+ totalMs: Number(checks.reduce((a, c) => a + c.durationMs, 0).toFixed(2)),
2523
+ checks,
2524
+ });
2525
+ },
2526
+ check_graph: async (ctx, args) => {
2527
+ const apply = args.apply === undefined
2528
+ ? false
2529
+ : validateWithSchema(args.apply, z.boolean(), 'Invalid apply');
2530
+ const graph = await ctx.storage.loadGraph();
2531
+ const names = new Set(graph.entities.map((e) => e.name));
2532
+ const orphans = [];
2533
+ for (const r of graph.relations) {
2534
+ const fromMissing = !names.has(r.from);
2535
+ const toMissing = !names.has(r.to);
2536
+ if (fromMissing || toMissing) {
2537
+ orphans.push({
2538
+ from: r.from, to: r.to, relationType: r.relationType,
2539
+ reason: fromMissing && toMissing ? 'both-missing' : fromMissing ? 'from-missing' : 'to-missing',
2540
+ });
2541
+ }
2542
+ }
2543
+ const byName = new Map();
2544
+ for (const e of graph.entities)
2545
+ byName.set(e.name, { name: e.name, parentId: e.parentId });
2546
+ const missing = [];
2547
+ const cycles = [];
2548
+ for (const e of graph.entities) {
2549
+ if (!e.parentId)
2550
+ continue;
2551
+ if (!byName.has(e.parentId)) {
2552
+ missing.push({ entity: e.name, parentId: e.parentId });
2553
+ continue;
2554
+ }
2555
+ const visited = new Set([e.name]);
2556
+ let cur = byName.get(e.parentId);
2557
+ while (cur && cur.parentId) {
2558
+ if (visited.has(cur.name)) {
2559
+ cycles.push({ entityInCycle: e.name, cycleThrough: cur.name });
2560
+ break;
2561
+ }
2562
+ visited.add(cur.name);
2563
+ cur = byName.get(cur.parentId);
2564
+ }
2565
+ }
2566
+ const ok = orphans.length === 0 && missing.length === 0 && cycles.length === 0;
2567
+ const result = {
2568
+ ok, applied: apply, orphanRelations: orphans, missingParents: missing, hierarchyCycles: cycles,
2569
+ };
2570
+ if (apply && (orphans.length > 0 || missing.length > 0)) {
2571
+ let deleted = 0;
2572
+ let cleared = 0;
2573
+ if (orphans.length > 0) {
2574
+ await ctx.relationManager.deleteRelations(orphans.map((o) => ({ from: o.from, to: o.to, relationType: o.relationType })));
2575
+ deleted = orphans.length;
2576
+ }
2577
+ for (const m of missing) {
2578
+ try {
2579
+ await ctx.hierarchyManager.setEntityParent(m.entity, null);
2580
+ cleared += 1;
2581
+ }
2582
+ catch { /* skip; entity may have vanished */ }
2583
+ }
2584
+ result.actions = { orphanRelationsDeleted: deleted, missingParentsCleared: cleared };
2585
+ }
2586
+ return formatToolResponse(result);
2587
+ },
2588
+ reindex: async (ctx, args) => {
2589
+ const ranked = args.ranked === undefined
2590
+ ? true
2591
+ : validateWithSchema(args.ranked, z.boolean(), 'Invalid ranked');
2592
+ const spell = args.spell === undefined
2593
+ ? true
2594
+ : validateWithSchema(args.spell, z.boolean(), 'Invalid spell');
2595
+ if (!ranked && !spell) {
2596
+ return formatToolResponse({ ok: true, failed: 0, result: {} });
2597
+ }
2598
+ const result = {};
2599
+ if (ranked) {
2600
+ const t = performance.now();
2601
+ try {
2602
+ // Same workaround as the CLI: default ctx.rankedSearch is constructed
2603
+ // without a storageDir so buildIndex() refuses. Construct an ad-hoc one
2604
+ // alongside the JSONL.
2605
+ const storageDir = path.dirname(getStorageFilePath(ctx));
2606
+ const r = new RankedSearch(ctx.storage, storageDir);
2607
+ await r.buildIndex();
2608
+ result.ranked = { ok: true, durationMs: performance.now() - t };
2609
+ }
2610
+ catch (e) {
2611
+ result.ranked = {
2612
+ ok: false,
2613
+ durationMs: performance.now() - t,
2614
+ detail: e instanceof Error ? e.message : String(e),
2615
+ };
2616
+ }
2617
+ }
2618
+ if (spell) {
2619
+ const t = performance.now();
2620
+ try {
2621
+ await ctx.spellChecker.rebuild();
2622
+ result.spell = { ok: true, durationMs: performance.now() - t };
2623
+ }
2624
+ catch (e) {
2625
+ result.spell = {
2626
+ ok: false,
2627
+ durationMs: performance.now() - t,
2628
+ detail: e instanceof Error ? e.message : String(e),
2629
+ };
2630
+ }
2631
+ }
2632
+ const failed = Object.values(result).filter((r) => !r.ok).length;
2633
+ return formatToolResponse({ ok: failed === 0, failed, result });
2634
+ },
2635
+ cache_stats: async (_ctx) => {
2636
+ return formatToolResponse({ stats: getAllCacheStats() });
2637
+ },
2638
+ cache_clear: async (_ctx) => {
2639
+ clearAllSearchCaches();
2640
+ return formatToolResponse({ cleared: true, caches: ['basic', 'ranked', 'boolean', 'fuzzy'] });
2641
+ },
2642
+ graph_size: async (ctx) => {
2643
+ const storagePath = getStorageFilePath(ctx);
2644
+ const graph = await ctx.storage.loadGraph();
2645
+ let observationCount = 0;
2646
+ const tagSet = new Set();
2647
+ for (const e of graph.entities) {
2648
+ observationCount += e.observations.length;
2649
+ if (e.tags)
2650
+ for (const t of e.tags)
2651
+ tagSet.add(t);
2652
+ }
2653
+ let exists = false;
2654
+ let sizeBytes = 0;
2655
+ let lineCount = 0;
2656
+ try {
2657
+ const stat = await fs.stat(storagePath);
2658
+ exists = true;
2659
+ sizeBytes = stat.size;
2660
+ if (sizeBytes > 0) {
2661
+ const content = await fs.readFile(storagePath, 'utf8');
2662
+ lineCount = content.split('\n').filter((l) => l.length > 0).length;
2663
+ }
2664
+ }
2665
+ catch { /* file may not exist */ }
2666
+ return formatToolResponse({
2667
+ graph: {
2668
+ entities: graph.entities.length,
2669
+ relations: graph.relations.length,
2670
+ observations: observationCount,
2671
+ distinctTags: tagSet.size,
2672
+ avgObservationsPerEntity: graph.entities.length === 0
2673
+ ? 0
2674
+ : Number((observationCount / graph.entities.length).toFixed(2)),
2675
+ },
2676
+ storage: { path: storagePath, exists, sizeBytes, lineCount },
2677
+ });
2678
+ },
2679
+ inspect_entity: async (ctx, args) => {
2680
+ const name = validateWithSchema(args.name, z.string().min(1), 'Invalid name');
2681
+ // getEntityByName reads an in-memory nameIndex hydrated by loadGraph.
2682
+ const graph = await ctx.storage.loadGraph();
2683
+ const entity = ctx.storage.getEntityByName(name);
2684
+ if (!entity)
2685
+ throw new Error(`entity not found: ${name}`);
2686
+ const observations = await ctx.observationManager.getObservationsFor(name);
2687
+ const outgoing = graph.relations
2688
+ .filter((r) => r.from === name)
2689
+ .map((r) => ({ to: r.to, type: r.relationType }));
2690
+ const incoming = graph.relations
2691
+ .filter((r) => r.to === name)
2692
+ .map((r) => ({ from: r.from, type: r.relationType }));
2693
+ const children = (await ctx.hierarchyManager.getChildren(name)).map((c) => c.name);
2694
+ const ancestors = (await ctx.hierarchyManager.getAncestors(name)).map((a) => a.name);
2695
+ return formatToolResponse({
2696
+ name: entity.name,
2697
+ entityType: entity.entityType,
2698
+ observations,
2699
+ tags: entity.tags,
2700
+ importance: entity.importance,
2701
+ createdAt: entity.createdAt,
2702
+ lastModified: entity.lastModified,
2703
+ parentId: entity.parentId,
2704
+ children,
2705
+ ancestors,
2706
+ relations: { outgoing, incoming },
2707
+ });
2708
+ },
2709
+ hierarchy_tree: async (ctx, args) => {
2710
+ const root = args.root === undefined
2711
+ ? undefined
2712
+ : validateWithSchema(args.root, z.string().min(1), 'Invalid root');
2713
+ await ctx.storage.loadGraph();
2714
+ async function walk(n) {
2715
+ const e = ctx.storage.getEntityByName(n);
2716
+ const kids = await ctx.hierarchyManager.getChildren(n);
2717
+ const childNodes = [];
2718
+ for (const c of kids)
2719
+ childNodes.push(await walk(c.name));
2720
+ return { name: n, entityType: e?.entityType ?? 'unknown', children: childNodes };
2721
+ }
2722
+ if (root) {
2723
+ const e = ctx.storage.getEntityByName(root);
2724
+ if (!e)
2725
+ throw new Error(`entity not found: ${root}`);
2726
+ return formatToolResponse({ trees: [await walk(root)] });
2727
+ }
2728
+ const roots = await ctx.hierarchyManager.getRootEntities();
2729
+ const trees = await Promise.all(roots.map((r) => walk(r.name)));
2730
+ return formatToolResponse({ trees });
2731
+ },
2732
+ entity_neighbors: async (ctx, args) => {
2733
+ const name = validateWithSchema(args.name, z.string().min(1), 'Invalid name');
2734
+ const graph = await ctx.storage.loadGraph();
2735
+ const entity = ctx.storage.getEntityByName(name);
2736
+ if (!entity)
2737
+ throw new Error(`entity not found: ${name}`);
2738
+ const outgoing = graph.relations.filter((r) => r.from === name).map((r) => ({ to: r.to, type: r.relationType }));
2739
+ const incoming = graph.relations.filter((r) => r.to === name).map((r) => ({ from: r.from, type: r.relationType }));
2740
+ return formatToolResponse({
2741
+ entity: name,
2742
+ outgoing,
2743
+ incoming,
2744
+ outDegree: outgoing.length,
2745
+ inDegree: incoming.length,
2746
+ });
2747
+ },
658
2748
  };
659
2749
  /**
660
2750
  * Handle a tool call by dispatching to the appropriate handler.