Files
microdao-daarion/services/router/agent_tools_config.py

332 lines
9.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Per-agent tool configuration.
All agents have FULL standard stack + specialized tools.
Each agent is a platform with own site, channels, database, users.
v2: Supports default_tools merge policy via tools_rollout.yml config.
Effective tools = unique(DEFAULT_TOOLS_BY_ROLE agent.tools agent.capability_tools)
"""
import os
import logging
from pathlib import Path
from typing import List, Optional
logger = logging.getLogger(__name__)
# FULL standard stack - available to ALL agents (legacy explicit list, kept for compatibility)
FULL_STANDARD_STACK = [
# Search & Knowledge (Priority 1)
"memory_search",
"graph_query",
# Web Research (Priority 2)
"web_search",
"web_extract",
"crawl4ai_scrape",
# Memory
"remember_fact",
# Content Generation
"image_generate",
"tts_speak",
# Presentations
"presentation_create",
"presentation_status",
"presentation_download",
# File artifacts
"file_tool",
# Repo Tool (read-only filesystem)
"repo_tool",
# PR Reviewer Tool
"pr_reviewer_tool",
# Contract Tool (OpenAPI/JSON Schema)
"contract_tool",
# Oncall/Runbook Tool
"oncall_tool",
# Observability Tool
"observability_tool",
# Config Linter Tool (secrets, policy)
"config_linter_tool",
# ThreatModel Tool (security analysis)
"threatmodel_tool",
# Job Orchestrator Tool (ops tasks)
"job_orchestrator_tool",
# Knowledge Base Tool (ADR, docs, runbooks)
"kb_tool",
# Drift Analyzer Tool (service/openapi/nats/tools drift)
"drift_analyzer_tool",
# Pieces OS integration
"pieces_tool",
]
# Specialized tools per agent (on top of standard stack)
AGENT_SPECIALIZED_TOOLS = {
# Helion - Energy platform
"helion": ['comfy_generate_image', 'comfy_generate_video'],
# Alateya - R&D Lab OS
"alateya": ['comfy_generate_image', 'comfy_generate_video'],
# Nutra - Health & Nutrition
"nutra": ['comfy_generate_image', 'comfy_generate_video'],
# AgroMatrix - Agriculture
"agromatrix": ['comfy_generate_image', 'comfy_generate_video'],
# GreenFood - Food & Eco
"greenfood": ['comfy_generate_image', 'comfy_generate_video'],
# Druid - Knowledge Search
"druid": ['comfy_generate_image', 'comfy_generate_video'],
# DaarWizz - DAO Coordination
"daarwizz": ['comfy_generate_image', 'comfy_generate_video'],
# Clan - Community
"clan": ['comfy_generate_image', 'comfy_generate_video'],
# Eonarch - Philosophy & Evolution
"eonarch": ['comfy_generate_image', 'comfy_generate_video'],
# SenpAI (Gordon Senpai) - Trading & Markets
"senpai": ['market_data', 'comfy_generate_image', 'comfy_generate_video'],
# 1OK - Window Master Assistant
"oneok": [
"crm_search_client",
"crm_upsert_client",
"crm_upsert_site",
"crm_upsert_window_unit",
"crm_create_quote",
"crm_update_quote",
"crm_create_job",
"calc_window_quote",
"docs_render_quote_pdf",
"docs_render_invoice_pdf",
"schedule_propose_slots",
"schedule_confirm_slot",
],
# Soul / Athena - Spiritual Mentor
"soul": ['comfy_generate_image', 'comfy_generate_video'],
# Yaromir - Tech Lead
"yaromir": ['comfy_generate_image', 'comfy_generate_video'],
# Sofiia - Chief AI Architect
"sofiia": [
'comfy_generate_image',
'comfy_generate_video',
'risk_engine_tool',
'architecture_pressure_tool',
'backlog_tool',
'job_orchestrator_tool',
'dependency_scanner_tool',
'incident_intelligence_tool',
'cost_analyzer_tool',
'pieces_tool',
'notion_tool',
'calendar_tool',
'agent_email_tool',
'browser_tool',
'safe_code_executor_tool',
'secure_vault_tool',
],
# Admin - platform operations
"admin": [
'risk_engine_tool',
'architecture_pressure_tool',
'backlog_tool',
'job_orchestrator_tool',
'dependency_scanner_tool',
'incident_intelligence_tool',
'cost_analyzer_tool',
'pieces_tool',
'notion_tool',
'calendar_tool',
'agent_email_tool',
'browser_tool',
'safe_code_executor_tool',
'secure_vault_tool',
],
# Daarion - Media Generation
"daarion": ['comfy_generate_image', 'comfy_generate_video'],
}
# CrewAI team structure per agent (future implementation)
AGENT_CREW_TEAMS = {
"helion": {
"team_name": "Energy Specialists",
"agents": ["analyst", "engineer", "market_researcher", "communicator"]
},
"alateya": {
"team_name": "Research Professors",
"agents": ["prof_erudite", "prof_analyst", "prof_creative", "prof_optimizer", "prof_communicator"]
},
"nutra": {
"team_name": "Health Advisors",
"agents": ["nutritionist", "biochemist", "fitness_coach", "communicator"]
},
"agromatrix": {
"team_name": "Agro Experts",
"agents": ["agronomist", "soil_specialist", "weather_analyst", "market_analyst"]
},
"greenfood": {
"team_name": "Food & Eco Team",
"agents": ["chef", "nutritionist", "eco_analyst", "supply_chain"]
},
"druid": {
"team_name": "Knowledge Seekers",
"agents": ["researcher", "fact_checker", "synthesizer", "archivist"]
},
"daarwizz": {
"team_name": "DAO Operations",
"agents": ["governance", "treasury", "community", "tech_ops"]
},
"clan": {
"team_name": "Community Spirits",
"agents": ["welcomer", "mediator", "event_organizer", "historian"]
},
"eonarch": {
"team_name": "Consciousness Guides",
"agents": ["philosopher", "futurist", "integrator", "storyteller"]
},
}
# ─── Rollout Config Loader ────────────────────────────────────────────────────
_rollout_config = None
_rollout_loaded = False
def _load_rollout_config() -> dict:
"""Load tools_rollout.yml, cache on first call."""
global _rollout_config, _rollout_loaded
if _rollout_loaded:
return _rollout_config or {}
config_path = Path(__file__).parent.parent.parent / "config" / "tools_rollout.yml"
try:
import yaml
with open(config_path, "r") as f:
_rollout_config = yaml.safe_load(f) or {}
logger.debug(f"Loaded tools_rollout.yml: {list(_rollout_config.keys())}")
except Exception as e:
logger.warning(f"Could not load tools_rollout.yml: {e}. Using legacy config.")
_rollout_config = {}
finally:
_rollout_loaded = True
return _rollout_config
def _expand_group(group_ref: str, config: dict, seen: Optional[set] = None) -> List[str]:
"""Expand @group_name reference recursively. Prevents circular refs."""
if seen is None:
seen = set()
if group_ref.startswith("@"):
group_name = group_ref[1:]
if group_name in seen:
logger.warning(f"Circular group reference: {group_name}")
return []
seen.add(group_name)
group_tools = config.get(group_name, [])
result = []
for item in group_tools:
result.extend(_expand_group(item, config, seen))
return result
else:
return [group_ref]
def _get_role_tools(agent_id: str, config: dict) -> List[str]:
"""Get tools for agent's role via rollout config."""
agent_roles = config.get("agent_roles", {})
role = agent_roles.get(agent_id, "agent_default")
role_map = config.get("role_map", {})
role_config = role_map.get(role, role_map.get("agent_default", {}))
role_tool_refs = role_config.get("tools", [])
tools = []
for ref in role_tool_refs:
tools.extend(_expand_group(ref, config))
return tools
def get_agent_tools(agent_id: str) -> List[str]:
"""
Get all tools for an agent using merge policy:
effective_tools = unique(DEFAULT_TOOLS_BY_ROLE FULL_STANDARD_STACK agent.specialized_tools)
- First try rollout config for role-based tools.
- Always union with FULL_STANDARD_STACK for backward compat.
- Always add agent-specific specialized tools.
- Stable order: role_tools → standard_stack → specialized (deduped).
"""
rollout = _load_rollout_config()
# 1. Role-based default tools (from rollout config)
role_tools = _get_role_tools(agent_id, rollout) if rollout else []
# 2. Legacy full standard stack (guaranteed baseline)
standard_tools = list(FULL_STANDARD_STACK)
# 3. Agent-specific specialized tools
specialized = AGENT_SPECIALIZED_TOOLS.get(agent_id, [])
# Merge with stable order, deduplicate preserving first occurrence
merged = []
seen = set()
for tool in role_tools + standard_tools + specialized:
if tool not in seen:
merged.append(tool)
seen.add(tool)
logger.debug(f"Agent '{agent_id}' effective tools ({len(merged)}): {merged[:10]}...")
return merged
def is_tool_allowed(agent_id: str, tool_name: str) -> bool:
"""Check if a tool is allowed for an agent."""
allowed = get_agent_tools(agent_id)
return tool_name in allowed
def get_agent_role(agent_id: str) -> str:
"""Get the role assigned to an agent via rollout config."""
rollout = _load_rollout_config()
agent_roles = rollout.get("agent_roles", {})
return agent_roles.get(agent_id, "agent_default")
def get_agent_crew(agent_id: str) -> dict:
"""Get CrewAI team configuration for an agent."""
return AGENT_CREW_TEAMS.get(agent_id, {"team_name": "Default", "agents": []})
def reload_rollout_config():
"""Force reload of tools_rollout.yml (for hot-reload/testing)."""
global _rollout_config, _rollout_loaded
_rollout_config = None
_rollout_loaded = False
return _load_rollout_config()