nathanrenting.dev
Project · in productie

ECHO — agent-orchestrator

Een persoonlijke Jarvis-achtige assistent, solo gebouwd, lokaal draaiend op mijn eigen hardware. Multi-brain LLM-routing, tool-dispatch, vault-backed memory, filesystem-watchers, drie-tier AI-fallback. Ongeveer 24+ Python-modules in de orchestrator, plus een React/Vite-HUD die alles in één dashboard surfacet.

Hand-getekende architectuur-schets van ECHO: USER → ROUTER vertakkend in FAST, MAIN en THINK brain-tiers, met een VAULT-notitieboek als geheugen, allemaal uitkomend op een HUD-dashboard.

Whiteboard-schets · de vorm van het systeem

Wat ECHO in de praktijk doet

Ik praat met haar, zij praat terug. Maar het interessante zit niet in de chat — het zit in wat er tussen turns gebeurt. ECHO observeert mijn filesystem, de recente commits over al mijn projecten, de open nudges in mijn vault, en synthetiseert dat tot ochtend-briefings, code-change-chronicles, time-tracking-aggregaten, en proactieve interventies als er iets niet klopt.

Concreet, in één turn kan ECHO:

De HUD laat alles zien: een "Brain Waves" trace, een token-burn-meter, een vault-graph, een CC-status-indicator, en project-specifieke panels (deels redacted in deze publieke versie).

Architectuur

┌─────────────────────────────────────────────────────────────┐
│  HUD (React + Vite)                                         │
│  Presence · Vitals · TokenBurn · VaultGraph · Project       │
│  panels · RailTabs · Identity                               │
└──────────────────────────────┬──────────────────────────────┘
                               │  HTTP polling + SSE
                               ▼
┌─────────────────────────────────────────────────────────────┐
│  Orchestrator (FastAPI · Python 3.13)                       │
│  ─────────────────────────────────────────────────────────  │
│  router.py       — Multi-brain selection (heuristics + LLM) │
│  tools.py        — 14+ tool dispatch                        │
│  skills.py       — agentskills.io-compatible workflow layer │
│  vault_graph.py  — Markdown vault + Wikilink edges          │
│  time_track.py   — Time entries, urencriterium, exports     │
│  runway.py       — Personal-finance runway dashboard        │
│  social.py       — Social-media accounts registry           │
│  cc_status.py    — Claude Code activity from JSONL          │
│  ... + nudges, agenda, hue, intents, voice, tts, en een     │
│      handvol product-specifieke integratie-modules          │
└──────┬──────────────────────────────┬───────────────────────┘
       │                              │
       ▼                              ▼
┌──────────────────┐         ┌────────────────────────────────┐
│  Memory Worker   │         │  External LLMs                 │
│  (APScheduler)   │         │  ─────────────────────────────  │
│  ──────────────  │         │  Anthropic (Claude API)         │
│  Drafter         │         │  Ollama local (Qwen, Llama)     │
│  Curator         │         │  ComfyUI local (Stable Diff)    │
│  Consolidator    │         │                                 │
│  Watchers        │         │  3-tier fallback:               │
│  Reflector       │         │   API → rules → hardcoded       │
│  Daily-summary   │         │                                 │
└──────────────────┘         └────────────────────────────────┘

De multi-brain router

De router bepaalt welke model-tier wordt aangesproken voordat de request de orchestrator verlaat. Eerst goedkope heuristieken, dan pas een LLM-classifier als die het niet weten.

async def decide(text: str, *, force: Brain | None = None) -> RouterDecision:
    if force is not None:
        return RouterDecision(force, "manual", "user-override")

    # Goedkope regex-heuristieken eerst
    h = _heuristic(text)
    if h is not None:
        return h

    # Fallback op een klein model als classifier
    return await _llm_classify(text)

Vier heuristiek-lagen draaien op volgorde: triviale-groet (sub-seconde antwoorden gaan naar fast), business-keyword (alles wat een project-context raakt gaat naar main zodat de juiste system-prompt laadt), deep-keyword (architectuur / code-review naar think), en shell-keyword (filesystem-vragen naar main waar shell-tools beschikbaar zijn).

Die laatste laag doet meer dan je denkt: kleine modellen schrijven PowerShell-commands routinematig uit als Markdown-block in plaats van de shell-tool aan te roepen. Die queries naar main routeren fixt de failure-mode aan de bron.

Tool-dispatch + skills-laag

ECHO biedt ~14 tools aan via het tool-use-protocol — vault_read, vault_search, shell_check, list_skills / run_skill, time_start / time_stop, time_summary, note_learning, en een handvol product-specifieke tools.

De skills-laag is agentskills.io-compatibel: elke skill leeft op memory/_skills/<skill-name>/SKILL.md met YAML-frontmatter voor metadata en Markdown-body voor het recept. list_skills is goedkoop (retourneert alleen frontmatter); run_skill(name) laadt de volledige body en ECHO volgt de genummerde stappen, callt de tools in de juiste volgorde.

Invocation-count, success-rate, en last-used-timestamp updaten zichzelf in de frontmatter — slow telemetry, geen aparte database.

Vault-backed memory

Het geheugen van ECHO is een Markdown-vault met WikiLink-edges. De vault heeft folders voor People, Projects, Daily-notes, Knowledge, Tasks, en System. De graph wordt live geparsed door vault_graph.py en gerenderd in de HUD als 3D force-directed visualisatie met folder-clustering.

Wat er in de vault leeft: mijn eigen notities, een inbox waar ECHO voorgestelde nudges in dropt, daily-notes die de consolidator schrijft, een chronicle van elke Claude Code-commit over al mijn projecten, en een CC-inbox.md-kanaal waar dev-Claude ECHO tussen turns kan briefen.

De watchers — Python-jobs gescheduled door APScheduler — monitoren commits, sentry, issues, project-state, agenda, idle. Elk emit een gestructureerde trigger als het iets ziet dat ECHO's aandacht waard is; de drafter maakt van triggers proposed nudges; de curator aggregeert periodiek.

Drie-tier AI-fallback

ECHO neemt nooit aan dat de API up is. Het patroon staat op WBSO-niveau in de technical-uncertainties-notes van het project:

  1. Tier 1 — API (primair, hoogste kwaliteit)
  2. Tier 2 — Rule-based reasoning (fallback als de API down of rate-limited is; minder vlot, maar nog steeds zinnig)
  3. Tier 3 — Hardcoded (offline minimum-viable response)

Dit patroon komt op meerdere lagen terug. Resultaat: ECHO blijft werken ook als externe services dat niet doen.

Lokale AI-infrastructuur

ECHO draait lokaal op mijn main werkplek — een Ryzen 7 3700X met AMD RX 6650 XT (8GB), Windows. De orchestrator, de HUD, de vault, de watchers en drafters, en ComfyUI voor image-generation draaien allemaal op dezelfde box. Een oudere AMD-machine staat ernaast als Linux-testbed voor side-projects, niet als onderdeel van het productie-ECHO-pad.

Voor zwaardere inference (grotere context-windows, model-fine-tuning, batch-image-werk) val ik terug op remote-toegang naar een meer capabele GPU-setup via TeamViewer of een direct port-forward.

Lokale LLMs draaien via Ollama (Qwen 2.5 7B, Llama 3.2 3B) voor routing, classificatie, en alles waar de API niet nodig is. De Anthropic Claude API is gereserveerd voor complex redeneerwerk waar lokale modellen de kwaliteit niet halen. Het meeste routine-werk draait op de main box; cloud-kosten blijven bescheiden.

Privacy-filter

De vault wordt elke turn in ECHO's persona geladen — wat betekent dat alles wat erin staat door de LLM gelezen wordt en naar Anthropic gestuurd. Een privacy-filter draait op elk write-entry-point (drafter, curator, extractor, commit-chronicles, daily-summarizer) en redact een kleine set lokaal-gevoelige programma-namen naar een generieke token. Het patroon werkt omdat de filter aan de write-kant zit, niet aan de read-kant.

Dit is het soort GDPR-by-design-beslissing dat niet bestaat als je een LLM achteraf op een bestaand systeem schroeft. Het moet erin ontworpen worden.

Hoe ECHO groeit

ECHO begon als een chatbot. Geleidelijk werd het meer.

Eerst kwam de geheugen-laag: een Obsidian-vault waar elk gesprek, elke beslissing en elke commit naartoe gaat. Die vault groeit elke dag. Na maanden gebruik herkent ECHO context die ik zelf alweer vergeten was — een experiment van zes weken terug, een uitspraak van een klant, een besluit dat ik op de fiets nam.

Daarna de watchers. Filesystem-events, git-commits, calendar-pings, sentry-issues — allemaal triggers waar ECHO autonoom op reageert. Een commit op een project? Drafted al een chronicle-regel terwijl ik koffie haal.

Daarna de drafters. Cron-jobs die 's nachts draaien: weekly recap, daily summary, time-tracking aggregaat, vault-consolidation. Als ik 's ochtends de HUD open ligt het al klaar — niet als "rapport", maar als proposed nudges in een inbox waar ik akkoord of weg-klik.

En nu zit ik in de fase waar ECHO meer voorstelt dan ik vraag. Niet alles is goed (een drafter mag soms gewoon stoppen met me wakker maken), maar de trend is duidelijk: ik typ minder, ECHO doet meer, en de output blijft op kwaliteit.

Wat de vault interessanter maakt dan een database: alles is Markdown. Geen lock-in, geen vendor, ik kan het zelf lezen zonder ECHO aan te zetten. Maar ECHO leest het ook elke turn. Een gesprek van vorige maand kan vandaag meegenomen worden in een beslissing — niet omdat ze het "herinnert" in de mensentaal-zin, maar omdat het in de vault staat en ECHO precies weet hoe ze daar moet zoeken.

De volgende laag in ontwikkeling: meer sleep-time-compute. Een goedkoop model dat 's nachts mee-leest in nieuwe vault-entries en zelf verbanden voorstelt. Niet om ECHO "slimmer" te maken — om de context die er al ligt beter benutbaar te maken.

Wat ik geleerd heb

Drie dingen die meer bleken te tellen dan de architectuur-diagrammen suggereerden:

  1. Document-ketens werken beter dan losse docs. Core → afgeleide-voor-doelgroep-A → afgeleide-voor-doelgroep-B. Elk in zijn eigen toon. Grote plannen rotten snel; kleine gerichte versies overleven iteratie.

  2. Watchers zijn ruis tot ze dat niet zijn. De eerste weken zaten vol false-positives en vroegen om een vier-niveau severity-taxonomy (info / action / critical / defcon) en DEFCON-only Explorer auto-reveal getrottled tot één per 10s. Signaal-ruis is meer design-werk dan ik dacht.

  3. Skills > tools op een hoger abstractie-niveau. Tools zijn atomair. Skills zijn de recepten die het model nodig heeft om consistent dezelfde output te produceren voor hetzelfde type request. De skills-laag is het verschil tussen "het model doet dit waarschijnlijk goed" en "het model doet dit betrouwbaar goed".

Stack in één oogopslag

| Laag | Keuze | |---|---| | Orchestrator | Python 3.13, FastAPI, Uvicorn (SelectorEventLoop op Windows) | | Scheduling | APScheduler | | LLMs | Anthropic (Claude API), Perplexity voor onderzoek, ChatGPT voor prompt-werk, Ollama lokaal | | Memory | Markdown-vault, NDJSON event-streams, SQLite voor state | | HUD | React + Vite + Tailwind + Framer Motion + ForceGraph3D | | Voice | Whisper STT (lokaal), TTS via Edge / Kokoro | | Tools | Tool-use-protocol, 14+ geregistreerde tools | | Skills | agentskills.io-compatibel SKILL.md-format |