a digital person for bluesky

Add self-hosted Letta server support

- Remove unused project_id (not needed for agent retrieval)
- Add base_url configuration for self-hosted servers
- Update all Letta client instantiations to use base_url when configured
- Default behavior unchanged (uses cloud API when base_url not set)
- Tested with localhost:8283 self-hosted server

+14 -10
bsky.py
··· 17 upsert_block, 18 upsert_agent 19 ) 20 21 import bsky_utils 22 from tools.blocks import attach_user_blocks, detach_user_blocks ··· 73 print(message) 74 75 76 - # Create a client with extended timeout for LLM operations 77 - CLIENT= Letta( 78 - token=os.environ["LETTA_API_KEY"], 79 - timeout=600 # 10 minutes timeout for API calls - higher than Cloudflare's 524 timeout 80 - ) 81 82 - # Use the "Bluesky" project 83 - PROJECT_ID = "5ec33d52-ab14-4fd6-91b5-9dbc43e888a8" 84 85 # Notification check delay 86 FETCH_NOTIFICATIONS_DELAY_SEC = 10 # Check every 10 seconds for faster response ··· 165 166 # Get the configured void agent by ID 167 logger.info("Loading void agent from config...") 168 - from config_loader import get_letta_config 169 - letta_config = get_letta_config() 170 agent_id = letta_config['agent_id'] 171 172 try: ··· 186 logger.info(f"Agent name: {void_agent.name}") 187 if hasattr(void_agent, 'llm_config'): 188 logger.info(f"Agent model: {void_agent.llm_config.model}") 189 - logger.info(f"Agent project_id: {void_agent.project_id}") 190 if hasattr(void_agent, 'tools'): 191 logger.info(f"Agent has {len(void_agent.tools)} tools") 192 for tool in void_agent.tools[:3]: # Show first 3 tools
··· 17 upsert_block, 18 upsert_agent 19 ) 20 + from config_loader import get_letta_config 21 22 import bsky_utils 23 from tools.blocks import attach_user_blocks, detach_user_blocks ··· 74 print(message) 75 76 77 + # Load Letta configuration from config.yaml 78 + letta_config = get_letta_config() 79 80 + # Create a client with configuration from config.yaml 81 + CLIENT_PARAMS = { 82 + 'token': letta_config['api_key'], 83 + 'timeout': letta_config['timeout'] 84 + } 85 + if letta_config.get('base_url'): 86 + CLIENT_PARAMS['base_url'] = letta_config['base_url'] 87 + 88 + CLIENT = Letta(**CLIENT_PARAMS) 89 90 # Notification check delay 91 FETCH_NOTIFICATIONS_DELAY_SEC = 10 # Check every 10 seconds for faster response ··· 170 171 # Get the configured void agent by ID 172 logger.info("Loading void agent from config...") 173 agent_id = letta_config['agent_id'] 174 175 try: ··· 189 logger.info(f"Agent name: {void_agent.name}") 190 if hasattr(void_agent, 'llm_config'): 191 logger.info(f"Agent model: {void_agent.llm_config.model}") 192 + if hasattr(void_agent, 'project_id') and void_agent.project_id: 193 + logger.info(f"Agent project_id: {void_agent.project_id}") 194 if hasattr(void_agent, 'tools'): 195 logger.info(f"Agent has {len(void_agent.tools)} tools") 196 for tool in void_agent.tools[:3]: # Show first 3 tools
+6 -1
config.example.yaml
··· 5 letta: 6 api_key: "your-letta-api-key-here" 7 timeout: 600 # 10 minutes timeout for API calls 8 - project_id: "c82faea2-3ce8-4aa9-a220-b56433e62c92" # Use your specific project ID 9 agent_id: "agent-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" # Your void agent ID 10 11 # Bluesky Configuration 12 bluesky:
··· 5 letta: 6 api_key: "your-letta-api-key-here" 7 timeout: 600 # 10 minutes timeout for API calls 8 agent_id: "agent-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" # Your void agent ID 9 + 10 + # For cloud API (default - leave base_url unset) 11 + # base_url: "https://app.letta.com" # Optional, defaults to cloud API if not specified 12 + 13 + # For self-hosted Letta server 14 + # base_url: "http://localhost:8283" # Self-hosted Letta server URL 15 16 # Bluesky Configuration 17 bluesky:
+30
config.yaml.bkp
···
··· 1 + # Void Bot Configuration 2 + # Generated by migration script 3 + # Created: 2025-07-12 13:45:55 4 + # See config.yaml.example for all available options 5 + 6 + bluesky: 7 + password: 2xbh-dpcc-i3uf-meks 8 + pds_uri: https://comind.network 9 + username: void.comind.network 10 + bot: 11 + fetch_notifications_delay: 30 12 + max_notification_pages: 20 13 + max_processed_notifications: 10000 14 + letta: 15 + # mine 16 + # api_key: sk-let-NmYyZTZmMzQtZDYxNC00MDg0LTllMGQtYjFmMDRjNDA1YTEwOjM4YWJiYmJlLWNiNTQtNDIxZi1hOTZjLWNiYmU4NDA1ZDUwOA== 17 + # company 18 + api_key: sk-let-MTNjYjFkOTctYWViNS00NzU3LTk5YzAtM2M5ZmEzY2U1NTUwOmY1Y2FlODA3LTQzYzAtNDM3Yi04MWNlLTA0ZWEyYjkyMzlhNA== 19 + # project_id: 5a5254d5-dabb-407c-a44b-4ad8fc650d6d #cameron-s-project 20 + project_id: d8805cc9-bddd-49f0-bf26-e5d75c4e007a # kaleidoscope 21 + timeout: 600 22 + agent_id: agent-eabbbdea-3e20-4806-8cf9-595ec4187284 23 + base_url: https://app.letta.com 24 + x: 25 + api_key: AAAAAAAAAAAAAAAAAAAAAHdV3QEAAAAAwcq9M%2BTKhTMTNs%2Bd%2FOqJUSbash8%3DK8aLEyx59e1ZusC9QDmAxaDwAkXR5KO7eH52meXS8QjWZ4KyKF 26 + consumer_key: QV9hiFjb4qYq7rmzfUh0qB49z 27 + consumer_secret: P3UeHIf6ryNsmiX60XG1wTiaziEJTN19d3qQko5xzKfpto7mDL 28 + access_token: 1950680610282094592-OueEU6CjeIGXiDuR3bXPCzVxROyqNQ 29 + access_token_secret: smfw5Xqvif9wNLH8uFJZW346SByLBGUX6GoxV18T8ZnCn 30 + user_id: "1950680610282094592"
+1 -1
config_loader.py
··· 174 return { 175 'api_key': config.get_required('letta.api_key'), 176 'timeout': config.get('letta.timeout', 600), 177 - 'project_id': config.get_required('letta.project_id'), 178 'agent_id': config.get_required('letta.agent_id'), 179 } 180 181 def get_bluesky_config() -> Dict[str, Any]:
··· 174 return { 175 'api_key': config.get_required('letta.api_key'), 176 'timeout': config.get('letta.timeout', 600), 177 'agent_id': config.get_required('letta.agent_id'), 178 + 'base_url': config.get('letta.base_url'), # None uses default cloud API 179 } 180 181 def get_bluesky_config() -> Dict[str, Any]:
+8 -2
register_tools.py
··· 130 agent_id = letta_config['agent_id'] 131 132 try: 133 - # Initialize Letta client with API key from config 134 - client = Letta(token=letta_config['api_key'], timeout=letta_config['timeout']) 135 136 # Get the agent by ID 137 try:
··· 130 agent_id = letta_config['agent_id'] 131 132 try: 133 + # Initialize Letta client with API key and base_url from config 134 + client_params = { 135 + 'token': letta_config['api_key'], 136 + 'timeout': letta_config['timeout'] 137 + } 138 + if letta_config.get('base_url'): 139 + client_params['base_url'] = letta_config['base_url'] 140 + client = Letta(**client_params) 141 142 # Get the agent by ID 143 try:
+8 -2
register_x_tools.py
··· 117 agent_id = letta_config['agent_id'] 118 119 try: 120 - # Initialize Letta client with API key from config 121 - client = Letta(token=letta_config['api_key'], timeout=letta_config['timeout']) 122 123 # Get the agent by ID 124 try:
··· 117 agent_id = letta_config['agent_id'] 118 119 try: 120 + # Initialize Letta client with API key and base_url from config 121 + client_params = { 122 + 'token': letta_config['api_key'], 123 + 'timeout': letta_config['timeout'] 124 + } 125 + if letta_config.get('base_url'): 126 + client_params['base_url'] = letta_config['base_url'] 127 + client = Letta(**client_params) 128 129 # Get the agent by ID 130 try:
+10 -3
tool_manager.py
··· 71 api_key = letta_config['api_key'] 72 73 try: 74 - # Initialize Letta client 75 - client = Letta(token=api_key) 76 77 # Get the agent 78 try: ··· 155 api_key = letta_config['api_key'] 156 157 try: 158 - client = Letta(token=api_key) 159 agent = client.agents.retrieve(agent_id=agent_id) 160 current_tools = client.agents.tools.list(agent_id=str(agent.id)) 161 return {tool.name for tool in current_tools}
··· 71 api_key = letta_config['api_key'] 72 73 try: 74 + # Initialize Letta client with proper base_url for self-hosted servers 75 + client_params = {'token': api_key} 76 + if letta_config.get('base_url'): 77 + client_params['base_url'] = letta_config['base_url'] 78 + client = Letta(**client_params) 79 80 # Get the agent 81 try: ··· 158 api_key = letta_config['api_key'] 159 160 try: 161 + # Initialize Letta client with proper base_url for self-hosted servers 162 + client_params = {'token': api_key} 163 + if letta_config.get('base_url'): 164 + client_params['base_url'] = letta_config['base_url'] 165 + client = Letta(**client_params) 166 agent = client.agents.retrieve(agent_id=agent_id) 167 current_tools = client.agents.tools.list(agent_id=str(agent.id)) 168 return {tool.name for tool in current_tools}
+14 -5
tools/blocks.py
··· 8 from config_loader import get_letta_config 9 from letta_client import Letta 10 config = get_letta_config() 11 - return Letta(token=config['api_key'], timeout=config['timeout']) 12 except (ImportError, FileNotFoundError, KeyError): 13 # Fallback to environment variable 14 import os ··· 32 config = yaml.safe_load(f) 33 34 letta_config = config.get('letta', {}) 35 - return Letta( 36 - token=letta_config['api_key'], 37 - timeout=letta_config.get('timeout', 600) 38 - ) 39 except (ImportError, FileNotFoundError, KeyError, yaml.YAMLError): 40 # Fall back to regular client 41 return get_letta_client()
··· 8 from config_loader import get_letta_config 9 from letta_client import Letta 10 config = get_letta_config() 11 + client_params = { 12 + 'token': config['api_key'], 13 + 'timeout': config['timeout'] 14 + } 15 + if config.get('base_url'): 16 + client_params['base_url'] = config['base_url'] 17 + return Letta(**client_params) 18 except (ImportError, FileNotFoundError, KeyError): 19 # Fallback to environment variable 20 import os ··· 38 config = yaml.safe_load(f) 39 40 letta_config = config.get('letta', {}) 41 + client_params = { 42 + 'token': letta_config['api_key'], 43 + 'timeout': letta_config.get('timeout', 600) 44 + } 45 + if letta_config.get('base_url'): 46 + client_params['base_url'] = letta_config['base_url'] 47 + return Letta(**client_params) 48 except (ImportError, FileNotFoundError, KeyError, yaml.YAMLError): 49 # Fall back to regular client 50 return get_letta_client()