Core Concepts
Architecture
The eight-layer model, the strict dependency rule, and why every backend is a plugin.
Jarvis is built as eight layers, each reaching the one below it only through a protocol. Lateral communication happens only through typed, immutable events on an in-process event bus. This is what keeps the voice layer swappable from the brain, and the brain swappable from the harness.
The eight layers
| Layer | Responsibility |
|---|---|
| L0 — OS / Hardware | Microphone, speakers, global hotkeys, platform APIs |
| L1 — Audio I/O | Device routing, chime feedback |
| L2 — Speech | Wake → VAD → speech-to-text → text-to-speech |
| L3 — Intent / Risk | Classifier, risk-tier policy, approval, rate-limit tracking |
| L4 — Brain | Five providers (Claude, OpenRouter, OpenAI, Gemini, Grok) + a sub-second Ack tier |
| L5 — Harness Adapter | Claude Code, Codex, Open Interpreter, Python script, MCP remote |
| L6 — Orchestrator | State machine, router, BrainManager, supervisor, Mission-Manager |
| L7 — UI / UX | Tray, toasts, desktop app (FastAPI + React + pywebview), orb overlay |
The dependency rule (strict)
Higher layers reach lower layers only via protocols. They never import a concrete implementation. That single rule is why you can swap faster-whisper for Google STT, or Claude for Gemini, without the layers above noticing.
The event bus
Everything that crosses sideways is a frozen dataclass event carrying a trace_id and a nanosecond timestamp. Immutability is not decoration — it’s what lets a flight recorder replay an entire session deterministically. A subscriber that throws is logged and dropped; one broken handler can never take down the pipeline.
Plugins, structurally
Backends live under jarvis/plugins/<group>/<name>.py and register through entry points. The system is structural, not nominal — a plugin only needs to match the protocol shape, and it must not import from the core package. The frozen plugin groups are:
wakeword · stt · tts · brain · harness · tool · channel
Streaming first
Every Brain, STT, TTS, and Harness method returns an async iterator. Non-streaming providers simply yield a single element. Consumers always async for over the result — so the first audible token can start playing before the full answer exists.
Related: the voice pipeline · harness dispatch.