Personal Jarvis / docs ← Back to site GitHub

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

LayerResponsibility
L0 — OS / HardwareMicrophone, speakers, global hotkeys, platform APIs
L1 — Audio I/ODevice routing, chime feedback
L2 — SpeechWake → VAD → speech-to-text → text-to-speech
L3 — Intent / RiskClassifier, risk-tier policy, approval, rate-limit tracking
L4 — BrainFive providers (Claude, OpenRouter, OpenAI, Gemini, Grok) + a sub-second Ack tier
L5 — Harness AdapterClaude Code, Codex, Open Interpreter, Python script, MCP remote
L6 — OrchestratorState machine, router, BrainManager, supervisor, Mission-Manager
L7 — UI / UXTray, 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.