From f9a797d5f747792caf90f831cbb1e11026633286 Mon Sep 17 00:00:00 2001 From: Cameron Pfiffer Date: Sun, 19 Oct 2025 12:13:50 -0700 Subject: [PATCH] Add flag_archival_memory_for_deletion tool for memory cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements a new tool that allows the agent to flag archival memories for deletion based on exact text matching. The tool works as a no-op that signals the bot loop to perform deletions at the end of the turn. Changes: - Created tools/flag_memory_deletion.py with the tool definition - Updated register_tools.py to include the new tool - Modified bsky.py to handle flagged memory deletion: - Tracks flagged memories during message processing - Searches for exact text matches using passages.list() - Deletes all matching passages using passages.delete() - Executes after message processing but before reply handling - Automatically skipped if halt_activity is called (due to immediate exit) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- bsky.py | 51 +++++++++++++++++++++++++++++++++++ register_tools.py | 7 +++++ tools/flag_memory_deletion.py | 30 +++++++++++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 tools/flag_memory_deletion.py diff --git a/bsky.py b/bsky.py index 0ea2b5e..e6ed65c 100644 --- a/bsky.py +++ b/bsky.py @@ -661,6 +661,7 @@ To reply, use the add_post_to_bluesky_reply_thread tool: reply_candidates = [] tool_call_results = {} # Map tool_call_id to status ack_note = None # Track any note from annotate_ack tool + flagged_memories = [] # Track memories flagged for deletion logger.debug(f"Processing {len(message_response.messages)} response messages...") @@ -766,6 +767,17 @@ To reply, use the add_post_to_bluesky_reply_thread tool: logger.debug(f"Found annotate_ack with note: {note[:50]}...") except json.JSONDecodeError as e: logger.error(f"Failed to parse annotate_ack arguments: {e}") + + # Collect flag_archival_memory_for_deletion tool calls + elif message.tool_call.name == 'flag_archival_memory_for_deletion': + try: + args = json.loads(message.tool_call.arguments) + memory_text = args.get('memory_text', '') + if memory_text: + flagged_memories.append(memory_text) + logger.debug(f"Found memory flagged for deletion: {memory_text[:50]}...") + except json.JSONDecodeError as e: + logger.error(f"Failed to parse flag_archival_memory_for_deletion arguments: {e}") # Collect add_post_to_bluesky_reply_thread tool calls - only if they were successful elif message.tool_call.name == 'add_post_to_bluesky_reply_thread': @@ -788,6 +800,45 @@ To reply, use the add_post_to_bluesky_reply_thread tool: else: logger.warning(f"⚠️ Skipping add_post_to_bluesky_reply_thread tool call with unknown status: {tool_status}") + # Handle archival memory deletion if any were flagged (only if no halt was received) + if flagged_memories: + logger.info(f"Processing {len(flagged_memories)} flagged memories for deletion") + for memory_text in flagged_memories: + try: + # Search for passages with this exact text + logger.debug(f"Searching for passages matching: {memory_text[:100]}...") + passages = CLIENT.agents.passages.list( + agent_id=void_agent.id, + query=memory_text + ) + + if not passages: + logger.warning(f"No passages found matching flagged memory: {memory_text[:50]}...") + continue + + # Delete all matching passages + deleted_count = 0 + for passage in passages: + # Check if the passage text exactly matches (to avoid partial matches) + if hasattr(passage, 'text') and passage.text == memory_text: + try: + CLIENT.agents.passages.delete( + agent_id=void_agent.id, + passage_id=str(passage.id) + ) + deleted_count += 1 + logger.debug(f"Deleted passage {passage.id}") + except Exception as delete_error: + logger.error(f"Failed to delete passage {passage.id}: {delete_error}") + + if deleted_count > 0: + logger.info(f"🗑️ Deleted {deleted_count} archival memory passage(s): {memory_text[:50]}...") + else: + logger.warning(f"No exact matches found for deletion: {memory_text[:50]}...") + + except Exception as e: + logger.error(f"Error processing memory deletion: {e}") + # Check for conflicting tool calls if reply_candidates and ignored_notification: logger.error(f"⚠️ CONFLICT: Agent called both add_post_to_bluesky_reply_thread and ignore_notification!") diff --git a/register_tools.py b/register_tools.py index 7bc6a32..3bbab55 100755 --- a/register_tools.py +++ b/register_tools.py @@ -20,6 +20,7 @@ from tools.ignore import ignore_notification, IgnoreNotificationArgs from tools.whitewind import create_whitewind_blog_post, WhitewindPostArgs from tools.ack import annotate_ack, AnnotateAckArgs from tools.webpage import fetch_webpage, WebpageArgs +from tools.flag_memory_deletion import flag_archival_memory_for_deletion, FlagArchivalMemoryForDeletionArgs letta_config = get_letta_config() logging.basicConfig(level=logging.INFO) @@ -115,6 +116,12 @@ TOOL_CONFIGS = [ "description": "Fetch a webpage and convert it to markdown/text format using Jina AI reader", "tags": ["web", "fetch", "webpage", "markdown", "jina"] }, + { + "func": flag_archival_memory_for_deletion, + "args_schema": FlagArchivalMemoryForDeletionArgs, + "description": "Flag an archival memory for deletion based on its exact text content", + "tags": ["memory", "archival", "delete", "cleanup"] + }, ] diff --git a/tools/flag_memory_deletion.py b/tools/flag_memory_deletion.py new file mode 100644 index 0000000..e288816 --- /dev/null +++ b/tools/flag_memory_deletion.py @@ -0,0 +1,30 @@ +"""Flag archival memory for deletion tool.""" +from pydantic import BaseModel, Field + + +class FlagArchivalMemoryForDeletionArgs(BaseModel): + memory_text: str = Field( + ..., + description="The exact text content of the archival memory to delete" + ) + + +def flag_archival_memory_for_deletion(memory_text: str) -> str: + """ + Flag an archival memory for deletion based on its exact text content. + + This is a "dummy" tool that doesn't directly delete memories but signals to the system + that the specified memory should be deleted at the end of the turn (if no halt_activity + has been received). + + The system will search for all archival memories with this exact text and delete them. + + Args: + memory_text: The exact text content of the archival memory to delete + + Returns: + Confirmation message + """ + # This is a dummy tool - it just returns a confirmation + # The actual deletion will be handled by the bot loop after the agent's turn completes + return f"Memory flagged for deletion. Will be removed at the end of this turn if no halt is received." -- 2.51.2