ligbox-ops-platform/projects/ops-desk/api/app/agents/store.py
Ligbox Spec Hub 2a5273201b Name Agentics A0-A7, add inter-agent messaging and operator inbox UI.
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>
2026-06-19 23:24:48 +00:00

107 lines
6 KiB
Python

"""Persistence Agentic Ops."""
from __future__ import annotations
import json, sqlite3
from datetime import datetime, timezone
from typing import Any
from app.agents.messages import init_messages_schema
def _now():
return datetime.now(timezone.utc).isoformat()
def init_agent_schema(conn):
init_messages_schema(conn)
conn.executescript("""
CREATE TABLE IF NOT EXISTS agent_scenarios (
id TEXT PRIMARY KEY, title TEXT NOT NULL, schedule TEXT,
severity_default TEXT NOT NULL DEFAULT 'warn', config_json TEXT NOT NULL,
enabled INTEGER NOT NULL DEFAULT 1, updated_at TEXT NOT NULL);
CREATE TABLE IF NOT EXISTS agent_runs (
id INTEGER PRIMARY KEY, scenario_id TEXT NOT NULL, trigger TEXT NOT NULL DEFAULT 'cron',
status TEXT NOT NULL, summary_text TEXT, llm_model TEXT, metadata_json TEXT,
started_at TEXT NOT NULL, finished_at TEXT);
CREATE TABLE IF NOT EXISTS agent_findings (
id INTEGER PRIMARY KEY, run_id INTEGER NOT NULL, severity TEXT NOT NULL,
category TEXT NOT NULL DEFAULT 'api', title TEXT NOT NULL, detail_md TEXT,
evidence_json TEXT, suggested_human_action TEXT, kb_refs_json TEXT,
acknowledged_at TEXT, acknowledged_by TEXT, created_at TEXT NOT NULL);
CREATE TABLE IF NOT EXISTS agent_action_log (
id INTEGER PRIMARY KEY, ts TEXT NOT NULL, agent_id TEXT NOT NULL DEFAULT 'sentinel',
run_id INTEGER, event_type TEXT NOT NULL, message TEXT, payload_json TEXT);
CREATE TABLE IF NOT EXISTS agent_kb_chunks (
id INTEGER PRIMARY KEY, source_path TEXT NOT NULL, chunk_text TEXT NOT NULL, indexed_at TEXT NOT NULL);
CREATE INDEX IF NOT EXISTS idx_agent_runs_scenario ON agent_runs(scenario_id);
""")
def log_event(conn, *, event_type, message, agent_id="sentinel", run_id=None, payload=None):
conn.execute("INSERT INTO agent_action_log (ts,agent_id,run_id,event_type,message,payload_json) VALUES (?,?,?,?,?,?)",
(_now(), agent_id, run_id, event_type, message, json.dumps(payload or {})))
try:
conn.execute("INSERT INTO desk_security_audit (username,event_type,client_ip,payload,created_at) VALUES (?,?,?,?,?)",
("agentic", f"agent.{event_type}", "vm122", json.dumps({"message": message, **(payload or {})}), _now()))
except sqlite3.OperationalError:
pass
def upsert_scenario(conn, scenario):
conn.execute("""INSERT INTO agent_scenarios (id,title,schedule,severity_default,config_json,enabled,updated_at)
VALUES (?,?,?,?,?,1,?) ON CONFLICT(id) DO UPDATE SET title=excluded.title, schedule=excluded.schedule,
severity_default=excluded.severity_default, config_json=excluded.config_json, updated_at=excluded.updated_at""",
(scenario["id"], scenario["title"], scenario.get("schedule","*/5 * * * *"),
scenario.get("severity_default","warn"), json.dumps(scenario), _now()))
def list_scenarios(conn):
out = []
for row in conn.execute("SELECT * FROM agent_scenarios WHERE enabled=1 ORDER BY id"):
item = dict(row)
item["config"] = json.loads(item.pop("config_json") or "{}")
last = conn.execute("SELECT status,started_at FROM agent_runs WHERE scenario_id=? ORDER BY id DESC LIMIT 1", (row["id"],)).fetchone()
item["last_run_status"] = last["status"] if last else None
item["last_run_at"] = last["started_at"] if last else None
out.append(item)
return out
def get_scenario(conn, scenario_id):
row = conn.execute("SELECT * FROM agent_scenarios WHERE id=? AND enabled=1", (scenario_id,)).fetchone()
if not row: return None
item = dict(row); item["config"] = json.loads(item.pop("config_json") or "{}"); return item
def create_run(conn, scenario_id, trigger):
return int(conn.execute("INSERT INTO agent_runs (scenario_id,trigger,status,started_at) VALUES (?,?,?,?)",
(scenario_id, trigger, "running", _now())).lastrowid)
def finish_run(conn, run_id, *, status, summary, llm_model=None, metadata=None):
conn.execute("UPDATE agent_runs SET status=?,summary_text=?,llm_model=?,metadata_json=?,finished_at=? WHERE id=?",
(status, summary, llm_model, json.dumps(metadata or {}), _now(), run_id))
def add_finding(conn, run_id, *, severity, category, title, detail_md="", evidence=None, human_action="", kb_refs=None):
return int(conn.execute("""INSERT INTO agent_findings (run_id,severity,category,title,detail_md,evidence_json,
suggested_human_action,kb_refs_json,created_at) VALUES (?,?,?,?,?,?,?,?,?)""",
(run_id, severity, category, title, detail_md, json.dumps(evidence or {}), human_action,
json.dumps(kb_refs or []), _now())).lastrowid)
def list_findings(conn, *, severity=None, limit=50, open_only=True):
q, params = "SELECT * FROM agent_findings WHERE 1=1", []
if severity: q += " AND severity=?"; params.append(severity)
if open_only: q += " AND acknowledged_at IS NULL"
q += " ORDER BY id DESC LIMIT ?"; params.append(limit)
return [dict(r) for r in conn.execute(q, params)]
def list_action_log(conn, limit=100):
return [dict(r) for r in conn.execute("SELECT * FROM agent_action_log ORDER BY id DESC LIMIT ?", (limit,))]
def index_kb_file(conn, source_path, text):
conn.execute("DELETE FROM agent_kb_chunks WHERE source_path=?", (source_path,))
now = _now()
for i in range(0, len(text), 1200):
conn.execute("INSERT INTO agent_kb_chunks (source_path,chunk_text,indexed_at) VALUES (?,?,?)",
(source_path, text[i:i+1200], now))
def search_kb(conn, query, limit=8):
terms = [t.strip().lower() for t in query.split() if len(t.strip()) > 2]
if not terms: return []
scored = []
for row in conn.execute("SELECT source_path,chunk_text FROM agent_kb_chunks"):
score = sum(1 for t in terms if t in row["chunk_text"].lower())
if score: scored.append((score, {"source": row["source_path"], "snippet": row["chunk_text"][:400]}))
scored.sort(key=lambda x: -x[0])
return [s[1] for s in scored[:limit]]