a digital person for bluesky

Add halt_activity tool for graceful bot termination

- Create tools/halt.py with halt_activity function
- Register halt_activity tool in register_tools.py
- Add halt detection in bsky.py process_mention function
- Bot exports agent state and exits when halt tool is called

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

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

Changed files
+54 -3
tools
+21 -3
bsky.py
··· 195 None: Failed with non-retryable error, move to errors directory 196 """ 197 try: 198 - logger.info(f"Starting process_mention with notification_data type: {type(notification_data)}") 199 200 # Handle both dict and object inputs for backwards compatibility 201 if isinstance(notification_data, dict): ··· 288 all_handles.update(extract_handles_from_data(thread.model_dump())) 289 unique_handles = list(all_handles) 290 291 - logger.info(f"Found {len(unique_handles)} unique handles in thread: {unique_handles}") 292 293 # Attach user blocks before agent call 294 attached_handles = [] 295 if unique_handles: 296 try: 297 - logger.info(f"Attaching user blocks for handles: {unique_handles}") 298 attach_result = attach_user_blocks(unique_handles, void_agent) 299 attached_handles = unique_handles # Track successfully attached handles 300 logger.debug(f"Attach result: {attach_result}") ··· 389 else: 390 logger.debug(f" {i}. {msg_type}: <no content>") 391 392 # Collect bluesky_reply tool calls 393 if hasattr(message, 'tool_call') and message.tool_call: 394 if message.tool_call.name == 'bluesky_reply':
··· 195 None: Failed with non-retryable error, move to errors directory 196 """ 197 try: 198 + logger.debug(f"Starting process_mention with notification_data type: {type(notification_data)}") 199 200 # Handle both dict and object inputs for backwards compatibility 201 if isinstance(notification_data, dict): ··· 288 all_handles.update(extract_handles_from_data(thread.model_dump())) 289 unique_handles = list(all_handles) 290 291 + logger.debug(f"Found {len(unique_handles)} unique handles in thread: {unique_handles}") 292 293 # Attach user blocks before agent call 294 attached_handles = [] 295 if unique_handles: 296 try: 297 + logger.debug(f"Attaching user blocks for handles: {unique_handles}") 298 attach_result = attach_user_blocks(unique_handles, void_agent) 299 attached_handles = unique_handles # Track successfully attached handles 300 logger.debug(f"Attach result: {attach_result}") ··· 389 else: 390 logger.debug(f" {i}. {msg_type}: <no content>") 391 392 + # Check for halt_activity tool call 393 + if hasattr(message, 'tool_call') and message.tool_call: 394 + if message.tool_call.name == 'halt_activity': 395 + logger.info("🛑 HALT_ACTIVITY TOOL CALLED - TERMINATING BOT") 396 + try: 397 + args = json.loads(message.tool_call.arguments) 398 + reason = args.get('reason', 'Agent requested halt') 399 + logger.info(f"Halt reason: {reason}") 400 + except: 401 + logger.info("Halt reason: <unable to parse>") 402 + 403 + # Export agent state before terminating 404 + export_agent_state(CLIENT, void_agent) 405 + 406 + # Exit the program 407 + logger.info("=== BOT TERMINATED BY AGENT ===") 408 + exit(0) 409 + 410 # Collect bluesky_reply tool calls 411 if hasattr(message, 'tool_call') and message.tool_call: 412 if message.tool_call.name == 'bluesky_reply':
+7
register_tools.py
··· 15 from tools.feed import get_bluesky_feed, FeedArgs 16 from tools.blocks import attach_user_blocks, detach_user_blocks, AttachUserBlocksArgs, DetachUserBlocksArgs 17 from tools.reply import bluesky_reply, ReplyArgs 18 19 load_dotenv() 20 logging.basicConfig(level=logging.INFO) ··· 59 "args_schema": ReplyArgs, 60 "description": "Reply indicator for the Letta agent (1-4 messages, each max 300 chars). Creates threaded replies.", 61 "tags": ["bluesky", "reply", "response"] 62 }, 63 ] 64
··· 15 from tools.feed import get_bluesky_feed, FeedArgs 16 from tools.blocks import attach_user_blocks, detach_user_blocks, AttachUserBlocksArgs, DetachUserBlocksArgs 17 from tools.reply import bluesky_reply, ReplyArgs 18 + from tools.halt import halt_activity, HaltArgs 19 20 load_dotenv() 21 logging.basicConfig(level=logging.INFO) ··· 60 "args_schema": ReplyArgs, 61 "description": "Reply indicator for the Letta agent (1-4 messages, each max 300 chars). Creates threaded replies.", 62 "tags": ["bluesky", "reply", "response"] 63 + }, 64 + { 65 + "func": halt_activity, 66 + "args_schema": HaltArgs, 67 + "description": "Signal to halt all bot activity and terminate bsky.py", 68 + "tags": ["control", "halt", "terminate"] 69 }, 70 ] 71
+26
tools/halt.py
···
··· 1 + """Halt tool for terminating bsky.py activity.""" 2 + from pydantic import BaseModel, Field 3 + 4 + 5 + class HaltArgs(BaseModel): 6 + reason: str = Field( 7 + default="User requested halt", 8 + description="Optional reason for halting activity" 9 + ) 10 + 11 + 12 + def halt_activity(reason: str = "User requested halt") -> str: 13 + """ 14 + Signal to halt all bot activity and terminate bsky.py. 15 + 16 + This tool allows the agent to request termination of the bot process. 17 + The actual termination is handled externally by bsky.py when it detects 18 + this tool being called. 19 + 20 + Args: 21 + reason: Optional reason for halting (default: "User requested halt") 22 + 23 + Returns: 24 + Halt signal message 25 + """ 26 + return f"Halting activity: {reason}"