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 +84 -130
- package/dist/lib/analysis/conflict-detector.js +4 -2
- package/dist/lib/analysis/context-builder.js +10 -7
- package/dist/lib/analysis/memory-stats.js +26 -22
- package/dist/lib/analysis/path-finder.js +4 -3
- package/dist/lib/collaboration/flag-manager.js +9 -6
- package/dist/lib/knowledge-graph-manager.js +40 -40
- package/dist/lib/maintenance/bulk-updater.js +3 -2
- package/dist/lib/maintenance/memory-pruner.js +22 -13
- package/dist/lib/operations/entity-operations.js +13 -4
- package/dist/lib/operations/observation-operations.js +9 -5
- package/dist/lib/operations/relation-operations.js +7 -5
- package/dist/lib/queries/entity-queries.js +26 -24
- package/dist/lib/queries/graph-reader.js +14 -3
- package/dist/lib/queries/search-service.js +32 -18
- package/dist/lib/save-memory-handler.js +2 -2
- package/dist/lib/schemas.js +157 -6
- package/dist/lib/versioning/observation-history.js +11 -5
- package/package.json +17 -3
- package/README.md +0 -399
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 (
|
|
181
|
-
const
|
|
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 (
|
|
198
|
-
const
|
|
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 (
|
|
225
|
-
const
|
|
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 (
|
|
243
|
-
|
|
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 (
|
|
264
|
-
|
|
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 (
|
|
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 (
|
|
308
|
-
|
|
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
|
|
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 (
|
|
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 (
|
|
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 (
|
|
383
|
-
const
|
|
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,
|
|
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 (
|
|
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 (
|
|
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 (
|
|
621
|
-
const
|
|
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 (
|
|
644
|
-
const
|
|
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 (
|
|
664
|
-
|
|
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 (
|
|
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
|
-
|
|
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.
|
|
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 =>
|
|
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
|
-
|
|
15
|
+
threadEntities.forEach(e => {
|
|
12
16
|
entityTypes[e.entityType] = (entityTypes[e.entityType] || 0) + 1;
|
|
13
17
|
});
|
|
14
18
|
// Calculate averages
|
|
15
|
-
const avgConfidence =
|
|
16
|
-
?
|
|
19
|
+
const avgConfidence = threadEntities.length > 0
|
|
20
|
+
? threadEntities.reduce((sum, e) => sum + e.confidence, 0) / threadEntities.length
|
|
17
21
|
: 0;
|
|
18
|
-
const avgImportance =
|
|
19
|
-
?
|
|
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
|
-
|
|
24
|
-
|
|
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 =
|
|
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:
|
|
41
|
-
relationCount:
|
|
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
|
|
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.
|
|
39
|
+
return graph.entities.filter(e => e.agentThreadId === threadId &&
|
|
40
|
+
e.observations.some(obs => obs.content.includes('[FLAGGED FOR REVIEW:')));
|
|
38
41
|
}
|