a digital person for bluesky

Add ignore_notification tool for explicit notification filtering

Implements a new tool that allows the agent to explicitly ignore notifications
without creating a reply. This is particularly useful for filtering out bot
interactions or spam.

Key features:
- New ignore_notification tool with reason and category tracking
- Conflict detection if agent calls both reply and ignore tools
- Ignored notifications are deleted from queue (not moved to no_reply)
- Proper logging and tracking of ignored notifications

The tool helps the agent make deliberate decisions about which notifications
to engage with, improving interaction quality.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

Changed files
+77 -2
tools
+41 -2
bsky.py
··· 491 491 logger.debug(f"Processing {len(message_response.messages)} response messages...") 492 492 493 493 # First pass: collect tool return statuses 494 + ignored_notification = False 495 + ignore_reason = "" 496 + ignore_category = "" 497 + 494 498 for message in message_response.messages: 495 499 if hasattr(message, 'tool_call_id') and hasattr(message, 'status') and hasattr(message, 'name'): 496 500 if message.name == 'add_post_to_bluesky_reply_thread': 497 501 tool_call_results[message.tool_call_id] = message.status 498 502 logger.debug(f"Tool result: {message.tool_call_id} -> {message.status}") 503 + elif message.name == 'ignore_notification': 504 + # Check if the tool was successful 505 + if hasattr(message, 'tool_return') and message.status == 'success': 506 + # Parse the return value to extract category and reason 507 + result_str = str(message.tool_return) 508 + if 'IGNORED_NOTIFICATION::' in result_str: 509 + parts = result_str.split('::') 510 + if len(parts) >= 3: 511 + ignore_category = parts[1] 512 + ignore_reason = parts[2] 513 + ignored_notification = True 514 + logger.info(f"🚫 Notification ignored - Category: {ignore_category}, Reason: {ignore_reason}") 499 515 elif message.name == 'bluesky_reply': 500 516 logger.error("❌ DEPRECATED TOOL DETECTED: bluesky_reply is no longer supported!") 501 517 logger.error("Please use add_post_to_bluesky_reply_thread instead.") ··· 584 600 else: 585 601 logger.warning(f"⚠️ Skipping add_post_to_bluesky_reply_thread tool call with unknown status: {tool_status}") 586 602 603 + # Check for conflicting tool calls 604 + if reply_candidates and ignored_notification: 605 + logger.error(f"⚠️ CONFLICT: Agent called both add_post_to_bluesky_reply_thread and ignore_notification!") 606 + logger.error(f"Reply candidates: {len(reply_candidates)}, Ignore reason: {ignore_reason}") 607 + logger.warning("Item will be left in queue for manual review") 608 + # Return False to keep in queue 609 + return False 610 + 587 611 if reply_candidates: 588 612 # Aggregate reply posts into a thread 589 613 reply_messages = [] ··· 642 666 logger.error(f"Failed to send reply to @{author_handle}") 643 667 return False 644 668 else: 645 - logger.warning(f"No add_post_to_bluesky_reply_thread tool calls found for mention from @{author_handle}, moving to no_reply folder") 646 - return "no_reply" 669 + # Check if notification was explicitly ignored 670 + if ignored_notification: 671 + logger.info(f"Notification from @{author_handle} was explicitly ignored (category: {ignore_category})") 672 + return "ignored" 673 + else: 674 + logger.warning(f"No add_post_to_bluesky_reply_thread tool calls found for mention from @{author_handle}, moving to no_reply folder") 675 + return "no_reply" 647 676 648 677 except Exception as e: 649 678 logger.error(f"Error processing mention: {e}") ··· 840 869 no_reply_path = QUEUE_NO_REPLY_DIR / filepath.name 841 870 filepath.rename(no_reply_path) 842 871 logger.info(f"📭 Moved {filepath.name} to no_reply directory") 872 + 873 + # Also mark as processed to avoid retrying 874 + processed_uris = load_processed_notifications() 875 + processed_uris.add(notif_data['uri']) 876 + save_processed_notifications(processed_uris) 877 + 878 + elif success == "ignored": # Special case for explicitly ignored notifications 879 + # For ignored notifications, we just delete them (not move to no_reply) 880 + filepath.unlink() 881 + logger.info(f"🚫 Deleted ignored notification: {filepath.name}") 843 882 844 883 # Also mark as processed to avoid retrying 845 884 processed_uris = load_processed_notifications()
+7
register_tools.py
··· 16 16 from tools.blocks import attach_user_blocks, detach_user_blocks, user_note_append, user_note_replace, user_note_set, user_note_view, AttachUserBlocksArgs, DetachUserBlocksArgs, UserNoteAppendArgs, UserNoteReplaceArgs, UserNoteSetArgs, UserNoteViewArgs 17 17 from tools.halt import halt_activity, HaltArgs 18 18 from tools.thread import add_post_to_bluesky_reply_thread, ReplyThreadPostArgs 19 + from tools.ignore import ignore_notification, IgnoreNotificationArgs 19 20 20 21 load_dotenv() 21 22 logging.basicConfig(level=logging.INFO) ··· 90 91 "args_schema": ReplyThreadPostArgs, 91 92 "description": "Add a single post to the current Bluesky reply thread atomically", 92 93 "tags": ["bluesky", "reply", "thread", "atomic"] 94 + }, 95 + { 96 + "func": ignore_notification, 97 + "args_schema": IgnoreNotificationArgs, 98 + "description": "Explicitly ignore a notification without replying (useful for ignoring bot interactions)", 99 + "tags": ["notification", "ignore", "control", "bot"] 93 100 }, 94 101 ] 95 102
+29
tools/ignore.py
··· 1 + """Ignore notification tool for Bluesky.""" 2 + from pydantic import BaseModel, Field 3 + from typing import Optional 4 + 5 + 6 + class IgnoreNotificationArgs(BaseModel): 7 + reason: str = Field(..., description="Reason for ignoring this notification") 8 + category: Optional[str] = Field( 9 + default="bot", 10 + description="Category of ignored notification (e.g., 'bot', 'spam', 'not_relevant', 'handled_elsewhere')" 11 + ) 12 + 13 + 14 + def ignore_notification(reason: str, category: str = "bot") -> str: 15 + """ 16 + Signal that the current notification should be ignored without a reply. 17 + 18 + This tool allows the agent to explicitly mark a notification as ignored 19 + rather than having it default to the no_reply folder. This is particularly 20 + useful for ignoring interactions from bots or spam accounts. 21 + 22 + Args: 23 + reason: Reason for ignoring this notification 24 + category: Category of ignored notification (default: 'bot') 25 + 26 + Returns: 27 + Confirmation message 28 + """ 29 + return f"IGNORED_NOTIFICATION::{category}::{reason}"