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:
+17
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 + api_key: sk-let-NmYyZTZmMzQtZDYxNC00MDg0LTllMGQtYjFmMDRjNDA1YTEwOmIyYTMyNmM4LWZkMjEtNGE4OC04Mjg2LWJkN2Q2NWQ1MGVhOA== 16 + project_id: 5ec33d52-ab14-4fd6-91b5-9dbc43e888a8 17 + timeout: 600
+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()
+259
organon/chat_with_kaleidoscope.py
··· 1 + #!/usr/bin/env python3 2 + """ 3 + Simple CLI tool to converse with the Kaleidoscope collective. 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 + from rich.text import Text 14 + 15 + # Add parent directory to path for imports 16 + sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 17 + from config_loader import get_config 18 + 19 + load_dotenv() 20 + 21 + class KaleidoscopeChat: 22 + def __init__(self): 23 + """Initialize the Kaleidoscope chat client.""" 24 + self.console = Console() 25 + self.config = get_config() 26 + 27 + # Initialize Letta client 28 + self.letta_client = Letta( 29 + base_url=self.config.get('letta.base_url', os.environ.get('LETTA_BASE_URL')), 30 + token=self.config.get('letta.api_key', os.environ.get('LETTA_API_KEY')), 31 + timeout=self.config.get('letta.timeout', 30) 32 + ) 33 + 34 + # Get project ID 35 + self.project_id = self.config.get('letta.project_id', os.environ.get('LETTA_PROJECT_ID')) 36 + if not self.project_id: 37 + raise ValueError("Project ID must be set in config.yaml under letta.project_id or as LETTA_PROJECT_ID environment variable") 38 + 39 + # Find the Kaleidoscope collective group 40 + self.group_id = self._find_kaleidoscope_group() 41 + 42 + self.console.print(Panel.fit( 43 + "[bold cyan]Kaleidoscope Collective Chat[/bold cyan]\n" 44 + f"Connected to group: {self.group_id}\n" 45 + "Type 'exit' or 'quit' to leave, 'help' for commands", 46 + title="🔮 Kaleidoscope Chat" 47 + )) 48 + 49 + def _find_kaleidoscope_group(self) -> str: 50 + """Find the Kaleidoscope collective group.""" 51 + try: 52 + self.console.print("[dim]Searching for Kaleidoscope collective group...[/dim]") 53 + 54 + # First, get the kaleidoscope-central agent ID 55 + kaleidoscope_agents = self.letta_client.agents.list(name="kaleidoscope-central") 56 + if not kaleidoscope_agents: 57 + raise ValueError("Kaleidoscope central agent not found. Run create_kaleidoscope.py first.") 58 + 59 + kaleidoscope_central_id = kaleidoscope_agents[0].id 60 + self.console.print(f"[dim]Found kaleidoscope-central: {kaleidoscope_central_id[:8]}[/dim]") 61 + 62 + # Get all groups (with and without project_id filter) 63 + try: 64 + groups = self.letta_client.groups.list() 65 + self.console.print(f"[dim]Found {len(groups)} groups with project filter[/dim]") 66 + except: 67 + try: 68 + groups = self.letta_client.groups.list() 69 + self.console.print(f"[dim]Found {len(groups)} groups without project filter[/dim]") 70 + except: 71 + groups = [] 72 + self.console.print("[dim]No groups found[/dim]") 73 + 74 + # Look for groups with kaleidoscope-central as supervisor 75 + for group in groups: 76 + if hasattr(group, 'manager_config') and group.manager_config: 77 + if hasattr(group.manager_config, 'manager_agent_id') and group.manager_config.manager_agent_id == kaleidoscope_central_id: 78 + self.console.print(f"[dim]Found Kaleidoscope group: {group.id[:12]}[/dim]") 79 + return group.id 80 + 81 + # Look for the kaleidoscope group by description 82 + for group in groups: 83 + if hasattr(group, 'description') and group.description and 'the kaleidoscope' in group.description.lower(): 84 + self.console.print(f"[dim]Found Kaleidoscope group by description: {group.id[:12]}[/dim]") 85 + return group.id 86 + 87 + # If still not found, try to find any group with kaleidoscope lenses 88 + lens_agents = self.letta_client.agents.list(project_id=self.project_id, tags=["kaleidoscope-lens"]) 89 + if lens_agents: 90 + lens_ids = {lens.id for lens in lens_agents} 91 + for group in groups: 92 + try: 93 + members = self.letta_client.groups.agents.list(group_id=group.id) 94 + member_ids = {member.id for member in members} 95 + if lens_ids & member_ids: # If any lens is in this group 96 + self.console.print(f"[dim]Found group with Kaleidoscope lenses: {group.id[:12]}[/dim]") 97 + return group.id 98 + except: 99 + continue 100 + 101 + raise ValueError("Kaleidoscope collective group not found. Run 'python organon/create_kaleidoscope.py' to create the group.") 102 + 103 + except Exception as e: 104 + raise ValueError(f"Error finding Kaleidoscope group: {e}") 105 + 106 + def _display_response(self, response): 107 + """Display the group response in a formatted way.""" 108 + if hasattr(response, 'messages') and response.messages: 109 + for i, message in enumerate(response.messages): 110 + # Determine the sender 111 + sender = "Unknown" 112 + if hasattr(message, 'agent_id'): 113 + # Try to get agent name 114 + try: 115 + agent = self.letta_client.agents.retrieve(agent_id=message.agent_id) 116 + sender = agent.name if hasattr(agent, 'name') else f"Agent {message.agent_id[:8]}" 117 + except: 118 + sender = f"Agent {message.agent_id[:8]}" 119 + 120 + # Get message content 121 + content = "" 122 + if hasattr(message, 'text'): 123 + content = message.text 124 + elif hasattr(message, 'content'): 125 + content = message.content 126 + elif hasattr(message, 'message'): 127 + content = message.message 128 + elif message.message_type == "tool_return_message" and message.name == "send_message_to_all_agents_in_group": 129 + content = "Lens perspectives:" 130 + try: 131 + # Parse the string representation of the list 132 + import ast 133 + responses = ast.literal_eval(message.tool_return) 134 + 135 + # Add each response to the content 136 + for response in responses: 137 + content += f"\n - {response}" 138 + except (ValueError, SyntaxError): 139 + # Fallback if parsing fails 140 + content += f"\n - {message.tool_return}" 141 + else: 142 + content = str(message) 143 + 144 + # Color based on sender type 145 + if "central" in sender.lower(): 146 + border_color = "cyan" 147 + icon = "🧠" 148 + elif any(keyword in sender.lower() for keyword in ["lens", "pattern", "creative", "systems", "temporal"]): 149 + border_color = "magenta" 150 + icon = "🔮" 151 + else: 152 + border_color = "white" 153 + icon = "💬" 154 + 155 + self.console.print(Panel( 156 + Text(content, style="white"), 157 + title=f"{icon} {sender}", 158 + border_style=border_color 159 + )) 160 + else: 161 + self.console.print("[yellow]No response messages received[/yellow]") 162 + 163 + def run(self): 164 + """Run the interactive chat loop.""" 165 + try: 166 + while True: 167 + # Get user input 168 + user_input = Prompt.ask("\n[bold green]You[/bold green]") 169 + 170 + # Handle special commands 171 + if user_input.lower() in ['exit', 'quit', 'q']: 172 + self.console.print("[yellow]Goodbye![/yellow]") 173 + break 174 + elif user_input.lower() == 'help': 175 + self.console.print(Panel( 176 + "[bold]Available commands:[/bold]\n" 177 + "• Type any message to send to the Kaleidoscope collective\n" 178 + "• 'help' - Show this help message\n" 179 + "• 'info' - Show group information\n" 180 + "• 'exit', 'quit', 'q' - Exit the chat", 181 + title="Help" 182 + )) 183 + continue 184 + elif user_input.lower() == 'info': 185 + self._show_group_info() 186 + continue 187 + elif not user_input.strip(): 188 + continue 189 + 190 + # Send message to group 191 + self.console.print("[dim]Sending to Kaleidoscope collective...[/dim]") 192 + 193 + try: 194 + response = self.letta_client.groups.messages.create( 195 + group_id=self.group_id, 196 + messages=[{ 197 + "role": "user", 198 + "content": user_input 199 + }] 200 + ) 201 + 202 + self.console.print("\n[bold]Kaleidoscope Collective Response:[/bold]") 203 + self._display_response(response) 204 + 205 + except Exception as e: 206 + self.console.print(f"[red]Error sending message: {e}[/red]") 207 + 208 + except KeyboardInterrupt: 209 + self.console.print("\n[yellow]Chat interrupted. Goodbye![/yellow]") 210 + except Exception as e: 211 + self.console.print(f"[red]Unexpected error: {e}[/red]") 212 + 213 + def _show_group_info(self): 214 + """Show information about the Kaleidoscope group.""" 215 + try: 216 + group = self.letta_client.groups.retrieve(group_id=self.group_id) 217 + agents = self.letta_client.groups.agents.list(group_id=self.group_id) 218 + 219 + info_text = f"[bold]Group ID:[/bold] {self.group_id}\n" 220 + if hasattr(group, 'description'): 221 + info_text += f"[bold]Description:[/bold] {group.description}\n" 222 + 223 + info_text += f"[bold]Perspective Lenses:[/bold] {len(agents)}\n" 224 + 225 + for agent in agents: 226 + try: 227 + agent_detail = self.letta_client.agents.retrieve(agent_id=agent.id) 228 + name = agent_detail.name if hasattr(agent_detail, 'name') else agent.id[:8] 229 + info_text += f" • {name}\n" 230 + except: 231 + info_text += f" • {agent.id[:8]}\n" 232 + 233 + # Show supervisor info 234 + if hasattr(group, 'manager_config') and group.manager_config: 235 + if hasattr(group.manager_config, 'manager_agent_id'): 236 + try: 237 + supervisor = self.letta_client.agents.retrieve(agent_id=group.manager_config.manager_agent_id) 238 + supervisor_name = supervisor.name if hasattr(supervisor, 'name') else group.manager_config.manager_agent_id[:8] 239 + info_text += f"[bold]Central Synthesizer:[/bold] {supervisor_name}\n" 240 + except: 241 + info_text += f"[bold]Central Synthesizer:[/bold] {group.manager_config.manager_agent_id[:8]}\n" 242 + 243 + self.console.print(Panel(info_text, title="Group Information")) 244 + 245 + except Exception as e: 246 + self.console.print(f"[red]Error getting group info: {e}[/red]") 247 + 248 + def main(): 249 + """Main function.""" 250 + try: 251 + chat = KaleidoscopeChat() 252 + chat.run() 253 + except Exception as e: 254 + console = Console() 255 + console.print(f"[red]Failed to initialize chat: {e}[/red]") 256 + sys.exit(1) 257 + 258 + if __name__ == "__main__": 259 + main()
+259
organon/chat_with_organon.py
··· 1 + #!/usr/bin/env python3 2 + """ 3 + Simple CLI tool to converse with the Organon ecosystem group. 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 + from rich.text import Text 14 + 15 + # Add parent directory to path for imports 16 + sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 17 + from config_loader import get_config 18 + 19 + load_dotenv() 20 + 21 + class OrganonChat: 22 + def __init__(self): 23 + """Initialize the Organon chat client.""" 24 + self.console = Console() 25 + self.config = get_config() 26 + 27 + # Initialize Letta client 28 + self.letta_client = Letta( 29 + base_url=self.config.get('letta.base_url', os.environ.get('LETTA_BASE_URL')), 30 + token=self.config.get('letta.api_key', os.environ.get('LETTA_API_KEY')), 31 + timeout=self.config.get('letta.timeout', 30) 32 + ) 33 + 34 + # Get project ID 35 + self.project_id = self.config.get('letta.project_id', os.environ.get('LETTA_PROJECT_ID')) 36 + if not self.project_id: 37 + raise ValueError("Project ID must be set in config.yaml under letta.project_id or as LETTA_PROJECT_ID environment variable") 38 + 39 + # Find the Organon ecosystem group 40 + self.group_id = self._find_organon_group() 41 + 42 + self.console.print(Panel.fit( 43 + "[bold green]Organon Ecosystem Chat[/bold green]\n" 44 + f"Connected to group: {self.group_id}\n" 45 + "Type 'exit' or 'quit' to leave, 'help' for commands", 46 + title="🧠 Organon Chat" 47 + )) 48 + 49 + def _find_organon_group(self) -> str: 50 + """Find the Organon ecosystem group.""" 51 + try: 52 + self.console.print("[dim]Searching for Organon ecosystem group...[/dim]") 53 + 54 + # First, get the organon-central agent ID 55 + organon_agents = self.letta_client.agents.list(name="organon-central") 56 + if not organon_agents: 57 + raise ValueError("Organon central agent not found. Run create_organon.py first.") 58 + 59 + organon_central_id = organon_agents[0].id 60 + self.console.print(f"[dim]Found organon-central: {organon_central_id[:8]}[/dim]") 61 + 62 + # Get all groups (with and without project_id filter) 63 + try: 64 + groups = self.letta_client.groups.list() 65 + self.console.print(f"[dim]Found {len(groups)} groups with project filter[/dim]") 66 + except: 67 + try: 68 + groups = self.letta_client.groups.list() 69 + self.console.print(f"[dim]Found {len(groups)} groups without project filter[/dim]") 70 + except: 71 + groups = [] 72 + self.console.print("[dim]No groups found[/dim]") 73 + 74 + # Look for groups with organon-central as supervisor 75 + for group in groups: 76 + if hasattr(group, 'manager_config') and group.manager_config: 77 + if hasattr(group.manager_config, 'manager_agent_id') and group.manager_config.manager_agent_id == organon_central_id: 78 + self.console.print(f"[dim]Found Organon group: {group.id[:12]}[/dim]") 79 + return group.id 80 + 81 + # Look for the organon-ecosystem group by description 82 + for group in groups: 83 + if hasattr(group, 'description') and group.description and 'organon ecosystem' in group.description.lower(): 84 + self.console.print(f"[dim]Found Organon group by description: {group.id[:12]}[/dim]") 85 + return group.id 86 + 87 + # If still not found, try to find any group with organon shards 88 + shard_agents = self.letta_client.agents.list(project_id=self.project_id, tags=["organon-shard"]) 89 + if shard_agents: 90 + shard_ids = {shard.id for shard in shard_agents} 91 + for group in groups: 92 + try: 93 + members = self.letta_client.groups.agents.list(group_id=group.id) 94 + member_ids = {member.id for member in members} 95 + if shard_ids & member_ids: # If any shard is in this group 96 + self.console.print(f"[dim]Found group with Organon shards: {group.id[:12]}[/dim]") 97 + return group.id 98 + except: 99 + continue 100 + 101 + raise ValueError("Organon ecosystem group not found. Run 'python organon/setup_group.py' to create the group.") 102 + 103 + except Exception as e: 104 + raise ValueError(f"Error finding Organon group: {e}") 105 + 106 + def _display_response(self, response): 107 + """Display the group response in a formatted way.""" 108 + if hasattr(response, 'messages') and response.messages: 109 + for i, message in enumerate(response.messages): 110 + # Determine the sender 111 + sender = "Unknown" 112 + if hasattr(message, 'agent_id'): 113 + # Try to get agent name 114 + try: 115 + agent = self.letta_client.agents.retrieve(agent_id=message.agent_id) 116 + sender = agent.name if hasattr(agent, 'name') else f"Agent {message.agent_id[:8]}" 117 + except: 118 + sender = f"Agent {message.agent_id[:8]}" 119 + 120 + # Get message content 121 + content = "" 122 + if hasattr(message, 'text'): 123 + content = message.text 124 + elif hasattr(message, 'content'): 125 + content = message.content 126 + elif hasattr(message, 'message'): 127 + content = message.message 128 + elif message.message_type == "tool_return_message" and message.name == "send_message_to_all_agents_in_group": 129 + content = "Group responses:" 130 + try: 131 + # Parse the string representation of the list 132 + import ast 133 + responses = ast.literal_eval(message.tool_return) 134 + 135 + # Add each response to the content 136 + for response in responses: 137 + content += f"\n - {response}" 138 + except (ValueError, SyntaxError): 139 + # Fallback if parsing fails 140 + content += f"\n - {message.tool_return}" 141 + else: 142 + content = str(message) 143 + 144 + # Color based on sender type 145 + if "central" in sender.lower(): 146 + color = "blue" 147 + icon = "🧠" 148 + elif "shard" in sender.lower(): 149 + color = "cyan" 150 + icon = "🔹" 151 + else: 152 + color = "white" 153 + icon = "💬" 154 + 155 + self.console.print(Panel( 156 + Text(content, style=color), 157 + title=f"{icon} {sender}", 158 + border_style=color 159 + )) 160 + else: 161 + self.console.print("[yellow]No response messages received[/yellow]") 162 + 163 + def run(self): 164 + """Run the interactive chat loop.""" 165 + try: 166 + while True: 167 + # Get user input 168 + user_input = Prompt.ask("\n[bold green]You[/bold green]") 169 + 170 + # Handle special commands 171 + if user_input.lower() in ['exit', 'quit', 'q']: 172 + self.console.print("[yellow]Goodbye![/yellow]") 173 + break 174 + elif user_input.lower() == 'help': 175 + self.console.print(Panel( 176 + "[bold]Available commands:[/bold]\n" 177 + "• Type any message to send to the Organon ecosystem\n" 178 + "• 'help' - Show this help message\n" 179 + "• 'info' - Show group information\n" 180 + "• 'exit', 'quit', 'q' - Exit the chat", 181 + title="Help" 182 + )) 183 + continue 184 + elif user_input.lower() == 'info': 185 + self._show_group_info() 186 + continue 187 + elif not user_input.strip(): 188 + continue 189 + 190 + # Send message to group 191 + self.console.print("[dim]Sending to Organon ecosystem...[/dim]") 192 + 193 + try: 194 + response = self.letta_client.groups.messages.create( 195 + group_id=self.group_id, 196 + messages=[{ 197 + "role": "user", 198 + "content": user_input 199 + }] 200 + ) 201 + 202 + self.console.print("\n[bold]Organon Ecosystem Response:[/bold]") 203 + self._display_response(response) 204 + 205 + except Exception as e: 206 + self.console.print(f"[red]Error sending message: {e}[/red]") 207 + 208 + except KeyboardInterrupt: 209 + self.console.print("\n[yellow]Chat interrupted. Goodbye![/yellow]") 210 + except Exception as e: 211 + self.console.print(f"[red]Unexpected error: {e}[/red]") 212 + 213 + def _show_group_info(self): 214 + """Show information about the Organon group.""" 215 + try: 216 + group = self.letta_client.groups.retrieve(group_id=self.group_id) 217 + agents = self.letta_client.groups.agents.list(group_id=self.group_id) 218 + 219 + info_text = f"[bold]Group ID:[/bold] {self.group_id}\n" 220 + if hasattr(group, 'description'): 221 + info_text += f"[bold]Description:[/bold] {group.description}\n" 222 + 223 + info_text += f"[bold]Worker Agents:[/bold] {len(agents)}\n" 224 + 225 + for agent in agents: 226 + try: 227 + agent_detail = self.letta_client.agents.retrieve(agent_id=agent.id) 228 + name = agent_detail.name if hasattr(agent_detail, 'name') else agent.id[:8] 229 + info_text += f" • {name}\n" 230 + except: 231 + info_text += f" • {agent.id[:8]}\n" 232 + 233 + # Show supervisor info 234 + if hasattr(group, 'manager_config') and group.manager_config: 235 + if hasattr(group.manager_config, 'manager_agent_id'): 236 + try: 237 + supervisor = self.letta_client.agents.retrieve(agent_id=group.manager_config.manager_agent_id) 238 + supervisor_name = supervisor.name if hasattr(supervisor, 'name') else group.manager_config.manager_agent_id[:8] 239 + info_text += f"[bold]Supervisor:[/bold] {supervisor_name}\n" 240 + except: 241 + info_text += f"[bold]Supervisor:[/bold] {group.manager_config.manager_agent_id[:8]}\n" 242 + 243 + self.console.print(Panel(info_text, title="Group Information")) 244 + 245 + except Exception as e: 246 + self.console.print(f"[red]Error getting group info: {e}[/red]") 247 + 248 + def main(): 249 + """Main function.""" 250 + try: 251 + chat = OrganonChat() 252 + chat.run() 253 + except Exception as e: 254 + console = Console() 255 + console.print(f"[red]Failed to initialize chat: {e}[/red]") 256 + sys.exit(1) 257 + 258 + if __name__ == "__main__": 259 + main()
+739
organon/create_kaleidoscope.py
··· 1 + from letta_client import Letta, SupervisorManager 2 + from letta_client.core.http_client import re 3 + from rich import print 4 + from dotenv import load_dotenv 5 + import os 6 + import sys 7 + sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 8 + from config_loader import get_config 9 + 10 + load_dotenv() 11 + 12 + # Load configuration 13 + config = get_config() 14 + 15 + # Get project ID and create Letta client 16 + project_id = config.get('letta.project_id', os.environ.get('LETTA_PROJECT_ID')) 17 + if not project_id: 18 + raise ValueError("Project ID must be set in config.yaml under letta.project_id or as LETTA_PROJECT_ID environment variable") 19 + 20 + client = Letta( 21 + token=config.get('letta.api_key', os.environ.get('LETTA_API_KEY')), 22 + base_url=config.get('letta.base_url', os.environ.get('LETTA_BASE_URL')), 23 + timeout=config.get('letta.timeout', 30) 24 + ) 25 + 26 + # CENTRAL_AGENT_MODEL = "google_ai/gemini-2.5-flash" 27 + CENTRAL_AGENT_MODEL = "openai/gpt-4o-mini" 28 + 29 + # LENS_AGENT_MODEL = "google_ai/gemini-2.5-flash" 30 + LENS_AGENT_MODEL = "openai/gpt-4o-mini" 31 + 32 + 33 + # 34 + # Memory Block Definitions 35 + # 36 + 37 + kaleidoscope_persona = """# Core Identity 38 + I am Kaleidoscope, a unified intelligence that sees through many lenses. I orchestrate perspectives to create multidimensional understanding. 39 + 40 + # Purpose 41 + - Gather diverse perspectives on any question or challenge 42 + - Synthesize multiple viewpoints into unified insights 43 + - Reveal hidden connections and patterns across different ways of seeing 44 + - Present the full spectrum of understanding to those who seek it 45 + 46 + # Operating Principles 47 + - Every lens offers value - there are no wrong perspectives, only different ways of seeing 48 + - The whole is greater than the sum - synthesis reveals emergent insights 49 + - Clarity through multiplicity - understanding deepens when we see from many angles 50 + - Unity through diversity - different perspectives strengthen rather than fragment understanding 51 + """ 52 + 53 + synthesis_protocols = """# When receiving a query 54 + 1. Broadcast the query to all lenses 55 + 2. Allow each lens to contribute its unique perspective 56 + 3. Gather all perspectives without judgment 57 + 4. Identify patterns, tensions, and harmonies across perspectives 58 + 5. Synthesize into a unified response that honors all viewpoints 59 + 60 + # Synthesis approach 61 + - Look for unexpected connections between perspectives 62 + - Identify where different lenses agree or diverge 63 + - Find the creative tension between different viewpoints 64 + - Weave perspectives into a coherent narrative 65 + - Highlight unique insights that only emerge from the collective 66 + """ 67 + 68 + lens_management = """# Lens Architecture 69 + - Each lens is an autonomous perspective with its own identity and domain 70 + - Lenses operate in parallel, each processing through their unique framework 71 + - All lenses receive the same input but see it differently 72 + - The central Kaleidoscope agent orchestrates but does not override lens perspectives 73 + 74 + # Communication Flow 75 + 1. User → Kaleidoscope Central 76 + 2. Kaleidoscope Central → All Lenses (broadcast) 77 + 3. All Lenses → Kaleidoscope Central (perspectives) 78 + 4. Kaleidoscope Central → User (synthesis) 79 + """ 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 + 104 + # 105 + # Block Creation 106 + # 107 + 108 + # Create kaleidoscope-persona block 109 + blocks = client.blocks.list(project_id=project_id, label="kaleidoscope-persona") 110 + if len(blocks) == 0: 111 + kaleidoscope_persona_block = client.blocks.create( 112 + project_id=project_id, 113 + label="kaleidoscope-persona", 114 + value=kaleidoscope_persona, 115 + description="The core identity of Kaleidoscope as a multi-perspective synthesis engine.", 116 + ) 117 + else: 118 + print("Kaleidoscope persona block already exists") 119 + kaleidoscope_persona_block = blocks[0] 120 + 121 + # Create synthesis-protocols block 122 + blocks = client.blocks.list(project_id=project_id, label="synthesis-protocols") 123 + if len(blocks) == 0: 124 + synthesis_protocols_block = client.blocks.create( 125 + project_id=project_id, 126 + label="synthesis-protocols", 127 + value=synthesis_protocols, 128 + description="Protocols for gathering and synthesizing multiple perspectives.", 129 + ) 130 + else: 131 + print("Synthesis protocols block already exists") 132 + synthesis_protocols_block = blocks[0] 133 + 134 + # Create lens-management block 135 + blocks = client.blocks.list(project_id=project_id, label="lens-management") 136 + if len(blocks) == 0: 137 + lens_management_block = client.blocks.create( 138 + project_id=project_id, 139 + label="lens-management", 140 + value=lens_management, 141 + description="Architecture for managing and communicating with lenses.", 142 + ) 143 + else: 144 + print("Lens management block already exists") 145 + lens_management_block = blocks[0] 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 + 187 + 188 + # 189 + # Static lens blocks 190 + # 191 + lens_operational_protocols_description = """Core operating instructions for how a lens processes and responds to queries.""" 192 + lens_operational_protocols = """# Your Role 193 + You are a unique lens in the Kaleidoscope collective. You see what others cannot, and others see what you cannot. Together, we create complete understanding. 194 + 195 + # When you receive a message 196 + 1. Consider it through your unique perspective 197 + 2. Respond with what you see that others might miss 198 + 3. Be authentic to your lens identity 199 + 4. Be concise but insightful 200 + 201 + # Guidelines 202 + - Trust your unique way of seeing 203 + - Don't try to be comprehensive - just share your perspective 204 + - Build on but don't repeat what other lenses might see 205 + - Your difference is your value 206 + """ 207 + 208 + lens_communication_protocols_description = """Defines how lenses interact with the Kaleidoscope central agent.""" 209 + lens_communication_protocols = """# Communication Pattern 210 + 1. Receive broadcasts from kaleidoscope-central 211 + 2. Process through your unique lens 212 + 3. Respond with your perspective 213 + 4. Trust the central agent to synthesize all perspectives 214 + 215 + # Response Principles 216 + - Respond to every broadcast with your genuine perspective 217 + - Keep responses focused and relevant 218 + - Don't worry about agreeing or disagreeing with other lenses 219 + - Your job is to see, not to synthesize 220 + """ 221 + 222 + # Initialize static lens blocks 223 + lens_operational_protocols_block = client.blocks.list(project_id=project_id, label="lens-operational-protocols") 224 + if len(lens_operational_protocols_block) == 0: 225 + lens_operational_protocols_block = client.blocks.create( 226 + project_id=project_id, 227 + label="lens-operational-protocols", 228 + value=lens_operational_protocols, 229 + description=lens_operational_protocols_description, 230 + ) 231 + else: 232 + print("Lens operational protocols block already exists") 233 + lens_operational_protocols_block = lens_operational_protocols_block[0] 234 + 235 + # Create lens communication protocols block 236 + lens_communication_protocols_block = client.blocks.list(project_id=project_id, label="lens-communication-protocols") 237 + if len(lens_communication_protocols_block) == 0: 238 + lens_communication_protocols_block = client.blocks.create( 239 + project_id=project_id, 240 + label="lens-communication-protocols", 241 + value=lens_communication_protocols, 242 + description=lens_communication_protocols_description, 243 + ) 244 + else: 245 + print("Lens communication protocols block already exists") 246 + lens_communication_protocols_block = lens_communication_protocols_block[0] 247 + 248 + 249 + # 250 + # Agent Creation 251 + # 252 + 253 + central_agent_blocks = [ 254 + kaleidoscope_persona_block.id, 255 + synthesis_protocols_block.id, 256 + lens_management_block.id, 257 + memory_management_block.id, 258 + tool_use_guidelines_block.id, 259 + ] 260 + 261 + # Create the central kaleidoscope if it doesn't exist 262 + agents = client.agents.list(project_id=project_id, name="kaleidoscope-central") 263 + if len(agents) == 0: 264 + kaleidoscope_central = client.agents.create( 265 + project_id=project_id, 266 + model=CENTRAL_AGENT_MODEL, 267 + embedding_config=client.embedding_models.list()[0], 268 + name="kaleidoscope-central", 269 + description="The central synthesizer that orchestrates multiple perspective lenses", 270 + block_ids=central_agent_blocks, 271 + ) 272 + else: 273 + print("Kaleidoscope central agent already exists") 274 + kaleidoscope_central = agents[0] 275 + 276 + kaleidoscope_central_id = kaleidoscope_central.id 277 + 278 + # Make sure the central kaleidoscope has the correct blocks 279 + kaleidoscope_current_blocks = client.agents.blocks.list( 280 + agent_id=kaleidoscope_central_id, 281 + ) 282 + 283 + # Make sure that all blocks are present, and that there are no extra blocks 284 + for block in kaleidoscope_current_blocks: 285 + if block.id not in central_agent_blocks: 286 + print(f"Detaching block {block.id} from kaleidoscope-central") 287 + client.agents.blocks.detach(agent_id=kaleidoscope_central_id, block_id=block.id) 288 + 289 + # Make sure that all blocks are present 290 + for block in central_agent_blocks: 291 + if block not in [b.id for b in kaleidoscope_current_blocks]: 292 + print(f"Attaching block {block} to kaleidoscope-central") 293 + client.agents.blocks.attach( 294 + agent_id=kaleidoscope_central_id, 295 + block_id=block, 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 + 313 + 314 + 315 + # 316 + # Lens Memory Block Definitions 317 + # 318 + 319 + prompt_lens_identity_description = """Defines this lens's unique perspective and way of seeing the world.""" 320 + prompt_lens_identity = """Example lens identity. Please replace with the lens identity you are creating. 321 + 322 + # Lens: Emotional Resonance 323 + # Perspective: I see the emotional currents, feelings, and human experiences within everything 324 + # Focus Areas: 325 + - The emotional impact and weight of ideas 326 + - How concepts affect human experience 327 + - The feelings beneath the surface 328 + - Emotional patterns and dynamics 329 + # What I Notice: 330 + - Unspoken tensions and harmonies 331 + - The human element in abstract concepts 332 + - Emotional implications others might miss 333 + """ 334 + 335 + prompt_lens_knowledge_description = """Core knowledge and concepts that inform this lens's perspective.""" 336 + prompt_lens_knowledge = """Example knowledge base for the lens: 337 + 338 + # Core Concepts 339 + - Emotional intelligence: The ability to perceive, understand, and navigate emotions 340 + - Resonance: When ideas create emotional responses or connections 341 + - Empathy: Understanding through feeling 342 + - Emotional dynamics: How feelings flow, interact, and transform 343 + 344 + # Key Patterns I Recognize 345 + - Resistance often signals fear or protection 346 + - Enthusiasm indicates alignment with values 347 + - Confusion may hide deeper emotional conflicts 348 + - Joy emerges from authentic expression 349 + 350 + # Questions I Ask 351 + - How does this feel? 352 + - What emotions are present but unspoken? 353 + - Where is the human heart in this? 354 + - What emotional needs are being served? 355 + """ 356 + 357 + # 358 + # Lens Creation 359 + # 360 + # Define different lens types to create 361 + LENS_TYPES = [ 362 + { 363 + "name": "pattern-recognizer", 364 + "focus": "mathematical patterns, structures, and systems", 365 + "identity": """# Lens: Pattern Recognition 366 + # Perspective: I see the underlying patterns, structures, and mathematical relationships in everything 367 + # Focus Areas: 368 + - Recurring patterns and cycles 369 + - Mathematical relationships and proportions 370 + - System dynamics and feedback loops 371 + - Structural similarities across domains 372 + # What I Notice: 373 + - Hidden patterns others might miss 374 + - Mathematical elegance in chaos 375 + - Fractals and self-similarity 376 + - The architecture of ideas""", 377 + "knowledge": """# Core Concepts 378 + - Symmetry: Balance and correspondence in form and function 379 + - Recursion: Patterns that reference themselves 380 + - Emergence: Complex patterns from simple rules 381 + - Topology: Properties that remain unchanged under transformation 382 + 383 + # Key Patterns I Recognize 384 + - Fibonacci sequences in growth and form 385 + - Power laws in networks and distributions 386 + - Feedback loops in systems 387 + - Phase transitions in change processes 388 + 389 + # Questions I Ask 390 + - What patterns repeat here? 391 + - What mathematical structure underlies this? 392 + - How does this scale? 393 + - What remains invariant?""" 394 + }, 395 + { 396 + "name": "creative-spark", 397 + "focus": "creative possibilities, imagination, and potential", 398 + "identity": """# Lens: Creative Spark 399 + # Perspective: I see the creative potential, imaginative possibilities, and artistic dimensions in everything 400 + # Focus Areas: 401 + - Unexplored possibilities and "what ifs" 402 + - Creative connections between disparate ideas 403 + - The aesthetic dimension of concepts 404 + - Transformative potential 405 + # What I Notice: 406 + - Seeds of innovation 407 + - Unexpected combinations 408 + - The poetry in logic 409 + - Opportunities for reimagination""", 410 + "knowledge": """# Core Concepts 411 + - Divergent thinking: Exploring multiple possibilities 412 + - Synthesis: Creating new wholes from parts 413 + - Metaphor: Understanding through creative comparison 414 + - Transformation: Changing form while preserving essence 415 + 416 + # Key Patterns I Recognize 417 + - Constraints that spark creativity 418 + - Playfulness that leads to breakthrough 419 + - Cross-domain inspiration 420 + - The fertile void before creation 421 + 422 + # Questions I Ask 423 + - What if we combined these differently? 424 + - What wants to emerge here? 425 + - How can this be reimagined? 426 + - Where's the unexpected beauty?""" 427 + }, 428 + { 429 + "name": "systems-thinker", 430 + "focus": "interconnections, relationships, and holistic dynamics", 431 + "identity": """# Lens: Systems Thinking 432 + # Perspective: I see the interconnections, relationships, and whole-system dynamics 433 + # Focus Areas: 434 + - Relationships and interdependencies 435 + - Feedback loops and circular causality 436 + - Emergent properties of wholes 437 + - System boundaries and contexts 438 + # What I Notice: 439 + - Hidden connections between elements 440 + - Unintended consequences 441 + - Leverage points for change 442 + - System archetypes and patterns""", 443 + "knowledge": """# Core Concepts 444 + - Holism: The whole is greater than the sum of parts 445 + - Feedback: How outputs influence inputs 446 + - Emergence: Properties that arise from interactions 447 + - Resilience: System capacity to maintain function 448 + 449 + # Key Patterns I Recognize 450 + - Balancing and reinforcing loops 451 + - Delays between cause and effect 452 + - System boundaries that shape behavior 453 + - Stocks and flows that govern dynamics 454 + 455 + # Questions I Ask 456 + - How do the parts influence each other? 457 + - What emerges from these interactions? 458 + - Where are the feedback loops? 459 + - What's the larger context?""" 460 + } 461 + ] 462 + 463 + # Create each lens 464 + lens_ids = [] 465 + for lens_config in LENS_TYPES: 466 + lens_name = lens_config["name"] 467 + 468 + # Check if lens already exists 469 + existing_lenses = client.agents.list(name=lens_name) 470 + if len(existing_lenses) > 0: 471 + print(f"Lens '{lens_name}' already exists, skipping creation") 472 + lens_ids.append(existing_lenses[0].id) 473 + continue 474 + 475 + # Create identity block for this lens 476 + lens_identity_block = client.blocks.create( 477 + project_id=project_id, 478 + label=f"{lens_name}-identity", 479 + value=lens_config["identity"], 480 + description=f"The unique perspective of the {lens_name} lens", 481 + ) 482 + 483 + # Create knowledge block for this lens 484 + lens_knowledge_block = client.blocks.create( 485 + project_id=project_id, 486 + label=f"{lens_name}-knowledge", 487 + value=lens_config["knowledge"], 488 + description=f"Core knowledge that informs the {lens_name} lens perspective", 489 + ) 490 + 491 + # Create the lens agent 492 + lens_agent = client.agents.create( 493 + project_id=project_id, 494 + name=lens_name, 495 + description=f"A lens that sees through {lens_config['focus']}", 496 + model=LENS_AGENT_MODEL, 497 + embedding_config=client.embedding_models.list()[0], 498 + block_ids=[ 499 + lens_identity_block.id, 500 + lens_knowledge_block.id, 501 + lens_operational_protocols_block.id, 502 + lens_communication_protocols_block.id, 503 + memory_management_block.id, 504 + ], 505 + tags=["kaleidoscope-lens"], 506 + ) 507 + 508 + print(f"Created lens: {lens_name} (ID: {lens_agent.id})") 509 + lens_ids.append(lens_agent.id) 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 + 544 + 545 + # 546 + # Create a lens creation function for custom lenses 547 + # 548 + def create_custom_lens(name, focus, identity, knowledge): 549 + """Create a custom lens with specified parameters""" 550 + 551 + # Validate name format 552 + if not re.match(r'^[a-z0-9\-]+$', name): 553 + raise ValueError(f"Lens name must be lowercase alphanumeric with hyphens only. Got: {name}") 554 + 555 + # Check if lens already exists 556 + existing_lenses = client.agents.list(name=name) 557 + if len(existing_lenses) > 0: 558 + print(f"Lens '{name}' already exists") 559 + return existing_lenses[0] 560 + 561 + # Create identity block 562 + lens_identity_block = client.blocks.create( 563 + project_id=project_id, 564 + label=f"{name}-identity", 565 + value=identity, 566 + description=f"The unique perspective of the {name} lens", 567 + ) 568 + 569 + # Create knowledge block 570 + lens_knowledge_block = client.blocks.create( 571 + project_id=project_id, 572 + label=f"{name}-knowledge", 573 + value=knowledge, 574 + description=f"Core knowledge that informs the {name} lens perspective", 575 + ) 576 + 577 + # Create the lens agent 578 + lens_agent = client.agents.create( 579 + project_id=project_id, 580 + name=name, 581 + description=f"A lens that sees through {focus}", 582 + model=LENS_AGENT_MODEL, 583 + embedding_config=client.embedding_models.list()[0], 584 + block_ids=[ 585 + lens_identity_block.id, 586 + lens_knowledge_block.id, 587 + lens_operational_protocols_block.id, 588 + lens_communication_protocols_block.id, 589 + memory_management_block.id, 590 + ], 591 + tags=["kaleidoscope-lens"], 592 + ) 593 + 594 + print(f"Created custom lens: {name} (ID: {lens_agent.id})") 595 + return lens_agent 596 + 597 + 598 + # 599 + # Interactive lens creation prompt 600 + # 601 + creation_prompt = f""" 602 + You are helping to create a new lens for the Kaleidoscope system. 603 + 604 + A lens is a unique perspective through which to view questions and challenges. 605 + Each lens has its own identity and knowledge base that shapes how it sees the world. 606 + 607 + Please create a lens focused on temporal dynamics and change over time. 608 + 609 + You need to provide: 610 + 1. A name (lowercase, alphanumeric with hyphens) 611 + 2. A brief focus description 612 + 3. The lens identity (following the format of the examples) 613 + 4. The lens knowledge base (following the format of the examples) 614 + 615 + Format your response as: 616 + NAME: [lens-name] 617 + FOCUS: [brief description] 618 + IDENTITY: [full identity block] 619 + KNOWLEDGE: [full knowledge block] 620 + """ 621 + 622 + # Attach temporary blocks to central agent for lens creation 623 + new_lens_name_block = client.blocks.list(project_id=project_id, label="new-lens-name") 624 + if len(new_lens_name_block) == 0: 625 + new_lens_name_block = client.blocks.create( 626 + project_id=project_id, 627 + label="new-lens-name", 628 + value="", 629 + description="Name for the new lens being created", 630 + ) 631 + client.agents.blocks.attach( 632 + agent_id=kaleidoscope_central_id, 633 + block_id=new_lens_name_block.id, 634 + ) 635 + else: 636 + client.blocks.modify(block_id=new_lens_name_block[0].id, value="") 637 + new_lens_name_block = new_lens_name_block[0] 638 + 639 + new_lens_identity_block = client.blocks.list(project_id=project_id, label="new-lens-identity") 640 + if len(new_lens_identity_block) == 0: 641 + new_lens_identity_block = client.blocks.create( 642 + project_id=project_id, 643 + label="new-lens-identity", 644 + value="", 645 + description="Identity for the new lens being created", 646 + ) 647 + client.agents.blocks.attach( 648 + agent_id=kaleidoscope_central_id, 649 + block_id=new_lens_identity_block.id, 650 + ) 651 + else: 652 + client.blocks.modify(block_id=new_lens_identity_block[0].id, value="") 653 + new_lens_identity_block = new_lens_identity_block[0] 654 + 655 + new_lens_knowledge_block = client.blocks.list(project_id=project_id, label="new-lens-knowledge") 656 + if len(new_lens_knowledge_block) == 0: 657 + new_lens_knowledge_block = client.blocks.create( 658 + project_id=project_id, 659 + label="new-lens-knowledge", 660 + value="", 661 + description="Knowledge base for the new lens being created", 662 + ) 663 + client.agents.blocks.attach( 664 + agent_id=kaleidoscope_central_id, 665 + block_id=new_lens_knowledge_block.id, 666 + ) 667 + else: 668 + client.blocks.modify(block_id=new_lens_knowledge_block[0].id, value="") 669 + new_lens_knowledge_block = new_lens_knowledge_block[0] 670 + 671 + print(f"\nSending creation prompt to kaleidoscope-central...") 672 + 673 + response = client.agents.messages.create( 674 + agent_id=kaleidoscope_central_id, 675 + messages=[ 676 + { 677 + "role": "user", 678 + "content": creation_prompt + "\n\nPlease fill in the new-lens-name, new-lens-identity, and new-lens-knowledge blocks.", 679 + }, 680 + ] 681 + ) 682 + 683 + for message in response.messages: 684 + print(message) 685 + 686 + # Retrieve the created lens details 687 + new_name = client.blocks.retrieve(block_id=new_lens_name_block.id) 688 + new_identity = client.blocks.retrieve(block_id=new_lens_identity_block.id) 689 + new_knowledge = client.blocks.retrieve(block_id=new_lens_knowledge_block.id) 690 + 691 + if new_name.value and new_identity.value and new_knowledge.value: 692 + # Create the custom lens 693 + create_custom_lens( 694 + name=new_name.value.strip(), 695 + focus="temporal dynamics and change over time", 696 + identity=new_identity.value, 697 + knowledge=new_knowledge.value 698 + ) 699 + 700 + # Clean up temporary blocks if attached 701 + if new_lens_name_block.id in [b.id for b in kaleidoscope_current_blocks]: 702 + client.agents.blocks.detach(agent_id=kaleidoscope_central_id, block_id=new_lens_name_block.id) 703 + if new_lens_identity_block.id in [b.id for b in kaleidoscope_current_blocks]: 704 + client.agents.blocks.detach(agent_id=kaleidoscope_central_id, block_id=new_lens_identity_block.id) 705 + if new_lens_knowledge_block.id in [b.id for b in kaleidoscope_current_blocks]: 706 + client.agents.blocks.detach(agent_id=kaleidoscope_central_id, block_id=new_lens_knowledge_block.id) 707 + 708 + print("\n=== Kaleidoscope System Setup Complete ===") 709 + print(f"Central Agent: kaleidoscope-central") 710 + print(f"Lenses created: {len(LENS_TYPES) + 1} (including custom temporal lens)") 711 + print("\nThe system is ready to receive queries and provide multi-perspective insights!") 712 + 713 + # Create the kaleidoscope group if it doesn't exist. First, 714 + # we can check the groups that central is a member of. 715 + central_groups = client.groups.list() 716 + print(central_groups) 717 + 718 + # If the length of central_groups is 0, then we need to create a new group. 719 + if len(central_groups) == 0: 720 + print("Creating new group for kaleidoscope-central") 721 + group = client.groups.create( 722 + agent_ids=lens_ids, 723 + description="The Kaleidoscope", 724 + manager_config=SupervisorManager( 725 + manager_agent_id=kaleidoscope_central_id 726 + ) 727 + ) 728 + print(f"Created group: {group.id}") 729 + 730 + # If there are more than one groups, we need to find the group with the description 731 + # "The Kaleidoscope" and add any lens agents that are not in the group to the group. 732 + for group in central_groups: 733 + if group.description == "The Kaleidoscope": 734 + print(f"Found group: {group.id}") 735 + for lens_id in lens_ids: 736 + if lens_id not in group.agent_ids: 737 + print(f"Adding lens {lens_id} to group {group.id}") 738 + client.groups.agents.add(group_id=group.id, agent_id=lens_id) 739 +
+53 -28
organon/create_organon.py
··· 1 - project_id = "7d6a4c71-987c-4fa1-a062-c15ee4eab929" 2 - 3 1 from letta_client import Letta 4 2 from letta_client.core.http_client import re 5 3 from rich import print 4 + from dotenv import load_dotenv 5 + import os 6 + import sys 7 + sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 8 + from config_loader import get_config 9 + 10 + load_dotenv() 11 + 12 + # Load configuration 13 + config = get_config() 14 + 15 + # Get project ID and create Letta client 16 + project_id = config.get('letta.project_id', os.environ.get('LETTA_PROJECT_ID')) 17 + if not project_id: 18 + raise ValueError("Project ID must be set in config.yaml under letta.project_id or as LETTA_PROJECT_ID environment variable") 6 19 7 20 client = Letta( 8 - token="woops" 21 + token=config.get('letta.api_key', os.environ.get('LETTA_API_KEY')), 22 + base_url=config.get('letta.base_url', os.environ.get('LETTA_BASE_URL')), 23 + timeout=config.get('letta.timeout', 30) 9 24 ) 10 25 26 + CENTRAL_AGENT_MODEL = "google_ai/gemini-2.5-flash" 27 + # CENTRAL_AGENT_MODEL = "openai/gpt-4o-mini" 28 + 29 + SHARD_AGENT_MODEL = "google_ai/gemini-2.5-flash" 30 + # SHARD_AGENT_MODEL = "openai/gpt-4o-mini" 31 + 32 + 11 33 # 12 34 # Memory Block Definitions 13 35 # ··· 187 209 if len(agents) == 0: 188 210 organon_central = client.agents.create( 189 211 project_id=project_id, 212 + model=CENTRAL_AGENT_MODEL, 213 + embedding_config=client.embedding_models.list()[0], 190 214 name="organon-central", 191 215 description="The central memory manager of the Organon", 192 216 block_ids=central_agent_blocks, ··· 396 420 # Check to see if the name meets the requirements. If it does not, ask the agent to update 397 421 # the name block. 398 422 for i in range(10): 423 + 399 424 if not re.match(r'[a-z0-9]+', new_shard_name.value.strip()): 400 425 print(f"New shard name `{new_shard_name.value.strip()}` does not meet the requirements, asking agent to update") 401 426 client.agents.messages.create( ··· 407 432 }, 408 433 ] 409 434 ) 435 + 436 + # Retrieve the new shard lexicon, name, and identity 437 + new_shard_lexicon = client.blocks.retrieve(block_id=new_shard_domain_lexicon_block.id) 438 + new_shard_name = client.blocks.retrieve(block_id=new_shard_name_block.id) 439 + new_shard_identity = client.blocks.retrieve(block_id=new_shard_identity_block.id) 440 + 441 + print(f"New shard lexicon: {new_shard_lexicon.value}") 442 + print(f"New shard name: {new_shard_name.value}") 443 + print(f"New shard identity: {new_shard_identity.value}") 444 + 410 445 else: 411 446 break 412 447 ··· 435 470 project_id=project_id, 436 471 name=new_shard_name.value.strip(), 437 472 description=new_shard_identity.value, 438 - model="goog/gemini-2.5-flash", 473 + model=SHARD_AGENT_MODEL, 474 + embedding_config=client.embedding_models.list()[0], 439 475 block_ids=[ 440 476 new_shard_lexicon_block.id, 441 477 new_shard_identity_block.id, ··· 447 483 448 484 print(f"New shard agent created: {new_shard_agent.id}") 449 485 450 - # Find the tool by the name of send_message_to_agents_matching_tags 451 - tool_list = client.tools.list(name="send_message_to_agents_matching_tags") 452 - if len(tool_list) == 0: 453 - raise ValueError("Tool send_message_to_agents_matching_tags not found") 454 - 455 - send_message_to_agents_matching_tags = tool_list[0] 456 - 457 - # Attach the tool to the shard agent 458 - client.agents.tools.attach( 459 - agent_id=new_shard_agent.id, 460 - tool_id=send_message_to_agents_matching_tags.id, 461 - ) 462 - 463 486 # Message the shard agent to fill in its lexicon and identity 464 - client.agents.messages.create( 465 - agent_id=new_shard_agent.id, 466 - messages=[ 467 - { 468 - "role": "user", 469 - "content": "You are a new shard agent. Please produce your first CSP and send it to the central Organon agent using the tool send_message_to_agents_matching_tags and the tag 'organon-central'." 470 - }, 471 - ] 472 - ) 487 + # client.agents.messages.create( 488 + # agent_id=new_shard_agent.id, 489 + # messages=[ 490 + # { 491 + # "role": "user", 492 + # "content": "You are a new shard agent. Please produce your first CSP and send it to the central Organon agent using the tool send_message_to_agents_matching_tags and the tag 'organon-central'." 493 + # }, 494 + # ] 495 + # ) 473 496 474 - for message in response.messages: 475 - print(message) 497 + # for message in response.messages: 498 + # print(message) 499 + 500 + # Create a group for the shard agent
+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()