● Zion Boggan
repos/Darwin/cortex/adapters/sources.py
zionboggan.com ↗
123 lines · python
History for this file →
1
"""Data-source health aggregation for Darwin's organism.
2
 
3
The Face's organism (health-as-anatomy: a fresh source glows, a stale one
4
withers) renders nodes keyed by name. Cortex `/health` historically reported
5
only the 5 query engines (rag/cbm/serena/memgraph/inbox). This module enriches
6
`/health` with the REAL ingestion data-sources so the organism reflects the
7
whole nervous system, not just the engines.
8
 
9
Freshness is read live from the homelab-rag manifest via the RAG bridge's
10
`GET /sources` endpoint (CT215:9875), which reads /var/tmp/homelab-rag/state.sqlite
11
(max sync_runs.finished_at per source_id). The Deck (CT215:9893/health) supplies
12
the live active-agent count for the `agents` node.
13
 
14
Organism node keys (must match DarwinOrganism.tsx SOURCE_CONFIG):
15
    proxmox, white-pc, 3060, browser-history, agents
16
    (the 5 engines are emitted by main.py, not here)
17
 
18
source_id -> organism node mapping (SOURCE_NODE_MAP). A node may be fed by more
19
than one source_id; we take the FRESHEST contributing source (min staleness).
20
A node with no contributing source present in the manifest is emitted as
21
status:"down" so the organism withers it (e.g. white-pc, which is not yet
22
ingesting).
23
"""
24
from __future__ import annotations
25
 
26
import os
27
from datetime import datetime, timezone
28
 
29
import httpx
30
 
31
from models import EngineStatus
32
 
33
RAG_BRIDGE_URL = os.environ.get("RAG_BRIDGE_URL", "http://REDACTED-IP:9875")
34
DECK_URL = os.environ.get("DECK_URL", "http://REDACTED-IP:9893")
35
 
36
DOWN_THRESHOLD_H = float(os.environ.get("SOURCE_DOWN_THRESHOLD_H", "48"))
37
 
38
SOURCE_NODE_MAP: dict[str, list[str]] = {
39
    "proxmox": ["pve-node"],
40
    "white-pc": ["desktop-qi823gr"],
41
    "3060": ["desktop-f8o1v2s"],
42
    "browser-history": ["desktop-f8o1v2s-browser"],
43
    "agents": ["ct215-shell-history", "ct241-shell-history"],
44
}
45
 
46
 
47
async def _fetch_bridge_sources() -> dict:
48
    async with httpx.AsyncClient(timeout=3.0) as client:
49
        resp = await client.get(f"{RAG_BRIDGE_URL}/sources")
50
        resp.raise_for_status()
51
        return resp.json()
52
 
53
 
54
async def _fetch_deck_agents() -> int | None:
55
    try:
56
        async with httpx.AsyncClient(timeout=2.5) as client:
57
            resp = await client.get(f"{DECK_URL}/health")
58
            resp.raise_for_status()
59
            return int(resp.json().get("active_agents", 0))
60
    except Exception:
61
        return None
62
 
63
 
64
def _parse_iso(val: str | None) -> datetime | None:
65
    if not val:
66
        return None
67
    try:
68
        return datetime.fromisoformat(str(val).replace("Z", "+00:00"))
69
    except Exception:
70
        return None
71
 
72
 
73
async def source_statuses() -> list[EngineStatus]:
74
    """Return one EngineStatus per organism data-source node.
75
 
76
    Same schema as the engine entries (engine=node-key, status up/down,
77
    last_ingest, staleness_h). Graceful: if the bridge is down, every node is
78
    emitted status:"down" so the organism withers rather than vanishing.
79
    """
80
    try:
81
        payload = await _fetch_bridge_sources()
82
        by_id = {
83
            s["source_id"]: s
84
            for s in payload.get("sources", [])
85
            if isinstance(s, dict) and s.get("source_id")
86
        }
87
    except Exception:
88
        by_id = {}
89
 
90
    out: list[EngineStatus] = []
91
    for node_key, source_ids in SOURCE_NODE_MAP.items():
92
        best: dict | None = None
93
        for sid in source_ids:
94
            s = by_id.get(sid)
95
            if not s:
96
                continue
97
            sh = s.get("staleness_h")
98
            if best is None or (sh is not None and (
99
                best.get("staleness_h") is None or sh < best["staleness_h"]
100
            )):
101
                best = s
102
 
103
        if best is None:
104
            out.append(EngineStatus(engine=node_key, status="down"))
105
            continue
106
 
107
        staleness_h = best.get("staleness_h")
108
        status = "up"
109
        if staleness_h is None or staleness_h > DOWN_THRESHOLD_H:
110
            status = "down"
111
        out.append(EngineStatus(
112
            engine=node_key,
113
            status=status,
114
            last_ingest=_parse_iso(best.get("last_ingest")),
115
            staleness_h=staleness_h,
116
        ))
117
 
118
    active = await _fetch_deck_agents()
119
    if active is not None:
120
        for st in out:
121
            if st.engine == "agents":
122
                st.ms = active
123
    return out