-6
.env.backup_20250712_134555
-6
.env.backup_20250712_134555
+3
-2
config.example.yaml
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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()