Skip to content

Architecture

The bird's-eye view of what runs in a CAESAR install. Every section links to the ADR that locked in the design — that's where the why lives.

Shape

                ┌──────────────────────────────────────┐
                │  HTTP clients (phone, browser, n8n)  │
                └───────────────────┬──────────────────┘
                                    │
            ┌───────────────────────▼────────────────────┐
            │                  Praetor                   │
            │   FastAPI + LangGraph brain (ADR-0006)     │
            │  /v1/chat  /v1/devices  /v1/hook/*         │
            │  /dashboard  /metrics  /healthz            │
            └──┬──────┬──────┬──────────────┬────────────┘
               │      │      │              │
       ┌───────┘      │      └─────┐        └──────────┐
       ▼              ▼            ▼                   ▼
  HA Bridge      LLM Gateway   SQLite + optional   NATS bus
  (REST + WS,    (Anthropic /  vector store        (optional;
   ADR-0007)     OpenAI /      audit_log, memory,  ADR-0009)
                 Ollama,       facts, episodes,         │
                 ADR-0011/26)  sessions                 │
                                                        ▼
                                              ┌──────────────────┐
                                              │   Legion pool    │
                                              │ in-tree workers  │
                                              │ + plugins via    │
                                              │ caesar.plugins   │
                                              │ entry points     │
                                              │ (ADR-0035)       │
                                              └──────────────────┘

                       proactive triggers (ADR-0030/31/32)
                       ─────────────────────────────────
                       Scheduler · HAEventDriver · Webhook
                       all fire the same brain graph as /v1/chat

Subsystems below are loaded conditionally based on CaesarSettings. The minimum running install is Praetor + SQLite + an LLM provider; everything else opts in.

Praetor — the central brain

  • Python service. FastAPI for HTTP/WS, LangGraph for the brain-graph state machine. See ADR-0006.
  • One process. The brain graph runs call_llm → dispatch_tools in a loop until the model either finishes or hits max_iterations.
  • Owns: conversation turn handling, system-prompt composition (facts + episodes + operator override), policy gating, audit-log writes, dispatch to Legion workers, the dashboard, and metrics.
  • Emits an audit row for every decision (chat completion, tool call, trigger fire, plugin load). See ADR-0012. Decisions are replayable.

LLM Gateway

  • Provider-agnostic abstraction over Anthropic, OpenAI, and Ollama. See ADR-0011 for the original shape and ADR-0026 for the multi-provider rewrite.
  • Per-task routing: an operator can send /v1/chat to Claude while routing memory_extract and memory_summarise to a cheap local Ollama, no code changes.

Legion — worker agent pool

  • Workers register with the bus on startup and advertise capabilities (light.turn_on, web_search, mqtt.publish, …). The brain finds workers by capability via the registry and dispatches over NATS request/reply. See ADR-0009.
  • In-tree workers ship inside the caesar package: memory_recall, semantic_recall, calculator, web_search, calendar_read, notify. Enabled via CAESAR_LEGION__INPROCESS_WORKERS.
  • Plugin workers are third-party PyPI packages discovered through the caesar.plugins entry-point group. Praetor scans them at startup, validates each manifest, and the result joins the in-process pool. See ADR-0035 + BUILD-A-PLUGIN.md.
  • Workers on other hosts authenticate to the bus with per-identity NKEYs. See ADR-0027 + RUN-A-WORKER.md.

HA Bridge

  • Single module owning the REST + WebSocket connection to Home Assistant. See ADR-0007.
  • Exposes call_service to the brain as a tool; reads HA state via the dashboard's device list at /v1/devices.
  • All real-world service calls flow through the Policy Engine first.
  • The same WS subscription powers the HAEventDriver (proactive ADR-0031).

Memory

Two layers, both opt-in beyond the always-on audit log:

  • Episodic. audit_log table in SQLite — every brain decision + the inputs that produced it. The retention sweeper deletes rows older than CAESAR_MEMORY__RETENTION_DAYS. See ADR-0019 + ADR-0020.
  • Semantic. A vector store fed by a background indexer that embeds recent audit rows; queried by the semantic_recall worker. See ADR-0010.
  • Personal facts (v1.8). personal_facts table; a background extractor reads recent chats and stores {key, value, confidence} rows that are auto-injected into future chat system prompts. See ADR-0033.
  • Episodes (v1.9). episode_summaries table; per-chat 1-2 sentence summaries embedded for similarity-ranked auto-recall. See ADR-0034.

Policy Engine

  • Gatekeeper between Praetor/Legion and the real world. See ADR-0013 (HA service-call gate) + ADR-0028 (generalisation to non-HA tools, plugin tools ride this same path).
  • YAML allow-list with optional per-call constraints (e.g. limit light.turn_on to specific entity_ids; restrict web_search domains).
  • Deny-all by default — CAESAR_POLICY__RULES_PATH unset means no tool runs.

Message Bus

  • NATS, per ADR-0009.
  • Subject namespacing: caesar.dispatch.<worker_id>, caesar.registry.*, caesar.reply.<inbox>.
  • Disabled by default. Enabling it brings up the Legion pool and the plugin loader; without it, Praetor runs single-process and the brain only has call_service (when HA is wired).
  • Multi-host operation gated by per-identity NKEYs (ADR-0027).

Proactive triggers

The same brain graph that serves /v1/chat also runs unattended when something else fires:

  • Scheduler — cron expressions in triggers.yaml (ADR-0030).
  • HAEventDriver — HA WS subscription dispatching matching events to the brain (ADR-0031).
  • WebhookDispatcher — POST /v1/hook/{trigger_id} with per-trigger bearer tokens (ADR-0032).

All three feed ProactiveRunner.fire(...), which invokes the brain with proactive=true in the safety preamble — biases toward summarise + notify over act-on-the-house. See PROACTIVE-CAESAR.md.

Dashboard

  • Server-side HTMX, embedded in Praetor. See ADR-0021.
  • Live audit log via SSE (the AuditEventBus fans new rows to subscribers in the same process).
  • Surfaces facts, episodes, sessions, and an operator-editable system-prompt override.
  • Opt-in: /dashboard/* returns 404 until CAESAR_DASHBOARD__TOKEN is set.

Observability

  • Prometheus /metrics — request counts, chat duration, policy decisions, audit-event counters.
  • OpenTelemetry tracing — optional install (caesar[otel]); spans cover the brain graph + every tool dispatch. See ADR-0023.
  • Structured JSON logging — caesar.log configures a structlog pipeline. See ADR-0018.

Distribution

  • Published as caesar on PyPI (Sigstore provenance via Trusted Publishers; SR-011 follow-up) and ghcr.io/sinidious/caesar as a multi-arch Docker image with attestations. See ADR-0029.
  • caesar init generates a working .env + policy.yaml + triggers.yaml + Praetor NKEY in one shot.

Voice in

CAESAR doesn't ship a voice satellite. Voice input is "any HTTP client that posts to /v1/chat": phone OS dictation (Gboard / iOS keyboard / Whisper-based system services), browser dictation, or a custom transcription pipeline you run yourself. ADR-0008 rewrote away from the original Wyoming-satellite plan.

Trust boundaries

See SECURITY-MODEL.md. Short version:

  • HTTP clients (phone, browser, webhook senders) are untrusted user surfaces. /v1/chat and /v1/devices are bearer-token gated when exposed beyond loopback (SR-001); /v1/hook/* has per-trigger bearer auth.
  • Praetor is the only component that holds long-lived credentials (LLM API keys, HA token, dashboard token, NKEY seed).
  • Workers run with the privileges of the process they live in; plugins are a code-trust act (pip install = trust). The Policy Engine is the last line that mediates real-world side effects, and it cannot distinguish plugin tools from in-tree tools.