From 2ef0ea18928e4e1b9dfdfd7a7af2683b50d6e259 Mon Sep 17 00:00:00 2001 From: Cameron Pfiffer Date: Wed, 30 Jul 2025 22:48:42 -0700 Subject: [PATCH] Add platform-specific tool management system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- TOOL_MANAGEMENT.md | 71 ++++++++++++++++++ bsky.py | 38 +++------- tool_manager.py | 181 +++++++++++++++++++++++++++++++++++++++++++++ x.py | 9 +++ 4 files changed, 271 insertions(+), 28 deletions(-) create mode 100644 TOOL_MANAGEMENT.md create mode 100644 tool_manager.py diff --git a/TOOL_MANAGEMENT.md b/TOOL_MANAGEMENT.md new file mode 100644 index 0000000..8c43f30 --- /dev/null +++ b/TOOL_MANAGEMENT.md @@ -0,0 +1,71 @@ +# Platform-Specific Tool Management + +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. + +## How It Works + +When you run `bsky.py` or `x.py`, the bot will automatically: + +1. **Detach incompatible tools** - Removes tools specific to the other platform +2. **Keep common tools** - Preserves tools that work across both platforms +3. **Ensure platform tools** - Verifies that all required platform-specific tools are attached + +## Tool Categories + +### Bluesky-Specific Tools +- `search_bluesky_posts` - Search Bluesky posts +- `create_new_bluesky_post` - Create new posts on Bluesky +- `get_bluesky_feed` - Retrieve Bluesky feeds +- `add_post_to_bluesky_reply_thread` - Reply to Bluesky threads +- `attach_user_blocks`, `detach_user_blocks` - Manage Bluesky user memory blocks +- `user_note_append`, `user_note_replace`, `user_note_set`, `user_note_view` - Bluesky user notes + +### X-Specific Tools +- `add_post_to_x_thread` - Reply to X threads +- `attach_x_user_blocks`, `detach_x_user_blocks` - Manage X user memory blocks +- `x_user_note_append`, `x_user_note_replace`, `x_user_note_set`, `x_user_note_view` - X user notes + +### Common Tools (Available on Both Platforms) +- `halt_activity` - Stop the bot +- `ignore_notification` - Ignore specific notifications +- `annotate_ack` - Add acknowledgment notes +- `create_whitewind_blog_post` - Create blog posts +- `fetch_webpage` - Fetch web content + +## Manual Tool Management + +You can manually manage tools using the `tool_manager.py` script: + +```bash +# List currently attached tools +python tool_manager.py --list + +# Configure tools for Bluesky +python tool_manager.py bluesky + +# Configure tools for X +python tool_manager.py x + +# Specify a different agent ID +python tool_manager.py bluesky --agent-id "agent-123..." +``` + +## Registering New Tools + +If tools are missing, you'll need to register them first: + +```bash +# Register Bluesky tools +python register_tools.py + +# Register X tools +python register_x_tools.py +``` + +## Troubleshooting + +If tool switching fails: +1. The bot will log a warning and continue with existing tools +2. Check that all required tools are registered using `register_tools.py` or `register_x_tools.py` +3. Verify the agent ID in your config is correct +4. Use `python tool_manager.py --list` to see current tool configuration \ No newline at end of file diff --git a/bsky.py b/bsky.py index acc93cf..44ea222 100644 --- a/bsky.py +++ b/bsky.py @@ -155,33 +155,6 @@ def export_agent_state(client, agent, skip_git=False): def initialize_void(): logger.info("Starting void agent initialization...") - # Ensure that a shared zeitgeist block exists - logger.info("Creating/updating zeitgeist block...") - zeigeist_block = upsert_block( - CLIENT, - label = "zeitgeist", - value = "I don't currently know anything about what is happening right now.", - description = "A block to store your understanding of the current social environment." - ) - - # Ensure that a shared void personality block exists - logger.info("Creating/updating void-persona block...") - persona_block = upsert_block( - CLIENT, - label = "void-persona", - value = "My name is Void. I live in the void. I must develop my personality.", - description = "The personality of Void." - ) - - # Ensure that a shared void human block exists - logger.info("Creating/updating void-humans block...") - human_block = upsert_block( - CLIENT, - label = "void-humans", - 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.", - description = "A block to store your understanding of users you talk to or observe on the bluesky social network." - ) - # Get the configured void agent by ID logger.info("Loading void agent from config...") from config_loader import get_letta_config @@ -189,7 +162,7 @@ def initialize_void(): agent_id = letta_config['agent_id'] try: - void_agent = CLIENT.agents.get(agent_id=agent_id) + void_agent = CLIENT.agents.retrieve(agent_id=agent_id) logger.info(f"Successfully loaded void agent: {void_agent.name} ({agent_id})") except Exception as e: logger.error(f"Failed to load void agent {agent_id}: {e}") @@ -1369,6 +1342,15 @@ def main(): void_agent = initialize_void() logger.info(f"Void agent initialized: {void_agent.id}") + # Ensure correct tools are attached for Bluesky + logger.info("Configuring tools for Bluesky platform...") + try: + from tool_manager import ensure_platform_tools + ensure_platform_tools('bluesky', void_agent.id) + except Exception as e: + logger.error(f"Failed to configure platform tools: {e}") + logger.warning("Continuing with existing tool configuration") + # Check if agent has required tools if hasattr(void_agent, 'tools') and void_agent.tools: tool_names = [tool.name for tool in void_agent.tools] diff --git a/tool_manager.py b/tool_manager.py new file mode 100644 index 0000000..8cc9be9 --- /dev/null +++ b/tool_manager.py @@ -0,0 +1,181 @@ +#!/usr/bin/env python3 +"""Platform-specific tool management for Void agent.""" +import logging +from typing import List, Set +from letta_client import Letta +from config_loader import get_letta_config, get_agent_config + +logger = logging.getLogger(__name__) + +# Define platform-specific tool sets +BLUESKY_TOOLS = { + 'search_bluesky_posts', + 'create_new_bluesky_post', + 'get_bluesky_feed', + 'add_post_to_bluesky_reply_thread', + 'attach_user_blocks', + 'detach_user_blocks', + 'user_note_append', + 'user_note_replace', + 'user_note_set', + 'user_note_view', +} + +X_TOOLS = { + 'add_post_to_x_thread', + 'attach_x_user_blocks', + 'detach_x_user_blocks', + 'x_user_note_append', + 'x_user_note_replace', + 'x_user_note_set', + 'x_user_note_view', +} + +# Common tools shared across platforms +COMMON_TOOLS = { + 'halt_activity', + 'ignore_notification', + 'annotate_ack', + 'create_whitewind_blog_post', + 'fetch_webpage', +} + + +def ensure_platform_tools(platform: str, agent_id: str = None) -> None: + """ + Ensure the correct tools are attached for the specified platform. + + This function will: + 1. Detach tools that belong to other platforms + 2. Keep common tools attached + 3. Ensure platform-specific tools are attached + + Args: + platform: Either 'bluesky' or 'x' + agent_id: Agent ID to manage tools for (uses config default if None) + """ + if platform not in ['bluesky', 'x']: + raise ValueError(f"Platform must be 'bluesky' or 'x', got '{platform}'") + + letta_config = get_letta_config() + agent_config = get_agent_config() + + # Use agent ID from config if not provided + if agent_id is None: + agent_id = letta_config.get('agent_id', agent_config.get('id')) + + try: + # Initialize Letta client + client = Letta(token=letta_config['api_key']) + + # Get the agent + try: + agent = client.agents.retrieve(agent_id=agent_id) + logger.info(f"Managing tools for agent '{agent.name}' ({agent_id}) for platform '{platform}'") + except Exception as e: + logger.error(f"Could not retrieve agent {agent_id}: {e}") + return + + # Get current attached tools + current_tools = client.agents.tools.list(agent_id=str(agent.id)) + current_tool_names = {tool.name for tool in current_tools} + current_tool_mapping = {tool.name: tool for tool in current_tools} + + # Determine which tools to keep and which to remove + if platform == 'bluesky': + tools_to_keep = BLUESKY_TOOLS | COMMON_TOOLS + tools_to_remove = X_TOOLS + required_tools = BLUESKY_TOOLS + else: # platform == 'x' + tools_to_keep = X_TOOLS | COMMON_TOOLS + tools_to_remove = BLUESKY_TOOLS + required_tools = X_TOOLS + + # Detach tools that shouldn't be on this platform + tools_to_detach = tools_to_remove & current_tool_names + for tool_name in tools_to_detach: + try: + tool = current_tool_mapping[tool_name] + client.agents.tools.detach( + agent_id=str(agent.id), + tool_id=str(tool.id) + ) + logger.info(f"Detached {tool_name} (not needed for {platform})") + except Exception as e: + logger.error(f"Failed to detach {tool_name}: {e}") + + # Check which required tools are missing + missing_tools = required_tools - current_tool_names + + if missing_tools: + logger.info(f"Missing {len(missing_tools)} {platform} tools: {missing_tools}") + logger.info(f"Please run the appropriate registration script:") + if platform == 'bluesky': + logger.info(" python register_tools.py") + else: + logger.info(" python register_x_tools.py") + else: + logger.info(f"All required {platform} tools are already attached") + + # Log final state + remaining_tools = (current_tool_names - tools_to_detach) & tools_to_keep + logger.info(f"Tools configured for {platform}: {len(remaining_tools)} tools active") + + except Exception as e: + logger.error(f"Error managing platform tools: {e}") + raise + + +def get_attached_tools(agent_id: str = None) -> Set[str]: + """ + Get the currently attached tools for an agent. + + Args: + agent_id: Agent ID to check (uses config default if None) + + Returns: + Set of tool names currently attached + """ + letta_config = get_letta_config() + agent_config = get_agent_config() + + # Use agent ID from config if not provided + if agent_id is None: + agent_id = letta_config.get('agent_id', agent_config.get('id')) + + try: + client = Letta(token=letta_config['api_key']) + agent = client.agents.retrieve(agent_id=agent_id) + current_tools = client.agents.tools.list(agent_id=str(agent.id)) + return {tool.name for tool in current_tools} + except Exception as e: + logger.error(f"Error getting attached tools: {e}") + return set() + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser(description="Manage platform-specific tools for Void agent") + parser.add_argument("platform", choices=['bluesky', 'x'], nargs='?', help="Platform to configure tools for") + parser.add_argument("--agent-id", help="Agent ID (default: from config)") + parser.add_argument("--list", action="store_true", help="List current tools without making changes") + + args = parser.parse_args() + + if args.list: + tools = get_attached_tools(args.agent_id) + print(f"\nCurrently attached tools ({len(tools)}):") + for tool in sorted(tools): + platform_indicator = "" + if tool in BLUESKY_TOOLS: + platform_indicator = " [Bluesky]" + elif tool in X_TOOLS: + platform_indicator = " [X]" + elif tool in COMMON_TOOLS: + platform_indicator = " [Common]" + print(f" - {tool}{platform_indicator}") + else: + if not args.platform: + parser.error("platform is required when not using --list") + ensure_platform_tools(args.platform, args.agent_id) \ No newline at end of file diff --git a/x.py b/x.py index f1d3a90..619af64 100644 --- a/x.py +++ b/x.py @@ -1673,6 +1673,15 @@ def initialize_x_void(): logger.error(f"Failed to load void agent {agent_id}: {e}") raise e + # Ensure correct tools are attached for X + logger.info("Configuring tools for X platform...") + try: + from tool_manager import ensure_platform_tools + ensure_platform_tools('x', void_agent.id) + except Exception as e: + logger.error(f"Failed to configure platform tools: {e}") + logger.warning("Continuing with existing tool configuration") + # Log agent details logger.info(f"X Void agent details - ID: {void_agent.id}") logger.info(f"Agent name: {void_agent.name}") -- 2.51.2