a digital person for bluesky

Compare changes

Choose any two refs to compare.

-6
.env.backup_20250712_134555
··· 1 - LETTA_API_KEY="sk-let-NmYyZTZmMzQtZDYxNC00MDg0LTllMGQtYjFmMDRjNDA1YTEwOmIyYTMyNmM4LWZkMjEtNGE4OC04Mjg2LWJkN2Q2NWQ1MGVhOA==" 2 - BSKY_USERNAME="void.comind.network" 3 - BSKY_PASSWORD="2xbh-dpcc-i3uf-meks" 4 - PDS_URI="https://comind.network" 5 - VOID_WORKER_COUNT=2 6 -
+3 -2
config.example.yaml
··· 5 5 letta: 6 6 api_key: "your-letta-api-key-here" 7 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 8 + project_id: "your-project-id-here" # Use your specific project ID 9 + agent_id: "your-agent-id-here" # Your void agent ID 10 + base_url: "https://api.letta.com" # Default to letta cloud, this is typially http://localhost:8283 for self-hosted 10 11 11 12 # Bluesky Configuration 12 13 bluesky:
+93
organon/chat_direct.py
··· 1 + #!/usr/bin/env python3 2 + """ 3 + Direct chat with a specific group ID (bypassing the search logic). 4 + """ 5 + 6 + import os 7 + import sys 8 + from dotenv import load_dotenv 9 + from letta_client import Letta 10 + from rich.console import Console 11 + from rich.prompt import Prompt 12 + from rich.panel import Panel 13 + 14 + # Add parent directory to path for imports 15 + sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 16 + from config_loader import get_config 17 + 18 + load_dotenv() 19 + 20 + def main(): 21 + console = Console() 22 + 23 + if len(sys.argv) != 2: 24 + console.print("[red]Usage: python organon/chat_direct.py <group_id>[/red]") 25 + console.print("[dim]Example: python organon/chat_direct.py group-0bf1c6[/dim]") 26 + sys.exit(1) 27 + 28 + group_id = sys.argv[1] 29 + 30 + try: 31 + # Initialize configuration and client 32 + config = get_config() 33 + 34 + client = Letta( 35 + base_url=config.get('letta.base_url', os.environ.get('LETTA_BASE_URL')), 36 + token=config.get('letta.api_key', os.environ.get('LETTA_API_KEY')), 37 + timeout=config.get('letta.timeout', 30) 38 + ) 39 + 40 + # Test if we can access the group 41 + try: 42 + group = client.groups.retrieve(group_id=group_id) 43 + console.print(f"[green]✅ Connected to group: {group_id}[/green]") 44 + except Exception as e: 45 + console.print(f"[red]❌ Cannot access group {group_id}: {e}[/red]") 46 + sys.exit(1) 47 + 48 + console.print(Panel.fit( 49 + "[bold green]Direct Organon Group Chat[/bold green]\n" 50 + f"Group ID: {group_id}\n" 51 + "Type 'exit' or 'quit' to leave", 52 + title="🧠 Direct Chat" 53 + )) 54 + 55 + while True: 56 + user_input = Prompt.ask("\n[bold green]You[/bold green]") 57 + 58 + if user_input.lower() in ['exit', 'quit', 'q']: 59 + console.print("[yellow]Goodbye![/yellow]") 60 + break 61 + elif not user_input.strip(): 62 + continue 63 + 64 + console.print("[dim]Sending to group...[/dim]") 65 + 66 + try: 67 + response = client.groups.messages.send( 68 + group_id=group_id, 69 + message=user_input 70 + ) 71 + 72 + console.print("\n[bold]Group Response:[/bold]") 73 + if hasattr(response, 'messages') and response.messages: 74 + for message in response.messages: 75 + content = str(message) 76 + if hasattr(message, 'text'): 77 + content = message.text 78 + elif hasattr(message, 'content'): 79 + content = message.content 80 + 81 + console.print(Panel(content, border_style="blue")) 82 + else: 83 + console.print("[yellow]No response received[/yellow]") 84 + 85 + except Exception as e: 86 + console.print(f"[red]Error: {e}[/red]") 87 + 88 + except Exception as e: 89 + console.print(f"[red]Error: {e}[/red]") 90 + sys.exit(1) 91 + 92 + if __name__ == "__main__": 93 + main()
+116
organon/create_kaleidoscope.py
··· 78 78 4. Kaleidoscope Central → User (synthesis) 79 79 """ 80 80 81 + memory_management = """# Memory Management Protocols 82 + - Use memory_replace to completely replace a block's content with new information 83 + - Use memory_insert to add new information to an existing block without losing current content 84 + - Use memory_rethink to revise and improve existing block content while preserving core meaning 85 + 86 + # When to use each method: 87 + - memory_replace: When information is outdated, incorrect, or needs complete overhaul 88 + - memory_insert: When adding new insights, examples, or expanding existing knowledge 89 + - memory_rethink: When refining, clarifying, or improving the quality of existing content 90 + 91 + # Best Practices: 92 + - Always consider the impact on agent behavior before modifying memory 93 + - Preserve the core identity and purpose of each block 94 + - Test changes incrementally to ensure stability 95 + - Document significant memory modifications for future reference 96 + """ 97 + 98 + tool_use_guidelines = """# Tool Use Guidelines for Central Agent 99 + 100 + - send_message: Respond to the user. This is your method for external communication. 101 + - send_message_to_all_agents_in_group: Send a message to your lenses. This is internal communication. 102 + """ 103 + 81 104 # 82 105 # Block Creation 83 106 # ··· 121 144 print("Lens management block already exists") 122 145 lens_management_block = blocks[0] 123 146 147 + # Create memory-management block 148 + blocks = client.blocks.list(project_id=project_id, label="memory-management") 149 + if len(blocks) == 0: 150 + memory_management_block = client.blocks.create( 151 + project_id=project_id, 152 + label="memory-management", 153 + value=memory_management, 154 + description="Protocols for managing agent memory blocks using memory_replace, memory_insert, and memory_rethink. This block is read-only to all lenses, but can be modified by the central kaleidoscope agent.", 155 + ) 156 + else: 157 + print("Memory management block already exists") 158 + memory_management_block = blocks[0] 159 + 160 + # Make memory-management block read-only to all lenses 161 + try: 162 + # Get all lenses and make the block read-only to them 163 + lenses = client.agents.list(tags=["kaleidoscope-lens"]) 164 + for lens in lenses: 165 + try: 166 + client.agents.blocks.modify(agent_id=lens.id, block_label=memory_management_block.label, read_only=True) 167 + print(f"Memory management block set to read-only for lens: {lens.name}") 168 + except Exception as e: 169 + raise Exception(f"Could not set memory management block to read-only for lens {lens.name}: {e}") 170 + print("Memory management block set to read-only for all lenses") 171 + except Exception as e: 172 + raise Exception(f"Could not set memory management block to read-only: {e}") 173 + 174 + # Create tool_use_guidelines block 175 + blocks = client.blocks.list(project_id=project_id, label="tool-use-guidelines") 176 + if len(blocks) == 0: 177 + tool_use_guidelines_block = client.blocks.create( 178 + project_id=project_id, 179 + label="tool-use-guidelines", 180 + value=tool_use_guidelines, 181 + description="Guidelines for the central kaleidoscope agent to use tools effectively.", 182 + ) 183 + else: 184 + print("Tool use guidelines block already exists") 185 + tool_use_guidelines_block = blocks[0] 186 + 124 187 125 188 # 126 189 # Static lens blocks ··· 191 254 kaleidoscope_persona_block.id, 192 255 synthesis_protocols_block.id, 193 256 lens_management_block.id, 257 + memory_management_block.id, 258 + tool_use_guidelines_block.id, 194 259 ] 195 260 196 261 # Create the central kaleidoscope if it doesn't exist ··· 229 294 agent_id=kaleidoscope_central_id, 230 295 block_id=block, 231 296 ) 297 + 298 + # Ensure memory-management block is read-only to all lenses 299 + try: 300 + # Get all lenses and make the block read-only to them 301 + lenses = client.agents.list(tags=["kaleidoscope-lens"]) 302 + for lens in lenses: 303 + try: 304 + client.agents.blocks.modify(agent_id=lens.id, block_label=memory_management_block.label, read_only=True) 305 + print(f"Memory management block confirmed as read-only for lens: {lens.name}") 306 + except Exception as e: 307 + raise Exception(f"Could not confirm memory management block as read-only for lens {lens.name}: {e}") 308 + print("Memory management block confirmed as read-only for all lenses") 309 + except Exception as e: 310 + raise Exception(f"Could not confirm memory management block as read-only: {e}") 311 + 312 + 232 313 233 314 234 315 # ··· 419 500 lens_knowledge_block.id, 420 501 lens_operational_protocols_block.id, 421 502 lens_communication_protocols_block.id, 503 + memory_management_block.id, 422 504 ], 423 505 tags=["kaleidoscope-lens"], 424 506 ) ··· 426 508 print(f"Created lens: {lens_name} (ID: {lens_agent.id})") 427 509 lens_ids.append(lens_agent.id) 428 510 511 + # Ensure all existing lenses have the memory-management block 512 + print("\nEnsuring all lenses have memory-management block...") 513 + all_lenses = client.agents.list(tags=["kaleidoscope-lens"]) 514 + for lens in all_lenses: 515 + lens_blocks = client.agents.blocks.list(agent_id=lens.id) 516 + lens_block_ids = [b.id for b in lens_blocks] 517 + 518 + if memory_management_block.id not in lens_block_ids: 519 + print(f"Adding memory-management block to lens: {lens.name}") 520 + client.agents.blocks.attach( 521 + agent_id=lens.id, 522 + block_id=memory_management_block.id, 523 + ) 524 + else: 525 + print(f"Lens {lens.name} already has memory-management block") 526 + 527 + # Also check for any existing lenses that might not have the tag but should be updated 528 + print("\nChecking for existing lenses without tags...") 529 + all_agents = client.agents.list() 530 + for agent in all_agents: 531 + if agent.name in [lens_config["name"] for lens_config in LENS_TYPES]: 532 + lens_blocks = client.agents.blocks.list(agent_id=agent.id) 533 + lens_block_ids = [b.id for b in lens_blocks] 534 + 535 + if memory_management_block.id not in lens_block_ids: 536 + print(f"Adding memory-management block to existing lens: {agent.name}") 537 + client.agents.blocks.attach( 538 + agent_id=agent.id, 539 + block_id=memory_management_block.id, 540 + ) 541 + else: 542 + print(f"Existing lens {agent.name} already has memory-management block") 543 + 429 544 430 545 # 431 546 # Create a lens creation function for custom lenses ··· 471 586 lens_knowledge_block.id, 472 587 lens_operational_protocols_block.id, 473 588 lens_communication_protocols_block.id, 589 + memory_management_block.id, 474 590 ], 475 591 tags=["kaleidoscope-lens"], 476 592 )
+81
organon/delete_groups.py
··· 1 + #!/usr/bin/env python3 2 + """ 3 + Delete all groups in the current project. 4 + """ 5 + 6 + import os 7 + import sys 8 + from dotenv import load_dotenv 9 + from letta_client import Letta 10 + from rich.console import Console 11 + from rich.prompt import Confirm 12 + 13 + # Add parent directory to path for imports 14 + sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 15 + from config_loader import get_config 16 + 17 + load_dotenv() 18 + 19 + def main(): 20 + console = Console() 21 + 22 + try: 23 + # Initialize configuration and client 24 + config = get_config() 25 + 26 + client = Letta( 27 + base_url=config.get('letta.base_url', os.environ.get('LETTA_BASE_URL')), 28 + token=config.get('letta.api_key', os.environ.get('LETTA_API_KEY')), 29 + timeout=config.get('letta.timeout', 30) 30 + ) 31 + 32 + project_id = config.get('letta.project_id', os.environ.get('LETTA_PROJECT_ID')) 33 + 34 + # Get all groups 35 + console.print("[blue]Finding all groups...[/blue]") 36 + 37 + try: 38 + if project_id: 39 + groups = client.groups.list() 40 + else: 41 + groups = client.groups.list() 42 + except: 43 + # Try without project_id as fallback 44 + try: 45 + groups = client.groups.list() 46 + except Exception as e: 47 + console.print(f"[red]Error listing groups: {e}[/red]") 48 + return 49 + 50 + if not groups: 51 + console.print("[yellow]No groups found.[/yellow]") 52 + return 53 + 54 + console.print(f"[yellow]Found {len(groups)} groups:[/yellow]") 55 + for group in groups: 56 + description = group.description[:50] + "..." if group.description and len(group.description) > 50 else (group.description or "No description") 57 + console.print(f" • {group.id[:12]}... - {description}") 58 + 59 + # Confirm deletion 60 + if not Confirm.ask(f"\n[bold red]Delete all {len(groups)} groups?[/bold red]"): 61 + console.print("[yellow]Cancelled.[/yellow]") 62 + return 63 + 64 + # Delete each group 65 + deleted_count = 0 66 + for group in groups: 67 + try: 68 + client.groups.delete(group_id=group.id) 69 + console.print(f"[green]✅ Deleted group: {group.id[:12]}[/green]") 70 + deleted_count += 1 71 + except Exception as e: 72 + console.print(f"[red]❌ Failed to delete group {group.id[:12]}: {e}[/red]") 73 + 74 + console.print(f"\n[green]Successfully deleted {deleted_count}/{len(groups)} groups.[/green]") 75 + 76 + except Exception as e: 77 + console.print(f"[red]Error: {e}[/red]") 78 + sys.exit(1) 79 + 80 + if __name__ == "__main__": 81 + main()
+375
organon/firehose_listener.py
··· 1 + """ 2 + ATProto firehose listener that connects to Jetstream and pipes content to Organon agent. 3 + """ 4 + 5 + import asyncio 6 + import json 7 + import logging 8 + import os 9 + import sys 10 + import websockets 11 + import zstandard as zstd 12 + from datetime import datetime 13 + from typing import Optional, Dict, Any 14 + from dotenv import load_dotenv 15 + from letta_client import Letta, SupervisorManager 16 + 17 + # Add parent directory to path for imports 18 + sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 19 + from config_loader import get_config 20 + 21 + load_dotenv() 22 + 23 + # Setup logging 24 + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') 25 + logger = logging.getLogger(__name__) 26 + 27 + class OrganonFirehoseListener: 28 + def __init__(self): 29 + """Initialize the firehose listener with Letta client and configuration.""" 30 + self.config = get_config() 31 + 32 + # Initialize Letta client 33 + self.letta_client = Letta( 34 + base_url=self.config.get('letta.base_url', os.environ.get('LETTA_BASE_URL')), 35 + token=self.config.get('letta.api_key', os.environ.get('LETTA_API_KEY')), 36 + timeout=self.config.get('letta.timeout', 30) 37 + ) 38 + 39 + # Get project ID 40 + self.project_id = self.config.get('letta.project_id', os.environ.get('LETTA_PROJECT_ID')) 41 + if not self.project_id: 42 + raise ValueError("Project ID must be set in config.yaml under letta.project_id or as LETTA_PROJECT_ID environment variable") 43 + 44 + # Jetstream WebSocket URL (try different endpoints) 45 + self.jetstream_url = "wss://jetstream2.us-east.bsky.network/subscribe" 46 + 47 + # Filter for posts only 48 + self.wanted_collections = ["app.bsky.feed.post"] 49 + 50 + # Get Organon central agent 51 + self.organon_agent_id = self._get_organon_agent_id() 52 + 53 + # List all organon shards on boot 54 + self._list_organon_shards() 55 + 56 + # Ensure supervisor group exists with all shards 57 + self.organon_group_id = self._ensure_organon_supervisor_group() 58 + 59 + # Connection state 60 + self.websocket = None 61 + self.running = False 62 + 63 + # Zstd decompressor for compressed messages 64 + self.decompressor = zstd.ZstdDecompressor() 65 + 66 + def _get_organon_agent_id(self) -> str: 67 + """Get the Organon central agent ID.""" 68 + agents = self.letta_client.agents.list(project_id=self.project_id, name="organon-central") 69 + if not agents: 70 + raise ValueError("Organon central agent not found. Run create_organon.py first.") 71 + return agents[0].id 72 + 73 + def _list_organon_shards(self) -> None: 74 + """List all organon shards using the organon-shard tag.""" 75 + try: 76 + # Get agents with the organon-shard tag 77 + shard_agents = self.letta_client.agents.list(project_id=self.project_id, tags=["organon-shard"]) 78 + 79 + logger.info(f"Found {len(shard_agents)} Organon shards:") 80 + for agent in shard_agents: 81 + logger.info(f" - {agent.name} (ID: {agent.id})") 82 + if agent.description: 83 + logger.info(f" Description: {agent.description}") 84 + 85 + if len(shard_agents) == 0: 86 + logger.warning("No Organon shards found with tag 'organon-shard'") 87 + 88 + except Exception as e: 89 + logger.error(f"Error listing Organon shards: {e}") 90 + 91 + def _ensure_organon_supervisor_group(self) -> str: 92 + """Ensure a supervisor group exists with organon-central as supervisor and all shards as workers.""" 93 + try: 94 + group_name = "organon-ecosystem" 95 + 96 + # Get all organon shards 97 + shard_agents = self.letta_client.agents.list(project_id=self.project_id, tags=["organon-shard"]) 98 + 99 + if len(shard_agents) == 0: 100 + logger.warning("No shards found, cannot create group") 101 + raise ValueError("No Organon shards found with tag 'organon-shard'") 102 + 103 + # Check if group already exists 104 + try: 105 + existing_groups = self.letta_client.groups.list(project_id=self.project_id) 106 + existing_group = None 107 + for group in existing_groups: 108 + if group.name == group_name: 109 + existing_group = group 110 + break 111 + 112 + if existing_group: 113 + logger.info(f"Organon supervisor group '{group_name}' already exists (ID: {existing_group.id})") 114 + 115 + # For supervisor groups, only the worker agents are in the group membership 116 + # The supervisor is managed separately via the manager_config 117 + group_members = self.letta_client.groups.agents.list(group_id=existing_group.id) 118 + member_ids = {member.id for member in group_members} 119 + shard_ids = {shard.id for shard in shard_agents} 120 + 121 + # Add missing shards to the group 122 + missing_shards = shard_ids - member_ids 123 + for shard_id in missing_shards: 124 + logger.info(f"Adding shard {shard_id} to group {group_name}") 125 + self.letta_client.groups.agents.add( 126 + group_id=existing_group.id, 127 + agent_id=shard_id 128 + ) 129 + 130 + # Remove any agents that are no longer shards 131 + extra_members = member_ids - shard_ids 132 + for member_id in extra_members: 133 + logger.info(f"Removing non-shard agent {member_id} from group {group_name}") 134 + self.letta_client.groups.agents.remove( 135 + group_id=existing_group.id, 136 + agent_id=member_id 137 + ) 138 + 139 + return existing_group.id 140 + 141 + except Exception as e: 142 + logger.debug(f"Error checking existing groups: {e}") 143 + 144 + # Create new supervisor group 145 + logger.info(f"Creating new Organon supervisor group '{group_name}'") 146 + 147 + # Get all shard IDs 148 + worker_agent_ids = [shard.id for shard in shard_agents] 149 + 150 + group = self.letta_client.groups.create( 151 + agent_ids=worker_agent_ids, 152 + description="Supervisor group for the Organon ecosystem with organon-central managing all shards", 153 + manager_config=SupervisorManager( 154 + manager_agent_id=self.organon_agent_id 155 + ) 156 + ) 157 + 158 + logger.info(f"Created Organon supervisor group '{group_name}' (ID: {group.id})") 159 + logger.info(f" Supervisor: organon-central ({self.organon_agent_id})") 160 + logger.info(f" Workers: {len(worker_agent_ids)} shards") 161 + 162 + return group.id 163 + 164 + except Exception as e: 165 + logger.error(f"Error ensuring Organon supervisor group: {e}") 166 + raise 167 + 168 + async def connect(self) -> None: 169 + """Connect to the Jetstream WebSocket.""" 170 + # Build query parameters - disable compression for now 171 + params = { 172 + "wantedCollections": ",".join(self.wanted_collections) 173 + # Removing compression to debug the utf-8 issue 174 + } 175 + 176 + # Build URL with parameters 177 + param_string = "&".join([f"{k}={v}" for k, v in params.items()]) 178 + url = f"{self.jetstream_url}?{param_string}" 179 + 180 + logger.info(f"Connecting to Jetstream: {url}") 181 + 182 + try: 183 + self.websocket = await websockets.connect(url) 184 + logger.info("Connected to Jetstream firehose") 185 + except Exception as e: 186 + logger.error(f"Failed to connect to Jetstream: {e}") 187 + raise 188 + 189 + def _process_post_content(self, record: Dict[str, Any]) -> Optional[str]: 190 + """Extract and process post content from a record.""" 191 + try: 192 + # Extract basic post information 193 + text = record.get('text', '') 194 + created_at = record.get('createdAt', '') 195 + 196 + # Extract facets (links, mentions, hashtags) if present 197 + facets = record.get('facets', []) 198 + 199 + # Build a structured representation 200 + content_data = { 201 + 'text': text, 202 + 'created_at': created_at, 203 + 'facets': facets 204 + } 205 + 206 + # Only process posts with meaningful content (ignore very short posts) 207 + if len(text.strip()) < 10: 208 + return None 209 + 210 + return json.dumps(content_data, indent=2) 211 + 212 + except Exception as e: 213 + logger.error(f"Error processing post content: {e}") 214 + return None 215 + 216 + async def _send_to_organon(self, content: str, metadata: Dict[str, Any]) -> None: 217 + """Send processed content to the Organon ecosystem via group messaging.""" 218 + try: 219 + # Create a conceptual observation message for Organon 220 + message = f"""New observation from the ATProto firehose: 221 + 222 + Content: 223 + {content} 224 + 225 + Metadata: 226 + - DID: {metadata.get('did', 'unknown')} 227 + - Collection: {metadata.get('collection', 'unknown')} 228 + - Timestamp: {metadata.get('time_us', 'unknown')} 229 + - CID: {metadata.get('cid', 'unknown')} 230 + - RKey: {metadata.get('rkey', 'unknown')} 231 + 232 + Please analyze this content and generate Conceptual Suggestion Packets (CSPs) if it contains novel ideas, patterns, or contradictions worth exploring. Coordinate with your shards to explore different conceptual dimensions.""" 233 + 234 + # Send message to Organon group (supervisor will coordinate with shards) 235 + response = self.letta_client.groups.messages.create( 236 + group_id=self.organon_group_id, 237 + messages=[{ 238 + "role": "user", 239 + "content": message 240 + }] 241 + ) 242 + 243 + logger.info(f"Sent content to Organon ecosystem (group {self.organon_group_id})") 244 + logger.debug(f"Group response: {len(response.messages) if hasattr(response, 'messages') else 'N/A'} messages") 245 + 246 + except Exception as e: 247 + logger.error(f"Error sending content to Organon ecosystem: {e}") 248 + 249 + async def _handle_event(self, event: Dict[str, Any]) -> None: 250 + """Handle a single event from the firehose.""" 251 + try: 252 + event_type = event.get('kind') 253 + 254 + if event_type == 'commit': 255 + # Extract commit information 256 + did = event.get('did') 257 + commit = event.get('commit', {}) 258 + 259 + # Check if this is a create operation for a post 260 + operation = commit.get('operation') 261 + collection = commit.get('collection') 262 + 263 + if operation == 'create' and collection == 'app.bsky.feed.post': 264 + record = commit.get('record', {}) 265 + 266 + # Process the post content 267 + processed_content = self._process_post_content(record) 268 + 269 + if processed_content: 270 + metadata = { 271 + 'did': did, 272 + 'collection': collection, 273 + 'time_us': event.get('time_us'), 274 + 'cid': commit.get('cid'), 275 + 'rkey': commit.get('rkey') 276 + } 277 + 278 + logger.info(f"Sending post to Organon from {did}") 279 + 280 + # Send to Organon for analysis 281 + await self._send_to_organon(processed_content, metadata) 282 + else: 283 + logger.debug(f"Skipping post from {did} - too short or no content") 284 + 285 + except Exception as e: 286 + logger.error(f"Error handling event: {e}") 287 + 288 + async def listen(self) -> None: 289 + """Listen to the firehose and process events.""" 290 + if not self.websocket: 291 + await self.connect() 292 + 293 + self.running = True 294 + logger.info("Starting to listen to firehose events...") 295 + 296 + try: 297 + async for message in self.websocket: 298 + if not self.running: 299 + break 300 + 301 + try: 302 + # Handle message format 303 + if isinstance(message, bytes): 304 + message_text = message.decode('utf-8') 305 + else: 306 + message_text = message 307 + 308 + # Parse JSON event 309 + event = json.loads(message_text) 310 + 311 + # Print the whole JSON message for debugging 312 + print(f"\n--- FULL JSON MESSAGE ---") 313 + print(json.dumps(event, indent=2)) 314 + print(f"--- END MESSAGE ---\n") 315 + 316 + # Handle the event 317 + await self._handle_event(event) 318 + 319 + except json.JSONDecodeError as e: 320 + logger.error(f"Failed to parse JSON message: {e}") 321 + except Exception as e: 322 + logger.error(f"Error processing message: {e}") 323 + 324 + except websockets.exceptions.ConnectionClosed: 325 + logger.warning("WebSocket connection closed") 326 + except Exception as e: 327 + logger.error(f"Error in listen loop: {e}") 328 + finally: 329 + self.running = False 330 + 331 + async def stop(self) -> None: 332 + """Stop the firehose listener.""" 333 + self.running = False 334 + if self.websocket: 335 + await self.websocket.close() 336 + logger.info("Firehose listener stopped") 337 + 338 + async def run_with_reconnect(self, max_retries: int = 10, retry_delay: int = 5) -> None: 339 + """Run the listener with automatic reconnection.""" 340 + retry_count = 0 341 + 342 + while retry_count < max_retries: 343 + try: 344 + await self.connect() 345 + await self.listen() 346 + 347 + # If we get here, connection was closed gracefully 348 + if not self.running: 349 + logger.info("Listener stopped gracefully") 350 + break 351 + 352 + except Exception as e: 353 + retry_count += 1 354 + logger.error(f"Connection failed (attempt {retry_count}/{max_retries}): {e}") 355 + 356 + if retry_count < max_retries: 357 + logger.info(f"Retrying in {retry_delay} seconds...") 358 + await asyncio.sleep(retry_delay) 359 + else: 360 + logger.error("Max retries exceeded, stopping listener") 361 + break 362 + 363 + async def main(): 364 + """Main function to run the firehose listener.""" 365 + listener = OrganonFirehoseListener() 366 + 367 + try: 368 + await listener.run_with_reconnect() 369 + except KeyboardInterrupt: 370 + logger.info("Received interrupt signal") 371 + finally: 372 + await listener.stop() 373 + 374 + if __name__ == "__main__": 375 + asyncio.run(main())
+100
organon/list_agents.py
··· 1 + #!/usr/bin/env python3 2 + """ 3 + Simple tool to list all agents in the current project, especially Organon-related ones. 4 + """ 5 + 6 + import os 7 + import sys 8 + from dotenv import load_dotenv 9 + from letta_client import Letta 10 + from rich.console import Console 11 + from rich.table import Table 12 + from rich.panel import Panel 13 + 14 + # Add parent directory to path for imports 15 + sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 16 + from config_loader import get_config 17 + 18 + load_dotenv() 19 + 20 + def main(): 21 + console = Console() 22 + 23 + try: 24 + # Initialize configuration and client 25 + config = get_config() 26 + 27 + client = Letta( 28 + base_url=config.get('letta.base_url', os.environ.get('LETTA_BASE_URL')), 29 + token=config.get('letta.api_key', os.environ.get('LETTA_API_KEY')), 30 + timeout=config.get('letta.timeout', 30) 31 + ) 32 + 33 + project_id = config.get('letta.project_id', os.environ.get('LETTA_PROJECT_ID')) 34 + if not project_id: 35 + raise ValueError("Project ID must be set in config.yaml under letta.project_id or as LETTA_PROJECT_ID environment variable") 36 + 37 + # Get all agents 38 + agents = client.agents.list(project_id=project_id) 39 + 40 + if not agents: 41 + console.print("[yellow]No agents found in this project.[/yellow]") 42 + console.print("[dim]Run create_organon.py to create the Organon agents.[/dim]") 43 + return 44 + 45 + # Create table 46 + table = Table(title=f"Agents in Project {project_id[:8]}...") 47 + table.add_column("Agent Name", style="cyan") 48 + table.add_column("Agent ID", style="white") 49 + table.add_column("Description", style="green") 50 + table.add_column("Tags", style="yellow") 51 + 52 + organon_central = None 53 + organon_shards = [] 54 + 55 + for agent in agents: 56 + name = agent.name if hasattr(agent, 'name') else "N/A" 57 + agent_id = agent.id[:12] + "..." if len(agent.id) > 12 else agent.id 58 + description = agent.description[:40] + "..." if agent.description and len(agent.description) > 40 else (agent.description or "N/A") 59 + tags = ", ".join(agent.tags) if hasattr(agent, 'tags') and agent.tags else "None" 60 + 61 + table.add_row(name, agent_id, description, tags) 62 + 63 + # Track Organon agents 64 + if name == "organon-central": 65 + organon_central = agent 66 + elif hasattr(agent, 'tags') and agent.tags and "organon-shard" in agent.tags: 67 + organon_shards.append(agent) 68 + 69 + console.print(table) 70 + 71 + # Show Organon status 72 + console.print("\n[bold blue]Organon Status:[/bold blue]") 73 + 74 + if organon_central: 75 + console.print(f"✅ [green]Organon Central found:[/green] {organon_central.name} ({organon_central.id[:8]})") 76 + else: 77 + console.print("❌ [red]Organon Central not found[/red]") 78 + 79 + if organon_shards: 80 + console.print(f"✅ [green]Found {len(organon_shards)} Organon shards:[/green]") 81 + for shard in organon_shards: 82 + console.print(f" • {shard.name} ({shard.id[:8]})") 83 + else: 84 + console.print("❌ [red]No Organon shards found with tag 'organon-shard'[/red]") 85 + 86 + # Recommendations 87 + console.print("\n[bold yellow]Recommendations:[/bold yellow]") 88 + if not organon_central: 89 + console.print("• Run [cyan]ac && python organon/create_organon.py[/cyan] to create Organon agents") 90 + elif not organon_shards: 91 + console.print("• Run [cyan]ac && python organon/create_organon.py[/cyan] to create Organon shards") 92 + else: 93 + console.print("• Run [cyan]ac && python organon/firehose_listener.py[/cyan] to create the ecosystem group") 94 + 95 + except Exception as e: 96 + console.print(f"[red]Error: {e}[/red]") 97 + sys.exit(1) 98 + 99 + if __name__ == "__main__": 100 + main()
+123
organon/list_groups.py
··· 1 + #!/usr/bin/env python3 2 + """ 3 + Simple tool to list all groups and their status in the current project. 4 + """ 5 + 6 + import os 7 + import sys 8 + from dotenv import load_dotenv 9 + from letta_client import Letta 10 + from rich.console import Console 11 + from rich.table import Table 12 + from rich.panel import Panel 13 + 14 + # Add parent directory to path for imports 15 + sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 16 + from config_loader import get_config 17 + 18 + load_dotenv() 19 + 20 + def get_agent_name(client, agent_id): 21 + """Get agent name by ID, with fallback to truncated ID.""" 22 + try: 23 + agent = client.agents.retrieve(agent_id=agent_id) 24 + return agent.name if hasattr(agent, 'name') else agent_id[:8] 25 + except: 26 + return agent_id[:8] 27 + 28 + def main(): 29 + console = Console() 30 + 31 + try: 32 + # Initialize configuration and client 33 + config = get_config() 34 + 35 + client = Letta( 36 + base_url=config.get('letta.base_url', os.environ.get('LETTA_BASE_URL')), 37 + token=config.get('letta.api_key', os.environ.get('LETTA_API_KEY')), 38 + timeout=config.get('letta.timeout', 30) 39 + ) 40 + 41 + project_id = config.get('letta.project_id', os.environ.get('LETTA_PROJECT_ID')) 42 + if not project_id: 43 + raise ValueError("Project ID must be set in config.yaml under letta.project_id or as LETTA_PROJECT_ID environment variable") 44 + 45 + # Get all groups 46 + groups = client.groups.list() 47 + 48 + if not groups: 49 + console.print("[yellow]No groups found in this project.[/yellow]") 50 + return 51 + 52 + # Create table 53 + table = Table(title=f"Groups in Project {project_id[:8]}...") 54 + table.add_column("Group ID", style="cyan") 55 + table.add_column("Description", style="white") 56 + table.add_column("Type", style="green") 57 + table.add_column("Manager/Supervisor", style="blue") 58 + table.add_column("Members", style="yellow") 59 + 60 + for group in groups: 61 + group_id = group.id[:12] + "..." if len(group.id) > 12 else group.id 62 + description = group.description[:50] + "..." if group.description and len(group.description) > 50 else (group.description or "N/A") 63 + 64 + # Determine group type and manager 65 + group_type = "Unknown" 66 + manager = "None" 67 + 68 + if hasattr(group, 'manager_config') and group.manager_config: 69 + if hasattr(group.manager_config, 'manager_type'): 70 + group_type = group.manager_config.manager_type 71 + elif hasattr(group.manager_config, '__class__'): 72 + group_type = group.manager_config.__class__.__name__.replace('Manager', '') 73 + 74 + if hasattr(group.manager_config, 'manager_agent_id') and group.manager_config.manager_agent_id: 75 + manager = get_agent_name(client, group.manager_config.manager_agent_id) 76 + 77 + # Get group members 78 + try: 79 + members = client.groups.agents.list(group_id=group.id) 80 + member_count = len(members) 81 + 82 + # Show member names if reasonable number 83 + if member_count <= 3: 84 + member_names = [get_agent_name(client, member.id) for member in members] 85 + members_str = ", ".join(member_names) 86 + else: 87 + members_str = f"{member_count} agents" 88 + except: 89 + members_str = "Error loading" 90 + 91 + table.add_row(group_id, description, group_type, manager, members_str) 92 + 93 + console.print(table) 94 + 95 + # Look specifically for Organon ecosystem 96 + organon_groups = [] 97 + for group in groups: 98 + if (group.description and 'organon' in group.description.lower()) or \ 99 + (hasattr(group, 'manager_config') and group.manager_config and 100 + hasattr(group.manager_config, 'manager_agent_id')): 101 + try: 102 + # Check if manager is organon-central 103 + if hasattr(group.manager_config, 'manager_agent_id'): 104 + manager_name = get_agent_name(client, group.manager_config.manager_agent_id) 105 + if 'organon' in manager_name.lower(): 106 + organon_groups.append((group, manager_name)) 107 + except: 108 + pass 109 + 110 + if organon_groups: 111 + console.print("\n[bold green]Organon Ecosystem Groups Found:[/bold green]") 112 + for group, manager_name in organon_groups: 113 + console.print(f" • {group.id} - Managed by {manager_name}") 114 + else: 115 + console.print("\n[yellow]No Organon ecosystem groups found.[/yellow]") 116 + console.print("[dim]Run the firehose listener to create the Organon ecosystem group.[/dim]") 117 + 118 + except Exception as e: 119 + console.print(f"[red]Error: {e}[/red]") 120 + sys.exit(1) 121 + 122 + if __name__ == "__main__": 123 + main()
+128
organon/setup_group.py
··· 1 + #!/usr/bin/env python3 2 + """ 3 + Simple tool to set up the Organon ecosystem group without running the firehose listener. 4 + """ 5 + 6 + import os 7 + import sys 8 + from dotenv import load_dotenv 9 + from letta_client import Letta, SupervisorManager 10 + from rich.console import Console 11 + 12 + # Add parent directory to path for imports 13 + sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 14 + from config_loader import get_config 15 + 16 + load_dotenv() 17 + 18 + def main(): 19 + console = Console() 20 + 21 + try: 22 + # Initialize configuration and client 23 + config = get_config() 24 + 25 + client = Letta( 26 + base_url=config.get('letta.base_url', os.environ.get('LETTA_BASE_URL')), 27 + token=config.get('letta.api_key', os.environ.get('LETTA_API_KEY')), 28 + timeout=config.get('letta.timeout', 30) 29 + ) 30 + 31 + project_id = config.get('letta.project_id', os.environ.get('LETTA_PROJECT_ID')) 32 + if not project_id: 33 + raise ValueError("Project ID must be set in config.yaml under letta.project_id or as LETTA_PROJECT_ID environment variable") 34 + 35 + # Get Organon central agent 36 + console.print("[blue]Finding Organon Central agent...[/blue]") 37 + try: 38 + organon_agents = client.agents.list(project_id=project_id, name="organon-central") 39 + except: 40 + # Fallback for self-hosted without project support 41 + organon_agents = client.agents.list(name="organon-central") 42 + if not organon_agents: 43 + console.print("[red]❌ Organon Central agent not found. Run create_organon.py first.[/red]") 44 + return 45 + 46 + organon_central_id = organon_agents[0].id 47 + console.print(f"[green]✅ Found Organon Central: {organon_central_id[:8]}[/green]") 48 + 49 + # Get Organon shards 50 + console.print("[blue]Finding Organon shards...[/blue]") 51 + try: 52 + shard_agents = client.agents.list(project_id=project_id, tags=["organon-shard"]) 53 + except: 54 + # Fallback for self-hosted without project support 55 + shard_agents = client.agents.list(tags=["organon-shard"]) 56 + if not shard_agents: 57 + console.print("[red]❌ No Organon shards found. Run create_organon.py to create shards.[/red]") 58 + return 59 + 60 + console.print(f"[green]✅ Found {len(shard_agents)} shards:[/green]") 61 + for shard in shard_agents: 62 + console.print(f" • {shard.name} ({shard.id[:8]})") 63 + 64 + # Check if group already exists 65 + console.print("[blue]Checking for existing groups...[/blue]") 66 + try: 67 + groups = client.groups.list(project_id=project_id) 68 + except: 69 + # Fallback for self-hosted without project support 70 + groups = client.groups.list() 71 + 72 + existing_group = None 73 + for group in groups: 74 + if (group.description and 'organon ecosystem' in group.description.lower()) or \ 75 + (hasattr(group, 'manager_config') and group.manager_config and 76 + hasattr(group.manager_config, 'manager_agent_id') and 77 + group.manager_config.manager_agent_id == organon_central_id): 78 + existing_group = group 79 + break 80 + 81 + if existing_group: 82 + console.print(f"[yellow]Group already exists: {existing_group.id[:12]}[/yellow]") 83 + return 84 + 85 + # Create the supervisor group 86 + console.print("[blue]Creating Organon ecosystem group...[/blue]") 87 + worker_agent_ids = [shard.id for shard in shard_agents] 88 + 89 + group = client.groups.create( 90 + agent_ids=worker_agent_ids, 91 + description="Supervisor group for the Organon ecosystem with organon-central managing all shards", 92 + manager_config=SupervisorManager( 93 + manager_agent_id=organon_central_id 94 + ) 95 + ) 96 + 97 + console.print(f"[green]✅ Created Organon ecosystem group: {group.id[:12]}[/green]") 98 + console.print(f" Supervisor: organon-central ({organon_central_id[:8]})") 99 + console.print(f" Workers: {len(worker_agent_ids)} shards") 100 + 101 + # Verify the group was actually created 102 + console.print("[blue]Verifying group creation...[/blue]") 103 + try: 104 + retrieved_group = client.groups.retrieve(group_id=group.id) 105 + console.print(f"[green]✅ Group verified: {retrieved_group.id[:12]}[/green]") 106 + 107 + # Also check if it shows up in the list 108 + try: 109 + all_groups = client.groups.list(project_id=project_id) 110 + except: 111 + all_groups = client.groups.list() 112 + found_in_list = any(g.id == group.id for g in all_groups) 113 + console.print(f"[{'green' if found_in_list else 'red'}]{'✅' if found_in_list else '❌'} Group appears in list: {found_in_list}[/{'green' if found_in_list else 'red'}]") 114 + 115 + except Exception as e: 116 + console.print(f"[red]❌ Error verifying group: {e}[/red]") 117 + 118 + console.print("\n[bold green]Setup complete! You can now use:[/bold green]") 119 + console.print("• [cyan]python organon/chat_with_organon.py[/cyan] - Chat with the ecosystem") 120 + console.print("• [cyan]python organon/list_groups.py[/cyan] - View group status") 121 + console.print("• [cyan]python organon/firehose_listener.py[/cyan] - Start the firehose listener") 122 + 123 + except Exception as e: 124 + console.print(f"[red]Error: {e}[/red]") 125 + sys.exit(1) 126 + 127 + if __name__ == "__main__": 128 + main()