aim_memory_forget
Remove specified entities and their associated links from the knowledge graph to forget unwanted memories.
Instructions
Forget memories. Removes memories and their associated links.
DATABASE SELECTION: Entities are deleted from the specified database's knowledge graph.
LOCATION OVERRIDE: Use the 'location' parameter to force deletion from 'project' (.aim directory) or 'global' (configured directory). Leave blank for auto-detection.
EXAMPLES:
Master database (default): aim_memory_forget({entityNames: ["OldProject"]})
Work database: aim_memory_forget({context: "work", entityNames: ["CompletedTask", "CancelledMeeting"]})
Master database in global location: aim_memory_forget({location: "global", entityNames: ["OldProject"]})
Personal database in project location: aim_memory_forget({context: "personal", location: "project", entityNames: ["ExpiredReminder"]})
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| context | No | Optional memory context. Entities will be deleted from the specified context's knowledge graph. | |
| location | No | Optional storage location override. 'project' forces project-local .aim directory, 'global' forces global directory. If not specified, uses automatic detection. | |
| entityNames | Yes | An array of entity names to delete |
Implementation Reference
- index.ts:254-259 (handler)The actual handler/implementation of deleteEntities - this is the core logic executed when aim_memory_forget is called. It filters out entities by name from the graph and also removes any relations referencing those entities, then saves.
async deleteEntities(entityNames: string[], context?: string, location?: 'project' | 'global'): Promise<void> { const graph = await this.loadGraph(context, location); graph.entities = graph.entities.filter(e => !entityNames.includes(e.name)); graph.relations = graph.relations.filter(r => !entityNames.includes(r.from) && !entityNames.includes(r.to)); await this.saveGraph(graph, context, location); } - index.ts:822-824 (handler)The tool handler in the CallToolRequestSchema switch-case that dispatches 'aim_memory_forget' to knowledgeGraphManager.deleteEntities().
case "aim_memory_forget": await knowledgeGraphManager.deleteEntities(args.entityNames as string[], args.context as string, args.location as 'project' | 'global'); return { content: [{ type: "text", text: "Entities deleted successfully" }] }; - index.ts:545-578 (schema)Tool definition and inputSchema for 'aim_memory_forget', including description, examples, and input validation schema with context, location, and entityNames parameters.
{ name: "aim_memory_forget", description: `Forget memories. Removes memories and their associated links. DATABASE SELECTION: Entities are deleted from the specified database's knowledge graph. LOCATION OVERRIDE: Use the 'location' parameter to force deletion from 'project' (.aim directory) or 'global' (configured directory). Leave blank for auto-detection. EXAMPLES: - Master database (default): aim_memory_forget({entityNames: ["OldProject"]}) - Work database: aim_memory_forget({context: "work", entityNames: ["CompletedTask", "CancelledMeeting"]}) - Master database in global location: aim_memory_forget({location: "global", entityNames: ["OldProject"]}) - Personal database in project location: aim_memory_forget({context: "personal", location: "project", entityNames: ["ExpiredReminder"]})`, inputSchema: { type: "object", properties: { context: { type: "string", description: "Optional memory context. Entities will be deleted from the specified context's knowledge graph." }, location: { type: "string", enum: ["project", "global"], description: "Optional storage location override. 'project' forces project-local .aim directory, 'global' forces global directory. If not specified, uses automatic detection." }, entityNames: { type: "array", items: { type: "string" }, description: "An array of entity names to delete" }, }, required: ["entityNames"], }, }, - index.ts:168-378 (helper)The KnowledgeGraphManager class that contains the deleteEntities method. This is the helper class managing all graph operations including loadGraph and saveGraph used by deleteEntities.
class KnowledgeGraphManager { private async loadGraph(context?: string, location?: 'project' | 'global'): Promise<KnowledgeGraph> { const filePath = getMemoryFilePath(context, location); try { const data = await fs.readFile(filePath, "utf-8"); const lines = data.split("\n").filter(line => line.trim() !== ""); if (lines.length === 0) { return { entities: [], relations: [] }; } // Check first line for our file marker const firstLine = JSON.parse(lines[0]!); if (firstLine.type !== "_aim" || firstLine.source !== "mcp-knowledge-graph") { throw new Error(`File ${filePath} does not contain required _aim safety marker. This file may not belong to the knowledge graph system. Expected first line: {"type":"_aim","source":"mcp-knowledge-graph"}`); } // Process remaining lines (skip metadata) return lines.slice(1).reduce((graph: KnowledgeGraph, line) => { const item = JSON.parse(line); if (item.type === "entity") graph.entities.push(item as Entity); if (item.type === "relation") graph.relations.push(item as Relation); return graph; }, { entities: [], relations: [] }); } catch (error) { if (error instanceof Error && 'code' in error && (error as any).code === "ENOENT") { // File doesn't exist - we'll create it with metadata on first save return { entities: [], relations: [] }; } throw error; } } private async saveGraph(graph: KnowledgeGraph, context?: string, location?: 'project' | 'global'): Promise<void> { const filePath = getMemoryFilePath(context, location); // Write our simple file marker const lines = [ JSON.stringify(FILE_MARKER), ...graph.entities.map(e => JSON.stringify({ type: "entity", ...e })), ...graph.relations.map(r => JSON.stringify({ type: "relation", ...r })), ]; // Ensure directory exists await fs.mkdir(path.dirname(filePath), { recursive: true }); await fs.writeFile(filePath, lines.join("\n")); } async createEntities(entities: Entity[], context?: string, location?: 'project' | 'global'): Promise<Entity[]> { const graph = await this.loadGraph(context, location); const newEntities = entities.filter(e => !graph.entities.some(existingEntity => existingEntity.name === e.name)); graph.entities.push(...newEntities); await this.saveGraph(graph, context, location); return newEntities; } async createRelations(relations: Relation[], context?: string, location?: 'project' | 'global'): Promise<Relation[]> { const graph = await this.loadGraph(context, location); const newRelations = relations.filter(r => !graph.relations.some(existingRelation => existingRelation.from === r.from && existingRelation.to === r.to && existingRelation.relationType === r.relationType )); graph.relations.push(...newRelations); await this.saveGraph(graph, context, location); return newRelations; } async addObservations(observations: { entityName: string; contents: string[] }[], context?: string, location?: 'project' | 'global'): Promise<{ entityName: string; addedObservations: string[] }[]> { const graph = await this.loadGraph(context, location); const results = observations.map(o => { const entity = graph.entities.find(e => e.name === o.entityName); if (!entity) { throw new Error(`Entity with name ${o.entityName} not found`); } const newObservations = o.contents.filter(content => !entity.observations.includes(content)); entity.observations.push(...newObservations); return { entityName: o.entityName, addedObservations: newObservations }; }); await this.saveGraph(graph, context, location); return results; } async deleteEntities(entityNames: string[], context?: string, location?: 'project' | 'global'): Promise<void> { const graph = await this.loadGraph(context, location); graph.entities = graph.entities.filter(e => !entityNames.includes(e.name)); graph.relations = graph.relations.filter(r => !entityNames.includes(r.from) && !entityNames.includes(r.to)); await this.saveGraph(graph, context, location); } async deleteObservations(deletions: { entityName: string; observations: string[] }[], context?: string, location?: 'project' | 'global'): Promise<void> { const graph = await this.loadGraph(context, location); deletions.forEach(d => { const entity = graph.entities.find(e => e.name === d.entityName); if (entity) { entity.observations = entity.observations.filter(o => !d.observations.includes(o)); } }); await this.saveGraph(graph, context, location); } async deleteRelations(relations: Relation[], context?: string, location?: 'project' | 'global'): Promise<void> { const graph = await this.loadGraph(context, location); graph.relations = graph.relations.filter(r => !relations.some(delRelation => r.from === delRelation.from && r.to === delRelation.to && r.relationType === delRelation.relationType )); await this.saveGraph(graph, context, location); } async readGraph(context?: string, location?: 'project' | 'global'): Promise<KnowledgeGraph> { return this.loadGraph(context, location); } // Very basic search function async searchNodes(query: string, context?: string, location?: 'project' | 'global'): Promise<KnowledgeGraph> { const graph = await this.loadGraph(context, location); // Filter entities const filteredEntities = graph.entities.filter(e => e.name.toLowerCase().includes(query.toLowerCase()) || e.entityType.toLowerCase().includes(query.toLowerCase()) || e.observations.some(o => o.toLowerCase().includes(query.toLowerCase())) ); // Create a Set of filtered entity names for quick lookup const filteredEntityNames = new Set(filteredEntities.map(e => e.name)); // Filter relations to only include those between filtered entities const filteredRelations = graph.relations.filter(r => filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to) ); const filteredGraph: KnowledgeGraph = { entities: filteredEntities, relations: filteredRelations, }; return filteredGraph; } async openNodes(names: string[], context?: string, location?: 'project' | 'global'): Promise<KnowledgeGraph> { const graph = await this.loadGraph(context, location); // Filter entities const filteredEntities = graph.entities.filter(e => names.includes(e.name)); // Create a Set of filtered entity names for quick lookup const filteredEntityNames = new Set(filteredEntities.map(e => e.name)); // Filter relations to only include those between filtered entities const filteredRelations = graph.relations.filter(r => filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to) ); const filteredGraph: KnowledgeGraph = { entities: filteredEntities, relations: filteredRelations, }; return filteredGraph; } async listDatabases(): Promise<{ project_databases: string[], global_databases: string[], current_location: string }> { const result = { project_databases: [] as string[], global_databases: [] as string[], current_location: "" }; // Check project-local .aim directory const projectRoot = findProjectRoot(); if (projectRoot) { const aimDir = path.join(projectRoot, '.aim'); if (existsSync(aimDir)) { result.current_location = "project (.aim directory detected)"; try { const files = await fs.readdir(aimDir); result.project_databases = files .filter(file => file.endsWith('.jsonl')) .map(file => file === 'memory.jsonl' ? 'default' : file.replace('memory-', '').replace('.jsonl', '')) .sort(); } catch (error) { // Directory exists but can't read - ignore } } else { result.current_location = "global (no .aim directory in project)"; } } else { result.current_location = "global (no project detected)"; } // Check global directory try { const files = await fs.readdir(baseMemoryPath); result.global_databases = files .filter(file => file.endsWith('.jsonl')) .map(file => file === 'memory.jsonl' ? 'default' : file.replace('memory-', '').replace('.jsonl', '')) .sort(); } catch (error) { // Directory doesn't exist or can't read result.global_databases = []; } return result; } } - index.ts:82-111 (helper)The getMemoryFilePath helper function used by deleteEntities (via loadGraph/saveGraph) to resolve the file path based on context and location parameters.
function getMemoryFilePath(context?: string, location?: 'project' | 'global'): string { const filename = context ? `memory-${context}.jsonl` : 'memory.jsonl'; // If location is explicitly specified, use it if (location === 'global') { return path.join(baseMemoryPath, filename); } if (location === 'project') { const projectRoot = findProjectRoot(); if (projectRoot) { const aimDir = path.join(projectRoot, '.aim'); return path.join(aimDir, filename); // Will create .aim if it doesn't exist } else { throw new Error('No project detected - cannot use project location'); } } // Auto-detect logic (existing behavior) const projectRoot = findProjectRoot(); if (projectRoot) { const aimDir = path.join(projectRoot, '.aim'); if (existsSync(aimDir)) { return path.join(aimDir, filename); } } // Fallback to configured base directory return path.join(baseMemoryPath, filename); }