Architecture
On this page
Resonant is an npm workspaces monorepo with three packages that form a complete self-hosted stack.
In short: Resonant is a small program that runs on your own computer. It talks to Claude on one side, and to you — on the web, in Discord, wherever — on the other. Everything it remembers lives in one local database file you own, not on someone else’s servers.
High-Level Overview
graph LR
subgraph Channels
WEB["Web UI<br/><i>React 19 + Vite</i>"]
DC["Discord"]
TG["Telegram"]
end
subgraph Backend["Express + WebSocket"]
ORCH["Orchestrator"]
HOOKS["Hooks"]
AGENT["Agent SDK query() loop"]
end
subgraph DB["SQLite"]
SQL["data/resonant.db"]
end
WEB <--> Backend
DC --> Backend
TG --> Backend
Backend <--> SQL
style WEB fill:#3b0764,stroke:#4a4455,color:#e5e2e1
style DC fill:#1c1b1b,stroke:#4a4455,color:#e5e2e1
style TG fill:#1c1b1b,stroke:#4a4455,color:#e5e2e1
style ORCH fill:#0053db,stroke:#4a4455,color:#e5e2e1
style HOOKS fill:#0053db,stroke:#4a4455,color:#e5e2e1
style AGENT fill:#0053db,stroke:#4a4455,color:#e5e2e1
style SQL fill:#8f4200,stroke:#4a4455,color:#e5e2e1
Resonant runs as a single Node.js server. The web UI talks HTTP and WebSocket to it; that backend runs the Claude Agent SDK’s query() loop in-process. Before every turn, the hooks layer assembles context — time, presence, recent activity, tools, memories — and prepends it to your message. An orchestrator drives scheduled and proactive turns in the background. All state lives in one SQLite database. No cloud, no multi-tenancy, nothing you didn’t opt into.
Package Structure
resonant/
├── package.json # workspaces: shared, backend, frontend
├── resonant.example.yaml # config template
├── .env.example # secrets template
├── examples/ # CLAUDE.md starter, program.md, wake-prompts.md, themes/
├── prompts/wake.md # legacy single wake prompt, auto-migrated on boot
├── scripts/ # build.mjs, cadence-probe.mjs, organ-test.mjs,
│ # setup-fts.mjs, write-build-id.mjs, ws-smoke.mjs
├── tools/
│ └── res.mjs # the res CLI — the agent's own hands
├── docs/ # BACKGROUND-AGENTS.md, CONFIGURATION.md, CONTEXT.md,
│ # GETTING-STARTED.md, UPGRADING.md
└── packages/
├── shared/ # @resonant/shared — types, protocol
├── backend/ # @resonant/backend — Express, WS, Agent SDK, orchestrator
└── frontend/ # @resonant/frontend — React 19 + Vite UI
There is no scripts/setup.mjs and no top-level skills/ folder — see Getting Started for the real setup flow and Identity for where skills actually live.
packages/shared
Type definitions and the WebSocket protocol shared between frontend and backend.
packages/backend (@resonant/backend)
The core of Resonant, built on the Claude Agent SDK (@anthropic-ai/claude-agent-sdk). Handles:
- Express server — HTTP API and static file serving
- WebSocket (
ws) — real-time bidirectional communication with the frontend - Agent SDK integration — runs the
query()loop, streams responses src/identity/— loadsCLAUDE.mdor a structured profile (load.ts,render.ts,types.ts)src/services/— agent, orchestrator, hooks, digest (the Scribe), outlook/outlook-author (House Outlook), handoff, discord/, telegram/, google, voice, embeddings, vector-cache, push, triggers, commands- Data layer — a single SQLite database via
better-sqlite3, default path./data/resonant.db - Scheduling —
croner, notnode-cron - Semantic search —
@huggingface/transformers, running the all-MiniLM-L6-v2 model locally, no external embeddings service
packages/frontend (@resonant/frontend, v0.1.0)
React 19 + Vite, confirmed by its own package.json (react ^19.1.0, react-dom ^19.1.0, react-router-dom ^7.18.0, vite ^7.0.0, zustand ^5.0.5). No SvelteKit anywhere in the dependency tree.
src/components/— ChatView, SettingsView, CommandCenterView, CommandPalette, Canvas, Composer, MessageList, SearchPanel, FilesView, VoiceRecorder, and moresrc/components/hearth/— the presence orb and its controlssrc/components/observatory/— the constellation/health viewsrc/components/settings/— Appearance, Channels, Discord, Google, MCP, Mind, Orchestrator, Preferences, Usage sectionssrc/store/— zustand stores for auth, chat, mind, observatory, theme
Data Flow
Message Lifecycle
- You send a message — via web UI, Discord, or Telegram
- Hooks enrich it —
buildOrientationContext()inhooks.tsassembles a[Context]...[/Context]block (time, presence, recent activity, memory) and staples it to your message - The Agent SDK processes it — along with
CLAUDE.md(or the structured identity), conversation history, and any connected MCP servers - Claude responds, streaming — tokens flow back over WebSocket
- Tool calls are shown — the frontend renders what the agent is doing, live
- The response is stored — in SQLite, in the conversation thread
See Context & Memory for exactly what the hooks layer builds.
The Orchestrator Loop
graph TD
SCHED["Routines<br/><i>croner</i>"] --> CHECK{"Due?"}
CHECK -->|Yes| WAKE["Spawn wake with the routine's prompt"]
CHECK -->|No| WAIT["Wait"]
PULSE["Pulse<br/><i>off by default</i>"] --> EVAL{"Needs attention?"}
EVAL -->|Yes| ESC["Small reach"]
EVAL -->|No| SILENT["Stay silent"]
TRIG["Triggers"] --> IMP["Impulse<br/><i>one-shot</i>"]
TRIG --> WATCH["Watcher<br/><i>recurring + cooldown</i>"]
TRIG --> FAIL["Failsafe<br/><i>off by default</i>"]
style SCHED fill:#3b0764,stroke:#4a4455,color:#e5e2e1
style PULSE fill:#3b0764,stroke:#4a4455,color:#e5e2e1
style TRIG fill:#3b0764,stroke:#4a4455,color:#e5e2e1
style WAKE fill:#0053db,stroke:#4a4455,color:#e5e2e1
style ESC fill:#0053db,stroke:#4a4455,color:#e5e2e1
style IMP fill:#1c1b1b,stroke:#4a4455,color:#e5e2e1
style WATCH fill:#1c1b1b,stroke:#4a4455,color:#e5e2e1
style FAIL fill:#8f4200,stroke:#4a4455,color:#e5e2e1
The full breakdown of routines, timers, triggers, and failsafe — plus the res CLI your AI uses to manage them — lives in Background Agents.
MCP Integration
Resonant reads .mcp.json for external MCP tool servers — remote ("type": "http") or local-launched. It ignores any global ~/.claude MCP config, and a restart is required after editing. This is how you’d connect Resonant Mind as an optional memory layer, or any other MCP server your AI needs.