ligbox-ops-platform/projects/ops-desk/api/app/agents/runner.py
Ligbox Spec Hub e0959e6fd7 Add Agentic Ops Spec 029: wire API, worker tick, T0/T1, staging stack.
Mounts agents router and schema init, adds VM123 checks, chat copilot,
Desk UI module, isolated docker-compose staging on ports 8180/8192,
and full spec documentation without touching production ports.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-19 23:22:33 +00:00

65 lines
3.1 KiB
Python

"""Agent orchestration — Spec 029."""
from __future__ import annotations
import os
from pathlib import Path
from app.agents import checks, llm_client, notify, registry, store
SPECS = Path(os.getenv("AGENTIC_SPECS_ROOT", "/opt/ligbox-ops-platform/specs"))
OPS_API = os.getenv("OPS_API_URL", "http://api:8080")
TOKEN = os.getenv("OPS_INTERNAL_TOKEN", "")
def sync_registry(conn):
for sc in registry.load_registry():
store.upsert_scenario(conn, sc)
return len(registry.load_registry())
def index_specs_kb(conn):
if not SPECS.exists(): return 0
n = 0
for p in sorted(SPECS.glob("**/*.md")):
try:
txt = p.read_text(encoding="utf-8", errors="ignore")
if len(txt) > 100:
store.index_kb_file(conn, str(p.relative_to(SPECS)), txt)
n += 1
except OSError:
pass
return n
def run_scenario(conn, scenario_id, *, trigger="cron"):
sc = store.get_scenario(conn, scenario_id)
if not sc:
return {"ok": False, "error": "not found"}
agent_id = (sc.get("config") or {}).get("agent_id") or sc.get("agent_id") or "sentinel"
fn = checks.SCENARIO_RUNNERS.get(scenario_id)
if not fn:
return {"ok": False, "error": "no runner"}
run_id = store.create_run(conn, scenario_id, trigger)
store.log_event(conn, event_type="run.start", message=scenario_id, run_id=run_id, agent_id=agent_id)
raw = fn(conn, ops_api_url=OPS_API, internal_token=TOKEN)
fids = []
for f in raw:
kb = store.search_kb(conn, f.get("title", ""))
human = f.get("human_action") or ""
if f.get("severity") in ("high", "critical", "warn"):
advice, model = llm_client.advise_human_action(
finding_title=f.get("title",""), finding_detail=f.get("detail_md",""),
kb_snippets=[k["snippet"] for k in kb],
)
if advice and not human: human = advice
fid = store.add_finding(conn, run_id, severity=f.get("severity", sc.get("severity_default","warn")),
category=f.get("category","api"), title=f.get("title","Finding"), detail_md=f.get("detail_md",""),
evidence=f.get("evidence"), human_action=human, kb_refs=[k["source"] for k in kb])
fids.append(fid)
if f.get("severity") in ("high", "critical"):
notify.notify_finding({**f, "suggested_human_action": human})
store.log_event(conn, event_type="finding.created", message=f.get("title",""), run_id=run_id, payload={"id": fid}, agent_id=agent_id)
status = "ok" if not raw else "degraded"
store.finish_run(conn, run_id, status=status, summary=f"{len(raw)} finding(s)" if raw else "healthy")
store.log_event(conn, event_type="run.finish", message=status, run_id=run_id, agent_id=agent_id)
return {"ok": True, "run_id": run_id, "scenario_id": scenario_id, "status": status, "findings_count": len(raw), "finding_ids": fids}
def run_all_enabled(conn, trigger="cron"):
sync_registry(conn)
return {"runs": [run_scenario(conn, s["id"], trigger=trigger) for s in store.list_scenarios(conn)],
"total": len(store.list_scenarios(conn))}