ai-memory-layer 2.0.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/CHANGELOG.md +19 -12
  2. package/README.md +435 -320
  3. package/bin/memory-server.mjs +0 -0
  4. package/dist/adapters/memory/embeddings.d.ts.map +1 -1
  5. package/dist/adapters/memory/embeddings.js +12 -1
  6. package/dist/adapters/memory/embeddings.js.map +1 -1
  7. package/dist/adapters/memory/index.d.ts.map +1 -1
  8. package/dist/adapters/memory/index.js +1281 -48
  9. package/dist/adapters/memory/index.js.map +1 -1
  10. package/dist/adapters/postgres/index.d.ts +1 -0
  11. package/dist/adapters/postgres/index.d.ts.map +1 -1
  12. package/dist/adapters/postgres/index.js +1770 -42
  13. package/dist/adapters/postgres/index.js.map +1 -1
  14. package/dist/adapters/sqlite/embeddings.d.ts.map +1 -1
  15. package/dist/adapters/sqlite/embeddings.js +49 -12
  16. package/dist/adapters/sqlite/embeddings.js.map +1 -1
  17. package/dist/adapters/sqlite/index.d.ts.map +1 -1
  18. package/dist/adapters/sqlite/index.js +1720 -38
  19. package/dist/adapters/sqlite/index.js.map +1 -1
  20. package/dist/adapters/sqlite/mappers.d.ts +39 -4
  21. package/dist/adapters/sqlite/mappers.d.ts.map +1 -1
  22. package/dist/adapters/sqlite/mappers.js +87 -0
  23. package/dist/adapters/sqlite/mappers.js.map +1 -1
  24. package/dist/adapters/sqlite/schema.d.ts +1 -1
  25. package/dist/adapters/sqlite/schema.d.ts.map +1 -1
  26. package/dist/adapters/sqlite/schema.js +297 -1
  27. package/dist/adapters/sqlite/schema.js.map +1 -1
  28. package/dist/adapters/sync-to-async.d.ts.map +1 -1
  29. package/dist/adapters/sync-to-async.js +54 -0
  30. package/dist/adapters/sync-to-async.js.map +1 -1
  31. package/dist/contracts/async-storage.d.ts +61 -1
  32. package/dist/contracts/async-storage.d.ts.map +1 -1
  33. package/dist/contracts/cognitive.d.ts +37 -0
  34. package/dist/contracts/cognitive.d.ts.map +1 -0
  35. package/dist/contracts/cognitive.js +24 -0
  36. package/dist/contracts/cognitive.js.map +1 -0
  37. package/dist/contracts/coordination.d.ts +101 -0
  38. package/dist/contracts/coordination.d.ts.map +1 -0
  39. package/dist/contracts/coordination.js +26 -0
  40. package/dist/contracts/coordination.js.map +1 -0
  41. package/dist/contracts/embedding.d.ts +1 -1
  42. package/dist/contracts/embedding.d.ts.map +1 -1
  43. package/dist/contracts/errors.d.ts +28 -0
  44. package/dist/contracts/errors.d.ts.map +1 -0
  45. package/dist/contracts/errors.js +41 -0
  46. package/dist/contracts/errors.js.map +1 -0
  47. package/dist/contracts/identity.d.ts +2 -0
  48. package/dist/contracts/identity.d.ts.map +1 -1
  49. package/dist/contracts/identity.js +26 -1
  50. package/dist/contracts/identity.js.map +1 -1
  51. package/dist/contracts/observability.d.ts +2 -1
  52. package/dist/contracts/observability.d.ts.map +1 -1
  53. package/dist/contracts/observability.js +11 -0
  54. package/dist/contracts/observability.js.map +1 -1
  55. package/dist/contracts/profile.d.ts +29 -0
  56. package/dist/contracts/profile.d.ts.map +1 -0
  57. package/dist/contracts/profile.js +2 -0
  58. package/dist/contracts/profile.js.map +1 -0
  59. package/dist/contracts/session-state.d.ts +10 -0
  60. package/dist/contracts/session-state.d.ts.map +1 -0
  61. package/dist/contracts/session-state.js +2 -0
  62. package/dist/contracts/session-state.js.map +1 -0
  63. package/dist/contracts/storage.d.ts +73 -1
  64. package/dist/contracts/storage.d.ts.map +1 -1
  65. package/dist/contracts/storage.js +16 -1
  66. package/dist/contracts/storage.js.map +1 -1
  67. package/dist/contracts/temporal.d.ts +112 -0
  68. package/dist/contracts/temporal.d.ts.map +1 -0
  69. package/dist/contracts/temporal.js +31 -0
  70. package/dist/contracts/temporal.js.map +1 -0
  71. package/dist/contracts/types.d.ts +135 -0
  72. package/dist/contracts/types.d.ts.map +1 -1
  73. package/dist/contracts/types.js +27 -0
  74. package/dist/contracts/types.js.map +1 -1
  75. package/dist/core/associations.d.ts +18 -0
  76. package/dist/core/associations.d.ts.map +1 -0
  77. package/dist/core/associations.js +185 -0
  78. package/dist/core/associations.js.map +1 -0
  79. package/dist/core/circuit-breaker.d.ts +9 -0
  80. package/dist/core/circuit-breaker.d.ts.map +1 -1
  81. package/dist/core/circuit-breaker.js +13 -1
  82. package/dist/core/circuit-breaker.js.map +1 -1
  83. package/dist/core/cognitive.d.ts +5 -0
  84. package/dist/core/cognitive.d.ts.map +1 -0
  85. package/dist/core/cognitive.js +120 -0
  86. package/dist/core/cognitive.js.map +1 -0
  87. package/dist/core/context.d.ts +72 -1
  88. package/dist/core/context.d.ts.map +1 -1
  89. package/dist/core/context.js +471 -45
  90. package/dist/core/context.js.map +1 -1
  91. package/dist/core/episodic.d.ts +28 -0
  92. package/dist/core/episodic.d.ts.map +1 -0
  93. package/dist/core/episodic.js +371 -0
  94. package/dist/core/episodic.js.map +1 -0
  95. package/dist/core/formatter.d.ts +4 -0
  96. package/dist/core/formatter.d.ts.map +1 -1
  97. package/dist/core/formatter.js +103 -0
  98. package/dist/core/formatter.js.map +1 -1
  99. package/dist/core/maintenance.d.ts +1 -0
  100. package/dist/core/maintenance.d.ts.map +1 -1
  101. package/dist/core/maintenance.js +75 -0
  102. package/dist/core/maintenance.js.map +1 -1
  103. package/dist/core/manager.d.ts +159 -7
  104. package/dist/core/manager.d.ts.map +1 -1
  105. package/dist/core/manager.js +740 -31
  106. package/dist/core/manager.js.map +1 -1
  107. package/dist/core/orchestrator.d.ts.map +1 -1
  108. package/dist/core/orchestrator.js +210 -178
  109. package/dist/core/orchestrator.js.map +1 -1
  110. package/dist/core/playbook.d.ts +35 -0
  111. package/dist/core/playbook.d.ts.map +1 -0
  112. package/dist/core/playbook.js +184 -0
  113. package/dist/core/playbook.js.map +1 -0
  114. package/dist/core/profile.d.ts +8 -0
  115. package/dist/core/profile.d.ts.map +1 -0
  116. package/dist/core/profile.js +103 -0
  117. package/dist/core/profile.js.map +1 -0
  118. package/dist/core/quick.d.ts +5 -0
  119. package/dist/core/quick.d.ts.map +1 -1
  120. package/dist/core/quick.js +10 -1
  121. package/dist/core/quick.js.map +1 -1
  122. package/dist/core/runtime.d.ts +17 -1
  123. package/dist/core/runtime.d.ts.map +1 -1
  124. package/dist/core/runtime.js +88 -5
  125. package/dist/core/runtime.js.map +1 -1
  126. package/dist/core/streaming.d.ts +1 -1
  127. package/dist/core/streaming.d.ts.map +1 -1
  128. package/dist/core/temporal.d.ts +29 -0
  129. package/dist/core/temporal.d.ts.map +1 -0
  130. package/dist/core/temporal.js +447 -0
  131. package/dist/core/temporal.js.map +1 -0
  132. package/dist/core/validation.d.ts +3 -0
  133. package/dist/core/validation.d.ts.map +1 -1
  134. package/dist/core/validation.js +25 -10
  135. package/dist/core/validation.js.map +1 -1
  136. package/dist/core/workspace-detect.d.ts +17 -0
  137. package/dist/core/workspace-detect.d.ts.map +1 -0
  138. package/dist/core/workspace-detect.js +55 -0
  139. package/dist/core/workspace-detect.js.map +1 -0
  140. package/dist/embeddings/resilience.d.ts.map +1 -1
  141. package/dist/embeddings/resilience.js +19 -8
  142. package/dist/embeddings/resilience.js.map +1 -1
  143. package/dist/index.d.ts +21 -4
  144. package/dist/index.d.ts.map +1 -1
  145. package/dist/index.js +9 -0
  146. package/dist/index.js.map +1 -1
  147. package/dist/integrations/claude-agent.d.ts +6 -0
  148. package/dist/integrations/claude-agent.d.ts.map +1 -1
  149. package/dist/integrations/claude-agent.js +5 -1
  150. package/dist/integrations/claude-agent.js.map +1 -1
  151. package/dist/integrations/claude-tools.d.ts +5 -4
  152. package/dist/integrations/claude-tools.d.ts.map +1 -1
  153. package/dist/integrations/claude-tools.js +155 -2
  154. package/dist/integrations/claude-tools.js.map +1 -1
  155. package/dist/integrations/middleware.d.ts +6 -0
  156. package/dist/integrations/middleware.d.ts.map +1 -1
  157. package/dist/integrations/middleware.js +11 -1
  158. package/dist/integrations/middleware.js.map +1 -1
  159. package/dist/integrations/openai-tools.d.ts +5 -4
  160. package/dist/integrations/openai-tools.d.ts.map +1 -1
  161. package/dist/integrations/openai-tools.js +170 -2
  162. package/dist/integrations/openai-tools.js.map +1 -1
  163. package/dist/integrations/vercel-ai.d.ts +6 -0
  164. package/dist/integrations/vercel-ai.d.ts.map +1 -1
  165. package/dist/integrations/vercel-ai.js +4 -0
  166. package/dist/integrations/vercel-ai.js.map +1 -1
  167. package/dist/server/http-server.d.ts +8 -0
  168. package/dist/server/http-server.d.ts.map +1 -1
  169. package/dist/server/http-server.js +976 -58
  170. package/dist/server/http-server.js.map +1 -1
  171. package/dist/server/mcp-server.d.ts +8 -0
  172. package/dist/server/mcp-server.d.ts.map +1 -1
  173. package/dist/server/mcp-server.js +1157 -37
  174. package/dist/server/mcp-server.js.map +1 -1
  175. package/dist/server/parsing.d.ts +12 -0
  176. package/dist/server/parsing.d.ts.map +1 -0
  177. package/dist/server/parsing.js +42 -0
  178. package/dist/server/parsing.js.map +1 -0
  179. package/dist/summarizers/prompts.d.ts +4 -0
  180. package/dist/summarizers/prompts.d.ts.map +1 -1
  181. package/dist/summarizers/prompts.js +42 -0
  182. package/dist/summarizers/prompts.js.map +1 -1
  183. package/docs/ULTIMATE_MEMORY_LAYER_ROADMAP.md +291 -0
  184. package/docs/prd.json +1498 -0
  185. package/openapi.yaml +1945 -112
  186. package/package.json +4 -2
@@ -1,9 +1,52 @@
1
1
  import { createMemoryWithAsyncAdapter, } from '../core/quick.js';
2
+ import { createMemoryRuntime } from '../core/runtime.js';
2
3
  import { normalizeScope } from '../contracts/identity.js';
4
+ import { isMemoryDomainError } from '../contracts/errors.js';
5
+ import { ASSOCIATION_TARGET_KINDS, ASSOCIATION_TYPES } from '../contracts/types.js';
6
+ import { ACTOR_KINDS, CONTEXT_VIEW_POLICIES, MEMORY_VISIBILITY_CLASSES } from '../contracts/coordination.js';
3
7
  import { createSQLiteAdapterWithEmbeddings } from '../adapters/sqlite/index.js';
4
8
  import { wrapSyncAdapter } from '../adapters/sync-to-async.js';
9
+ import { parseOptionalFiniteInteger, parseOptionalFiniteNumber, parseOptionalTemporalIdValue, } from './parsing.js';
5
10
  class McpValidationError extends Error {
6
11
  }
12
+ const MAX_LIST_LIMIT = 100;
13
+ const MANAGER_CACHE_LIMIT = 256;
14
+ const SESSION_MANAGER_CACHE_LIMIT = 256;
15
+ const RUNTIME_CACHE_LIMIT = 256;
16
+ const DEFAULT_DIFF_MAX_EVENTS = 5000;
17
+ const MAX_DIFF_MAX_EVENTS = 20000;
18
+ const TEMPORAL_ENTITY_KINDS = [
19
+ 'turn',
20
+ 'working_memory',
21
+ 'knowledge_memory',
22
+ 'work_item',
23
+ 'association',
24
+ 'playbook',
25
+ 'playbook_revision',
26
+ 'session_state',
27
+ 'work_claim',
28
+ 'handoff',
29
+ ];
30
+ function failMcpValidation(message) {
31
+ throw new McpValidationError(message);
32
+ }
33
+ function resolveDiffEventCaps(defaultMaxEvents, maxMaxEvents) {
34
+ const resolvedMax = maxMaxEvents ?? MAX_DIFF_MAX_EVENTS;
35
+ const resolvedDefault = defaultMaxEvents ?? DEFAULT_DIFF_MAX_EVENTS;
36
+ if (!Number.isInteger(resolvedMax) || resolvedMax < 1) {
37
+ throw new Error('memory-layer: maxDiffMaxEvents must be a positive integer');
38
+ }
39
+ if (!Number.isInteger(resolvedDefault) || resolvedDefault < 1) {
40
+ throw new Error('memory-layer: defaultDiffMaxEvents must be a positive integer');
41
+ }
42
+ if (resolvedDefault > resolvedMax) {
43
+ throw new Error('memory-layer: defaultDiffMaxEvents must not exceed maxDiffMaxEvents');
44
+ }
45
+ return {
46
+ defaultDiffMaxEvents: resolvedDefault,
47
+ maxDiffMaxEvents: resolvedMax,
48
+ };
49
+ }
7
50
  const TOOLS = [
8
51
  {
9
52
  name: 'memory_store_turn',
@@ -37,6 +80,103 @@ const TOOLS = [
37
80
  type: 'object',
38
81
  properties: {
39
82
  relevanceQuery: { type: 'string', description: 'Optional query to rank knowledge by relevance' },
83
+ view: {
84
+ type: 'string',
85
+ enum: ['local_only', 'local_plus_shared_collaboration', 'operator_supervisor', 'workspace_shared'],
86
+ description: 'Optional context visibility/view policy',
87
+ },
88
+ viewer: { type: 'object', description: 'Optional viewer actor for operator/supervisor coordination views' },
89
+ includeCoordinationState: { type: 'boolean', description: 'Include coordination state in the response' },
90
+ includeDebug: { type: 'boolean', description: 'Include selection reasons and debug trace' },
91
+ },
92
+ },
93
+ },
94
+ {
95
+ name: 'memory_get_state_at',
96
+ description: 'Get exact temporal state and assembled context at a specific unix timestamp.',
97
+ inputSchema: {
98
+ type: 'object',
99
+ properties: {
100
+ asOf: { type: 'number', description: 'Unix timestamp to replay at' },
101
+ relevanceQuery: { type: 'string', description: 'Optional query to rank knowledge' },
102
+ view: {
103
+ type: 'string',
104
+ enum: ['local_only', 'local_plus_shared_collaboration', 'operator_supervisor', 'workspace_shared'],
105
+ description: 'Optional context visibility/view policy',
106
+ },
107
+ viewer: { type: 'object', description: 'Optional viewer actor for operator/supervisor coordination views' },
108
+ includeCoordinationState: { type: 'boolean', description: 'Include coordination state in the response' },
109
+ includeDebug: { type: 'boolean', description: 'Include debug traces in the context payload' },
110
+ },
111
+ required: ['asOf'],
112
+ },
113
+ },
114
+ {
115
+ name: 'memory_get_timeline',
116
+ description: 'List memory events in chronological order.',
117
+ inputSchema: {
118
+ type: 'object',
119
+ properties: {
120
+ sessionId: { type: 'string', description: 'Optional session filter' },
121
+ entityKind: {
122
+ type: 'string',
123
+ enum: [...TEMPORAL_ENTITY_KINDS],
124
+ description: 'Optional entity kind filter',
125
+ },
126
+ entityId: { type: 'string', description: 'Optional entity id filter' },
127
+ startAt: { type: 'number', description: 'Optional start unix timestamp' },
128
+ endAt: { type: 'number', description: 'Optional end unix timestamp' },
129
+ limit: { type: 'number', description: 'Optional page size' },
130
+ cursor: {
131
+ anyOf: [{ type: 'number' }, { type: 'string' }],
132
+ description: 'Optional event-id cursor',
133
+ },
134
+ },
135
+ },
136
+ },
137
+ {
138
+ name: 'memory_diff_state',
139
+ description: 'Summarize memory events between two unix timestamps.',
140
+ inputSchema: {
141
+ type: 'object',
142
+ properties: {
143
+ from: { type: 'number', description: 'Start unix timestamp (exclusive)' },
144
+ to: { type: 'number', description: 'End unix timestamp (inclusive)' },
145
+ sessionId: { type: 'string', description: 'Optional session filter' },
146
+ entityKind: {
147
+ type: 'string',
148
+ enum: [...TEMPORAL_ENTITY_KINDS],
149
+ description: 'Optional entity kind filter',
150
+ },
151
+ entityId: { type: 'string', description: 'Optional entity id filter' },
152
+ maxEvents: {
153
+ type: 'number',
154
+ description: 'Optional maximum event count to accumulate. Defaults to the server-configured diff cap and must not exceed the configured maximum.',
155
+ },
156
+ },
157
+ required: ['from', 'to'],
158
+ },
159
+ },
160
+ {
161
+ name: 'memory_list_events',
162
+ description: 'List memory events in reverse chronological order for inspection.',
163
+ inputSchema: {
164
+ type: 'object',
165
+ properties: {
166
+ sessionId: { type: 'string', description: 'Optional session filter' },
167
+ entityKind: {
168
+ type: 'string',
169
+ enum: [...TEMPORAL_ENTITY_KINDS],
170
+ description: 'Optional entity kind filter',
171
+ },
172
+ entityId: { type: 'string', description: 'Optional entity id filter' },
173
+ startAt: { type: 'number', description: 'Optional start unix timestamp' },
174
+ endAt: { type: 'number', description: 'Optional end unix timestamp' },
175
+ limit: { type: 'number', description: 'Optional page size' },
176
+ cursor: {
177
+ anyOf: [{ type: 'number' }, { type: 'string' }],
178
+ description: 'Optional event-id cursor',
179
+ },
40
180
  },
41
181
  },
42
182
  },
@@ -104,6 +244,148 @@ const TOOLS = [
104
244
  required: ['title'],
105
245
  },
106
246
  },
247
+ {
248
+ name: 'memory_update_work_item',
249
+ description: 'Update an existing work item.',
250
+ inputSchema: {
251
+ type: 'object',
252
+ properties: {
253
+ id: { type: 'number' },
254
+ title: { type: 'string' },
255
+ detail: { type: 'string' },
256
+ status: { type: 'string', enum: ['open', 'in_progress', 'blocked', 'done'] },
257
+ visibility_class: { type: 'string', enum: ['private', 'shared_collaboration', 'workspace', 'tenant'] },
258
+ expectedVersion: { type: 'number' },
259
+ },
260
+ required: ['id'],
261
+ },
262
+ },
263
+ {
264
+ name: 'memory_claim_work_item',
265
+ description: 'Acquire or renew an exclusive claim on a work item.',
266
+ inputSchema: {
267
+ type: 'object',
268
+ properties: {
269
+ workItemId: { type: 'number' },
270
+ actor: { type: 'object' },
271
+ leaseSeconds: { type: 'number' },
272
+ },
273
+ required: ['workItemId', 'actor'],
274
+ },
275
+ },
276
+ {
277
+ name: 'memory_renew_work_claim',
278
+ description: 'Renew an exclusive claim on a work item.',
279
+ inputSchema: {
280
+ type: 'object',
281
+ properties: {
282
+ claimId: { type: 'number' },
283
+ actor: { type: 'object' },
284
+ leaseSeconds: { type: 'number' },
285
+ },
286
+ required: ['claimId', 'actor'],
287
+ },
288
+ },
289
+ {
290
+ name: 'memory_release_work_claim',
291
+ description: 'Release a claimed work item.',
292
+ inputSchema: {
293
+ type: 'object',
294
+ properties: {
295
+ claimId: { type: 'number' },
296
+ actor: { type: 'object' },
297
+ reason: { type: 'string' },
298
+ },
299
+ required: ['claimId', 'actor'],
300
+ },
301
+ },
302
+ {
303
+ name: 'memory_list_work_claims',
304
+ description: 'List current work claims.',
305
+ inputSchema: {
306
+ type: 'object',
307
+ properties: {},
308
+ },
309
+ },
310
+ {
311
+ name: 'memory_handoff_work_item',
312
+ description: 'Create a handoff for a work item.',
313
+ inputSchema: {
314
+ type: 'object',
315
+ properties: {
316
+ workItemId: { type: 'number' },
317
+ fromActor: { type: 'object' },
318
+ toActor: { type: 'object' },
319
+ summary: { type: 'string' },
320
+ contextBundleRef: { type: 'string' },
321
+ expiresAt: { type: 'number' },
322
+ },
323
+ required: ['workItemId', 'fromActor', 'toActor', 'summary'],
324
+ },
325
+ },
326
+ {
327
+ name: 'memory_accept_handoff',
328
+ description: 'Accept a handoff.',
329
+ inputSchema: {
330
+ type: 'object',
331
+ properties: {
332
+ handoffId: { type: 'number' },
333
+ actor: { type: 'object' },
334
+ reason: { type: 'string' },
335
+ },
336
+ required: ['handoffId', 'actor'],
337
+ },
338
+ },
339
+ {
340
+ name: 'memory_reject_handoff',
341
+ description: 'Reject a handoff.',
342
+ inputSchema: {
343
+ type: 'object',
344
+ properties: {
345
+ handoffId: { type: 'number' },
346
+ actor: { type: 'object' },
347
+ reason: { type: 'string' },
348
+ },
349
+ required: ['handoffId', 'actor'],
350
+ },
351
+ },
352
+ {
353
+ name: 'memory_cancel_handoff',
354
+ description: 'Cancel a handoff.',
355
+ inputSchema: {
356
+ type: 'object',
357
+ properties: {
358
+ handoffId: { type: 'number' },
359
+ actor: { type: 'object' },
360
+ reason: { type: 'string' },
361
+ },
362
+ required: ['handoffId', 'actor'],
363
+ },
364
+ },
365
+ {
366
+ name: 'memory_list_pending_handoffs',
367
+ description: 'List pending handoffs.',
368
+ inputSchema: {
369
+ type: 'object',
370
+ properties: {
371
+ direction: { type: 'string', enum: ['inbound', 'outbound', 'all'] },
372
+ },
373
+ },
374
+ },
375
+ {
376
+ name: 'memory_stream_changes',
377
+ description: 'List durable change events after an optional cursor.',
378
+ inputSchema: {
379
+ type: 'object',
380
+ properties: {
381
+ cursor: { anyOf: [{ type: 'number' }, { type: 'string' }] },
382
+ sessionId: { type: 'string' },
383
+ entityKind: { type: 'string', enum: [...TEMPORAL_ENTITY_KINDS] },
384
+ entityId: { type: 'string' },
385
+ limit: { type: 'number' },
386
+ },
387
+ },
388
+ },
107
389
  {
108
390
  name: 'memory_force_compact',
109
391
  description: 'Force compaction of conversation history into a summary.',
@@ -128,12 +410,403 @@ const TOOLS = [
128
410
  properties: {},
129
411
  },
130
412
  },
413
+ {
414
+ name: 'memory_search_episodes',
415
+ description: 'Search episodic memory for past sessions matching a query. Returns structured recaps with objectives, actions, outcomes, and unresolved items.',
416
+ inputSchema: {
417
+ type: 'object',
418
+ properties: {
419
+ query: { type: 'string', description: 'Search query' },
420
+ detailLevel: {
421
+ type: 'string',
422
+ enum: ['abstract', 'overview', 'full'],
423
+ description: 'Detail level for results (default: overview)',
424
+ },
425
+ limit: { type: 'number', description: 'Max results (default: 10)' },
426
+ timeRange: {
427
+ type: 'object',
428
+ properties: {
429
+ start_at: { type: 'number', description: 'Unix timestamp lower bound' },
430
+ end_at: { type: 'number', description: 'Unix timestamp upper bound' },
431
+ },
432
+ description: 'Optional time range filter',
433
+ },
434
+ },
435
+ required: ['query'],
436
+ },
437
+ },
438
+ {
439
+ name: 'memory_summarize_episode',
440
+ description: 'Summarize a specific session into a structured episodic recap.',
441
+ inputSchema: {
442
+ type: 'object',
443
+ properties: {
444
+ sessionId: { type: 'string', description: 'Session ID to summarize' },
445
+ detailLevel: {
446
+ type: 'string',
447
+ enum: ['abstract', 'overview', 'full'],
448
+ description: 'Detail level (default: overview)',
449
+ },
450
+ },
451
+ required: ['sessionId'],
452
+ },
453
+ },
454
+ {
455
+ name: 'memory_reflect',
456
+ description: 'Synthesize an answer from episodic and declarative memory. Returns a coherent synthesis with source attribution.',
457
+ inputSchema: {
458
+ type: 'object',
459
+ properties: {
460
+ query: { type: 'string', description: 'Reflection query' },
461
+ detailLevel: {
462
+ type: 'string',
463
+ enum: ['abstract', 'overview', 'full'],
464
+ description: 'Detail level (default: overview)',
465
+ },
466
+ includeEpisodic: { type: 'boolean', description: 'Include episodic memory (default: true)' },
467
+ includeDeclarative: { type: 'boolean', description: 'Include declarative memory (default: true)' },
468
+ limit: { type: 'number', description: 'Max sources (default: 10)' },
469
+ timeRange: {
470
+ type: 'object',
471
+ properties: {
472
+ start_at: { type: 'number', description: 'Unix timestamp lower bound' },
473
+ end_at: { type: 'number', description: 'Unix timestamp upper bound' },
474
+ },
475
+ description: 'Optional time range filter',
476
+ },
477
+ },
478
+ required: ['query'],
479
+ },
480
+ },
481
+ {
482
+ name: 'memory_search_cognitive',
483
+ description: 'Search memory using the cognitive taxonomy (episodic, semantic, procedural, working). Returns results grouped by cognitive type.',
484
+ inputSchema: {
485
+ type: 'object',
486
+ properties: {
487
+ query: { type: 'string', description: 'Search query' },
488
+ types: {
489
+ type: 'array',
490
+ items: { type: 'string', enum: ['episodic', 'semantic', 'procedural', 'working'] },
491
+ description: 'Cognitive memory types to search (default: all)',
492
+ },
493
+ limit: { type: 'number', description: 'Max results (default: 10)' },
494
+ minimumTrustScore: { type: 'number', description: 'Minimum trust score filter' },
495
+ activeOnly: { type: 'boolean', description: 'Only return active memories (default: true)' },
496
+ },
497
+ required: ['query'],
498
+ },
499
+ },
500
+ {
501
+ name: 'memory_get_profile',
502
+ description: 'Get a materialized profile view built from trusted knowledge. Returns identity, preferences, communication, constraints, and workflows.',
503
+ inputSchema: {
504
+ type: 'object',
505
+ properties: {
506
+ view: {
507
+ type: 'string',
508
+ enum: ['user', 'operator', 'workspace'],
509
+ description: 'Profile view type (default: user)',
510
+ },
511
+ sections: {
512
+ type: 'array',
513
+ items: { type: 'string', enum: ['identity', 'preferences', 'communication', 'constraints', 'workflows'] },
514
+ description: 'Specific sections to include (default: all)',
515
+ },
516
+ minimumTrustScore: { type: 'number', description: 'Minimum trust score filter' },
517
+ includeProvisional: { type: 'boolean', description: 'Include provisional knowledge entries (default: false — profiles are trusted-only)' },
518
+ includeDisputed: { type: 'boolean', description: 'Include disputed knowledge entries (default: false)' },
519
+ },
520
+ },
521
+ },
522
+ {
523
+ name: 'memory_create_playbook',
524
+ description: 'Create a reusable playbook from a title, description, and instructions.',
525
+ inputSchema: {
526
+ type: 'object',
527
+ properties: {
528
+ title: { type: 'string', description: 'Playbook title' },
529
+ description: { type: 'string', description: 'What this playbook is for' },
530
+ instructions: { type: 'string', description: 'Step-by-step instructions' },
531
+ references: { type: 'array', items: { type: 'string' }, description: 'Referenced files, URLs, or tools' },
532
+ templates: { type: 'array', items: { type: 'string' }, description: 'Reusable templates or patterns' },
533
+ scripts: { type: 'array', items: { type: 'string' }, description: 'Commands or scripts' },
534
+ assets: { type: 'array', items: { type: 'string' }, description: 'Associated assets' },
535
+ tags: {
536
+ type: 'array',
537
+ items: { type: 'string' },
538
+ description: 'Optional tags for categorization',
539
+ },
540
+ status: {
541
+ type: 'string',
542
+ enum: ['draft', 'active', 'deprecated', 'archived'],
543
+ description: 'Playbook status (default: draft)',
544
+ },
545
+ },
546
+ required: ['title', 'description', 'instructions'],
547
+ },
548
+ },
549
+ {
550
+ name: 'memory_search_playbooks',
551
+ description: 'Search for relevant playbooks by query. Returns ranked results.',
552
+ inputSchema: {
553
+ type: 'object',
554
+ properties: {
555
+ query: { type: 'string', description: 'Search query' },
556
+ limit: { type: 'number', description: 'Max results (default: 20)' },
557
+ },
558
+ required: ['query'],
559
+ },
560
+ },
561
+ {
562
+ name: 'memory_revise_playbook',
563
+ description: 'Revise a playbook by storing the current instructions as a revision and updating with new instructions.',
564
+ inputSchema: {
565
+ type: 'object',
566
+ properties: {
567
+ playbookId: { type: 'number', description: 'ID of the playbook to revise' },
568
+ newInstructions: { type: 'string', description: 'Updated instructions' },
569
+ revisionReason: { type: 'string', description: 'Why the playbook is being revised' },
570
+ sourceSessionId: { type: 'string', description: 'Optional session ID that triggered the revision' },
571
+ },
572
+ required: ['playbookId', 'newInstructions', 'revisionReason'],
573
+ },
574
+ },
575
+ {
576
+ name: 'memory_create_playbook_from_task',
577
+ description: 'Create a playbook by summarizing the turns of a completed task session into structured instructions.',
578
+ inputSchema: {
579
+ type: 'object',
580
+ properties: {
581
+ title: { type: 'string', description: 'Playbook title' },
582
+ description: { type: 'string', description: 'What this playbook is for' },
583
+ sessionId: { type: 'string', description: 'Session to derive the playbook from' },
584
+ tags: { type: 'array', items: { type: 'string' }, description: 'Optional tags' },
585
+ sourceWorkingMemoryId: {
586
+ type: 'number',
587
+ description: 'Optional working memory id that anchors the source task',
588
+ },
589
+ },
590
+ required: ['title', 'description', 'sessionId'],
591
+ },
592
+ },
593
+ {
594
+ name: 'memory_use_playbook',
595
+ description: 'Record that a playbook was used and return the full playbook record.',
596
+ inputSchema: {
597
+ type: 'object',
598
+ properties: {
599
+ playbookId: { type: 'number', description: 'ID of the playbook that was used' },
600
+ },
601
+ required: ['playbookId'],
602
+ },
603
+ },
604
+ {
605
+ name: 'memory_get_associations',
606
+ description: 'Get or traverse associations for a memory artifact. Use traverse mode for multi-hop graph expansion.',
607
+ inputSchema: {
608
+ type: 'object',
609
+ properties: {
610
+ kind: { type: 'string', enum: ['knowledge', 'playbook', 'working_memory', 'work_item'], description: 'Type of the source artifact' },
611
+ id: { type: 'number', description: 'ID of the source artifact' },
612
+ traverse: { type: 'boolean', description: 'If true, perform BFS traversal instead of direct lookup' },
613
+ maxDepth: { type: 'number', description: 'Max traversal depth (default 2, only used with traverse)' },
614
+ maxNodes: { type: 'number', description: 'Max nodes to return (default 20, only used with traverse)' },
615
+ },
616
+ required: ['kind', 'id'],
617
+ },
618
+ },
619
+ {
620
+ name: 'memory_add_association',
621
+ description: 'Create an association between two memory artifacts.',
622
+ inputSchema: {
623
+ type: 'object',
624
+ properties: {
625
+ source_kind: { type: 'string', enum: ['knowledge', 'playbook', 'working_memory', 'work_item'] },
626
+ source_id: { type: 'number' },
627
+ target_kind: { type: 'string', enum: ['knowledge', 'playbook', 'working_memory', 'work_item'] },
628
+ target_id: { type: 'number' },
629
+ association_type: { type: 'string', enum: ['related_to', 'supports', 'contradicts', 'supersedes', 'depends_on', 'solves', 'applies_to', 'derived_from'] },
630
+ confidence: { type: 'number', description: 'Confidence score 0-1 (default 0.5)' },
631
+ },
632
+ required: ['source_kind', 'source_id', 'target_kind', 'target_id', 'association_type'],
633
+ },
634
+ },
635
+ {
636
+ name: 'memory_remove_association',
637
+ description: 'Delete an association by ID.',
638
+ inputSchema: {
639
+ type: 'object',
640
+ properties: {
641
+ id: { type: 'number', description: 'Association ID to delete' },
642
+ },
643
+ required: ['id'],
644
+ },
645
+ },
646
+ {
647
+ name: 'memory_snapshot',
648
+ description: 'Manage session snapshots for cache-stable prompt injection. Actions: capture (freeze current state), refresh (recapture), get (return cached snapshot or null).',
649
+ inputSchema: {
650
+ type: 'object',
651
+ properties: {
652
+ action: {
653
+ type: 'string',
654
+ enum: ['capture', 'refresh', 'get'],
655
+ description: 'Snapshot action to perform',
656
+ },
657
+ sessionId: {
658
+ type: 'string',
659
+ description: 'Session ID the snapshot belongs to',
660
+ },
661
+ relevanceQuery: {
662
+ type: 'string',
663
+ description: 'Optional query to rank knowledge during capture/refresh',
664
+ },
665
+ },
666
+ required: ['action', 'sessionId'],
667
+ },
668
+ },
131
669
  ];
132
670
  function jsonResult(data) {
133
671
  return {
134
672
  content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
135
673
  };
136
674
  }
675
+ function serializeContextResponse(context, options = {}) {
676
+ return {
677
+ currentObjective: context.currentObjective,
678
+ sessionState: context.sessionState,
679
+ activeTurnCount: context.activeTurns.length,
680
+ workingMemory: context.workingMemory
681
+ ? {
682
+ summary: context.workingMemory.summary,
683
+ key_entities: context.workingMemory.key_entities,
684
+ topic_tags: context.workingMemory.topic_tags,
685
+ }
686
+ : null,
687
+ relevantKnowledge: context.relevantKnowledge.map((knowledge) => ({
688
+ id: knowledge.id,
689
+ fact: knowledge.fact,
690
+ fact_type: knowledge.fact_type,
691
+ confidence: knowledge.confidence,
692
+ })),
693
+ activeObjectives: context.activeObjectives.map((objective) => ({
694
+ id: objective.id,
695
+ title: objective.title,
696
+ status: objective.status,
697
+ visibility_class: objective.visibility_class,
698
+ })),
699
+ associatedKnowledge: context.associatedKnowledge.map((knowledge) => ({
700
+ id: knowledge.id,
701
+ fact: knowledge.fact,
702
+ fact_type: knowledge.fact_type,
703
+ knowledge_class: knowledge.knowledge_class,
704
+ trust_score: knowledge.trust_score,
705
+ })),
706
+ unresolvedWork: context.unresolvedWork,
707
+ coordinationState: context.coordinationState
708
+ ? {
709
+ ownedClaims: context.coordinationState.ownedClaims.map(serializeWorkClaim),
710
+ pendingInboundHandoffs: context.coordinationState.pendingInboundHandoffs.map(serializeHandoffRecord),
711
+ pendingOutboundHandoffs: context.coordinationState.pendingOutboundHandoffs.map(serializeHandoffRecord),
712
+ sharedWorkItems: context.coordinationState.sharedWorkItems.map((item) => ({
713
+ id: item.id,
714
+ title: item.title,
715
+ status: item.status,
716
+ visibility_class: item.visibility_class,
717
+ })),
718
+ }
719
+ : null,
720
+ tokenEstimate: context.tokenEstimate,
721
+ ...(options.includeDebug
722
+ ? {
723
+ debugTrace: context.debugTrace,
724
+ knowledgeSelectionReasons: context.knowledgeSelectionReasons,
725
+ }
726
+ : {}),
727
+ };
728
+ }
729
+ function serializeActorRef(actor) {
730
+ return {
731
+ actor_kind: actor.actor_kind,
732
+ actor_id: actor.actor_id,
733
+ system_id: actor.system_id,
734
+ display_name: actor.display_name,
735
+ metadata: actor.metadata,
736
+ };
737
+ }
738
+ function serializeWorkClaim(claim) {
739
+ return {
740
+ id: claim.id,
741
+ work_item_id: claim.work_item_id,
742
+ actor: serializeActorRef(claim.actor),
743
+ session_id: claim.session_id,
744
+ claim_token: claim.claim_token,
745
+ status: claim.status,
746
+ claimed_at: claim.claimed_at,
747
+ expires_at: claim.expires_at,
748
+ released_at: claim.released_at,
749
+ release_reason: claim.release_reason,
750
+ source_event_id: claim.source_event_id,
751
+ visibility_class: claim.visibility_class,
752
+ version: claim.version,
753
+ };
754
+ }
755
+ function serializeHandoffRecord(handoff) {
756
+ return {
757
+ id: handoff.id,
758
+ work_item_id: handoff.work_item_id,
759
+ from_actor: serializeActorRef(handoff.from_actor),
760
+ to_actor: serializeActorRef(handoff.to_actor),
761
+ session_id: handoff.session_id,
762
+ summary: handoff.summary,
763
+ context_bundle_ref: handoff.context_bundle_ref,
764
+ status: handoff.status,
765
+ created_at: handoff.created_at,
766
+ accepted_at: handoff.accepted_at,
767
+ rejected_at: handoff.rejected_at,
768
+ canceled_at: handoff.canceled_at,
769
+ expires_at: handoff.expires_at,
770
+ decision_reason: handoff.decision_reason,
771
+ source_event_id: handoff.source_event_id,
772
+ visibility_class: handoff.visibility_class,
773
+ version: handoff.version,
774
+ };
775
+ }
776
+ function serializeTimelineResult(result) {
777
+ return {
778
+ events: result.events,
779
+ nextCursor: result.nextCursor,
780
+ };
781
+ }
782
+ function serializeTemporalState(state, options = {}) {
783
+ return {
784
+ asOf: state.asOf,
785
+ exact: state.exact,
786
+ cutoverAt: state.cutoverAt,
787
+ watermarkEventId: state.watermarkEventId,
788
+ context: serializeContextResponse(state.context, {
789
+ includeDebug: options.includeDebug,
790
+ }),
791
+ sessionState: state.sessionState,
792
+ turns: state.turns,
793
+ workingMemory: state.workingMemory,
794
+ knowledge: state.knowledge,
795
+ workItems: state.workItems,
796
+ workClaims: state.workClaims.map(serializeWorkClaim),
797
+ handoffs: state.handoffs.map(serializeHandoffRecord),
798
+ coordinationState: state.coordinationState
799
+ ? {
800
+ ownedClaims: state.coordinationState.ownedClaims.map(serializeWorkClaim),
801
+ pendingInboundHandoffs: state.coordinationState.pendingInboundHandoffs.map(serializeHandoffRecord),
802
+ pendingOutboundHandoffs: state.coordinationState.pendingOutboundHandoffs.map(serializeHandoffRecord),
803
+ sharedWorkItems: state.coordinationState.sharedWorkItems,
804
+ }
805
+ : null,
806
+ associations: state.associations,
807
+ playbooks: state.playbooks,
808
+ };
809
+ }
137
810
  function errorResult(message) {
138
811
  return {
139
812
  content: [{ type: 'text', text: message }],
@@ -157,6 +830,25 @@ function optionalString(value, name) {
157
830
  }
158
831
  return value;
159
832
  }
833
+ function parseContextViewPolicy(value, name = 'view') {
834
+ if (value == null)
835
+ return undefined;
836
+ return requireEnum(value, CONTEXT_VIEW_POLICIES, name);
837
+ }
838
+ function parseActorRef(value, name = 'actor') {
839
+ if (value == null)
840
+ return undefined;
841
+ if (!isRecord(value)) {
842
+ throw new McpValidationError(`Invalid field: ${name}`);
843
+ }
844
+ return {
845
+ actor_kind: requireEnum(value.actor_kind, ACTOR_KINDS, `${name}.actor_kind`),
846
+ actor_id: requireString(value.actor_id, `${name}.actor_id`),
847
+ system_id: value.system_id == null ? null : requireString(value.system_id, `${name}.system_id`),
848
+ display_name: value.display_name == null ? null : requireString(value.display_name, `${name}.display_name`),
849
+ metadata: isRecord(value.metadata) ? value.metadata : null,
850
+ };
851
+ }
160
852
  function requireEnum(value, allowed, name) {
161
853
  if (typeof value !== 'string' || !allowed.includes(value)) {
162
854
  throw new McpValidationError(`Invalid field: ${name}`);
@@ -169,8 +861,29 @@ function parseLimit(value) {
169
861
  if (typeof value !== 'number' || !Number.isInteger(value)) {
170
862
  throw new McpValidationError('Invalid field: limit');
171
863
  }
864
+ if (value > MAX_LIST_LIMIT) {
865
+ throw new McpValidationError(`Invalid field: limit (maximum ${MAX_LIST_LIMIT})`);
866
+ }
172
867
  return value;
173
868
  }
869
+ function parseOptionalNonNegativeInteger(value, name) {
870
+ if (value === undefined || value === null)
871
+ return undefined;
872
+ if (typeof value !== 'number' || !Number.isInteger(value) || value < 0) {
873
+ throw new McpValidationError(`Invalid field: ${name} (must be a non-negative integer)`);
874
+ }
875
+ return value;
876
+ }
877
+ function parseRequiredFiniteInteger(value, name, options = {}) {
878
+ const parsed = parseOptionalFiniteInteger(value, { name, ...options }, failMcpValidation);
879
+ if (parsed == null) {
880
+ throw new McpValidationError(`Missing or invalid field: ${name}`);
881
+ }
882
+ return parsed;
883
+ }
884
+ function parseOptionalTemporalId(value, name) {
885
+ return parseOptionalTemporalIdValue(value, name, failMcpValidation);
886
+ }
174
887
  function resolveScopeInput(fallbackScope, args) {
175
888
  if (args.scope) {
176
889
  if (!isRecord(args.scope)) {
@@ -190,8 +903,24 @@ function resolveScopeInput(fallbackScope, args) {
190
903
  * For a ready-to-run stdio server, use `startMcpServer()`.
191
904
  */
192
905
  export function createMcpServerHandler(config = {}) {
906
+ const { defaultDiffMaxEvents, maxDiffMaxEvents } = resolveDiffEventCaps(config.defaultDiffMaxEvents, config.maxDiffMaxEvents);
193
907
  const managers = new Map();
908
+ const runtimes = new Map();
909
+ const sessionManagers = new Map();
910
+ const sessionRuntimes = new Map();
194
911
  let adapterPromise = null;
912
+ function touchCache(cache, key, value, limit, onEvict) {
913
+ cache.delete(key);
914
+ cache.set(key, value);
915
+ while (cache.size > limit) {
916
+ const oldestEntry = cache.entries().next().value;
917
+ if (!oldestEntry)
918
+ break;
919
+ const [oldestKey, oldestValue] = oldestEntry;
920
+ cache.delete(oldestKey);
921
+ onEvict?.(oldestKey, oldestValue);
922
+ }
923
+ }
195
924
  async function getAsyncAdapter() {
196
925
  if (!adapterPromise) {
197
926
  adapterPromise = (async () => {
@@ -200,6 +929,9 @@ export function createMcpServerHandler(config = {}) {
200
929
  return {
201
930
  asyncAdapter: wrapSyncAdapter(sqlite),
202
931
  embeddingAdapter: sqlite.embeddings,
932
+ close: async () => {
933
+ sqlite.close();
934
+ },
203
935
  };
204
936
  }
205
937
  const moduleName = 'pg';
@@ -211,10 +943,13 @@ export function createMcpServerHandler(config = {}) {
211
943
  const pool = new Pool({
212
944
  connectionString: config.databaseUrl ?? process.env.MEMORY_DATABASE_URL,
213
945
  });
214
- const asyncAdapter = createPostgresAdapter(pool);
946
+ const asyncAdapter = createPostgresAdapter(pool, { ownsPool: false });
215
947
  return {
216
948
  asyncAdapter,
217
949
  embeddingAdapter: createPostgresEmbeddingAdapter(pool),
950
+ close: async () => {
951
+ await pool.end();
952
+ },
218
953
  };
219
954
  })();
220
955
  }
@@ -225,11 +960,11 @@ export function createMcpServerHandler(config = {}) {
225
960
  ? `scope:${scopeInput}`
226
961
  : JSON.stringify(normalizeScope(scopeInput));
227
962
  const existing = managers.get(key);
228
- if (existing)
963
+ if (existing) {
964
+ touchCache(managers, key, existing, MANAGER_CACHE_LIMIT);
229
965
  return existing;
966
+ }
230
967
  const baseOptions = {
231
- adapter: 'sqlite',
232
- path: config.dbPath ?? ':memory:',
233
968
  scope: scopeInput,
234
969
  summarizer: config.summarizer ?? 'extractive',
235
970
  extractor: config.extractor ?? 'regex',
@@ -237,17 +972,80 @@ export function createMcpServerHandler(config = {}) {
237
972
  qualityMode: config.qualityMode,
238
973
  qualityTier: config.qualityTier,
239
974
  crossScopeLevel: config.crossScopeLevel,
975
+ autoDetectWorkspace: config.autoDetectWorkspace,
976
+ structuredClient: config.structuredClient,
240
977
  };
241
978
  const adapterContext = await getAsyncAdapter();
242
979
  const manager = createMemoryWithAsyncAdapter({
243
980
  ...baseOptions,
244
981
  asyncAdapter: adapterContext.asyncAdapter,
245
982
  embeddingAdapter: adapterContext.embeddingAdapter,
983
+ closeAdapter: false,
984
+ });
985
+ touchCache(managers, key, manager, MANAGER_CACHE_LIMIT, (evictedKey) => {
986
+ runtimes.delete(evictedKey);
246
987
  });
247
- managers.set(key, manager);
248
988
  return manager;
249
989
  }
250
- const managerPromise = getManager(config.scope ?? 'default');
990
+ async function getRuntime(scopeInput) {
991
+ const key = typeof scopeInput === 'string'
992
+ ? `scope:${scopeInput}`
993
+ : JSON.stringify(normalizeScope(scopeInput));
994
+ const existing = runtimes.get(key);
995
+ if (existing) {
996
+ runtimes.delete(key);
997
+ runtimes.set(key, existing);
998
+ return existing;
999
+ }
1000
+ const manager = await getManager(scopeInput);
1001
+ const runtime = createMemoryRuntime(manager, { snapshotMode: true });
1002
+ touchCache(runtimes, key, runtime, RUNTIME_CACHE_LIMIT);
1003
+ return runtime;
1004
+ }
1005
+ /**
1006
+ * Snapshot-specific runtime keyed by (scope, sessionId). The plain
1007
+ * getRuntime/getManager pair binds each scope to a single session, which
1008
+ * collapses snapshots across multiple URL-named sessions. Snapshot actions
1009
+ * must route to a manager whose bound sessionId matches the caller.
1010
+ */
1011
+ async function getSessionRuntime(scopeInput, sessionId) {
1012
+ const scopeKey = typeof scopeInput === 'string'
1013
+ ? `scope:${scopeInput}`
1014
+ : JSON.stringify(normalizeScope(scopeInput));
1015
+ const key = `${scopeKey}|session:${sessionId}`;
1016
+ const existing = sessionRuntimes.get(key);
1017
+ if (existing) {
1018
+ sessionRuntimes.delete(key);
1019
+ sessionRuntimes.set(key, existing);
1020
+ return existing;
1021
+ }
1022
+ const baseOptions = {
1023
+ scope: scopeInput,
1024
+ sessionId,
1025
+ summarizer: config.summarizer ?? 'extractive',
1026
+ extractor: config.extractor ?? 'regex',
1027
+ preset: config.preset,
1028
+ qualityMode: config.qualityMode,
1029
+ qualityTier: config.qualityTier,
1030
+ crossScopeLevel: config.crossScopeLevel,
1031
+ autoDetectWorkspace: config.autoDetectWorkspace,
1032
+ structuredClient: config.structuredClient,
1033
+ };
1034
+ const adapterContext = await getAsyncAdapter();
1035
+ const manager = createMemoryWithAsyncAdapter({
1036
+ ...baseOptions,
1037
+ asyncAdapter: adapterContext.asyncAdapter,
1038
+ embeddingAdapter: adapterContext.embeddingAdapter,
1039
+ closeAdapter: false,
1040
+ });
1041
+ touchCache(sessionManagers, key, manager, SESSION_MANAGER_CACHE_LIMIT, (evictedKey, evictedManager) => {
1042
+ sessionRuntimes.delete(evictedKey);
1043
+ void evictedManager.close().catch(() => undefined);
1044
+ });
1045
+ const runtime = createMemoryRuntime(manager, { snapshotMode: true });
1046
+ touchCache(sessionRuntimes, key, runtime, RUNTIME_CACHE_LIMIT);
1047
+ return runtime;
1048
+ }
251
1049
  async function callTool(name, args) {
252
1050
  try {
253
1051
  const requestManager = await getManager(resolveScopeInput(config.scope, args));
@@ -266,30 +1064,68 @@ export function createMcpServerHandler(config = {}) {
266
1064
  });
267
1065
  }
268
1066
  case 'memory_get_context': {
269
- const context = await requestManager.getContext(args.relevanceQuery ? String(args.relevanceQuery) : undefined);
270
- return jsonResult({
271
- currentObjective: context.currentObjective,
272
- activeTurnCount: context.activeTurns.length,
273
- workingMemory: context.workingMemory
274
- ? {
275
- summary: context.workingMemory.summary,
276
- key_entities: context.workingMemory.key_entities,
277
- topic_tags: context.workingMemory.topic_tags,
278
- }
279
- : null,
280
- relevantKnowledge: context.relevantKnowledge.map((k) => ({
281
- id: k.id,
282
- fact: k.fact,
283
- fact_type: k.fact_type,
284
- confidence: k.confidence,
285
- })),
286
- activeObjectives: context.activeObjectives.map((o) => ({
287
- title: o.title,
288
- status: o.status,
289
- })),
290
- unresolvedWork: context.unresolvedWork,
291
- tokenEstimate: context.tokenEstimate,
1067
+ const context = await requestManager.getContext(args.relevanceQuery ? String(args.relevanceQuery) : undefined, {
1068
+ view: parseContextViewPolicy(args.view),
1069
+ viewer: parseActorRef(args.viewer, 'viewer'),
1070
+ includeCoordinationState: args.includeCoordinationState === true,
1071
+ });
1072
+ return jsonResult(serializeContextResponse(context, {
1073
+ includeDebug: args.includeDebug === true,
1074
+ }));
1075
+ }
1076
+ case 'memory_get_state_at': {
1077
+ const asOf = parseOptionalFiniteNumber(args.asOf, { name: 'asOf' }, failMcpValidation);
1078
+ if (asOf == null) {
1079
+ throw new McpValidationError('Missing or invalid field: asOf');
1080
+ }
1081
+ const state = await requestManager.getStateAt(asOf, {
1082
+ relevanceQuery: optionalString(args.relevanceQuery, 'relevanceQuery'),
1083
+ view: parseContextViewPolicy(args.view),
1084
+ viewer: parseActorRef(args.viewer, 'viewer'),
1085
+ includeCoordinationState: args.includeCoordinationState === true,
1086
+ });
1087
+ return jsonResult(serializeTemporalState(state, {
1088
+ includeDebug: args.includeDebug === true,
1089
+ }));
1090
+ }
1091
+ case 'memory_get_timeline': {
1092
+ const timeline = await requestManager.getTimeline({
1093
+ sessionId: optionalString(args.sessionId, 'sessionId'),
1094
+ entityKind: args.entityKind,
1095
+ entityId: optionalString(args.entityId, 'entityId'),
1096
+ startAt: parseOptionalFiniteNumber(args.startAt, { name: 'startAt' }, failMcpValidation),
1097
+ endAt: parseOptionalFiniteNumber(args.endAt, { name: 'endAt' }, failMcpValidation),
1098
+ limit: parseLimit(args.limit),
1099
+ cursor: parseOptionalTemporalId(args.cursor, 'cursor'),
1100
+ });
1101
+ return jsonResult(serializeTimelineResult(timeline));
1102
+ }
1103
+ case 'memory_diff_state': {
1104
+ const from = parseOptionalFiniteNumber(args.from, { name: 'from' }, failMcpValidation);
1105
+ const to = parseOptionalFiniteNumber(args.to, { name: 'to' }, failMcpValidation);
1106
+ const maxEvents = parseOptionalFiniteInteger(args.maxEvents, { name: 'maxEvents', min: 1, max: maxDiffMaxEvents }, failMcpValidation);
1107
+ if (from == null || to == null) {
1108
+ throw new McpValidationError('Missing or invalid fields: from/to');
1109
+ }
1110
+ const diff = await requestManager.diffState(from, to, {
1111
+ sessionId: optionalString(args.sessionId, 'sessionId'),
1112
+ entityKind: args.entityKind,
1113
+ entityId: optionalString(args.entityId, 'entityId'),
1114
+ maxEvents: maxEvents ?? defaultDiffMaxEvents,
292
1115
  });
1116
+ return jsonResult(diff);
1117
+ }
1118
+ case 'memory_list_events': {
1119
+ const events = await requestManager.listMemoryEvents({
1120
+ sessionId: optionalString(args.sessionId, 'sessionId'),
1121
+ entityKind: args.entityKind,
1122
+ entityId: optionalString(args.entityId, 'entityId'),
1123
+ startAt: parseOptionalFiniteNumber(args.startAt, { name: 'startAt' }, failMcpValidation),
1124
+ endAt: parseOptionalFiniteNumber(args.endAt, { name: 'endAt' }, failMcpValidation),
1125
+ limit: parseLimit(args.limit),
1126
+ cursor: parseOptionalTemporalId(args.cursor, 'cursor'),
1127
+ });
1128
+ return jsonResult(serializeTimelineResult(events));
293
1129
  }
294
1130
  case 'memory_search': {
295
1131
  const results = await requestManager.search(requireString(args.query, 'query'), args.limit != null ? { limit: parseLimit(args.limit) } : undefined);
@@ -330,9 +1166,112 @@ export function createMcpServerHandler(config = {}) {
330
1166
  return jsonResult({ stored: true, knowledgeId: fact.id });
331
1167
  }
332
1168
  case 'memory_track_work': {
333
- const item = await requestManager.trackWorkItem(requireString(args.title, 'title'), requireEnum(args.kind ?? 'objective', ['objective', 'unresolved_work', 'constraint'], 'kind'), requireEnum(args.status ?? 'open', ['open', 'in_progress', 'blocked', 'done'], 'status'), optionalString(args.detail, 'detail'));
1169
+ const item = await requestManager.trackWorkItem(requireString(args.title, 'title'), requireEnum(args.kind ?? 'objective', ['objective', 'unresolved_work', 'constraint'], 'kind'), requireEnum(args.status ?? 'open', ['open', 'in_progress', 'blocked', 'done'], 'status'), optionalString(args.detail, 'detail'), {
1170
+ visibilityClass: args.visibility_class == null
1171
+ ? undefined
1172
+ : requireEnum(args.visibility_class, MEMORY_VISIBILITY_CLASSES, 'visibility_class'),
1173
+ });
334
1174
  return jsonResult({ tracked: true, workItemId: item.id });
335
1175
  }
1176
+ case 'memory_update_work_item': {
1177
+ const item = await requestManager.updateWorkItem(parseRequiredFiniteInteger(args.id, 'id', { min: 1 }), {
1178
+ title: args.title != null ? requireString(args.title, 'title') : undefined,
1179
+ detail: args.detail != null ? optionalString(args.detail, 'detail') ?? null : undefined,
1180
+ status: args.status != null
1181
+ ? requireEnum(args.status, ['open', 'in_progress', 'blocked', 'done'], 'status')
1182
+ : undefined,
1183
+ visibility_class: args.visibility_class != null
1184
+ ? requireEnum(args.visibility_class, MEMORY_VISIBILITY_CLASSES, 'visibility_class')
1185
+ : undefined,
1186
+ }, {
1187
+ expectedVersion: parseOptionalFiniteInteger(args.expectedVersion, { name: 'expectedVersion', min: 0 }, failMcpValidation),
1188
+ });
1189
+ return jsonResult({ workItem: item });
1190
+ }
1191
+ case 'memory_claim_work_item': {
1192
+ const actor = parseActorRef(args.actor, 'actor');
1193
+ if (!actor)
1194
+ throw new McpValidationError('Missing or invalid field: actor');
1195
+ const claim = await requestManager.claimWorkItem({
1196
+ workItemId: parseRequiredFiniteInteger(args.workItemId, 'workItemId', { min: 1 }),
1197
+ actor,
1198
+ leaseSeconds: parseOptionalFiniteInteger(args.leaseSeconds, { name: 'leaseSeconds', min: 1 }, failMcpValidation),
1199
+ });
1200
+ return jsonResult({ claim: serializeWorkClaim(claim) });
1201
+ }
1202
+ case 'memory_renew_work_claim': {
1203
+ const actor = parseActorRef(args.actor, 'actor');
1204
+ if (!actor)
1205
+ throw new McpValidationError('Missing or invalid field: actor');
1206
+ const claim = await requestManager.renewWorkClaim(parseRequiredFiniteInteger(args.claimId, 'claimId', { min: 1 }), actor, parseOptionalFiniteInteger(args.leaseSeconds, { name: 'leaseSeconds', min: 1 }, failMcpValidation));
1207
+ return jsonResult({ claim: claim ? serializeWorkClaim(claim) : null });
1208
+ }
1209
+ case 'memory_release_work_claim': {
1210
+ const actor = parseActorRef(args.actor, 'actor');
1211
+ if (!actor)
1212
+ throw new McpValidationError('Missing or invalid field: actor');
1213
+ const claim = await requestManager.releaseWorkClaim(parseRequiredFiniteInteger(args.claimId, 'claimId', { min: 1 }), actor, optionalString(args.reason, 'reason'));
1214
+ return jsonResult({ claim: claim ? serializeWorkClaim(claim) : null });
1215
+ }
1216
+ case 'memory_list_work_claims': {
1217
+ const claims = await requestManager.listWorkClaims();
1218
+ return jsonResult({ claims: claims.map(serializeWorkClaim) });
1219
+ }
1220
+ case 'memory_handoff_work_item': {
1221
+ const fromActor = parseActorRef(args.fromActor, 'fromActor');
1222
+ const toActor = parseActorRef(args.toActor, 'toActor');
1223
+ if (!fromActor || !toActor) {
1224
+ throw new McpValidationError('Missing or invalid field: fromActor/toActor');
1225
+ }
1226
+ const handoff = await requestManager.handoffWorkItem({
1227
+ workItemId: parseRequiredFiniteInteger(args.workItemId, 'workItemId', { min: 1 }),
1228
+ fromActor,
1229
+ toActor,
1230
+ summary: requireString(args.summary, 'summary'),
1231
+ contextBundleRef: optionalString(args.contextBundleRef, 'contextBundleRef') ?? null,
1232
+ expiresAt: parseOptionalFiniteInteger(args.expiresAt, { name: 'expiresAt', min: 0 }, failMcpValidation) ?? null,
1233
+ });
1234
+ return jsonResult({ handoff: serializeHandoffRecord(handoff) });
1235
+ }
1236
+ case 'memory_accept_handoff': {
1237
+ const actor = parseActorRef(args.actor, 'actor');
1238
+ if (!actor)
1239
+ throw new McpValidationError('Missing or invalid field: actor');
1240
+ const handoff = await requestManager.acceptHandoff(parseRequiredFiniteInteger(args.handoffId, 'handoffId', { min: 1 }), actor, optionalString(args.reason, 'reason'));
1241
+ return jsonResult({ handoff: handoff ? serializeHandoffRecord(handoff) : null });
1242
+ }
1243
+ case 'memory_reject_handoff': {
1244
+ const actor = parseActorRef(args.actor, 'actor');
1245
+ if (!actor)
1246
+ throw new McpValidationError('Missing or invalid field: actor');
1247
+ const handoff = await requestManager.rejectHandoff(parseRequiredFiniteInteger(args.handoffId, 'handoffId', { min: 1 }), actor, optionalString(args.reason, 'reason'));
1248
+ return jsonResult({ handoff: handoff ? serializeHandoffRecord(handoff) : null });
1249
+ }
1250
+ case 'memory_cancel_handoff': {
1251
+ const actor = parseActorRef(args.actor, 'actor');
1252
+ if (!actor)
1253
+ throw new McpValidationError('Missing or invalid field: actor');
1254
+ const handoff = await requestManager.cancelHandoff(parseRequiredFiniteInteger(args.handoffId, 'handoffId', { min: 1 }), actor, optionalString(args.reason, 'reason'));
1255
+ return jsonResult({ handoff: handoff ? serializeHandoffRecord(handoff) : null });
1256
+ }
1257
+ case 'memory_list_pending_handoffs': {
1258
+ const handoffs = await requestManager.listPendingHandoffs({
1259
+ direction: args.direction == null
1260
+ ? 'all'
1261
+ : requireEnum(args.direction, ['inbound', 'outbound', 'all'], 'direction'),
1262
+ });
1263
+ return jsonResult({ handoffs: handoffs.map(serializeHandoffRecord) });
1264
+ }
1265
+ case 'memory_stream_changes': {
1266
+ const events = await requestManager.listMemoryEvents({
1267
+ cursor: parseOptionalTemporalId(args.cursor, 'cursor'),
1268
+ sessionId: optionalString(args.sessionId, 'sessionId'),
1269
+ entityKind: optionalString(args.entityKind, 'entityKind'),
1270
+ entityId: optionalString(args.entityId, 'entityId'),
1271
+ limit: parseLimit(args.limit),
1272
+ });
1273
+ return jsonResult(serializeTimelineResult(events));
1274
+ }
336
1275
  case 'memory_force_compact': {
337
1276
  const result = await requestManager.forceCompact();
338
1277
  return jsonResult({
@@ -341,13 +1280,18 @@ export function createMcpServerHandler(config = {}) {
341
1280
  });
342
1281
  }
343
1282
  case 'memory_get_health': {
344
- const context = await requestManager.getContext();
1283
+ const [context, diagnostics] = await Promise.all([
1284
+ requestManager.getContext(),
1285
+ requestManager.getRuntimeDiagnostics(),
1286
+ ]);
345
1287
  return jsonResult({
346
1288
  activeTurnCount: context.activeTurns.length,
347
1289
  tokenEstimate: context.tokenEstimate,
348
1290
  knowledgeCount: context.relevantKnowledge.length,
349
1291
  objectiveCount: context.activeObjectives.length,
350
1292
  unresolvedWorkCount: context.unresolvedWork.length,
1293
+ sessionStateUpdatedAt: context.sessionState.updatedAt,
1294
+ circuitBreakers: diagnostics.circuitBreakers,
351
1295
  });
352
1296
  }
353
1297
  case 'memory_run_maintenance': {
@@ -358,11 +1302,185 @@ export function createMcpServerHandler(config = {}) {
358
1302
  deletedWorkItems: report.deletedWorkItemIds.length,
359
1303
  });
360
1304
  }
1305
+ case 'memory_search_episodes': {
1306
+ const episodeTimeRange = isRecord(args.timeRange)
1307
+ ? {
1308
+ start_at: parseOptionalFiniteNumber(args.timeRange.start_at, { name: 'timeRange.start_at' }, failMcpValidation),
1309
+ end_at: parseOptionalFiniteNumber(args.timeRange.end_at, { name: 'timeRange.end_at' }, failMcpValidation),
1310
+ }
1311
+ : undefined;
1312
+ const episodes = await requestManager.searchEpisodes({
1313
+ query: requireString(args.query, 'query'),
1314
+ detailLevel: args.detailLevel != null
1315
+ ? requireEnum(args.detailLevel, ['abstract', 'overview', 'full'], 'detailLevel')
1316
+ : undefined,
1317
+ limit: parseLimit(args.limit),
1318
+ timeRange: episodeTimeRange,
1319
+ });
1320
+ return jsonResult({ episodes });
1321
+ }
1322
+ case 'memory_summarize_episode': {
1323
+ const summary = await requestManager.summarizeEpisode(requireString(args.sessionId, 'sessionId'), args.detailLevel != null
1324
+ ? { detailLevel: requireEnum(args.detailLevel, ['abstract', 'overview', 'full'], 'detailLevel') }
1325
+ : undefined);
1326
+ return jsonResult(summary);
1327
+ }
1328
+ case 'memory_reflect': {
1329
+ const reflectTimeRange = isRecord(args.timeRange)
1330
+ ? {
1331
+ start_at: parseOptionalFiniteNumber(args.timeRange.start_at, { name: 'timeRange.start_at' }, failMcpValidation),
1332
+ end_at: parseOptionalFiniteNumber(args.timeRange.end_at, { name: 'timeRange.end_at' }, failMcpValidation),
1333
+ }
1334
+ : undefined;
1335
+ const result = await requestManager.reflect({
1336
+ query: requireString(args.query, 'query'),
1337
+ detailLevel: args.detailLevel != null
1338
+ ? requireEnum(args.detailLevel, ['abstract', 'overview', 'full'], 'detailLevel')
1339
+ : undefined,
1340
+ includeEpisodic: args.includeEpisodic != null ? Boolean(args.includeEpisodic) : undefined,
1341
+ includeDeclarative: args.includeDeclarative != null ? Boolean(args.includeDeclarative) : undefined,
1342
+ limit: parseLimit(args.limit),
1343
+ timeRange: reflectTimeRange,
1344
+ });
1345
+ return jsonResult(result);
1346
+ }
1347
+ case 'memory_search_cognitive': {
1348
+ const cognitiveResult = await requestManager.searchCognitive({
1349
+ query: requireString(args.query, 'query'),
1350
+ types: Array.isArray(args.types) ? args.types : undefined,
1351
+ limit: parseLimit(args.limit),
1352
+ minimumTrustScore: parseOptionalFiniteNumber(args.minimumTrustScore, { name: 'minimumTrustScore', min: 0, max: 1 }, failMcpValidation),
1353
+ activeOnly: args.activeOnly != null ? Boolean(args.activeOnly) : undefined,
1354
+ });
1355
+ return jsonResult(cognitiveResult);
1356
+ }
1357
+ case 'memory_get_profile': {
1358
+ const profile = await requestManager.getProfile({
1359
+ view: args.view != null
1360
+ ? requireEnum(args.view, ['user', 'operator', 'workspace'], 'view')
1361
+ : undefined,
1362
+ sections: Array.isArray(args.sections)
1363
+ ? args.sections.map((s) => requireEnum(s, ['identity', 'preferences', 'communication', 'constraints', 'workflows'], 'sections'))
1364
+ : undefined,
1365
+ minimumTrustScore: parseOptionalFiniteNumber(args.minimumTrustScore, { name: 'minimumTrustScore', min: 0, max: 1 }, failMcpValidation),
1366
+ includeProvisional: args.includeProvisional != null ? Boolean(args.includeProvisional) : undefined,
1367
+ includeDisputed: args.includeDisputed != null ? Boolean(args.includeDisputed) : undefined,
1368
+ });
1369
+ return jsonResult(profile);
1370
+ }
1371
+ case 'memory_create_playbook': {
1372
+ const playbook = await requestManager.createPlaybook({
1373
+ title: requireString(args.title, 'title'),
1374
+ description: requireString(args.description, 'description'),
1375
+ instructions: requireString(args.instructions, 'instructions'),
1376
+ references: Array.isArray(args.references) ? args.references.map(String) : undefined,
1377
+ templates: Array.isArray(args.templates) ? args.templates.map(String) : undefined,
1378
+ scripts: Array.isArray(args.scripts) ? args.scripts.map(String) : undefined,
1379
+ assets: Array.isArray(args.assets) ? args.assets.map(String) : undefined,
1380
+ tags: Array.isArray(args.tags) ? args.tags.map(String) : undefined,
1381
+ status: args.status != null
1382
+ ? requireEnum(args.status, ['draft', 'active', 'deprecated', 'archived'], 'status')
1383
+ : undefined,
1384
+ });
1385
+ return jsonResult({ playbook });
1386
+ }
1387
+ case 'memory_create_playbook_from_task': {
1388
+ const playbook = await requestManager.createPlaybookFromTask({
1389
+ title: requireString(args.title, 'title'),
1390
+ description: requireString(args.description, 'description'),
1391
+ sessionId: requireString(args.sessionId, 'sessionId'),
1392
+ tags: Array.isArray(args.tags) ? args.tags.map(String) : undefined,
1393
+ sourceWorkingMemoryId: parseOptionalFiniteInteger(args.sourceWorkingMemoryId, { name: 'sourceWorkingMemoryId', min: 1 }, failMcpValidation),
1394
+ });
1395
+ return jsonResult({ playbook });
1396
+ }
1397
+ case 'memory_search_playbooks': {
1398
+ const results = await requestManager.searchPlaybooks(requireString(args.query, 'query'), { limit: parseLimit(args.limit) });
1399
+ // Return full playbook records with rank so consumers get the
1400
+ // same shape as the HTTP /v1/playbooks search response.
1401
+ return jsonResult({
1402
+ playbooks: results.map((r) => ({ ...r.item, rank: r.rank })),
1403
+ });
1404
+ }
1405
+ case 'memory_revise_playbook': {
1406
+ const playbookId = parseRequiredFiniteInteger(args.playbookId, 'playbookId', { min: 1 });
1407
+ const result = await requestManager.revisePlaybook(playbookId, requireString(args.newInstructions, 'newInstructions'), requireString(args.revisionReason, 'revisionReason'), optionalString(args.sourceSessionId, 'sourceSessionId'));
1408
+ return jsonResult({ playbook: result.playbook, revision: result.revision });
1409
+ }
1410
+ case 'memory_use_playbook': {
1411
+ const playbookId = parseRequiredFiniteInteger(args.playbookId, 'playbookId', { min: 1 });
1412
+ await requestManager.recordPlaybookUse(playbookId);
1413
+ const playbook = await requestManager.getPlaybook(playbookId);
1414
+ return jsonResult({ playbook });
1415
+ }
1416
+ case 'memory_get_associations': {
1417
+ const kind = requireEnum(args.kind, ASSOCIATION_TARGET_KINDS, 'kind');
1418
+ const id = parseRequiredFiniteInteger(args.id, 'id', { min: 1 });
1419
+ if (args.traverse) {
1420
+ const graph = await requestManager.traverseAssociations(kind, id, {
1421
+ maxDepth: parseOptionalNonNegativeInteger(args.maxDepth, 'maxDepth'),
1422
+ maxNodes: parseOptionalNonNegativeInteger(args.maxNodes, 'maxNodes'),
1423
+ });
1424
+ return jsonResult(graph);
1425
+ }
1426
+ const assocs = await requestManager.getAssociations(kind, id);
1427
+ return jsonResult(assocs);
1428
+ }
1429
+ case 'memory_add_association': {
1430
+ const sourceId = typeof args.source_id === 'number' && Number.isInteger(args.source_id) && args.source_id > 0
1431
+ ? args.source_id
1432
+ : (() => { throw new McpValidationError('Missing or invalid field: source_id (must be positive integer)'); })();
1433
+ const targetId = typeof args.target_id === 'number' && Number.isInteger(args.target_id) && args.target_id > 0
1434
+ ? args.target_id
1435
+ : (() => { throw new McpValidationError('Missing or invalid field: target_id (must be positive integer)'); })();
1436
+ let confidence;
1437
+ if (args.confidence !== undefined && args.confidence !== null) {
1438
+ if (typeof args.confidence !== 'number' || Number.isNaN(args.confidence) || args.confidence < 0 || args.confidence > 1) {
1439
+ throw new McpValidationError('Invalid field: confidence (must be a number in [0, 1])');
1440
+ }
1441
+ confidence = args.confidence;
1442
+ }
1443
+ const association = await requestManager.addAssociation({
1444
+ source_kind: requireEnum(args.source_kind, ASSOCIATION_TARGET_KINDS, 'source_kind'),
1445
+ source_id: sourceId,
1446
+ target_kind: requireEnum(args.target_kind, ASSOCIATION_TARGET_KINDS, 'target_kind'),
1447
+ target_id: targetId,
1448
+ association_type: requireEnum(args.association_type, ASSOCIATION_TYPES, 'association_type'),
1449
+ confidence,
1450
+ });
1451
+ return jsonResult({ created: true, associationId: association.id });
1452
+ }
1453
+ case 'memory_remove_association': {
1454
+ const id = parseRequiredFiniteInteger(args.id, 'id', { min: 1 });
1455
+ await requestManager.removeAssociation(id);
1456
+ return jsonResult({ deleted: true });
1457
+ }
1458
+ case 'memory_snapshot': {
1459
+ const action = requireEnum(args.action, ['capture', 'refresh', 'get'], 'action');
1460
+ const sessionId = requireString(args.sessionId, 'sessionId');
1461
+ const runtime = await getSessionRuntime(resolveScopeInput(config.scope, args), sessionId);
1462
+ const relevanceQuery = optionalString(args.relevanceQuery, 'relevanceQuery');
1463
+ if (action === 'capture') {
1464
+ await runtime.startSession(relevanceQuery);
1465
+ const snapshot = runtime.getSnapshot();
1466
+ return jsonResult({ snapshot: snapshot ? { ...snapshot, sessionId } : null });
1467
+ }
1468
+ if (action === 'refresh') {
1469
+ const snapshot = await runtime.refreshSnapshot(relevanceQuery);
1470
+ return jsonResult({ snapshot: snapshot ? { ...snapshot, sessionId } : null });
1471
+ }
1472
+ // get
1473
+ const snapshot = runtime.getSnapshot();
1474
+ return jsonResult({ snapshot: snapshot ? { ...snapshot, sessionId } : null });
1475
+ }
361
1476
  default:
362
1477
  return errorResult(`Unknown tool: ${name}`);
363
1478
  }
364
1479
  }
365
1480
  catch (error) {
1481
+ if (isMemoryDomainError(error)) {
1482
+ return errorResult(`Error in ${name}: ${error.message}`);
1483
+ }
366
1484
  return errorResult(`Error in ${name}: ${error instanceof Error ? error.message : String(error)}`);
367
1485
  }
368
1486
  }
@@ -371,12 +1489,14 @@ export function createMcpServerHandler(config = {}) {
371
1489
  callTool,
372
1490
  manager: undefined,
373
1491
  async close() {
374
- const manager = await managerPromise;
375
- for (const cachedManager of managers.values()) {
376
- await cachedManager.close();
377
- }
378
1492
  managers.clear();
379
- await manager.close();
1493
+ sessionManagers.clear();
1494
+ sessionRuntimes.clear();
1495
+ runtimes.clear();
1496
+ if (adapterPromise) {
1497
+ const adapterContext = await adapterPromise;
1498
+ await adapterContext.close();
1499
+ }
380
1500
  },
381
1501
  };
382
1502
  }
@@ -405,7 +1525,7 @@ export async function startMcpServer(config = {}) {
405
1525
  capabilities: { tools: {} },
406
1526
  serverInfo: {
407
1527
  name: 'memory-layer',
408
- version: '2.0.0',
1528
+ version: '3.0.0',
409
1529
  },
410
1530
  },
411
1531
  }) + '\n');