server-memory-enhanced 2.3.2 → 3.1.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.
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { promises as fs } from 'fs';
6
6
  import path from 'path';
7
7
  import { fileURLToPath } from 'url';
8
8
  import { KnowledgeGraphManager } from './lib/knowledge-graph-manager.js';
9
- import { EntitySchema, RelationSchema, SaveMemoryInputSchema, SaveMemoryOutputSchema, GetAnalyticsInputSchema, GetAnalyticsOutputSchema, GetObservationHistoryInputSchema, GetObservationHistoryOutputSchema, ListEntitiesInputSchema, ListEntitiesOutputSchema, ValidateMemoryInputSchema, ValidateMemoryOutputSchema, UpdateObservationInputSchema, UpdateObservationOutputSchema } from './lib/schemas.js';
9
+ import { EntitySchema, RelationSchema, SaveMemoryInputSchema, SaveMemoryOutputSchema, GetAnalyticsInputSchema, GetAnalyticsOutputSchema, GetObservationHistoryInputSchema, GetObservationHistoryOutputSchema, ListEntitiesInputSchema, ListEntitiesOutputSchema, ValidateMemoryInputSchema, ValidateMemoryOutputSchema, UpdateObservationInputSchema, UpdateObservationOutputSchema, ReadGraphInputSchema, SearchNodesInputSchema, OpenNodesInputSchema, QueryNodesInputSchema, GetMemoryStatsInputSchema, GetRecentChangesInputSchema, FindRelationPathInputSchema, DetectConflictsInputSchema, GetFlaggedEntitiesInputSchema, GetContextInputSchema, CreateEntitiesInputSchema, CreateRelationsInputSchema, AddObservationsInputSchema, DeleteEntitiesInputSchema, DeleteObservationsInputSchema, DeleteRelationsInputSchema, PruneMemoryInputSchema, BulkUpdateInputSchema, FlagForReviewInputSchema } from './lib/schemas.js';
10
10
  import { handleSaveMemory } from './lib/save-memory-handler.js';
11
11
  import { validateSaveMemoryRequest } from './lib/validation.js';
12
12
  import { JsonlStorageAdapter } from './lib/jsonl-storage-adapter.js';
@@ -115,7 +115,7 @@ server.registerTool("save_memory", {
115
115
  inputSchema: SaveMemoryInputSchema,
116
116
  outputSchema: SaveMemoryOutputSchema
117
117
  }, async (input) => {
118
- const result = await handleSaveMemory(input, (entities) => knowledgeGraphManager.createEntities(entities), (relations) => knowledgeGraphManager.createRelations(relations), (threadId) => knowledgeGraphManager.getEntityNamesInThread(threadId));
118
+ const result = await handleSaveMemory(input, (threadId, entities) => knowledgeGraphManager.createEntities(threadId, entities), (threadId, relations) => knowledgeGraphManager.createRelations(threadId, relations), (threadId) => knowledgeGraphManager.getEntityNamesInThread(threadId));
119
119
  if (result.success) {
120
120
  // Build success message with entity names
121
121
  let successText = `✓ Successfully saved ${result.created.entities} entities and ${result.created.relations} relations.\n` +
@@ -171,14 +171,13 @@ server.registerTool("save_memory", {
171
171
  server.registerTool("create_entities", {
172
172
  title: "Create Entities",
173
173
  description: "Create multiple new entities in the knowledge graph with metadata (agent thread ID, timestamp, confidence, importance)",
174
- inputSchema: {
175
- entities: z.array(EntitySchemaCompat)
176
- },
174
+ inputSchema: CreateEntitiesInputSchema,
177
175
  outputSchema: {
178
176
  entities: z.array(EntitySchemaCompat)
179
177
  }
180
- }, async ({ entities }) => {
181
- const result = await knowledgeGraphManager.createEntities(entities);
178
+ }, async (input) => {
179
+ const { threadId, entities } = input;
180
+ const result = await knowledgeGraphManager.createEntities(threadId, entities);
182
181
  return {
183
182
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
184
183
  structuredContent: { entities: result }
@@ -188,14 +187,13 @@ server.registerTool("create_entities", {
188
187
  server.registerTool("create_relations", {
189
188
  title: "Create Relations",
190
189
  description: "Create multiple new relations between entities in the knowledge graph with metadata (agent thread ID, timestamp, confidence, importance). Relations should be in active voice",
191
- inputSchema: {
192
- relations: z.array(RelationSchemaCompat)
193
- },
190
+ inputSchema: CreateRelationsInputSchema,
194
191
  outputSchema: {
195
192
  relations: z.array(RelationSchemaCompat)
196
193
  }
197
- }, async ({ relations }) => {
198
- const result = await knowledgeGraphManager.createRelations(relations);
194
+ }, async (input) => {
195
+ const { threadId, relations } = input;
196
+ const result = await knowledgeGraphManager.createRelations(threadId, relations);
199
197
  return {
200
198
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
201
199
  structuredContent: { relations: result }
@@ -205,24 +203,16 @@ server.registerTool("create_relations", {
205
203
  server.registerTool("add_observations", {
206
204
  title: "Add Observations",
207
205
  description: "Add new observations to existing entities in the knowledge graph with metadata (agent thread ID, timestamp, confidence, importance)",
208
- inputSchema: {
209
- observations: z.array(z.object({
210
- entityName: z.string().describe("The name of the entity to add the observations to"),
211
- contents: z.array(z.string()).describe("An array of observation contents to add"),
212
- agentThreadId: z.string().describe("The agent thread ID adding these observations"),
213
- timestamp: z.string().describe("ISO 8601 timestamp of when the observations are added"),
214
- confidence: z.number().min(0).max(1).describe("Confidence coefficient from 0 to 1"),
215
- importance: z.number().min(0).max(1).describe("Importance for memory integrity if lost: 0 (not important) to 1 (critical)")
216
- }))
217
- },
206
+ inputSchema: AddObservationsInputSchema,
218
207
  outputSchema: {
219
208
  results: z.array(z.object({
220
209
  entityName: z.string(),
221
210
  addedObservations: z.array(z.string())
222
211
  }))
223
212
  }
224
- }, async ({ observations }) => {
225
- const result = await knowledgeGraphManager.addObservations(observations);
213
+ }, async (input) => {
214
+ const { threadId, observations } = input;
215
+ const result = await knowledgeGraphManager.addObservations(threadId, observations);
226
216
  return {
227
217
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
228
218
  structuredContent: { results: result }
@@ -232,15 +222,14 @@ server.registerTool("add_observations", {
232
222
  server.registerTool("delete_entities", {
233
223
  title: "Delete Entities",
234
224
  description: "Delete multiple entities and their associated relations from the knowledge graph",
235
- inputSchema: {
236
- entityNames: z.array(z.string()).describe("An array of entity names to delete")
237
- },
225
+ inputSchema: DeleteEntitiesInputSchema,
238
226
  outputSchema: {
239
227
  success: z.boolean(),
240
228
  message: z.string()
241
229
  }
242
- }, async ({ entityNames }) => {
243
- await knowledgeGraphManager.deleteEntities(entityNames);
230
+ }, async (input) => {
231
+ const { threadId, entityNames } = input;
232
+ await knowledgeGraphManager.deleteEntities(threadId, entityNames);
244
233
  return {
245
234
  content: [{ type: "text", text: "Entities deleted successfully" }],
246
235
  structuredContent: { success: true, message: "Entities deleted successfully" }
@@ -250,18 +239,14 @@ server.registerTool("delete_entities", {
250
239
  server.registerTool("delete_observations", {
251
240
  title: "Delete Observations",
252
241
  description: "Delete specific observations from entities in the knowledge graph",
253
- inputSchema: {
254
- deletions: z.array(z.object({
255
- entityName: z.string().describe("The name of the entity containing the observations"),
256
- observations: z.array(z.string()).describe("An array of observations to delete")
257
- }))
258
- },
242
+ inputSchema: DeleteObservationsInputSchema,
259
243
  outputSchema: {
260
244
  success: z.boolean(),
261
245
  message: z.string()
262
246
  }
263
- }, async ({ deletions }) => {
264
- await knowledgeGraphManager.deleteObservations(deletions);
247
+ }, async (input) => {
248
+ const { threadId, deletions } = input;
249
+ await knowledgeGraphManager.deleteObservations(threadId, deletions);
265
250
  return {
266
251
  content: [{ type: "text", text: "Observations deleted successfully" }],
267
252
  structuredContent: { success: true, message: "Observations deleted successfully" }
@@ -273,7 +258,8 @@ server.registerTool("update_observation", {
273
258
  description: "Update an existing observation by creating a new version with updated content. This maintains version history through the supersedes/superseded_by chain.",
274
259
  inputSchema: UpdateObservationInputSchema.shape,
275
260
  outputSchema: UpdateObservationOutputSchema.shape
276
- }, async ({ entityName, observationId, newContent, agentThreadId, timestamp, confidence, importance }) => {
261
+ }, async (input) => {
262
+ const { entityName, observationId, newContent, agentThreadId, timestamp, confidence, importance } = input;
277
263
  const updatedObservation = await knowledgeGraphManager.updateObservation({
278
264
  entityName,
279
265
  observationId,
@@ -297,15 +283,14 @@ server.registerTool("update_observation", {
297
283
  server.registerTool("delete_relations", {
298
284
  title: "Delete Relations",
299
285
  description: "Delete multiple relations from the knowledge graph",
300
- inputSchema: {
301
- relations: z.array(RelationSchemaCompat).describe("An array of relations to delete")
302
- },
286
+ inputSchema: DeleteRelationsInputSchema,
303
287
  outputSchema: {
304
288
  success: z.boolean(),
305
289
  message: z.string()
306
290
  }
307
- }, async ({ relations }) => {
308
- await knowledgeGraphManager.deleteRelations(relations);
291
+ }, async (input) => {
292
+ const { threadId, relations } = input;
293
+ await knowledgeGraphManager.deleteRelations(threadId, relations);
309
294
  return {
310
295
  content: [{ type: "text", text: "Relations deleted successfully" }],
311
296
  structuredContent: { success: true, message: "Relations deleted successfully" }
@@ -314,14 +299,14 @@ server.registerTool("delete_relations", {
314
299
  // Register read_graph tool
315
300
  server.registerTool("read_graph", {
316
301
  title: "Read Graph",
317
- description: "Read the entire knowledge graph",
318
- inputSchema: {},
302
+ description: "Read the knowledge graph for a specific thread (thread isolation enforced)",
303
+ inputSchema: ReadGraphInputSchema,
319
304
  outputSchema: {
320
305
  entities: z.array(EntitySchemaCompat),
321
306
  relations: z.array(RelationSchemaCompat)
322
307
  }
323
- }, async () => {
324
- const graph = await knowledgeGraphManager.readGraph();
308
+ }, async (input) => {
309
+ const graph = await knowledgeGraphManager.readGraph(input.threadId);
325
310
  return {
326
311
  content: [{ type: "text", text: JSON.stringify(graph, null, 2) }],
327
312
  structuredContent: { ...graph }
@@ -330,16 +315,14 @@ server.registerTool("read_graph", {
330
315
  // Register search_nodes tool
331
316
  server.registerTool("search_nodes", {
332
317
  title: "Search Nodes",
333
- description: "Search for nodes in the knowledge graph based on a query",
334
- inputSchema: {
335
- query: z.string().describe("The search query to match against entity names, types, and observation content")
336
- },
318
+ description: "Search for nodes in the knowledge graph based on a query (thread isolation enforced)",
319
+ inputSchema: SearchNodesInputSchema,
337
320
  outputSchema: {
338
321
  entities: z.array(EntitySchemaCompat),
339
322
  relations: z.array(RelationSchemaCompat)
340
323
  }
341
- }, async ({ query }) => {
342
- const graph = await knowledgeGraphManager.searchNodes(query);
324
+ }, async (input) => {
325
+ const graph = await knowledgeGraphManager.searchNodes(input.threadId, input.query);
343
326
  return {
344
327
  content: [{ type: "text", text: JSON.stringify(graph, null, 2) }],
345
328
  structuredContent: { ...graph }
@@ -348,16 +331,14 @@ server.registerTool("search_nodes", {
348
331
  // Register open_nodes tool
349
332
  server.registerTool("open_nodes", {
350
333
  title: "Open Nodes",
351
- description: "Open specific nodes in the knowledge graph by their names",
352
- inputSchema: {
353
- names: z.array(z.string()).describe("An array of entity names to retrieve")
354
- },
334
+ description: "Open specific nodes in the knowledge graph by their names (thread isolation enforced)",
335
+ inputSchema: OpenNodesInputSchema,
355
336
  outputSchema: {
356
337
  entities: z.array(EntitySchemaCompat),
357
338
  relations: z.array(RelationSchemaCompat)
358
339
  }
359
- }, async ({ names }) => {
360
- const graph = await knowledgeGraphManager.openNodes(names);
340
+ }, async (input) => {
341
+ const graph = await knowledgeGraphManager.openNodes(input.threadId, input.names);
361
342
  return {
362
343
  content: [{ type: "text", text: JSON.stringify(graph, null, 2) }],
363
344
  structuredContent: { ...graph }
@@ -366,21 +347,15 @@ server.registerTool("open_nodes", {
366
347
  // Register query_nodes tool for advanced filtering
367
348
  server.registerTool("query_nodes", {
368
349
  title: "Query Nodes",
369
- description: "Query nodes and relations in the knowledge graph with advanced filtering by timestamp, confidence, and importance ranges",
370
- inputSchema: {
371
- timestampStart: z.string().optional().describe("ISO 8601 timestamp - filter for items created on or after this time"),
372
- timestampEnd: z.string().optional().describe("ISO 8601 timestamp - filter for items created on or before this time"),
373
- confidenceMin: z.number().min(0).max(1).optional().describe("Minimum confidence value (0-1)"),
374
- confidenceMax: z.number().min(0).max(1).optional().describe("Maximum confidence value (0-1)"),
375
- importanceMin: z.number().min(0).max(1).optional().describe("Minimum importance value (0-1)"),
376
- importanceMax: z.number().min(0).max(1).optional().describe("Maximum importance value (0-1)")
377
- },
350
+ description: "Query nodes and relations in the knowledge graph with advanced filtering by timestamp, confidence, and importance ranges (thread isolation enforced)",
351
+ inputSchema: QueryNodesInputSchema,
378
352
  outputSchema: {
379
353
  entities: z.array(EntitySchemaCompat),
380
354
  relations: z.array(RelationSchemaCompat)
381
355
  }
382
- }, async (filters) => {
383
- const graph = await knowledgeGraphManager.queryNodes(filters);
356
+ }, async (input) => {
357
+ const { threadId, ...filters } = input;
358
+ const graph = await knowledgeGraphManager.queryNodes(threadId, Object.keys(filters).length > 0 ? filters : undefined);
384
359
  return {
385
360
  content: [{ type: "text", text: JSON.stringify(graph, null, 2) }],
386
361
  structuredContent: { ...graph }
@@ -389,7 +364,7 @@ server.registerTool("query_nodes", {
389
364
  // Register list_entities tool for simple entity lookup
390
365
  server.registerTool("list_entities", {
391
366
  title: "List Entities",
392
- description: "List entities with optional filtering by entity type and name pattern. Returns a simple list of entity names and types for quick discovery.",
367
+ description: "List entities with optional filtering by entity type and name pattern. Returns a simple list of entity names and types for quick discovery. Thread isolation enforced - only shows entities from the specified thread.",
393
368
  inputSchema: ListEntitiesInputSchema,
394
369
  outputSchema: ListEntitiesOutputSchema
395
370
  }, async (input) => {
@@ -522,8 +497,8 @@ server.registerTool("validate_memory", {
522
497
  // Register get_memory_stats tool
523
498
  server.registerTool("get_memory_stats", {
524
499
  title: "Get Memory Statistics",
525
- description: "Get comprehensive statistics about the knowledge graph including entity counts, thread activity, and confidence/importance metrics",
526
- inputSchema: {},
500
+ description: "Get comprehensive statistics about the knowledge graph for a specific thread including entity counts, activity, and confidence/importance metrics (thread isolation enforced)",
501
+ inputSchema: GetMemoryStatsInputSchema,
527
502
  outputSchema: {
528
503
  entityCount: z.number(),
529
504
  relationCount: z.number(),
@@ -536,8 +511,8 @@ server.registerTool("get_memory_stats", {
536
511
  entityCount: z.number()
537
512
  }))
538
513
  }
539
- }, async () => {
540
- const stats = await knowledgeGraphManager.getMemoryStats();
514
+ }, async (input) => {
515
+ const stats = await knowledgeGraphManager.getMemoryStats(input.threadId);
541
516
  return {
542
517
  content: [{ type: "text", text: JSON.stringify(stats, null, 2) }],
543
518
  structuredContent: stats
@@ -546,16 +521,14 @@ server.registerTool("get_memory_stats", {
546
521
  // Register get_recent_changes tool
547
522
  server.registerTool("get_recent_changes", {
548
523
  title: "Get Recent Changes",
549
- description: "Retrieve entities and relations that were created or modified since a specific timestamp",
550
- inputSchema: {
551
- since: z.string().describe("ISO 8601 timestamp - return changes since this time")
552
- },
524
+ description: "Retrieve entities and relations that were created or modified since a specific timestamp (thread isolation enforced)",
525
+ inputSchema: GetRecentChangesInputSchema,
553
526
  outputSchema: {
554
527
  entities: z.array(EntitySchemaCompat),
555
528
  relations: z.array(RelationSchemaCompat)
556
529
  }
557
- }, async ({ since }) => {
558
- const changes = await knowledgeGraphManager.getRecentChanges(since);
530
+ }, async (input) => {
531
+ const changes = await knowledgeGraphManager.getRecentChanges(input.threadId, input.since);
559
532
  return {
560
533
  content: [{ type: "text", text: JSON.stringify(changes, null, 2) }],
561
534
  structuredContent: { ...changes }
@@ -564,19 +537,15 @@ server.registerTool("get_recent_changes", {
564
537
  // Register find_relation_path tool
565
538
  server.registerTool("find_relation_path", {
566
539
  title: "Find Relationship Path",
567
- description: "Find a path of relationships connecting two entities in the knowledge graph",
568
- inputSchema: {
569
- from: z.string().describe("Starting entity name"),
570
- to: z.string().describe("Target entity name"),
571
- maxDepth: z.number().optional().default(5).describe("Maximum path depth to search (default: 5)")
572
- },
540
+ description: "Find a path of relationships connecting two entities in the knowledge graph (thread isolation enforced)",
541
+ inputSchema: FindRelationPathInputSchema,
573
542
  outputSchema: {
574
543
  found: z.boolean(),
575
544
  path: z.array(z.string()),
576
545
  relations: z.array(RelationSchemaCompat)
577
546
  }
578
- }, async ({ from, to, maxDepth }) => {
579
- const result = await knowledgeGraphManager.findRelationPath(from, to, maxDepth || 5);
547
+ }, async (input) => {
548
+ const result = await knowledgeGraphManager.findRelationPath(input.threadId, input.from, input.to, input.maxDepth || 5);
580
549
  return {
581
550
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
582
551
  structuredContent: result
@@ -585,8 +554,8 @@ server.registerTool("find_relation_path", {
585
554
  // Register detect_conflicts tool
586
555
  server.registerTool("detect_conflicts", {
587
556
  title: "Detect Conflicts",
588
- description: "Detect potentially conflicting observations within entities using pattern matching and negation detection",
589
- inputSchema: {},
557
+ description: "Detect potentially conflicting observations within entities using pattern matching and negation detection (thread isolation enforced)",
558
+ inputSchema: DetectConflictsInputSchema,
590
559
  outputSchema: {
591
560
  conflicts: z.array(z.object({
592
561
  entityName: z.string(),
@@ -597,8 +566,8 @@ server.registerTool("detect_conflicts", {
597
566
  }))
598
567
  }))
599
568
  }
600
- }, async () => {
601
- const conflicts = await knowledgeGraphManager.detectConflicts();
569
+ }, async (input) => {
570
+ const conflicts = await knowledgeGraphManager.detectConflicts(input.threadId);
602
571
  return {
603
572
  content: [{ type: "text", text: JSON.stringify({ conflicts }, null, 2) }],
604
573
  structuredContent: { conflicts }
@@ -608,17 +577,14 @@ server.registerTool("detect_conflicts", {
608
577
  server.registerTool("prune_memory", {
609
578
  title: "Prune Memory",
610
579
  description: "Remove old or low-importance entities to manage memory size, with option to keep minimum number of entities",
611
- inputSchema: {
612
- olderThan: z.string().optional().describe("ISO 8601 timestamp - remove entities older than this"),
613
- importanceLessThan: z.number().min(0).max(1).optional().describe("Remove entities with importance less than this value"),
614
- keepMinEntities: z.number().optional().describe("Minimum number of entities to keep regardless of filters")
615
- },
580
+ inputSchema: PruneMemoryInputSchema,
616
581
  outputSchema: {
617
582
  removedEntities: z.number(),
618
583
  removedRelations: z.number()
619
584
  }
620
- }, async (options) => {
621
- const result = await knowledgeGraphManager.pruneMemory(options);
585
+ }, async (input) => {
586
+ const { threadId, ...options } = input;
587
+ const result = await knowledgeGraphManager.pruneMemory(threadId, options);
622
588
  return {
623
589
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
624
590
  structuredContent: result
@@ -628,20 +594,14 @@ server.registerTool("prune_memory", {
628
594
  server.registerTool("bulk_update", {
629
595
  title: "Bulk Update",
630
596
  description: "Efficiently update multiple entities at once with new confidence, importance, or observations",
631
- inputSchema: {
632
- updates: z.array(z.object({
633
- entityName: z.string(),
634
- confidence: z.number().min(0).max(1).optional(),
635
- importance: z.number().min(0).max(1).optional(),
636
- addObservations: z.array(z.string()).optional()
637
- }))
638
- },
597
+ inputSchema: BulkUpdateInputSchema,
639
598
  outputSchema: {
640
599
  updated: z.number(),
641
600
  notFound: z.array(z.string())
642
601
  }
643
- }, async ({ updates }) => {
644
- const result = await knowledgeGraphManager.bulkUpdate(updates);
602
+ }, async (input) => {
603
+ const { threadId, updates } = input;
604
+ const result = await knowledgeGraphManager.bulkUpdate(threadId, updates);
645
605
  return {
646
606
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
647
607
  structuredContent: result
@@ -651,17 +611,14 @@ server.registerTool("bulk_update", {
651
611
  server.registerTool("flag_for_review", {
652
612
  title: "Flag Entity for Review",
653
613
  description: "Mark an entity for human review with a specific reason (Human-in-the-Loop)",
654
- inputSchema: {
655
- entityName: z.string().describe("Name of entity to flag"),
656
- reason: z.string().describe("Reason for flagging"),
657
- reviewer: z.string().optional().describe("Optional reviewer name")
658
- },
614
+ inputSchema: FlagForReviewInputSchema,
659
615
  outputSchema: {
660
616
  success: z.boolean(),
661
617
  message: z.string()
662
618
  }
663
- }, async ({ entityName, reason, reviewer }) => {
664
- await knowledgeGraphManager.flagForReview(entityName, reason, reviewer);
619
+ }, async (input) => {
620
+ const { threadId, entityName, reason, reviewer } = input;
621
+ await knowledgeGraphManager.flagForReview(threadId, entityName, reason, reviewer);
665
622
  return {
666
623
  content: [{ type: "text", text: `Entity "${entityName}" flagged for review` }],
667
624
  structuredContent: { success: true, message: `Entity "${entityName}" flagged for review` }
@@ -670,13 +627,13 @@ server.registerTool("flag_for_review", {
670
627
  // Register get_flagged_entities tool
671
628
  server.registerTool("get_flagged_entities", {
672
629
  title: "Get Flagged Entities",
673
- description: "Retrieve all entities that have been flagged for human review",
674
- inputSchema: {},
630
+ description: "Retrieve all entities that have been flagged for human review (thread isolation enforced)",
631
+ inputSchema: GetFlaggedEntitiesInputSchema,
675
632
  outputSchema: {
676
633
  entities: z.array(EntitySchemaCompat)
677
634
  }
678
- }, async () => {
679
- const entities = await knowledgeGraphManager.getFlaggedEntities();
635
+ }, async (input) => {
636
+ const entities = await knowledgeGraphManager.getFlaggedEntities(input.threadId);
680
637
  return {
681
638
  content: [{ type: "text", text: JSON.stringify({ entities }, null, 2) }],
682
639
  structuredContent: { entities }
@@ -685,17 +642,14 @@ server.registerTool("get_flagged_entities", {
685
642
  // Register get_context tool
686
643
  server.registerTool("get_context", {
687
644
  title: "Get Context",
688
- description: "Retrieve entities and relations related to specified entities up to a certain depth, useful for understanding context around specific topics",
689
- inputSchema: {
690
- entityNames: z.array(z.string()).describe("Names of entities to get context for"),
691
- depth: z.number().optional().default(1).describe("How many relationship hops to include (default: 1)")
692
- },
645
+ description: "Retrieve entities and relations related to specified entities up to a certain depth, useful for understanding context around specific topics (thread isolation enforced)",
646
+ inputSchema: GetContextInputSchema,
693
647
  outputSchema: {
694
648
  entities: z.array(EntitySchemaCompat),
695
649
  relations: z.array(RelationSchemaCompat)
696
650
  }
697
- }, async ({ entityNames, depth }) => {
698
- const context = await knowledgeGraphManager.getContext(entityNames, depth || 1);
651
+ }, async (input) => {
652
+ const context = await knowledgeGraphManager.getContext(input.threadId, input.entityNames, input.depth || 1);
699
653
  return {
700
654
  content: [{ type: "text", text: JSON.stringify(context, null, 2) }],
701
655
  structuredContent: { ...context }
@@ -738,11 +692,11 @@ server.registerTool("get_analytics", {
738
692
  // Register get_observation_history tool
739
693
  server.registerTool("get_observation_history", {
740
694
  title: "Get Observation History",
741
- description: "Retrieve the full version chain for a specific observation, showing how it evolved over time",
695
+ description: "Retrieve the full version chain for a specific observation, showing how it evolved over time (thread isolation enforced)",
742
696
  inputSchema: GetObservationHistoryInputSchema,
743
697
  outputSchema: GetObservationHistoryOutputSchema
744
698
  }, async (input) => {
745
- const result = await knowledgeGraphManager.getObservationHistory(input.entityName, input.observationId);
699
+ const result = await knowledgeGraphManager.getObservationHistory(input.threadId, input.entityName, input.observationId);
746
700
  return {
747
701
  content: [{ type: "text", text: JSON.stringify({ history: result }, null, 2) }],
748
702
  structuredContent: { history: result }
@@ -5,11 +5,13 @@ import { hasNegation, NEGATION_WORDS } from '../utils/negation-detector.js';
5
5
  /**
6
6
  * Detect conflicting observations within entities
7
7
  * Identifies potential contradictions by checking for negation patterns
8
+ * Filtered by threadId for thread isolation
8
9
  */
9
- export async function detectConflicts(storage) {
10
+ export async function detectConflicts(storage, threadId) {
10
11
  const graph = await storage.loadGraph();
11
12
  const conflicts = [];
12
- for (const entity of graph.entities) {
13
+ // Only analyze entities in the specified thread
14
+ for (const entity of graph.entities.filter(e => e.agentThreadId === threadId)) {
13
15
  const entityConflicts = [];
14
16
  for (let i = 0; i < entity.observations.length; i++) {
15
17
  for (let j = i + 1; j < entity.observations.length; j++) {
@@ -4,16 +4,18 @@
4
4
  /**
5
5
  * Get context (entities related to specified entities up to a certain depth)
6
6
  * Expands to include related entities up to specified depth
7
+ * Filtered by threadId for thread isolation
7
8
  */
8
- export async function getContext(storage, entityNames, depth = 1) {
9
+ export async function getContext(storage, threadId, entityNames, depth = 1) {
9
10
  const graph = await storage.loadGraph();
10
11
  const contextEntityNames = new Set(entityNames);
11
- // Expand to include related entities up to specified depth
12
+ // Expand to include related entities up to specified depth - only within this thread
12
13
  for (let d = 0; d < depth; d++) {
13
14
  const currentEntities = Array.from(contextEntityNames);
14
15
  for (const entityName of currentEntities) {
15
- // Find all relations involving this entity
16
- const relatedRelations = graph.relations.filter(r => r.from === entityName || r.to === entityName);
16
+ // Find all relations involving this entity - only from this thread
17
+ const relatedRelations = graph.relations.filter(r => r.agentThreadId === threadId &&
18
+ (r.from === entityName || r.to === entityName));
17
19
  // Add related entities
18
20
  relatedRelations.forEach(r => {
19
21
  contextEntityNames.add(r.from);
@@ -21,9 +23,10 @@ export async function getContext(storage, entityNames, depth = 1) {
21
23
  });
22
24
  }
23
25
  }
24
- // Get all entities and relations in context
25
- const contextEntities = graph.entities.filter(e => contextEntityNames.has(e.name));
26
- const contextRelations = graph.relations.filter(r => contextEntityNames.has(r.from) && contextEntityNames.has(r.to));
26
+ // Get all entities and relations in context - only from this thread
27
+ const contextEntities = graph.entities.filter(e => e.agentThreadId === threadId && contextEntityNames.has(e.name));
28
+ const contextRelations = graph.relations.filter(r => r.agentThreadId === threadId &&
29
+ contextEntityNames.has(r.from) && contextEntityNames.has(r.to));
27
30
  return {
28
31
  entities: contextEntities,
29
32
  relations: contextRelations
@@ -2,31 +2,34 @@
2
2
  * Memory statistics service
3
3
  */
4
4
  /**
5
- * Get comprehensive memory statistics
5
+ * Get comprehensive memory statistics for a specific thread
6
+ * Filtered by threadId for thread isolation
6
7
  */
7
- export async function getMemoryStats(storage) {
8
+ export async function getMemoryStats(storage, threadId) {
8
9
  const graph = await storage.loadGraph();
10
+ // Filter entities and relations by threadId
11
+ const threadEntities = graph.entities.filter(e => e.agentThreadId === threadId);
12
+ const threadRelations = graph.relations.filter(r => r.agentThreadId === threadId);
9
13
  // Count entity types
10
14
  const entityTypes = {};
11
- graph.entities.forEach(e => {
15
+ threadEntities.forEach(e => {
12
16
  entityTypes[e.entityType] = (entityTypes[e.entityType] || 0) + 1;
13
17
  });
14
18
  // Calculate averages
15
- const avgConfidence = graph.entities.length > 0
16
- ? graph.entities.reduce((sum, e) => sum + e.confidence, 0) / graph.entities.length
19
+ const avgConfidence = threadEntities.length > 0
20
+ ? threadEntities.reduce((sum, e) => sum + e.confidence, 0) / threadEntities.length
17
21
  : 0;
18
- const avgImportance = graph.entities.length > 0
19
- ? graph.entities.reduce((sum, e) => sum + e.importance, 0) / graph.entities.length
22
+ const avgImportance = threadEntities.length > 0
23
+ ? threadEntities.reduce((sum, e) => sum + e.importance, 0) / threadEntities.length
20
24
  : 0;
21
- // Count unique threads
22
- const threads = new Set([
23
- ...graph.entities.map(e => e.agentThreadId),
24
- ...graph.relations.map(r => r.agentThreadId)
25
- ]);
26
- // Recent activity (last 7 days, grouped by day)
25
+ // Count unique threads in the system (across all entities in the graph)
26
+ const threads = new Set(graph.entities
27
+ .map(e => e.agentThreadId)
28
+ .filter((id) => !!id));
29
+ // Recent activity (last 7 days, grouped by day) - only for this thread
27
30
  const now = new Date();
28
31
  const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
29
- const recentEntities = graph.entities.filter(e => new Date(e.timestamp) >= sevenDaysAgo);
32
+ const recentEntities = threadEntities.filter(e => new Date(e.timestamp) >= sevenDaysAgo);
30
33
  // Group by day
31
34
  const activityByDay = {};
32
35
  recentEntities.forEach(e => {
@@ -37,8 +40,8 @@ export async function getMemoryStats(storage) {
37
40
  .map(([timestamp, entityCount]) => ({ timestamp, entityCount }))
38
41
  .sort((a, b) => a.timestamp.localeCompare(b.timestamp));
39
42
  return {
40
- entityCount: graph.entities.length,
41
- relationCount: graph.relations.length,
43
+ entityCount: threadEntities.length,
44
+ relationCount: threadRelations.length,
42
45
  threadCount: threads.size,
43
46
  entityTypes,
44
47
  avgConfidence,
@@ -47,15 +50,16 @@ export async function getMemoryStats(storage) {
47
50
  };
48
51
  }
49
52
  /**
50
- * Get recent changes since a specific timestamp
53
+ * Get recent changes since a specific timestamp for a specific thread
54
+ * Filtered by threadId for thread isolation
51
55
  */
52
- export async function getRecentChanges(storage, since) {
56
+ export async function getRecentChanges(storage, threadId, since) {
53
57
  const graph = await storage.loadGraph();
54
58
  const sinceDate = new Date(since);
55
- // Only return entities and relations that were actually modified since the specified time
56
- const recentEntities = graph.entities.filter(e => new Date(e.timestamp) >= sinceDate);
57
- // Only include relations that are recent themselves
58
- const recentRelations = graph.relations.filter(r => new Date(r.timestamp) >= sinceDate);
59
+ // Only return entities and relations from this thread that were modified since the specified time
60
+ const recentEntities = graph.entities.filter(e => e.agentThreadId === threadId && new Date(e.timestamp) >= sinceDate);
61
+ // Only include relations from this thread that are recent themselves
62
+ const recentRelations = graph.relations.filter(r => r.agentThreadId === threadId && new Date(r.timestamp) >= sinceDate);
59
63
  return {
60
64
  entities: recentEntities,
61
65
  relations: recentRelations
@@ -4,16 +4,17 @@
4
4
  /**
5
5
  * Find the shortest path between two entities in the knowledge graph
6
6
  * Uses BFS algorithm with bidirectional search
7
+ * Filtered by threadId for thread isolation
7
8
  */
8
- export async function findRelationPath(storage, from, to, maxDepth = 5) {
9
+ export async function findRelationPath(storage, threadId, from, to, maxDepth = 5) {
9
10
  const graph = await storage.loadGraph();
10
11
  if (from === to) {
11
12
  return { found: true, path: [from], relations: [] };
12
13
  }
13
- // Build indexes for efficient relation lookup
14
+ // Build indexes for efficient relation lookup - only for relations in this thread
14
15
  const relationsFrom = new Map();
15
16
  const relationsTo = new Map();
16
- for (const rel of graph.relations) {
17
+ for (const rel of graph.relations.filter(r => r.agentThreadId === threadId)) {
17
18
  if (!relationsFrom.has(rel.from)) {
18
19
  relationsFrom.set(rel.from, []);
19
20
  }
@@ -4,12 +4,13 @@
4
4
  import { randomUUID } from 'crypto';
5
5
  /**
6
6
  * Flag an entity for review
7
+ * Thread isolation: Only flags entities in the specified thread
7
8
  */
8
- export async function flagForReview(storage, entityName, reason, reviewer) {
9
+ export async function flagForReview(storage, threadId, entityName, reason, reviewer) {
9
10
  const graph = await storage.loadGraph();
10
- const entity = graph.entities.find(e => e.name === entityName);
11
+ const entity = graph.entities.find(e => e.name === entityName && e.agentThreadId === threadId);
11
12
  if (!entity) {
12
- throw new Error(`Entity with name ${entityName} not found`);
13
+ throw new Error(`Entity with name ${entityName} not found in thread ${threadId}`);
13
14
  }
14
15
  // Add a special observation to mark for review
15
16
  const flagContent = `[FLAGGED FOR REVIEW: ${reason}${reviewer ? ` - Reviewer: ${reviewer}` : ''}]`;
@@ -30,9 +31,11 @@ export async function flagForReview(storage, entityName, reason, reviewer) {
30
31
  }
31
32
  }
32
33
  /**
33
- * Get all entities flagged for review
34
+ * Get all entities flagged for review in a specific thread
35
+ * Filtered by threadId for thread isolation
34
36
  */
35
- export async function getFlaggedEntities(storage) {
37
+ export async function getFlaggedEntities(storage, threadId) {
36
38
  const graph = await storage.loadGraph();
37
- return graph.entities.filter(e => e.observations.some(obs => obs.content.includes('[FLAGGED FOR REVIEW:')));
39
+ return graph.entities.filter(e => e.agentThreadId === threadId &&
40
+ e.observations.some(obs => obs.content.includes('[FLAGGED FOR REVIEW:')));
38
41
  }