@@ -806,6 +806,220 @@ def clear_all(self):
806806
807807 self ._log ("Cleared all memory" )
808808
809+ # -------------------------------------------------------------------------
810+ # Selective Deletion
811+ # -------------------------------------------------------------------------
812+
813+ def delete_short_term (self , memory_id : str ) -> bool :
814+ """
815+ Delete a specific short-term memory by ID.
816+
817+ Args:
818+ memory_id: The unique ID of the memory to delete
819+
820+ Returns:
821+ True if memory was found and deleted, False otherwise
822+ """
823+ for i , item in enumerate (self ._short_term ):
824+ if item .id == memory_id :
825+ del self ._short_term [i ]
826+ self ._save_short_term ()
827+ self ._log (f"Deleted short-term memory: { memory_id } " )
828+ return True
829+ return False
830+
831+ def delete_long_term (self , memory_id : str ) -> bool :
832+ """
833+ Delete a specific long-term memory by ID.
834+
835+ Args:
836+ memory_id: The unique ID of the memory to delete
837+
838+ Returns:
839+ True if memory was found and deleted, False otherwise
840+ """
841+ for i , item in enumerate (self ._long_term ):
842+ if item .id == memory_id :
843+ del self ._long_term [i ]
844+ self ._save_long_term ()
845+ self ._log (f"Deleted long-term memory: { memory_id } " )
846+ return True
847+ return False
848+
849+ def delete_entity (self , name : str ) -> bool :
850+ """
851+ Delete an entity by name.
852+
853+ Args:
854+ name: The name of the entity to delete
855+
856+ Returns:
857+ True if entity was found and deleted, False otherwise
858+ """
859+ # Find entity by name using existing helper
860+ entity = self ._find_entity_by_name (name )
861+ if entity :
862+ # Delete by entity's ID (which is the dict key)
863+ if entity .id in self ._entities :
864+ del self ._entities [entity .id ]
865+ self ._save_entities ()
866+ self ._log (f"Deleted entity: { name } " )
867+ return True
868+
869+ # Try direct ID match as fallback (in case name IS the ID)
870+ if name in self ._entities :
871+ del self ._entities [name ]
872+ self ._save_entities ()
873+ self ._log (f"Deleted entity by ID: { name } " )
874+ return True
875+
876+ return False
877+
878+ def delete_episodic (self , memory_id : str , date : Optional [str ] = None ) -> bool :
879+ """
880+ Delete a specific episodic memory by ID.
881+
882+ Args:
883+ memory_id: The unique ID of the memory to delete
884+ date: Optional date string (YYYY-MM-DD) to narrow search
885+
886+ Returns:
887+ True if memory was found and deleted, False otherwise
888+ """
889+ from datetime import datetime , timedelta
890+
891+ # Determine which files to search
892+ if date :
893+ dates_to_check = [date ]
894+ else :
895+ # Check last 30 days
896+ dates_to_check = [
897+ (datetime .now () - timedelta (days = i )).strftime ("%Y-%m-%d" )
898+ for i in range (30 )
899+ ]
900+
901+ for check_date in dates_to_check :
902+ episodic_file = self .episodic_path / f"{ check_date } .json"
903+ if episodic_file .exists ():
904+ data = self ._read_json (episodic_file , [])
905+ for i , item in enumerate (data ):
906+ if item .get ("id" ) == memory_id :
907+ del data [i ]
908+ self ._write_json (episodic_file , data )
909+ self ._log (f"Deleted episodic memory: { memory_id } " )
910+ return True
911+
912+ return False
913+
914+ def delete_memory (
915+ self ,
916+ memory_id : str ,
917+ memory_type : Optional [str ] = None
918+ ) -> bool :
919+ """
920+ Delete a specific memory by ID.
921+
922+ This is the unified deletion method that searches across all memory types.
923+ Use this when you have a memory ID but don't know its type.
924+
925+ Particularly useful for:
926+ - Cleaning up image-based memories after processing to free context window
927+ - Removing outdated or incorrect information
928+ - Privacy compliance (selective erasure)
929+
930+ Args:
931+ memory_id: The unique ID of the memory to delete
932+ memory_type: Optional type hint to narrow search:
933+ 'short_term', 'long_term', 'entity', 'episodic'
934+ If None, searches all types.
935+
936+ Returns:
937+ True if memory was found and deleted, False otherwise
938+
939+ Example:
940+ # Delete a specific memory after processing an image
941+ memory.delete_memory("abc123def456")
942+
943+ # Delete with type hint for faster lookup
944+ memory.delete_memory("abc123", memory_type="short_term")
945+ """
946+ # If type specified, only search that type
947+ if memory_type == "short_term" :
948+ return self .delete_short_term (memory_id )
949+ elif memory_type == "long_term" :
950+ return self .delete_long_term (memory_id )
951+ elif memory_type == "entity" :
952+ return self .delete_entity (memory_id )
953+ elif memory_type == "episodic" :
954+ return self .delete_episodic (memory_id )
955+
956+ # Search all types
957+ if self .delete_short_term (memory_id ):
958+ return True
959+ if self .delete_long_term (memory_id ):
960+ return True
961+ if self .delete_entity (memory_id ):
962+ return True
963+ if self .delete_episodic (memory_id ):
964+ return True
965+
966+ return False
967+
968+ def delete_memories (self , memory_ids : List [str ]) -> int :
969+ """
970+ Delete multiple memories by their IDs.
971+
972+ Args:
973+ memory_ids: List of memory IDs to delete
974+
975+ Returns:
976+ Number of memories successfully deleted
977+ """
978+ count = 0
979+ for memory_id in memory_ids :
980+ if self .delete_memory (memory_id ):
981+ count += 1
982+ return count
983+
984+ def delete_memories_matching (
985+ self ,
986+ query : str ,
987+ memory_types : Optional [List [str ]] = None ,
988+ limit : int = 10
989+ ) -> int :
990+ """
991+ Delete memories matching a search query.
992+
993+ Useful for bulk cleanup of related memories, e.g., all image-related
994+ context after finishing an image analysis session.
995+
996+ Args:
997+ query: Search query to match memories
998+ memory_types: Optional list of types to search
999+ limit: Maximum number of memories to delete
1000+
1001+ Returns:
1002+ Number of memories deleted
1003+ """
1004+ # Search for matching memories
1005+ results = self .search (query , memory_types = memory_types , limit = limit )
1006+
1007+ # Extract IDs and delete
1008+ deleted = 0
1009+ for result in results :
1010+ item = result .get ("item" , {})
1011+ memory_id = item .get ("id" )
1012+ memory_type = result .get ("type" )
1013+
1014+ if memory_id :
1015+ if self .delete_memory (memory_id , memory_type = memory_type ):
1016+ deleted += 1
1017+
1018+ if deleted :
1019+ self ._log (f"Deleted { deleted } memories matching '{ query } '" )
1020+
1021+ return deleted
1022+
8091023 def get_stats (self ) -> Dict [str , Any ]:
8101024 """Get memory statistics."""
8111025 episodic_count = sum (1 for _ in self .episodic_path .glob ("*.json" ))
@@ -1222,6 +1436,66 @@ def handle_command(self, command: str) -> Dict[str, Any]:
12221436 else :
12231437 return {"error" : "Usage: /memory clear [short|all]" }
12241438
1439+ elif action == "delete" :
1440+ if not args :
1441+ return {"error" : "Usage: /memory delete <id> or /memory delete --query <search_query>" }
1442+
1443+ # Handle --query flag for bulk deletion
1444+ if args .startswith ("--query " ):
1445+ query = args [8 :] # Remove "--query " prefix
1446+ if not query :
1447+ return {"error" : "Usage: /memory delete --query <search_query>" }
1448+ deleted = self .delete_memories_matching (query , limit = 10 )
1449+ return {
1450+ "action" : "delete" ,
1451+ "type" : "query" ,
1452+ "query" : query ,
1453+ "deleted_count" : deleted
1454+ }
1455+
1456+ # Single ID deletion
1457+ success = self .delete_memory (args )
1458+ return {
1459+ "action" : "delete" ,
1460+ "type" : "single" ,
1461+ "id" : args ,
1462+ "success" : success
1463+ }
1464+
1465+ elif action == "list" :
1466+ # List memories with IDs for easy deletion reference
1467+ memory_type = args .lower () if args else "all"
1468+ items = []
1469+
1470+ if memory_type in ("all" , "short" , "short_term" ):
1471+ for item in self .get_short_term (limit = 20 ):
1472+ items .append ({
1473+ "type" : "short_term" ,
1474+ "id" : item .id ,
1475+ "content" : item .content [:100 ] + "..." if len (item .content ) > 100 else item .content ,
1476+ "importance" : item .importance
1477+ })
1478+
1479+ if memory_type in ("all" , "long" , "long_term" ):
1480+ for item in self .get_long_term (limit = 20 ):
1481+ items .append ({
1482+ "type" : "long_term" ,
1483+ "id" : item .id ,
1484+ "content" : item .content [:100 ] + "..." if len (item .content ) > 100 else item .content ,
1485+ "importance" : item .importance
1486+ })
1487+
1488+ if memory_type in ("all" , "entity" , "entities" ):
1489+ for name , entity in self ._entities .items ():
1490+ items .append ({
1491+ "type" : "entity" ,
1492+ "id" : name ,
1493+ "name" : entity .name ,
1494+ "entity_type" : entity .entity_type
1495+ })
1496+
1497+ return {"action" : "list" , "filter" : memory_type , "items" : items }
1498+
12251499 elif action == "search" :
12261500 if not args :
12271501 return {"error" : "Usage: /memory search <query>" }
@@ -1274,8 +1548,11 @@ def handle_command(self, command: str) -> Dict[str, Any]:
12741548 "action" : "help" ,
12751549 "commands" : {
12761550 "/memory show" : "Display memory stats and recent items" ,
1551+ "/memory list [short|long|entity]" : "List memories with IDs for deletion" ,
12771552 "/memory add <content>" : "Add to long-term memory" ,
1278- "/memory clear [short|all]" : "Clear memory" ,
1553+ "/memory delete <id>" : "Delete a specific memory by ID" ,
1554+ "/memory delete --query <q>" : "Delete memories matching query" ,
1555+ "/memory clear [short|all]" : "Clear all memory (bulk)" ,
12791556 "/memory search <query>" : "Search memories" ,
12801557 "/memory save <name>" : "Save session" ,
12811558 "/memory resume <name>" : "Resume session" ,
0 commit comments