Docs/Resonant/Architecture

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/ — loads CLAUDE.md or 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
  • Schedulingcroner, not node-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 more
  • src/components/hearth/ — the presence orb and its controls
  • src/components/observatory/ — the constellation/health view
  • src/components/settings/ — Appearance, Channels, Discord, Google, MCP, Mind, Orchestrator, Preferences, Usage sections
  • src/store/ — zustand stores for auth, chat, mind, observatory, theme

Data Flow

Message Lifecycle

  1. You send a message — via web UI, Discord, or Telegram
  2. Hooks enrich itbuildOrientationContext() in hooks.ts assembles a [Context]...[/Context] block (time, presence, recent activity, memory) and staples it to your message
  3. The Agent SDK processes it — along with CLAUDE.md (or the structured identity), conversation history, and any connected MCP servers
  4. Claude responds, streaming — tokens flow back over WebSocket
  5. Tool calls are shown — the frontend renders what the agent is doing, live
  6. 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.