Files
microdao-daarion/scripts/maintenance/find_orphan_agents.py

172 lines
5.3 KiB
Python

#!/usr/bin/env python3
"""
Find orphan agents that don't belong to any MicroDAO.
This script checks:
1. Agents without MicroDAO membership
2. Agents without node_id (home node)
3. Agents that are not in router-config
Usage:
python scripts/maintenance/find_orphan_agents.py
"""
import asyncio
import os
import asyncpg
from typing import List, Dict, Any
DATABASE_URL = os.getenv(
"DATABASE_URL",
"postgresql://postgres:postgres@localhost:5432/daarion"
)
async def get_pool():
"""Get database connection pool."""
return await asyncpg.create_pool(DATABASE_URL)
async def find_agents_without_microdao(pool) -> List[Dict[str, Any]]:
"""Find agents that don't belong to any MicroDAO."""
query = """
SELECT a.id, a.display_name, a.kind, a.node_id, a.is_public, a.is_archived
FROM agents a
LEFT JOIN microdao_agents ma ON ma.agent_id = a.id
WHERE ma.agent_id IS NULL
AND COALESCE(a.is_archived, false) = false
ORDER BY a.display_name
"""
rows = await pool.fetch(query)
return [dict(row) for row in rows]
async def find_agents_without_node(pool) -> List[Dict[str, Any]]:
"""Find agents without home node."""
query = """
SELECT id, display_name, kind
FROM agents
WHERE (node_id IS NULL OR node_id = '')
AND COALESCE(is_archived, false) = false
ORDER BY display_name
"""
rows = await pool.fetch(query)
return [dict(row) for row in rows]
async def find_microdao_without_orchestrator(pool) -> List[Dict[str, Any]]:
"""Find MicroDAOs without an orchestrator."""
query = """
SELECT m.id, m.slug, m.name, COUNT(ma.agent_id) as agents_count
FROM microdaos m
LEFT JOIN microdao_agents ma ON ma.microdao_id = m.id AND ma.role = 'orchestrator'
WHERE COALESCE(m.is_archived, false) = false
GROUP BY m.id
HAVING COUNT(ma.agent_id) = 0
ORDER BY m.name
"""
rows = await pool.fetch(query)
return [dict(row) for row in rows]
async def find_empty_microdao(pool) -> List[Dict[str, Any]]:
"""Find MicroDAOs with 0 agents."""
query = """
SELECT m.id, m.slug, m.name
FROM microdaos m
LEFT JOIN microdao_agents ma ON ma.microdao_id = m.id
WHERE COALESCE(m.is_archived, false) = false
GROUP BY m.id
HAVING COUNT(ma.agent_id) = 0
ORDER BY m.name
"""
rows = await pool.fetch(query)
return [dict(row) for row in rows]
async def get_stats(pool) -> Dict[str, int]:
"""Get overall stats."""
total_agents = await pool.fetchval(
"SELECT COUNT(*) FROM agents WHERE COALESCE(is_archived, false) = false"
)
total_microdao = await pool.fetchval(
"SELECT COUNT(*) FROM microdaos WHERE COALESCE(is_archived, false) = false"
)
agents_with_microdao = await pool.fetchval("""
SELECT COUNT(DISTINCT a.id)
FROM agents a
JOIN microdao_agents ma ON ma.agent_id = a.id
WHERE COALESCE(a.is_archived, false) = false
""")
return {
"total_agents": total_agents,
"total_microdao": total_microdao,
"agents_with_microdao": agents_with_microdao,
"agents_without_microdao": total_agents - agents_with_microdao,
}
async def main():
print("=" * 60)
print("DAARION Agent Orphan Finder")
print("=" * 60)
pool = await get_pool()
try:
# Stats
stats = await get_stats(pool)
print(f"\n📊 Overall Stats:")
print(f" Total agents: {stats['total_agents']}")
print(f" Total MicroDAOs: {stats['total_microdao']}")
print(f" Agents with MicroDAO: {stats['agents_with_microdao']}")
print(f" Agents without MicroDAO: {stats['agents_without_microdao']}")
# Orphan agents
orphans = await find_agents_without_microdao(pool)
if orphans:
print(f"\n⚠️ Agents WITHOUT MicroDAO ({len(orphans)}):")
for agent in orphans:
print(f" - {agent['display_name']} ({agent['kind']}) @ {agent['node_id']}")
else:
print("\n✅ All agents belong to at least one MicroDAO!")
# Agents without node
no_node = await find_agents_without_node(pool)
if no_node:
print(f"\n⚠️ Agents WITHOUT node_id ({len(no_node)}):")
for agent in no_node:
print(f" - {agent['display_name']} ({agent['kind']})")
else:
print("\n✅ All agents have a home node!")
# MicroDAOs without orchestrator
no_orch = await find_microdao_without_orchestrator(pool)
if no_orch:
print(f"\n⚠️ MicroDAOs WITHOUT orchestrator ({len(no_orch)}):")
for dao in no_orch:
print(f" - {dao['name']} ({dao['slug']})")
else:
print("\n✅ All MicroDAOs have an orchestrator!")
# Empty MicroDAOs
empty = await find_empty_microdao(pool)
if empty:
print(f"\n⚠️ Empty MicroDAOs ({len(empty)}):")
for dao in empty:
print(f" - {dao['name']} ({dao['slug']})")
else:
print("\n✅ All MicroDAOs have at least one agent!")
print("\n" + "=" * 60)
print("Done!")
finally:
await pool.close()
if __name__ == "__main__":
asyncio.run(main())