Melian

Architecture

Overview

Melian is a local-first application built on Bun. Everything runs in a single process on your machine: the HTTP/WebSocket server, cron scheduler, iMessage poller, and all integration clients. No application state lives in the cloud; only specific third-party APIs (Google Calendar, Google Vision, Govee Cloud) require outbound connections.

Tech Stack

Layer Technology Notes
Runtime Bun v1.3+ TypeScript, bundler, test runner
Web framework Hono Lightweight, edge-compatible router
Frontend React 19 + Tailwind CSS v4 Bundled by Bun's HTML importer
Relational DB SQLite via bun:sqlite Six databases, see topology below
Vector DB Qdrant Two collections, Cosine distance
LLM inference OpenAI-compatible HTTP LM Studio (local) or any compatible server
Embeddings OpenAI-compatible HTTP nomic-embed-text-v1.5 (768-dim)
Email imapflow (IMAP), nodemailer (SMTP) Multi-account
Calendar Google Calendar REST API v3 OAuth 2.0 device flow
Vision / OCR Google Cloud Vision API Optional; disabled if credentials absent
Smart home (Govee) Govee LAN API + Govee Cloud API LAN preferred; cloud fallback for H5179
Smart home (Wiz) WiZ UDP protocol (port 38899) LAN only
Tablet sync Remarkable Cloud API Optional
Scheduler croner In-process cron, no daemon needed
Process manager launchd (macOS) / systemd (Linux) For unattended startup

System Diagram

┌────────────────────────────────────────────────────────────────┐
│                        Web Browser                             │
│              React 19 · Tailwind · Nan Elmoth UI               │
│  /  (Mirror)  /glade (Chat)  /knowledge  /devices  /tasks      │
└────────────────────┬───────────────────────────────────────────┘
                     │  HTTP + WebSocket
┌────────────────────▼───────────────────────────────────────────┐
│                     Bun Process (single)                        │
│                                                                 │
│  ┌─────────────┐  ┌─────────────────────────────────────────┐  │
│  │  Hono API   │  │             Agent Core                  │  │
│  │  /api/*     │  │  ┌──────────┐  ┌──────────────────────┐│  │
│  │  WebSocket  │  │  │  Router  │  │   Prompt Builder     ││  │
│  └──────┬──────┘  │  │ (simple/ │  │ core memory +        ││  │
│         │         │  │  complex)│  │ conversation history ││  │
│         │         │  └────┬─────┘  └──────────┬───────────┘│  │
│         │         │       │                   │             │  │
│         │         │  ┌────▼─────────────────── ▼──────────┐│  │
│         │         │  │         Tool Executor               ││  │
│         │         │  │  memory · email · calendar · home   ││  │
│         │         │  └─────────────────────────────────────┘│  │
│         │         └─────────────────────────────────────────┘  │
│         │                                                       │
│  ┌──────▼──────────────────────────────────────────────────┐   │
│  │                   Service Layer                          │   │
│  │  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌───────────┐  │   │
│  │  │  Memory  │ │  Jobs /  │ │ Attention│ │  Integr.  │  │   │
│  │  │  System  │ │ Scheduler│ │ Pipeline │ │  Clients  │  │   │
│  │  └──────────┘ └──────────┘ └──────────┘ └───────────┘  │   │
│  └──────────────────────────────────────────────────────────┘  │
│                                                                 │
│  ┌──────────────────────┐   ┌──────────────────────────────┐   │
│  │    SQLite (6 DBs)    │   │    Qdrant (2 collections)    │   │
│  │  recall.db           │   │  archival_memory             │   │
│  │  jobs.db             │   │  knowledge                   │   │
│  │  email-cache.db      │   └──────────────────────────────┘   │
│  │  calendar-cache.db   │                                       │
│  │  attention.db        │                                       │
│  │  imessage-state.db   │                                       │
│  └──────────────────────┘                                       │
└────────────────────────────────────────────────────────────────┘
           │                │                │
  ┌────────▼───┐   ┌────────▼───┐   ┌────────▼──────┐
  │  LM Studio │   │  Google    │   │  Govee / Wiz  │
  │  (local)   │   │  APIs      │   │  Smart Home   │
  └────────────┘   └────────────┘   └───────────────┘

Source Layout

The codebase is organized into four tiers under src/:

src/
├── core/              # Foundational capabilities
│   ├── agent/         # LLM agent router, tools, personality, spirit
│   ├── attention/     # Message classification pipeline
│   ├── jobs/          # Cron scheduler, job store, self-healing
│   ├── knowledge/     # Document chunking, vector search
│   ├── llm/           # LLM client, model routing, complexity scoring
│   └── memory/        # Core, archival (Qdrant), recall (SQLite)

├── integrations/      # External service connectors
│   ├── browser/       # Browser automation
│   ├── calendar/      # Google Calendar
│   ├── email/         # IMAP/SMTP, permissions
│   ├── govee/         # Govee smart home (LAN + cloud)
│   ├── imessage/      # macOS iMessage
│   ├── mcp/           # Model Context Protocol
│   ├── tasks/         # Obsidian + Remarkable
│   ├── weather/       # NOAA weather data
│   └── wiz/           # Wiz smart lights

├── surfaces/          # User-facing entry points
│   ├── cli/           # Command-line interface
│   ├── server/        # Hono API + routes
│   └── web/           # React UI (Nan Elmoth)

└── lib/               # Cross-cutting utilities
    ├── config.ts      # Configuration loader
    ├── types.ts       # Shared type definitions
    ├── google/        # Google OAuth helpers
    ├── logging/       # Error capture
    ├── platform/      # launchd/systemd service management
    ├── push/          # Web Push notifications
    └── vision/        # OCR (Google Cloud Vision)

Dependency Boundaries

The tiered structure enforces a strict dependency hierarchy:

Tier Can import from Cannot import from
lib/ Nothing (leaf tier) core, integrations, surfaces
core/ lib/ integrations, surfaces
integrations/ lib/, core/ surfaces, other integrations
surfaces/ Everything (composition layer)

This prevents circular dependencies and keeps core capabilities independent of any specific integration or UI surface.

Data Flow

A complete request cycle, from user input to streamed response:

  1. Input arrives via the WebSocket from the browser (or via the CLI REPL's local HTTP call)
  2. Thread is loaded: conversation history is fetched from recall.db, limited to llm.maxContextTokens tokens using a token-counting truncation pass
  3. Core memory is injected: all facts stored in the core memory table in recall.db are prepended as a system context block
  4. Complexity scoring: if llm.complexityRouting is enabled, the query is scored using the lightweight feature extractor; the result selects which model in llm.preferred handles the request
  5. LLM call begins: the assembled prompt is sent to the configured OpenAI-compatible endpoint with stream: true
  6. Tokens stream to the client: each SSE chunk is forwarded over the WebSocket as it arrives; the frontend appends to the active bubble in real time
  7. Tool calls are parsed: if the stream contains a tool call, streaming pauses, the tool is executed (memory search, email read, calendar query, device command, etc.), and the result is fed back to the LLM for synthesis
  8. Tool results may trigger further calls: the agent loop continues until the LLM emits a final text response with no pending tool calls
  9. Response is persisted: the completed exchange (user message + tool calls + Melian's response) is written to recall.db
  10. Core memory is updated: if the response contains memory-worthy facts (detected via a lightweight heuristic on the response content), they are written to the core memory table

Database Topology

SQLite Databases

All six databases use bun:sqlite (the Bun built-in SQLite binding). Migrations are applied via a versioned migration runner on startup.

Database Tables Purpose
recall.db threads, messages, core_memory, facts Conversation history and long-term facts
jobs.db jobs, runs, logs Job definitions, run history, output
email-cache.db accounts, messages, drafts, approvals Email index and draft queue
calendar-cache.db events, calendars, sync_tokens Google Calendar event cache
attention.db seeds, candidates, decisions, digest_runs Attention pipeline state
imessage-state.db contacts, last_seen, pending iMessage read cursor per contact

Qdrant Collections

Collection Dimensions Metric Purpose
archival_memory 768 Cosine Long-form memory retrieved by semantic similarity
knowledge 768 Cosine Imported documents, notes, reference material

Both collections are created on startup if they don't exist. Vector payloads include source, timestamp, thread_id, and text fields, enabling filtered search (e.g. "only search memories from this month").

Process Model

Melian runs as a single Bun process. There is no separate worker process, message queue, or inter-process communication. Concurrency is handled via Bun's event loop:

  • Cron scheduler: croner runs inside the same process, firing job callbacks on schedule. Jobs that need to avoid blocking the event loop (long LLM calls, bulk email indexing) yield by awaiting async operations naturally.
  • iMessage poller: a setInterval polling chat.db every 3 seconds. Uses bun:sqlite in read-only mode to avoid interfering with the Messages app.
  • WebSocket connections: handled by Bun.serve()'s built-in WebSocket support. Multiple simultaneous connections (browser tabs, CLI clients) are supported.
  • LLM streaming: uses native fetch with a ReadableStream body; Bun handles the async stream with no additional library.

External Dependencies

Dependency Protocol Required Fallback behavior
LM Studio (or equivalent) HTTP (OpenAI API) Yes Server starts but chat is non-functional
Qdrant HTTP REST Yes Server starts; archival memory disabled
Google Calendar API HTTPS REST No Calendar features disabled
Google Cloud Vision API HTTPS REST No OCR features disabled
Govee Cloud API HTTPS REST No Cloud-only devices (H5179) show as unreachable
Govee LAN API UDP No LAN-controllable devices show as unreachable
Wiz UDP (38899) No Wiz cards show no devices
Remarkable Cloud HTTPS REST No Tablet sync disabled
NOAA Weather API HTTPS REST No Weather features disabled
Census Bureau Geocoder HTTPS REST No Location name lookup unavailable; lat/lon still works

Platform Integration

macOS (launchd)

On macOS, Melian is registered as a launchd user agent so it starts at login and restarts on crash:

<!-- ~/Library/LaunchAgents/com.your-username.melian.plist -->
<plist version="1.0">
<dict>
  <key>Label</key>       <string>com.your-username.melian</string>
  <key>ProgramArguments</key>
  <array>
    <string>/Users/you/.bun/bin/bun</string>
    <string>run</string>
    <string>start</string>
  </array>
  <key>WorkingDirectory</key>  <string>/Users/you/melian</string>
  <key>RunAtLoad</key>         <true/>
  <key>KeepAlive</key>         <true/>
  <key>StandardOutPath</key>   <string>/Users/you/.melian/melian.log</string>
  <key>StandardErrorPath</key> <string>/Users/you/.melian/melian-error.log</string>
</dict>
</plist>

Load with: launchctl load ~/Library/LaunchAgents/com.your-username.melian.plist

Linux (systemd)

On Linux, a user-level systemd unit provides equivalent behavior:

# ~/.config/systemd/user/melian.service
[Unit]
Description=Melian personal assistant
After=network.target

[Service]
ExecStart=/home/you/.bun/bin/bun run start
WorkingDirectory=/home/you/melian
Restart=on-failure
Environment=HOME=/home/you

[Install]
WantedBy=default.target

Enable with: systemctl --user enable --now melian

Self-Healing Agent

A nightly integration test suite runs at 2:00 AM via launchd/systemd. At 2:30 AM, a separate auto-fix agent reviews any failures and opens PRs against the Melian repository with proposed fixes. This ensures the system self-monitors without manual intervention. See docs/known-issues.md for current open issues surfaced by this process.