"""Catálogo nomeado dos Agentics A0–A7 — Spec 027 + 029.""" from __future__ import annotations from dataclasses import dataclass @dataclass(frozen=True) class AgentProfile: id: str codename: str name: str role: str reads: tuple[str, ...] actions: tuple[str, ...] approval: str scenarios: tuple[str, ...] = () AGENT_CATALOG: dict[str, AgentProfile] = { "A0": AgentProfile( id="A0", codename="orchestrator", name="Maestro", role="Orquestrador multi-agente", reads=("todos os feeds", "action_log", "findings abertos", "threads activas"), actions=( "Delegar cenários aos agentes especializados", "Sintetizar estado global do ambiente Ligbox", "Abrir thread de coordenação entre agentes", "Escalar para humano quando confiança < limiar", ), approval="agentic_operator / ops_lead", scenarios=(), ), "A1": AgentProfile( id="A1", codename="node_health", name="Pulso", role="Saúde de nós e serviços Carbonio", reads=("métricas VM112", "CPU/RAM Proxmox", "status containers"), actions=( "Detectar serviço Carbonio/wizard down", "Criar finding + alerta ticket", "Sugerir restart (info auto; restart com ops_lead)", ), approval="auto (info) · ops_lead (restart)", scenarios=("wizard.vm112.bundle", "proxmox.cluster"), ), "A2": AgentProfile( id="A2", codename="infra_mail", name="Trilho", role="DNS, certificados, Traefik, nginx", reads=("DNS Cloudflare", "certs LE", "Traefik CT114", "SNI"), actions=( "Validar propagação DNS pós-onboard", "Detectar cert expirado ou mismatch SNI", "Propor fix DNS/Traefik (nunca aplicar sem devops/ops_lead)", ), approval="devops ou ops_lead antes de aplicar", scenarios=("pfsense.api.system",), ), "A3": AgentProfile( id="A3", codename="deliverability", name="Carta", role="SPF, DKIM, DMARC, reputação mail", reads=("registos DNS mail", "relatórios entregabilidade"), actions=( "Auditar SPF/DKIM/DMARC por domínio tenant", "Gerar relatório de entregabilidade", "Abrir finding para seo/technician revisão", ), approval="seo / technician revisão", scenarios=(), ), "A4": AgentProfile( id="A4", codename="security_mail", name="Escudo Mail", role="amavis, spam, clamav, filas mail", reads=("filas mail", "logs amavis/clamav", "quarentena"), actions=( "Detectar pico spam ou fila bloqueada", "Sugerir quarentena / release", "Correlacionar com alertas segurança VM112", ), approval="security_analyst", scenarios=(), ), "A5": AgentProfile( id="A5", codename="wazuh_soc", name="Sentinela SOC", role="Correlação SIEM Wazuh VM104", reads=("alertas Wazuh", "webhooks vm104", "tickets correlacionados"), actions=( "Correlacionar alerta L≥10 com domínio/sessão", "Enriquecer timeline do chamado", "Propor runbook R0/R1 segurança", ), approval="security_analyst / noc", scenarios=(), ), "A6": AgentProfile( id="A6", codename="support_copilot", name="Copiloto", role="Assistência tickets e janela humana", reads=("tickets Desk", "timeline onboarding", "findings", "KB specs"), actions=( "Rascunhar resposta ao cliente/ticket", "Responder janela /chat do operador humano", "Resumir contexto para technician enviar", ), approval="technician envia · agentic_operator vê tudo", scenarios=("funnel.stuck.onboarding",), ), "A7": AgentProfile( id="A7", codename="remediation", name="Remediador", role="Runbooks aprovados pós-incidente", reads=("playbooks aprovados", "findings critical/high", "aprovações pendentes"), actions=( "Propor runbook com confiança %", "Executar R0 auto (poll/refresh)", "Executar R1+ apenas após OK humano", "Registar action_executed na timeline", ), approval="agentic_operator obrigatório (R2/R3 dupla)", scenarios=(), ), "sentinel": AgentProfile( id="sentinel", codename="sentinel", name="Vigia", role="Health checks T0 — APIs e infra", reads=("endpoints HTTP", "integrações health", "Ollama", "VM123 stack"), actions=( "Executar cenários desk/wizard/pfsense/proxmox/ollama/VM123", "Criar findings com severidade", "Disparar e-mail em high/critical", ), approval="automático (detecção) · humano trata finding", scenarios=( "desk.api.health", "wizard.vm112.bundle", "pfsense.api.system", "integration.webhook.gap", "proxmox.cluster", "ollama.vm123.health", "vm123.finance.stack", "vm123.openpanel.bridge", ), ), "curator": AgentProfile( id="curator", codename="curator", name="Curador", role="Base de conhecimento (RAG)", reads=("specs/**/*.md", "agent_kb_chunks"), actions=("Indexar specs no SQLite", "Fornecer snippets ao Copiloto/Advisor"), approval="automático", scenarios=(), ), } # Map legacy agent_id in scenarios → A* principal SCENARIO_AGENT_MAP = { "desk.api.health": "sentinel", "wizard.vm112.bundle": "A1", "pfsense.api.system": "A2", "funnel.stuck.onboarding": "A6", "integration.webhook.gap": "sentinel", "proxmox.cluster": "A1", "ollama.vm123.health": "sentinel", "vm123.finance.stack": "sentinel", "vm123.openpanel.bridge": "sentinel", } def resolve_agent(scenario_id: str, agent_id: str | None = None) -> AgentProfile: key = SCENARIO_AGENT_MAP.get(scenario_id) or agent_id or "sentinel" return AGENT_CATALOG.get(key, AGENT_CATALOG["sentinel"]) def roster_public() -> list[dict]: order = ["A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "sentinel", "curator"] out = [] for k in order: if k not in AGENT_CATALOG: continue p = AGENT_CATALOG[k] out.append( { "id": p.id, "codename": p.codename, "name": p.name, "role": p.role, "reads": list(p.reads), "actions": list(p.actions), "approval": p.approval, "scenarios": list(p.scenarios), } ) return out