Architecture
JuStAI is a modular monolith with clear ports & adapters: one core process (FastAPI, asyncio) contains the domain modules. Hardware, AI providers and integrations come exclusively through plugins. Whatever is not trusted runs in a separate sandbox container. Rationale: ADR 0001.
Containers (Docker Compose)
Section titled “Containers (Docker Compose)”| Service | Task | Networks |
|---|---|---|
secrets-init |
creates DB password, master key and sandbox token once in the volume secrets |
none |
postgres |
PostgreSQL 17 + pgvector (configuration, events, memory, embeddings, jobs) | db (internal) |
go2rtc |
media server: RTSP/Tapo/ONVIF streams, snapshots, talkback (backchannel) | media |
core |
API, admin UI, audio, conversations, vision, automations, trusted plugins | db, media, sandbox |
sandbox |
runs untrusted plugins and generated tools | sandbox (internal) |
The networks db and sandbox are internal: database and sandbox have neither internet
nor LAN access; the sandbox only reaches the core.
Modules in the core (backend/src/justai)
Section titled “Modules in the core (backend/src/justai)”api/ HTTP/WebSocket layer (routes, auth dependencies) – no logiccontainer.py composition root: builds all components, explicit DIextensions.py feature modules (speech, memory, vision, PTZ, display, automation, devtools, retention)common/ errors, secret redaction, encryption, supervisor, spawn()db/ SQLAlchemy models, Alembic migrationsevents/ event catalog, event bus, event storesystem/ settings, job queue, telemetry, retentionauth/ users, roles (RBAC), JWT, auditrooms/ devices/ endpoints/ topology: rooms, devices (+ discovery), interaction endpointsmedia/ go2rtc clientaudio/ DSP, VAD, echo, I/O channels, processor per endpoint, arbitration, engineconversation/ state machine, sessions (realtime and pipeline drivers), echo filterai/ AI configuration, router (fallback, privacy, cost), usage trackingoutput/ output router, topology, speech outputtools/ tool registry, executor, confirmations, built-in toolsplugins/ loader, manager, context, provider registry, sandbox clientmemory/ identity/ vision/ ptz/ display/ automation/ devtools/ domain modulesi18n.py fixed spoken texts in English, German and ItalianRules:
- Modules only depend on ports (protocols/ABCs), not on concrete plugins. The ports live
in the SDK (
justai_sdk.ports.*) so plugins and core use the same contracts. container.pyis the only place that knows everything. Components get their dependencies through the constructor.- Long-running tasks run under the
Supervisor(restart with backoff) or are started withspawn()(errors are logged, references kept).
Languages
Section titled “Languages”- Interface: English, German, Italian (
frontend/src/shared/i18n).en.tsis the source;de.tsandit.tsare typed against it, so a missing translation fails the build. Every user picks their language at the top right; the default follows the browser. - Backend: everything the language model reads (system prompt, tool descriptions and
results, error messages, event catalog) is English, with the instruction to always
answer in the language of the conversation. The few texts spoken or stored without the
model (error apology, cloud notice, watch announcements) are in
justai/i18n.py. - Plugins: name and description in
plugin.yamlare English, translations underi18n:; settings forms translate their fields withx-i18nin the JSON schema. - Documentation: English in
docs/, German indocs/de/.
Central data flows
Section titled “Central data flows”Voice interaction
Section titled “Voice interaction”microphone ─► go2rtc ─► FfmpegMicSource (16 kHz PCM, timestamps) ─► MicChannel (fan-out to all endpoints that use the device) ─► EndpointAudioProcessor HPF/AEC3 (reference shifted by measured latency) · NS · Silero VAD echo window (PlaybackTracker) · half-duplex gating · openWakeWord ─► WakeArbiter (per arbitration group, 0.8 s window) ─► ConversationManager.start_session(endpoint) realtime driver (e.g. Gemini Live) or pipeline driver (STT → LLM → TTS) ─► tools (registry, permission check, confirmation, audit) ─► OutputRouter.resolve(endpoint room) ─► SpeakerChannel ─► go2rtc backchannel └─► ReferenceBuffer/PlaybackTracker (echo reference)The session is bound to its endpoint. Also after tool calls, provider switches or reconnects the output router determines the target again from the endpoint’s room, never from model output. Details: AUDIO.md, ROOMS.md.
Events and automations
Section titled “Events and automations”source (vision, audio, display, plugin, cron) ─► EventBus.publish ├─ durable events ─► EventStore (Postgres) ├─ subscribers (own queue per subscriber, error isolation) └─ AutomationEngine ─► triggers/conditions ─► job queue (Postgres, SKIP LOCKED) └─► actions (assistant.invoke, speak, tool, display.show …)Events carry hop and caused_by. The bus drops events above the hop limit
(system.event_loop_detected), and an automation never reacts to its own events.
Vision
Section titled “Vision”go2rtc substream (low fps) ─► privacy mask ─► motion ─► YOLOX (only on motion/active tracks) ─► IoU tracker ─► face check (YuNet + SFace, full-res snapshot, only for unknown person tracks) ─► events (vision.*), sightings, watches, PTZ trackingDisplays
Section titled “Displays”tool display.show ─► component validation ─► OutputRouter (display in the room) ─► WebSocket to the display client ─► render with built-in components ─► ack ─► only then the tool reports successPersistence
Section titled “Persistence”PostgreSQL is the only database (ADR 0003): relational
tables for topology, users, automations, jobs; JSONB for flexible configuration and event
payloads; pgvector (384 dim.) for memory and face embeddings. Migrations: Alembic, run
automatically at start (db/migrate.py).
Configuration that users change at runtime (rooms, devices, AI roles, plugin settings)
lives in the database. Environment variables (JUSTAI_*, config.py) only contain
bootstrap values. Plugin secrets are stored encrypted with Fernet (master key from the
volume secrets).
Extensibility
Section titled “Extensibility”- Plugins (manifest, permissions, lifecycle): PLUGINS.md
- Provider ports:
ai(chat, realtime, STT, TTS, embedding),devices(drivers, audio sources/sinks, snapshots, privacy mode),ptz,vision(detector, face analysis),wakeword,display,discovery - Tools: declarative with Pydantic parameters, category, permissions, side effect
(
none/read/write/physical) and optional confirmation - Events: plugins can register their own event types in the namespace
plugin.<id>.*; automations can react to them - Background jobs: plugins run periodic work with
ctx.run_every()
The interfaces between the modules (event bus, job queue, provider ports) are cut so that parts can later move into their own processes, e.g. vision inference or the bus over NATS. That is deliberately not done yet.