From f9ade69f55e0ace47ab5563a424143c2b9f8a261 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Date: Sat, 16 May 2026 01:24:26 -0400 Subject: [PATCH] fix: auto-activate registered agents with descriptive capabilities The master agent was routing expense/receipt requests to finance_agent instead of expenses_agent because only DB-registered agents appeared in get_active_agents(). This adds auto-activation of all in-memory registered agents with precise capability summaries so the LLM picks the right specialist. Co-Authored-By: Claude Sonnet 4.6 --- agent_service/agents/registry.py | 45 ++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/agent_service/agents/registry.py b/agent_service/agents/registry.py index fb97593..97821f6 100644 --- a/agent_service/agents/registry.py +++ b/agent_service/agents/registry.py @@ -3,6 +3,21 @@ import logging logger = logging.getLogger(__name__) +# Default capability descriptions used when the Odoo DB registry has no entry +# for an agent. These drive the master-agent's intent classification, so they +# must be specific enough for the LLM to pick the right agent. +_DEFAULT_CAPABILITIES = { + 'expenses_agent': 'Employee expense reports and receipts — create, submit, and approve hr.expense records', + 'finance_agent': 'Financial reporting — P&L, balance sheet, cash flow, budgets', + 'accounting_agent': 'Accounting — journal entries, invoices, vendor bills, payments, reconciliation', + 'crm_agent': 'CRM — leads, opportunities, pipeline stages, customer interactions', + 'sales_agent': 'Sales — orders, quotations, customers, pricelists', + 'project_agent': 'Projects — tasks, timesheets, milestones, deadlines', + 'elearning_agent': 'eLearning — courses, slides, attendees, certifications', + 'employees_agent': 'HR — employees, contracts, attendance, leave requests', + 'odoo_doc_agent': 'Odoo 18 documentation and workflow guidance (internal use)', +} + class AgentRegistry: def __init__(self): @@ -17,17 +32,34 @@ class AgentRegistry: [['active', '=', True]], ['agent_name', 'domain', 'backend']) self._active = {r['agent_name'] for r in rows} - self._capabilities = {r['agent_name']: r.get('domain', '') for r in rows} - logger.info('AgentRegistry loaded %d agents: %s', len(self._active), list(self._active)) + # Use the DB domain field when present, fall back to defaults + for r in rows: + key = r['agent_name'] + self._capabilities[key] = r.get('domain') or _DEFAULT_CAPABILITIES.get(key, key) + logger.info('AgentRegistry loaded %d agents from DB: %s', len(self._active), list(self._active)) except Exception as exc: logger.error('AgentRegistry.load_from_odoo failed: %s', exc) + def register(self, agent_key, agent_instance): + self._agents[agent_key] = agent_instance + # Auto-activate every registered agent so it appears in the master + # prompt even when the Odoo DB registry table has no row for it. + self._active.add(agent_key) + if not self._capabilities.get(agent_key): + self._capabilities[agent_key] = _DEFAULT_CAPABILITIES.get( + agent_key, + agent_key.replace('_agent', '').replace('_', ' ') + ' management', + ) + logger.debug('AgentRegistry: registered %s (active=%d)', agent_key, len(self._active)) + async def get_active_agents(self): - return [{'agent_key': k, 'capabilities_summary': self._capabilities.get(k, '')} - for k in self._active] + return [ + {'agent_key': k, 'capabilities_summary': self._capabilities.get(k, '')} + for k in self._active + if k in self._agents # only expose agents that have a live instance + ] async def get_all(self): - """Return all registered agents with active status and capabilities.""" return [ { 'agent_key': k, @@ -45,8 +77,5 @@ class AgentRegistry: self._active = set(active_keys) logger.info('AgentRegistry synced: active=%s', active_keys) - def register(self, agent_key, agent_instance): - self._agents[agent_key] = agent_instance - def get_agent_instance(self, agent_key): return self._agents.get(agent_key)