Melian

Configuration

The roots beneath the soil: how Melian finds her bearings.

Config Directory

Melian's configuration lives in ~/.melian/, split across multiple files, one per subsystem. Each file is independently loaded, so you only need to create configs for the features you use. Missing files fall back to sensible defaults (usually "disabled").

~/.melian/
├── config.yaml              # Server, LLM, Qdrant, embeddings
├── email.yaml               # Email accounts & permissions
├── imessage.yaml            # iMessage contacts & modes
├── calendar.yaml            # Google Calendar settings
├── tasks.yaml               # Obsidian vault & Remarkable sync
├── govee.yaml               # Govee smart home
├── wiz.yaml                 # Wiz smart lights
├── mcp.json                 # MCP server definitions
├── personality.yaml         # Personality traits & context overrides
├── memory/
│   └── core.json            # Core memory (auto-managed)
└── spirit.md                # Spirit entries (auto-managed)

Runtime data (databases) lives separately:

  • macOS: ~/Library/Application Support/melian/
  • Linux: ~/.local/share/melian/

config.yaml: Server & LLM

The main config file. Controls the server, LLM connection, vector database, and embeddings.

server:
  port: 3000
  host: "127.0.0.1"
  tls:                                    # Optional: HTTPS
    cert: ~/.melian/certs/tls.crt
    key: ~/.melian/certs/tls.key

llm:
  baseUrl: "http://localhost:1234/v1"     # OpenAI-compatible endpoint
  maxRetries: 3
  timeout: 30000                          # ms
  maxContextTokens: 131072
  models:
    preferred:                            # Model priority list
      - "qwen2.5-14b-instruct"
    complexity_routing: false             # Route by query complexity
    complexity_threshold: 0.5             # 0–1 score threshold
    discovery_cache_ttl: 30               # seconds
  fallback:                               # Optional: fallback provider
    enabled: false
    baseUrl: ""
    apiKey: ""

qdrant:
  url: "http://localhost:6333"
  collection: "archival_memory"
  embeddingDimensions: 768                # Must match embedding model

embeddings:
  baseUrl: "http://localhost:1234/v1"
  model: "nomic-embed-text-v1.5"

user:                                     # Optional: identity context
  name: "Your Name"
  interests:
    - software engineering
    - Tolkien

selfHealing:                              # Optional
  enabled: true
  dryRun: false
  maxFixesPerRun: 5

push:                                     # Optional: Web Push
  vapidPublicKey: "..."
  vapidPrivateKey: "..."
  vapidSubject: "mailto:[email protected]"

email.yaml: Email Accounts

Each account has its own IMAP/SMTP credentials and permission tier.

accounts:
  - name: personal
    provider: gmail                       # gmail | protonmail | generic
    email: [email protected]
    oauth:
      client_id: "872077090790-..."
      client_secret: "GOCSPX-..."
      refresh_token: "1//04zx90aF..."
    imap:
      host: imap.gmail.com
      port: 993
      tls: true
    smtp:
      host: smtp.gmail.com
      port: 465
      tls: true
    send_enabled: true
    check_interval: 30m

permissions:
  autonomous:                             # Auto-send to these domains
    - your-domain.com
  blocked:                                # Never touch these
    - irs.gov

Permission tiers: autonomous (auto-send), draft (approval required), everything else defaults to draft.

imessage.yaml: iMessage

Controls the iMessage watcher, contact modes, and self-chat configuration.

imessage:
  enabled: true
  pipeline: legacy                        # legacy | shadow | unified
  poll_interval: 3                        # seconds
  session_timeout: 900                    # seconds
  max_reply_length: 500

  self_chat:
    identifier: "[email protected]"
    watch:
      - "+15555550100"
      - "[email protected]"

  contacts:
    - identifier: "+15555550100"
      name: "Mom"
      mode: auto                          # auto | draft | notify | triage | spam
      context: "Custom prompt for this contact..."  # Optional custom prompt
      unconstrained: false                # Optional: relaxed guardrails
      busy_auto_reply: false              # Optional: reply when busy

Modes: auto (reply directly), draft (generate for approval via self-chat), notify (summarize without reply), triage (attention pipeline), spam (discard).

calendar.yaml: Google Calendar

enabled: true
default_account: personal                 # Must match an email account name
default_calendar: [email protected]           # Google calendar ID

reminders:
  morning_briefing: "8:00am"
  pre_event_minutes: 15
  skip_all_day: false

defaults:
  duration_minutes: 60
  reminder_minutes: 15
  timezone: America/Denver

Calendar uses the OAuth credentials from the matching email account. On first run with calendar enabled, Melian prints an authorization URL; open it, approve, and paste the code.

tasks.yaml: Obsidian & Remarkable

enabled: true

obsidian:
  vault_path: "/Users/you/Documents/Vault"
  tasks_file: tasks.md

remarkable:                               # Optional
  notebooks:
    - path: Work/Tasks
      defaultTag: work
    - path: Personal Tasks
      defaultTag: personal

sync:
  schedule: "0 * * * *"                   # Cron: sync every hour
followup:
  schedule: "0 13 * * *"                  # Daily at 1pm

govee.yaml: Govee Smart Home

enabled: true
multicast_address: 239.255.255.250        # LAN discovery
discovery_timeout_ms: 3000
api_key: "ed812abf-..."                   # Optional: cloud API key

The api_key enables cloud API fallback for devices that don't support LAN control (e.g., the H5179 hygrometer).

wiz.yaml: Wiz Lights

enabled: true
broadcast_address: 255.255.255.255        # LAN broadcast
discovery_timeout_ms: 2000

Wiz is LAN-only. No cloud API.

mcp.json: MCP Servers

{
  "servers": {
    "duckduckgo": {
      "type": "stdio",
      "command": "uvx",
      "args": ["duckduckgo-mcp-server"]
    },
    "custom-tool": {
      "type": "stdio",
      "command": "bun",
      "args": ["run", "/path/to/server.ts"],
      "env": {
        "API_KEY": "..."
      }
    }
  }
}

MCP servers are started at boot and their tools appear alongside Melian's built-in tools.

personality.yaml: Tone & Personality

personality:
  base:
    tone: warm, thoughtful, concise
    humor: dry, understated
    verbosity: concise, lead with the answer
    traits: |
      - Proactive about remembering things
      - Asks clarifying questions
      - Respects the user's time

  contexts:                               # Override per context
    imessage:
      tone: casual, conversational
      verbosity: brief
    email:
      tone: professional but human
    cron:
      tone: efficient, informative
      verbosity: only report what's noteworthy

Environment Variables

Bun loads .env from the project root automatically. Environment variables override config.yaml values.

Variable Overrides Description
PORT server.port HTTP server port
HOST server.host Bind address
LLM_BASE_URL llm.baseUrl LLM API endpoint
LLM_API_KEY llm.apiKey API key (blank for local)
QDRANT_URL qdrant.url Qdrant server URL
GOVEE_API_KEY govee.api_key Govee Cloud API key

Complexity Routing

When models.complexity_routing is true, Melian scores each query before selecting a model. Scores below complexity_threshold use the first model in preferred; scores above use the next available model. The scorer is a lightweight feature extractor (no LLM call) that checks message length, turn count, code blocks, tool calls, and question count.