Adds catalog with Maestro/Pulso/Trilho etc., agent_threads/messages bus, inbox and context window API, and complete Desk Agentic Ops panel for human operators to read, reply, and chat with agents. Co-authored-by: Cursor <cursoragent@cursor.com>
208 lines
6.8 KiB
Python
208 lines
6.8 KiB
Python
"""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
|