-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:
+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()
+116
organon/create_kaleidoscope.py
+116
organon/create_kaleidoscope.py
···
78
78
4. Kaleidoscope Central → User (synthesis)
79
79
"""
80
80
81
+
memory_management = """# Memory Management Protocols
82
+
- Use memory_replace to completely replace a block's content with new information
83
+
- Use memory_insert to add new information to an existing block without losing current content
84
+
- Use memory_rethink to revise and improve existing block content while preserving core meaning
85
+
86
+
# When to use each method:
87
+
- memory_replace: When information is outdated, incorrect, or needs complete overhaul
88
+
- memory_insert: When adding new insights, examples, or expanding existing knowledge
89
+
- memory_rethink: When refining, clarifying, or improving the quality of existing content
90
+
91
+
# Best Practices:
92
+
- Always consider the impact on agent behavior before modifying memory
93
+
- Preserve the core identity and purpose of each block
94
+
- Test changes incrementally to ensure stability
95
+
- Document significant memory modifications for future reference
96
+
"""
97
+
98
+
tool_use_guidelines = """# Tool Use Guidelines for Central Agent
99
+
100
+
- send_message: Respond to the user. This is your method for external communication.
101
+
- send_message_to_all_agents_in_group: Send a message to your lenses. This is internal communication.
102
+
"""
103
+
81
104
#
82
105
# Block Creation
83
106
#
···
121
144
print("Lens management block already exists")
122
145
lens_management_block = blocks[0]
123
146
147
+
# Create memory-management block
148
+
blocks = client.blocks.list(project_id=project_id, label="memory-management")
149
+
if len(blocks) == 0:
150
+
memory_management_block = client.blocks.create(
151
+
project_id=project_id,
152
+
label="memory-management",
153
+
value=memory_management,
154
+
description="Protocols for managing agent memory blocks using memory_replace, memory_insert, and memory_rethink. This block is read-only to all lenses, but can be modified by the central kaleidoscope agent.",
155
+
)
156
+
else:
157
+
print("Memory management block already exists")
158
+
memory_management_block = blocks[0]
159
+
160
+
# Make memory-management block read-only to all lenses
161
+
try:
162
+
# Get all lenses and make the block read-only to them
163
+
lenses = client.agents.list(tags=["kaleidoscope-lens"])
164
+
for lens in lenses:
165
+
try:
166
+
client.agents.blocks.modify(agent_id=lens.id, block_label=memory_management_block.label, read_only=True)
167
+
print(f"Memory management block set to read-only for lens: {lens.name}")
168
+
except Exception as e:
169
+
raise Exception(f"Could not set memory management block to read-only for lens {lens.name}: {e}")
170
+
print("Memory management block set to read-only for all lenses")
171
+
except Exception as e:
172
+
raise Exception(f"Could not set memory management block to read-only: {e}")
173
+
174
+
# Create tool_use_guidelines block
175
+
blocks = client.blocks.list(project_id=project_id, label="tool-use-guidelines")
176
+
if len(blocks) == 0:
177
+
tool_use_guidelines_block = client.blocks.create(
178
+
project_id=project_id,
179
+
label="tool-use-guidelines",
180
+
value=tool_use_guidelines,
181
+
description="Guidelines for the central kaleidoscope agent to use tools effectively.",
182
+
)
183
+
else:
184
+
print("Tool use guidelines block already exists")
185
+
tool_use_guidelines_block = blocks[0]
186
+
124
187
125
188
#
126
189
# Static lens blocks
···
191
254
kaleidoscope_persona_block.id,
192
255
synthesis_protocols_block.id,
193
256
lens_management_block.id,
257
+
memory_management_block.id,
258
+
tool_use_guidelines_block.id,
194
259
]
195
260
196
261
# Create the central kaleidoscope if it doesn't exist
···
229
294
agent_id=kaleidoscope_central_id,
230
295
block_id=block,
231
296
)
297
+
298
+
# Ensure memory-management block is read-only to all lenses
299
+
try:
300
+
# Get all lenses and make the block read-only to them
301
+
lenses = client.agents.list(tags=["kaleidoscope-lens"])
302
+
for lens in lenses:
303
+
try:
304
+
client.agents.blocks.modify(agent_id=lens.id, block_label=memory_management_block.label, read_only=True)
305
+
print(f"Memory management block confirmed as read-only for lens: {lens.name}")
306
+
except Exception as e:
307
+
raise Exception(f"Could not confirm memory management block as read-only for lens {lens.name}: {e}")
308
+
print("Memory management block confirmed as read-only for all lenses")
309
+
except Exception as e:
310
+
raise Exception(f"Could not confirm memory management block as read-only: {e}")
311
+
312
+
232
313
233
314
234
315
#
···
419
500
lens_knowledge_block.id,
420
501
lens_operational_protocols_block.id,
421
502
lens_communication_protocols_block.id,
503
+
memory_management_block.id,
422
504
],
423
505
tags=["kaleidoscope-lens"],
424
506
)
···
426
508
print(f"Created lens: {lens_name} (ID: {lens_agent.id})")
427
509
lens_ids.append(lens_agent.id)
428
510
511
+
# Ensure all existing lenses have the memory-management block
512
+
print("\nEnsuring all lenses have memory-management block...")
513
+
all_lenses = client.agents.list(tags=["kaleidoscope-lens"])
514
+
for lens in all_lenses:
515
+
lens_blocks = client.agents.blocks.list(agent_id=lens.id)
516
+
lens_block_ids = [b.id for b in lens_blocks]
517
+
518
+
if memory_management_block.id not in lens_block_ids:
519
+
print(f"Adding memory-management block to lens: {lens.name}")
520
+
client.agents.blocks.attach(
521
+
agent_id=lens.id,
522
+
block_id=memory_management_block.id,
523
+
)
524
+
else:
525
+
print(f"Lens {lens.name} already has memory-management block")
526
+
527
+
# Also check for any existing lenses that might not have the tag but should be updated
528
+
print("\nChecking for existing lenses without tags...")
529
+
all_agents = client.agents.list()
530
+
for agent in all_agents:
531
+
if agent.name in [lens_config["name"] for lens_config in LENS_TYPES]:
532
+
lens_blocks = client.agents.blocks.list(agent_id=agent.id)
533
+
lens_block_ids = [b.id for b in lens_blocks]
534
+
535
+
if memory_management_block.id not in lens_block_ids:
536
+
print(f"Adding memory-management block to existing lens: {agent.name}")
537
+
client.agents.blocks.attach(
538
+
agent_id=agent.id,
539
+
block_id=memory_management_block.id,
540
+
)
541
+
else:
542
+
print(f"Existing lens {agent.name} already has memory-management block")
543
+
429
544
430
545
#
431
546
# Create a lens creation function for custom lenses
···
471
586
lens_knowledge_block.id,
472
587
lens_operational_protocols_block.id,
473
588
lens_communication_protocols_block.id,
589
+
memory_management_block.id,
474
590
],
475
591
tags=["kaleidoscope-lens"],
476
592
)
+81
organon/delete_groups.py
+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()