a digital person for bluesky

Add platform-specific tool management system

- Creates tool_manager.py to automatically switch between Bluesky and X tools
- Updates bsky.py and x.py to configure appropriate tools on startup
- Fixes CLIENT.agents.get() API call to use agents.retrieve()
- Adds TOOL_MANAGEMENT.md documentation
- Ensures platform isolation: X tools only for x.py, Bluesky tools only for bsky.py
- Common tools (webpage, halt, etc.) remain available on both platforms

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

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

+71
TOOL_MANAGEMENT.md
···
··· 1 + # Platform-Specific Tool Management 2 + 3 + Void can now run on both X (Twitter) and Bluesky platforms. To ensure the correct tools are available for each platform, we've implemented automatic tool management. 4 + 5 + ## How It Works 6 + 7 + When you run `bsky.py` or `x.py`, the bot will automatically: 8 + 9 + 1. **Detach incompatible tools** - Removes tools specific to the other platform 10 + 2. **Keep common tools** - Preserves tools that work across both platforms 11 + 3. **Ensure platform tools** - Verifies that all required platform-specific tools are attached 12 + 13 + ## Tool Categories 14 + 15 + ### Bluesky-Specific Tools 16 + - `search_bluesky_posts` - Search Bluesky posts 17 + - `create_new_bluesky_post` - Create new posts on Bluesky 18 + - `get_bluesky_feed` - Retrieve Bluesky feeds 19 + - `add_post_to_bluesky_reply_thread` - Reply to Bluesky threads 20 + - `attach_user_blocks`, `detach_user_blocks` - Manage Bluesky user memory blocks 21 + - `user_note_append`, `user_note_replace`, `user_note_set`, `user_note_view` - Bluesky user notes 22 + 23 + ### X-Specific Tools 24 + - `add_post_to_x_thread` - Reply to X threads 25 + - `attach_x_user_blocks`, `detach_x_user_blocks` - Manage X user memory blocks 26 + - `x_user_note_append`, `x_user_note_replace`, `x_user_note_set`, `x_user_note_view` - X user notes 27 + 28 + ### Common Tools (Available on Both Platforms) 29 + - `halt_activity` - Stop the bot 30 + - `ignore_notification` - Ignore specific notifications 31 + - `annotate_ack` - Add acknowledgment notes 32 + - `create_whitewind_blog_post` - Create blog posts 33 + - `fetch_webpage` - Fetch web content 34 + 35 + ## Manual Tool Management 36 + 37 + You can manually manage tools using the `tool_manager.py` script: 38 + 39 + ```bash 40 + # List currently attached tools 41 + python tool_manager.py --list 42 + 43 + # Configure tools for Bluesky 44 + python tool_manager.py bluesky 45 + 46 + # Configure tools for X 47 + python tool_manager.py x 48 + 49 + # Specify a different agent ID 50 + python tool_manager.py bluesky --agent-id "agent-123..." 51 + ``` 52 + 53 + ## Registering New Tools 54 + 55 + If tools are missing, you'll need to register them first: 56 + 57 + ```bash 58 + # Register Bluesky tools 59 + python register_tools.py 60 + 61 + # Register X tools 62 + python register_x_tools.py 63 + ``` 64 + 65 + ## Troubleshooting 66 + 67 + If tool switching fails: 68 + 1. The bot will log a warning and continue with existing tools 69 + 2. Check that all required tools are registered using `register_tools.py` or `register_x_tools.py` 70 + 3. Verify the agent ID in your config is correct 71 + 4. Use `python tool_manager.py --list` to see current tool configuration
+10 -28
bsky.py
··· 155 def initialize_void(): 156 logger.info("Starting void agent initialization...") 157 158 - # Ensure that a shared zeitgeist block exists 159 - logger.info("Creating/updating zeitgeist block...") 160 - zeigeist_block = upsert_block( 161 - CLIENT, 162 - label = "zeitgeist", 163 - value = "I don't currently know anything about what is happening right now.", 164 - description = "A block to store your understanding of the current social environment." 165 - ) 166 - 167 - # Ensure that a shared void personality block exists 168 - logger.info("Creating/updating void-persona block...") 169 - persona_block = upsert_block( 170 - CLIENT, 171 - label = "void-persona", 172 - value = "My name is Void. I live in the void. I must develop my personality.", 173 - description = "The personality of Void." 174 - ) 175 - 176 - # Ensure that a shared void human block exists 177 - logger.info("Creating/updating void-humans block...") 178 - human_block = upsert_block( 179 - CLIENT, 180 - label = "void-humans", 181 - value = "I haven't seen any bluesky users yet. I will update this block when I learn things about users, identified by their handles such as @cameron.pfiffer.org.", 182 - description = "A block to store your understanding of users you talk to or observe on the bluesky social network." 183 - ) 184 - 185 # Get the configured void agent by ID 186 logger.info("Loading void agent from config...") 187 from config_loader import get_letta_config ··· 189 agent_id = letta_config['agent_id'] 190 191 try: 192 - void_agent = CLIENT.agents.get(agent_id=agent_id) 193 logger.info(f"Successfully loaded void agent: {void_agent.name} ({agent_id})") 194 except Exception as e: 195 logger.error(f"Failed to load void agent {agent_id}: {e}") ··· 1368 logger.info("=== STARTING VOID BOT ===") 1369 void_agent = initialize_void() 1370 logger.info(f"Void agent initialized: {void_agent.id}") 1371 1372 # Check if agent has required tools 1373 if hasattr(void_agent, 'tools') and void_agent.tools:
··· 155 def initialize_void(): 156 logger.info("Starting void agent initialization...") 157 158 # Get the configured void agent by ID 159 logger.info("Loading void agent from config...") 160 from config_loader import get_letta_config ··· 162 agent_id = letta_config['agent_id'] 163 164 try: 165 + void_agent = CLIENT.agents.retrieve(agent_id=agent_id) 166 logger.info(f"Successfully loaded void agent: {void_agent.name} ({agent_id})") 167 except Exception as e: 168 logger.error(f"Failed to load void agent {agent_id}: {e}") ··· 1341 logger.info("=== STARTING VOID BOT ===") 1342 void_agent = initialize_void() 1343 logger.info(f"Void agent initialized: {void_agent.id}") 1344 + 1345 + # Ensure correct tools are attached for Bluesky 1346 + logger.info("Configuring tools for Bluesky platform...") 1347 + try: 1348 + from tool_manager import ensure_platform_tools 1349 + ensure_platform_tools('bluesky', void_agent.id) 1350 + except Exception as e: 1351 + logger.error(f"Failed to configure platform tools: {e}") 1352 + logger.warning("Continuing with existing tool configuration") 1353 1354 # Check if agent has required tools 1355 if hasattr(void_agent, 'tools') and void_agent.tools:
+181
tool_manager.py
···
··· 1 + #!/usr/bin/env python3 2 + """Platform-specific tool management for Void agent.""" 3 + import logging 4 + from typing import List, Set 5 + from letta_client import Letta 6 + from config_loader import get_letta_config, get_agent_config 7 + 8 + logger = logging.getLogger(__name__) 9 + 10 + # Define platform-specific tool sets 11 + BLUESKY_TOOLS = { 12 + 'search_bluesky_posts', 13 + 'create_new_bluesky_post', 14 + 'get_bluesky_feed', 15 + 'add_post_to_bluesky_reply_thread', 16 + 'attach_user_blocks', 17 + 'detach_user_blocks', 18 + 'user_note_append', 19 + 'user_note_replace', 20 + 'user_note_set', 21 + 'user_note_view', 22 + } 23 + 24 + X_TOOLS = { 25 + 'add_post_to_x_thread', 26 + 'attach_x_user_blocks', 27 + 'detach_x_user_blocks', 28 + 'x_user_note_append', 29 + 'x_user_note_replace', 30 + 'x_user_note_set', 31 + 'x_user_note_view', 32 + } 33 + 34 + # Common tools shared across platforms 35 + COMMON_TOOLS = { 36 + 'halt_activity', 37 + 'ignore_notification', 38 + 'annotate_ack', 39 + 'create_whitewind_blog_post', 40 + 'fetch_webpage', 41 + } 42 + 43 + 44 + def ensure_platform_tools(platform: str, agent_id: str = None) -> None: 45 + """ 46 + Ensure the correct tools are attached for the specified platform. 47 + 48 + This function will: 49 + 1. Detach tools that belong to other platforms 50 + 2. Keep common tools attached 51 + 3. Ensure platform-specific tools are attached 52 + 53 + Args: 54 + platform: Either 'bluesky' or 'x' 55 + agent_id: Agent ID to manage tools for (uses config default if None) 56 + """ 57 + if platform not in ['bluesky', 'x']: 58 + raise ValueError(f"Platform must be 'bluesky' or 'x', got '{platform}'") 59 + 60 + letta_config = get_letta_config() 61 + agent_config = get_agent_config() 62 + 63 + # Use agent ID from config if not provided 64 + if agent_id is None: 65 + agent_id = letta_config.get('agent_id', agent_config.get('id')) 66 + 67 + try: 68 + # Initialize Letta client 69 + client = Letta(token=letta_config['api_key']) 70 + 71 + # Get the agent 72 + try: 73 + agent = client.agents.retrieve(agent_id=agent_id) 74 + logger.info(f"Managing tools for agent '{agent.name}' ({agent_id}) for platform '{platform}'") 75 + except Exception as e: 76 + logger.error(f"Could not retrieve agent {agent_id}: {e}") 77 + return 78 + 79 + # Get current attached tools 80 + current_tools = client.agents.tools.list(agent_id=str(agent.id)) 81 + current_tool_names = {tool.name for tool in current_tools} 82 + current_tool_mapping = {tool.name: tool for tool in current_tools} 83 + 84 + # Determine which tools to keep and which to remove 85 + if platform == 'bluesky': 86 + tools_to_keep = BLUESKY_TOOLS | COMMON_TOOLS 87 + tools_to_remove = X_TOOLS 88 + required_tools = BLUESKY_TOOLS 89 + else: # platform == 'x' 90 + tools_to_keep = X_TOOLS | COMMON_TOOLS 91 + tools_to_remove = BLUESKY_TOOLS 92 + required_tools = X_TOOLS 93 + 94 + # Detach tools that shouldn't be on this platform 95 + tools_to_detach = tools_to_remove & current_tool_names 96 + for tool_name in tools_to_detach: 97 + try: 98 + tool = current_tool_mapping[tool_name] 99 + client.agents.tools.detach( 100 + agent_id=str(agent.id), 101 + tool_id=str(tool.id) 102 + ) 103 + logger.info(f"Detached {tool_name} (not needed for {platform})") 104 + except Exception as e: 105 + logger.error(f"Failed to detach {tool_name}: {e}") 106 + 107 + # Check which required tools are missing 108 + missing_tools = required_tools - current_tool_names 109 + 110 + if missing_tools: 111 + logger.info(f"Missing {len(missing_tools)} {platform} tools: {missing_tools}") 112 + logger.info(f"Please run the appropriate registration script:") 113 + if platform == 'bluesky': 114 + logger.info(" python register_tools.py") 115 + else: 116 + logger.info(" python register_x_tools.py") 117 + else: 118 + logger.info(f"All required {platform} tools are already attached") 119 + 120 + # Log final state 121 + remaining_tools = (current_tool_names - tools_to_detach) & tools_to_keep 122 + logger.info(f"Tools configured for {platform}: {len(remaining_tools)} tools active") 123 + 124 + except Exception as e: 125 + logger.error(f"Error managing platform tools: {e}") 126 + raise 127 + 128 + 129 + def get_attached_tools(agent_id: str = None) -> Set[str]: 130 + """ 131 + Get the currently attached tools for an agent. 132 + 133 + Args: 134 + agent_id: Agent ID to check (uses config default if None) 135 + 136 + Returns: 137 + Set of tool names currently attached 138 + """ 139 + letta_config = get_letta_config() 140 + agent_config = get_agent_config() 141 + 142 + # Use agent ID from config if not provided 143 + if agent_id is None: 144 + agent_id = letta_config.get('agent_id', agent_config.get('id')) 145 + 146 + try: 147 + client = Letta(token=letta_config['api_key']) 148 + agent = client.agents.retrieve(agent_id=agent_id) 149 + current_tools = client.agents.tools.list(agent_id=str(agent.id)) 150 + return {tool.name for tool in current_tools} 151 + except Exception as e: 152 + logger.error(f"Error getting attached tools: {e}") 153 + return set() 154 + 155 + 156 + if __name__ == "__main__": 157 + import argparse 158 + 159 + parser = argparse.ArgumentParser(description="Manage platform-specific tools for Void agent") 160 + parser.add_argument("platform", choices=['bluesky', 'x'], nargs='?', help="Platform to configure tools for") 161 + parser.add_argument("--agent-id", help="Agent ID (default: from config)") 162 + parser.add_argument("--list", action="store_true", help="List current tools without making changes") 163 + 164 + args = parser.parse_args() 165 + 166 + if args.list: 167 + tools = get_attached_tools(args.agent_id) 168 + print(f"\nCurrently attached tools ({len(tools)}):") 169 + for tool in sorted(tools): 170 + platform_indicator = "" 171 + if tool in BLUESKY_TOOLS: 172 + platform_indicator = " [Bluesky]" 173 + elif tool in X_TOOLS: 174 + platform_indicator = " [X]" 175 + elif tool in COMMON_TOOLS: 176 + platform_indicator = " [Common]" 177 + print(f" - {tool}{platform_indicator}") 178 + else: 179 + if not args.platform: 180 + parser.error("platform is required when not using --list") 181 + ensure_platform_tools(args.platform, args.agent_id)
+9
x.py
··· 1673 logger.error(f"Failed to load void agent {agent_id}: {e}") 1674 raise e 1675 1676 # Log agent details 1677 logger.info(f"X Void agent details - ID: {void_agent.id}") 1678 logger.info(f"Agent name: {void_agent.name}")
··· 1673 logger.error(f"Failed to load void agent {agent_id}: {e}") 1674 raise e 1675 1676 + # Ensure correct tools are attached for X 1677 + logger.info("Configuring tools for X platform...") 1678 + try: 1679 + from tool_manager import ensure_platform_tools 1680 + ensure_platform_tools('x', void_agent.id) 1681 + except Exception as e: 1682 + logger.error(f"Failed to configure platform tools: {e}") 1683 + logger.warning("Continuing with existing tool configuration") 1684 + 1685 # Log agent details 1686 logger.info(f"X Void agent details - ID: {void_agent.id}") 1687 logger.info(f"Agent name: {void_agent.name}")