server-memory-enhanced 3.0.0 → 3.2.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 +43 -69
- package/dist/lib/collaboration/flag-manager.js +4 -3
- package/dist/lib/knowledge-graph-manager.js +23 -19
- 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/graph-reader.js +140 -6
- package/dist/lib/queries/search-service.js +8 -3
- package/dist/lib/save-memory-handler.js +2 -2
- package/dist/lib/schemas.js +113 -4
- 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, ReadGraphInputSchema, SearchNodesInputSchema, OpenNodesInputSchema, QueryNodesInputSchema, GetMemoryStatsInputSchema, GetRecentChangesInputSchema, FindRelationPathInputSchema, DetectConflictsInputSchema, GetFlaggedEntitiesInputSchema, GetContextInputSchema } 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,15 @@ 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 knowledge graph for a specific thread (thread isolation enforced)",
|
|
302
|
+
description: "Read the knowledge graph for a specific thread (thread isolation enforced). Supports filtering by minimum importance threshold.",
|
|
318
303
|
inputSchema: ReadGraphInputSchema,
|
|
319
304
|
outputSchema: {
|
|
320
305
|
entities: z.array(EntitySchemaCompat),
|
|
321
306
|
relations: z.array(RelationSchemaCompat)
|
|
322
307
|
}
|
|
323
308
|
}, async (input) => {
|
|
324
|
-
const
|
|
309
|
+
const { threadId, minImportance } = input;
|
|
310
|
+
const graph = await knowledgeGraphManager.readGraph(threadId, minImportance);
|
|
325
311
|
return {
|
|
326
312
|
content: [{ type: "text", text: JSON.stringify(graph, null, 2) }],
|
|
327
313
|
structuredContent: { ...graph }
|
|
@@ -592,17 +578,14 @@ server.registerTool("detect_conflicts", {
|
|
|
592
578
|
server.registerTool("prune_memory", {
|
|
593
579
|
title: "Prune Memory",
|
|
594
580
|
description: "Remove old or low-importance entities to manage memory size, with option to keep minimum number of entities",
|
|
595
|
-
inputSchema:
|
|
596
|
-
olderThan: z.string().optional().describe("ISO 8601 timestamp - remove entities older than this"),
|
|
597
|
-
importanceLessThan: z.number().min(0).max(1).optional().describe("Remove entities with importance less than this value"),
|
|
598
|
-
keepMinEntities: z.number().optional().describe("Minimum number of entities to keep regardless of filters")
|
|
599
|
-
},
|
|
581
|
+
inputSchema: PruneMemoryInputSchema,
|
|
600
582
|
outputSchema: {
|
|
601
583
|
removedEntities: z.number(),
|
|
602
584
|
removedRelations: z.number()
|
|
603
585
|
}
|
|
604
|
-
}, async (
|
|
605
|
-
const
|
|
586
|
+
}, async (input) => {
|
|
587
|
+
const { threadId, ...options } = input;
|
|
588
|
+
const result = await knowledgeGraphManager.pruneMemory(threadId, options);
|
|
606
589
|
return {
|
|
607
590
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
608
591
|
structuredContent: result
|
|
@@ -612,20 +595,14 @@ server.registerTool("prune_memory", {
|
|
|
612
595
|
server.registerTool("bulk_update", {
|
|
613
596
|
title: "Bulk Update",
|
|
614
597
|
description: "Efficiently update multiple entities at once with new confidence, importance, or observations",
|
|
615
|
-
inputSchema:
|
|
616
|
-
updates: z.array(z.object({
|
|
617
|
-
entityName: z.string(),
|
|
618
|
-
confidence: z.number().min(0).max(1).optional(),
|
|
619
|
-
importance: z.number().min(0).max(1).optional(),
|
|
620
|
-
addObservations: z.array(z.string()).optional()
|
|
621
|
-
}))
|
|
622
|
-
},
|
|
598
|
+
inputSchema: BulkUpdateInputSchema,
|
|
623
599
|
outputSchema: {
|
|
624
600
|
updated: z.number(),
|
|
625
601
|
notFound: z.array(z.string())
|
|
626
602
|
}
|
|
627
|
-
}, async (
|
|
628
|
-
const
|
|
603
|
+
}, async (input) => {
|
|
604
|
+
const { threadId, updates } = input;
|
|
605
|
+
const result = await knowledgeGraphManager.bulkUpdate(threadId, updates);
|
|
629
606
|
return {
|
|
630
607
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
631
608
|
structuredContent: result
|
|
@@ -635,17 +612,14 @@ server.registerTool("bulk_update", {
|
|
|
635
612
|
server.registerTool("flag_for_review", {
|
|
636
613
|
title: "Flag Entity for Review",
|
|
637
614
|
description: "Mark an entity for human review with a specific reason (Human-in-the-Loop)",
|
|
638
|
-
inputSchema:
|
|
639
|
-
entityName: z.string().describe("Name of entity to flag"),
|
|
640
|
-
reason: z.string().describe("Reason for flagging"),
|
|
641
|
-
reviewer: z.string().optional().describe("Optional reviewer name")
|
|
642
|
-
},
|
|
615
|
+
inputSchema: FlagForReviewInputSchema,
|
|
643
616
|
outputSchema: {
|
|
644
617
|
success: z.boolean(),
|
|
645
618
|
message: z.string()
|
|
646
619
|
}
|
|
647
|
-
}, async (
|
|
648
|
-
|
|
620
|
+
}, async (input) => {
|
|
621
|
+
const { threadId, entityName, reason, reviewer } = input;
|
|
622
|
+
await knowledgeGraphManager.flagForReview(threadId, entityName, reason, reviewer);
|
|
649
623
|
return {
|
|
650
624
|
content: [{ type: "text", text: `Entity "${entityName}" flagged for review` }],
|
|
651
625
|
structuredContent: { success: true, message: `Entity "${entityName}" flagged for review` }
|
|
@@ -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}` : ''}]`;
|
|
@@ -43,39 +43,43 @@ export class KnowledgeGraphManager {
|
|
|
43
43
|
await this.initializePromise;
|
|
44
44
|
}
|
|
45
45
|
// Entity Operations
|
|
46
|
-
async createEntities(entities) {
|
|
46
|
+
async createEntities(threadId, entities) {
|
|
47
47
|
await this.ensureInitialized();
|
|
48
|
-
return EntityOps.createEntities(this.storage, entities);
|
|
48
|
+
return EntityOps.createEntities(this.storage, threadId, entities);
|
|
49
49
|
}
|
|
50
|
-
async deleteEntities(entityNames) {
|
|
50
|
+
async deleteEntities(threadId, entityNames) {
|
|
51
51
|
await this.ensureInitialized();
|
|
52
|
-
return EntityOps.deleteEntities(this.storage, entityNames);
|
|
52
|
+
return EntityOps.deleteEntities(this.storage, threadId, entityNames);
|
|
53
53
|
}
|
|
54
54
|
// Relation Operations
|
|
55
|
-
async createRelations(relations) {
|
|
55
|
+
async createRelations(threadId, relations) {
|
|
56
56
|
await this.ensureInitialized();
|
|
57
|
-
return RelationOps.createRelations(this.storage, relations);
|
|
57
|
+
return RelationOps.createRelations(this.storage, threadId, relations);
|
|
58
58
|
}
|
|
59
|
-
async deleteRelations(relations) {
|
|
59
|
+
async deleteRelations(threadId, relations) {
|
|
60
60
|
await this.ensureInitialized();
|
|
61
|
-
return RelationOps.deleteRelations(this.storage, relations);
|
|
61
|
+
return RelationOps.deleteRelations(this.storage, threadId, relations);
|
|
62
62
|
}
|
|
63
63
|
// Observation Operations
|
|
64
|
-
async addObservations(observations) {
|
|
64
|
+
async addObservations(threadId, observations) {
|
|
65
65
|
await this.ensureInitialized();
|
|
66
|
-
return ObservationOps.addObservations(this.storage, observations);
|
|
66
|
+
return ObservationOps.addObservations(this.storage, threadId, observations);
|
|
67
67
|
}
|
|
68
|
-
async deleteObservations(deletions) {
|
|
68
|
+
async deleteObservations(threadId, deletions) {
|
|
69
69
|
await this.ensureInitialized();
|
|
70
|
-
return ObservationOps.deleteObservations(this.storage, deletions);
|
|
70
|
+
return ObservationOps.deleteObservations(this.storage, threadId, deletions);
|
|
71
71
|
}
|
|
72
72
|
async updateObservation(params) {
|
|
73
73
|
await this.ensureInitialized();
|
|
74
74
|
return ObservationOps.updateObservation(this.storage, params);
|
|
75
75
|
}
|
|
76
76
|
// Graph Reading Operations
|
|
77
|
-
async readGraph(threadId) {
|
|
77
|
+
async readGraph(threadId, minImportance) {
|
|
78
78
|
await this.ensureInitialized();
|
|
79
|
+
// Pass minImportance if provided, otherwise let readGraph use its default
|
|
80
|
+
if (minImportance !== undefined) {
|
|
81
|
+
return GraphReader.readGraph(this.storage, threadId, minImportance);
|
|
82
|
+
}
|
|
79
83
|
return GraphReader.readGraph(this.storage, threadId);
|
|
80
84
|
}
|
|
81
85
|
// Search Operations
|
|
@@ -131,18 +135,18 @@ export class KnowledgeGraphManager {
|
|
|
131
135
|
return AnalyticsService.getAnalytics(this.storage, threadId);
|
|
132
136
|
}
|
|
133
137
|
// Memory Maintenance
|
|
134
|
-
async pruneMemory(options) {
|
|
138
|
+
async pruneMemory(threadId, options) {
|
|
135
139
|
await this.ensureInitialized();
|
|
136
|
-
return MemoryPruner.pruneMemory(this.storage, options);
|
|
140
|
+
return MemoryPruner.pruneMemory(this.storage, threadId, options);
|
|
137
141
|
}
|
|
138
|
-
async bulkUpdate(updates) {
|
|
142
|
+
async bulkUpdate(threadId, updates) {
|
|
139
143
|
await this.ensureInitialized();
|
|
140
|
-
return BulkUpdater.bulkUpdate(this.storage, updates);
|
|
144
|
+
return BulkUpdater.bulkUpdate(this.storage, threadId, updates);
|
|
141
145
|
}
|
|
142
146
|
// Collaboration Features
|
|
143
|
-
async flagForReview(entityName, reason, reviewer) {
|
|
147
|
+
async flagForReview(threadId, entityName, reason, reviewer) {
|
|
144
148
|
await this.ensureInitialized();
|
|
145
|
-
return FlagManager.flagForReview(this.storage, entityName, reason, reviewer);
|
|
149
|
+
return FlagManager.flagForReview(this.storage, threadId, entityName, reason, reviewer);
|
|
146
150
|
}
|
|
147
151
|
async getFlaggedEntities(threadId) {
|
|
148
152
|
await this.ensureInitialized();
|
|
@@ -4,13 +4,14 @@
|
|
|
4
4
|
import { randomUUID } from 'crypto';
|
|
5
5
|
/**
|
|
6
6
|
* Perform bulk updates on multiple entities
|
|
7
|
+
* Thread isolation: Only updates entities in the specified thread
|
|
7
8
|
*/
|
|
8
|
-
export async function bulkUpdate(storage, updates) {
|
|
9
|
+
export async function bulkUpdate(storage, threadId, updates) {
|
|
9
10
|
const graph = await storage.loadGraph();
|
|
10
11
|
let updated = 0;
|
|
11
12
|
const notFound = [];
|
|
12
13
|
for (const update of updates) {
|
|
13
|
-
const entity = graph.entities.find(e => e.name === update.entityName);
|
|
14
|
+
const entity = graph.entities.find(e => e.name === update.entityName && e.agentThreadId === threadId);
|
|
14
15
|
if (!entity) {
|
|
15
16
|
notFound.push(update.entityName);
|
|
16
17
|
continue;
|
|
@@ -3,13 +3,18 @@
|
|
|
3
3
|
*/
|
|
4
4
|
/**
|
|
5
5
|
* Prune memory based on age and importance criteria
|
|
6
|
+
* Thread isolation: Only prunes entities and relations in the specified thread
|
|
6
7
|
*/
|
|
7
|
-
export async function pruneMemory(storage, options) {
|
|
8
|
+
export async function pruneMemory(storage, threadId, options) {
|
|
8
9
|
const graph = await storage.loadGraph();
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
// Filter to only entities in the specified thread
|
|
11
|
+
const threadEntities = graph.entities.filter(e => e.agentThreadId === threadId);
|
|
12
|
+
const initialEntityCount = threadEntities.length;
|
|
13
|
+
// Count initial relations in the thread
|
|
14
|
+
const threadEntityNames = new Set(threadEntities.map(e => e.name));
|
|
15
|
+
const initialRelationCount = graph.relations.filter(r => r.agentThreadId === threadId && threadEntityNames.has(r.from) && threadEntityNames.has(r.to)).length;
|
|
16
|
+
// Filter entities to remove within the thread
|
|
17
|
+
let entitiesToKeep = threadEntities;
|
|
13
18
|
if (options.olderThan) {
|
|
14
19
|
const cutoffDate = new Date(options.olderThan);
|
|
15
20
|
entitiesToKeep = entitiesToKeep.filter(e => new Date(e.timestamp) >= cutoffDate);
|
|
@@ -18,13 +23,13 @@ export async function pruneMemory(storage, options) {
|
|
|
18
23
|
entitiesToKeep = entitiesToKeep.filter(e => e.importance >= options.importanceLessThan);
|
|
19
24
|
}
|
|
20
25
|
// Ensure we keep minimum entities
|
|
21
|
-
// If keepMinEntities is set and we need more entities, backfill from the original
|
|
26
|
+
// If keepMinEntities is set and we need more entities, backfill from the original thread entities
|
|
22
27
|
// sorted by importance and recency
|
|
23
28
|
if (options.keepMinEntities && entitiesToKeep.length < options.keepMinEntities) {
|
|
24
29
|
const minToKeep = options.keepMinEntities;
|
|
25
30
|
const alreadyKeptNames = new Set(entitiesToKeep.map(e => e.name));
|
|
26
|
-
// Candidates are entities from the
|
|
27
|
-
const candidates =
|
|
31
|
+
// Candidates are entities from the thread that are not already kept
|
|
32
|
+
const candidates = threadEntities
|
|
28
33
|
.filter(e => !alreadyKeptNames.has(e.name))
|
|
29
34
|
.sort((a, b) => {
|
|
30
35
|
if (a.importance !== b.importance)
|
|
@@ -36,13 +41,17 @@ export async function pruneMemory(storage, options) {
|
|
|
36
41
|
entitiesToKeep = [...entitiesToKeep, ...backfill];
|
|
37
42
|
}
|
|
38
43
|
const keptEntityNames = new Set(entitiesToKeep.map(e => e.name));
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
graph
|
|
42
|
-
graph.
|
|
44
|
+
const removedEntityNames = new Set(threadEntityNames);
|
|
45
|
+
keptEntityNames.forEach(name => removedEntityNames.delete(name));
|
|
46
|
+
// Update the main graph: remove entities from this thread that should be pruned
|
|
47
|
+
graph.entities = graph.entities.filter(e => e.agentThreadId !== threadId || keptEntityNames.has(e.name));
|
|
48
|
+
// Remove relations from this thread that reference removed entities
|
|
49
|
+
graph.relations = graph.relations.filter(r => !(r.agentThreadId === threadId && (removedEntityNames.has(r.from) || removedEntityNames.has(r.to))));
|
|
43
50
|
await storage.saveGraph(graph);
|
|
51
|
+
// Count remaining relations in the thread after pruning
|
|
52
|
+
const finalRelationCount = graph.relations.filter(r => r.agentThreadId === threadId && keptEntityNames.has(r.from) && keptEntityNames.has(r.to)).length;
|
|
44
53
|
return {
|
|
45
54
|
removedEntities: initialEntityCount - entitiesToKeep.length,
|
|
46
|
-
removedRelations: initialRelationCount -
|
|
55
|
+
removedRelations: initialRelationCount - finalRelationCount
|
|
47
56
|
};
|
|
48
57
|
}
|
|
@@ -5,10 +5,12 @@
|
|
|
5
5
|
* Create new entities in the knowledge graph
|
|
6
6
|
* Entity names are globally unique across all threads in the collaborative knowledge graph
|
|
7
7
|
* This prevents duplicate entities while allowing multiple threads to contribute to the same entity
|
|
8
|
+
* @param threadId - Thread ID passed for context (entities already have agentThreadId set)
|
|
8
9
|
*/
|
|
9
|
-
export async function createEntities(storage, entities) {
|
|
10
|
+
export async function createEntities(storage, threadId, entities) {
|
|
10
11
|
const graph = await storage.loadGraph();
|
|
11
12
|
const existingNames = new Set(graph.entities.map(e => e.name));
|
|
13
|
+
// Filter out entities that already exist, entities are expected to have agentThreadId already set
|
|
12
14
|
const newEntities = entities.filter(e => !existingNames.has(e.name));
|
|
13
15
|
graph.entities.push(...newEntities);
|
|
14
16
|
await storage.saveGraph(graph);
|
|
@@ -17,11 +19,18 @@ export async function createEntities(storage, entities) {
|
|
|
17
19
|
/**
|
|
18
20
|
* Delete entities from the knowledge graph
|
|
19
21
|
* Also removes all relations referencing the deleted entities
|
|
22
|
+
* Thread isolation: Only deletes entities that belong to the specified thread
|
|
20
23
|
*/
|
|
21
|
-
export async function deleteEntities(storage, entityNames) {
|
|
24
|
+
export async function deleteEntities(storage, threadId, entityNames) {
|
|
22
25
|
const graph = await storage.loadGraph();
|
|
23
26
|
const namesToDelete = new Set(entityNames);
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
// Determine which entities will actually be deleted for this thread
|
|
28
|
+
const deletedEntityNames = new Set(graph.entities
|
|
29
|
+
.filter(e => namesToDelete.has(e.name) && e.agentThreadId === threadId)
|
|
30
|
+
.map(e => e.name));
|
|
31
|
+
// Delete entities that were identified for deletion
|
|
32
|
+
graph.entities = graph.entities.filter(e => !deletedEntityNames.has(e.name));
|
|
33
|
+
// Only delete relations that belong to the specified thread and reference actually deleted entities
|
|
34
|
+
graph.relations = graph.relations.filter(r => !(r.agentThreadId === threadId && (deletedEntityNames.has(r.from) || deletedEntityNames.has(r.to))));
|
|
26
35
|
await storage.saveGraph(graph);
|
|
27
36
|
}
|
|
@@ -7,13 +7,15 @@ import { validateObservationNotSuperseded, createObservationVersion } from '../u
|
|
|
7
7
|
/**
|
|
8
8
|
* Add observations to entities
|
|
9
9
|
* Checks for duplicate content and creates version chains when content is updated
|
|
10
|
+
* Thread parameter is used for validation to ensure only entities in the thread are modified
|
|
10
11
|
*/
|
|
11
|
-
export async function addObservations(storage, observations) {
|
|
12
|
+
export async function addObservations(storage, threadId, observations) {
|
|
12
13
|
const graph = await storage.loadGraph();
|
|
13
14
|
const results = observations.map(o => {
|
|
14
|
-
|
|
15
|
+
// Find entity - thread validation happens here to ensure we only modify entities from this thread
|
|
16
|
+
const entity = graph.entities.find(e => e.name === o.entityName && e.agentThreadId === threadId);
|
|
15
17
|
if (!entity) {
|
|
16
|
-
throw new Error(`Entity with name ${o.entityName} not found`);
|
|
18
|
+
throw new Error(`Entity with name ${o.entityName} not found in thread ${threadId}`);
|
|
17
19
|
}
|
|
18
20
|
// Check for existing observations with same content to create version chain
|
|
19
21
|
const newObservations = [];
|
|
@@ -56,11 +58,13 @@ export async function addObservations(storage, observations) {
|
|
|
56
58
|
/**
|
|
57
59
|
* Delete observations from entities
|
|
58
60
|
* Supports deletion by content (backward compatibility) or by ID
|
|
61
|
+
* Thread parameter is used for validation to ensure only entities in the thread are modified
|
|
59
62
|
*/
|
|
60
|
-
export async function deleteObservations(storage, deletions) {
|
|
63
|
+
export async function deleteObservations(storage, threadId, deletions) {
|
|
61
64
|
const graph = await storage.loadGraph();
|
|
62
65
|
deletions.forEach(d => {
|
|
63
|
-
|
|
66
|
+
// Find entity - thread validation happens here to ensure we only modify entities from this thread
|
|
67
|
+
const entity = graph.entities.find(e => e.name === d.entityName && e.agentThreadId === threadId);
|
|
64
68
|
if (entity) {
|
|
65
69
|
// Delete observations by content (for backward compatibility) or by ID
|
|
66
70
|
entity.observations = entity.observations.filter(o => !d.observations.includes(o.content) && !d.observations.includes(o.id));
|
|
@@ -6,8 +6,9 @@ import { createRelationKey } from '../utils/relation-key.js';
|
|
|
6
6
|
* Create new relations in the knowledge graph
|
|
7
7
|
* Relations are globally unique by (from, to, relationType) across all threads
|
|
8
8
|
* This enables multiple threads to collaboratively build the knowledge graph
|
|
9
|
+
* @param threadId - Thread ID passed for context (relations already have agentThreadId set)
|
|
9
10
|
*/
|
|
10
|
-
export async function createRelations(storage, relations) {
|
|
11
|
+
export async function createRelations(storage, threadId, relations) {
|
|
11
12
|
const graph = await storage.loadGraph();
|
|
12
13
|
// Validate that referenced entities exist
|
|
13
14
|
const entityNames = new Set(graph.entities.map(e => e.name));
|
|
@@ -33,13 +34,14 @@ export async function createRelations(storage, relations) {
|
|
|
33
34
|
}
|
|
34
35
|
/**
|
|
35
36
|
* Delete relations from the knowledge graph
|
|
36
|
-
*
|
|
37
|
+
* Thread isolation: Only deletes relations that belong to the specified thread
|
|
37
38
|
*/
|
|
38
|
-
export async function deleteRelations(storage, relations) {
|
|
39
|
+
export async function deleteRelations(storage, threadId, relations) {
|
|
39
40
|
const graph = await storage.loadGraph();
|
|
40
|
-
// Delete relations
|
|
41
|
+
// Delete relations only from the specified thread by matching (from, to, relationType, threadId)
|
|
41
42
|
graph.relations = graph.relations.filter(r => !relations.some(delRelation => r.from === delRelation.from &&
|
|
42
43
|
r.to === delRelation.to &&
|
|
43
|
-
r.relationType === delRelation.relationType
|
|
44
|
+
r.relationType === delRelation.relationType &&
|
|
45
|
+
r.agentThreadId === threadId));
|
|
44
46
|
await storage.saveGraph(graph);
|
|
45
47
|
}
|