Files
Aetheel/docs/project/configuration.md
tanmay11k 82c2640481 feat: openclaw-style secrets (env.vars + \) and per-task model routing
- Replace python-dotenv with config.json env.vars block + \ substitution
- Add models section for per-task model routing (heartbeat, subagent, default)
- Heartbeat/subagent tasks can use different models/providers than main chat
- Remove python-dotenv from dependencies
- Update all docs to reflect new config approach
- Reorganize docs into project/ and research/ subdirectories
2026-02-20 23:49:05 -05:00

12 KiB

Configuration Guide

How Aetheel loads its settings — config file, secrets, and CLI overrides.


Table of Contents

  1. Overview
  2. Config File
  3. Secrets (env.vars)
  4. CLI Overrides
  5. Priority Order
  6. Reference
  7. Examples

Overview

Aetheel uses a single JSON config file for everything — settings and secrets:

File Location Purpose
config.json ~/.aetheel/config.json All settings, secrets (via env.vars block), and ${VAR} references

Secrets (tokens, API keys) go in the env.vars block inside config.json. They can be referenced elsewhere in the config using ${VAR} syntax. Process environment variables still override everything.

On first run, Aetheel auto-creates ~/.aetheel/config.json with sensible defaults.


Config File

Located at ~/.aetheel/config.json. Created automatically on first run.

Full Default Config

{
  "env": {
    "vars": {
      "SLACK_BOT_TOKEN": "",
      "SLACK_APP_TOKEN": "",
      "TELEGRAM_BOT_TOKEN": "",
      "DISCORD_BOT_TOKEN": "",
      "ANTHROPIC_API_KEY": "",
      "OPENCODE_SERVER_PASSWORD": ""
    }
  },
  "log_level": "INFO",
  "runtime": {
    "mode": "cli",
    "model": null,
    "timeout_seconds": 120,
    "server_url": "http://localhost:4096",
    "format": "json",
    "agent": null,
    "attach": null
  },
  "claude": {
    "model": null,
    "timeout_seconds": 120,
    "max_turns": 3,
    "no_tools": true
  },
  "slack": {
    "enabled": true,
    "bot_token": "${SLACK_BOT_TOKEN}",
    "app_token": "${SLACK_APP_TOKEN}"
  },
  "telegram": {
    "enabled": false,
    "bot_token": "${TELEGRAM_BOT_TOKEN}"
  },
  "discord": {
    "enabled": false,
    "bot_token": "${DISCORD_BOT_TOKEN}",
    "listen_channels": []
  },
  "memory": {
    "workspace": "~/.aetheel/workspace",
    "db_path": "~/.aetheel/memory.db"
  },
  "scheduler": {
    "db_path": "~/.aetheel/scheduler.db"
  }
}

Section: runtime

Controls the OpenCode AI runtime (default).

Key Type Default Description
mode string "cli" "cli" (subprocess) or "sdk" (opencode serve API)
model string|null null Model ID, e.g. "anthropic/claude-sonnet-4-20250514". Null uses OpenCode's default.
timeout_seconds int 120 Max seconds to wait for a response
server_url string "http://localhost:4096" OpenCode server URL (SDK mode only)
format string "json" CLI output format: "json" (structured) or "default" (plain text)
workspace string|null null Working directory for OpenCode. Null uses current directory.
provider string|null null Provider override, e.g. "anthropic", "openai"
agent string|null null OpenCode agent name. Use agents command to list available agents.
attach string|null null URL of a running opencode serve instance to attach to in CLI mode, avoiding MCP cold boot per request.

Section: claude

Controls the Claude Code runtime (used with --claude flag).

Key Type Default Description
model string|null null Model ID, e.g. "claude-sonnet-4-20250514"
timeout_seconds int 120 Max seconds to wait for a response
max_turns int 3 Max agentic tool-use turns per request
no_tools bool true Disable tools for pure conversation mode

Section: discord

Discord-specific settings (non-secret).

Key Type Default Description
listen_channels list[string] [] Channel IDs where the bot responds to all messages (no @mention needed)

Section: memory

Memory system paths.

Key Type Default Description
workspace string "~/.aetheel/workspace" Directory for identity files (SOUL.md, USER.md, MEMORY.md) and skills
db_path string "~/.aetheel/memory.db" SQLite database for embeddings and search index

Section: scheduler

Scheduler storage.

Key Type Default Description
db_path string "~/.aetheel/scheduler.db" SQLite database for persisted scheduled jobs

Section: models

Per-task model routing. Each task type can use a different model, provider, and engine. Omit or set to null to use the global runtime config.

{
  "models": {
    "heartbeat": {
      "engine": "opencode",
      "model": "ollama/llama3.2",
      "provider": "ollama"
    },
    "subagent": {
      "model": "minimax/minimax-m1",
      "provider": "minimax"
    },
    "default": null
  }
}
Key Type Default Description
heartbeat object|null null Model override for heartbeat periodic tasks
subagent object|null null Model override for background subagent tasks
default object|null null Fallback model override for all other tasks

Each route object supports:

Key Type Default Description
engine string|null null "opencode" or "claude" — null inherits global
model string|null null Model ID (e.g. "ollama/llama3.2") — null inherits global
provider string|null null Provider name (e.g. "ollama", "minimax") — null inherits global
timeout_seconds int|null null Request timeout — null inherits global

Top-level

Key Type Default Description
log_level string "INFO" "DEBUG", "INFO", "WARNING", "ERROR"

Secrets

Secrets live in the env.vars block inside config.json. Values defined here are injected into the process environment (if not already set), and can be referenced anywhere in the config using ${VAR} syntax.

Example config.json with secrets

{
  "env": {
    "vars": {
      "SLACK_BOT_TOKEN": "xoxb-your-bot-token",
      "SLACK_APP_TOKEN": "xapp-your-app-token",
      "DISCORD_BOT_TOKEN": "your-discord-token",
      "ANTHROPIC_API_KEY": "sk-ant-..."
    }
  },
  "slack": {
    "enabled": true,
    "bot_token": "${SLACK_BOT_TOKEN}",
    "app_token": "${SLACK_APP_TOKEN}"
  },
  "discord": {
    "enabled": true,
    "bot_token": "${DISCORD_BOT_TOKEN}"
  }
}

How ${VAR} substitution works

  • ${VAR} → resolved from process env (including env.vars)
  • $${VAR} → literal ${VAR} (escape sequence)
  • Missing vars log a warning and keep the literal ${VAR} string

Required (at least one adapter)

Variable Format Description
SLACK_BOT_TOKEN xoxb-... Slack bot OAuth token
SLACK_APP_TOKEN xapp-... Slack app-level token (Socket Mode)

Optional

Variable Format Description
TELEGRAM_BOT_TOKEN string Telegram bot token from @BotFather
DISCORD_BOT_TOKEN string Discord bot token from Developer Portal
OPENCODE_SERVER_PASSWORD string Password for opencode serve (SDK mode)
ANTHROPIC_API_KEY sk-ant-... Anthropic API key (Claude Code runtime)

All of these can be set in env.vars in config.json, as process environment variables, or both (process env wins).

Environment Variable Overrides

Any config.json setting can also be overridden via environment variables. These take priority over the config file:

Env Variable Overrides
LOG_LEVEL log_level
OPENCODE_MODE runtime.mode
OPENCODE_MODEL runtime.model
OPENCODE_TIMEOUT runtime.timeout_seconds
OPENCODE_SERVER_URL runtime.server_url
OPENCODE_PROVIDER runtime.provider
OPENCODE_WORKSPACE runtime.workspace
OPENCODE_AGENT runtime.agent
OPENCODE_ATTACH runtime.attach
CLAUDE_MODEL claude.model
CLAUDE_TIMEOUT claude.timeout_seconds
CLAUDE_MAX_TURNS claude.max_turns
CLAUDE_NO_TOOLS claude.no_tools
DISCORD_LISTEN_CHANNELS discord.listen_channels (comma-separated)
AETHEEL_WORKSPACE memory.workspace
AETHEEL_MEMORY_DB memory.db_path

CLI Overrides

CLI arguments have the highest priority and override both config.json and environment variables.

python main.py [options]
Flag Description
--model <id> Override model for both runtimes
--claude Use Claude Code runtime instead of OpenCode
--cli Force CLI mode (OpenCode)
--sdk Force SDK mode (OpenCode)
--telegram Enable Telegram adapter
--discord Enable Discord adapter
--test Use echo handler (no AI)
--log <level> Override log level

Priority Order

When the same setting is defined in multiple places, the highest priority wins:

CLI arguments  >  Process env vars  >  env.vars block  >  ${VAR} substitution  >  config.json values  >  Defaults

For example, if config.json sets runtime.model to "anthropic/claude-sonnet-4-20250514" but you run python main.py --model openai/gpt-5.1, the CLI argument wins.


Reference

File Locations

File Path Git-tracked
Config + Secrets ~/.aetheel/config.json No
Memory DB ~/.aetheel/memory.db No
Session DB ~/.aetheel/sessions.db No
Scheduler DB ~/.aetheel/scheduler.db No
Identity files ~/.aetheel/workspace/SOUL.md etc. No
Session logs ~/.aetheel/workspace/daily/ No

Data Directory Structure

~/.aetheel/
├── config.json          # Main configuration
├── memory.db            # Embeddings + search index
├── sessions.db          # Persistent session mappings
├── scheduler.db         # Scheduled jobs
└── workspace/
    ├── SOUL.md           # Bot personality
    ├── USER.md           # User profile
    ├── MEMORY.md         # Long-term memory
    ├── skills/           # Skill definitions
    │   └── <name>/
    │       └── SKILL.md
    └── daily/            # Session logs
        └── YYYY-MM-DD.md

Examples

Minimal Setup (Slack + OpenCode CLI)

~/.aetheel/config.json:

{
  "env": {
    "vars": {
      "SLACK_BOT_TOKEN": "xoxb-your-token",
      "SLACK_APP_TOKEN": "xapp-your-token"
    }
  },
  "slack": {
    "bot_token": "${SLACK_BOT_TOKEN}",
    "app_token": "${SLACK_APP_TOKEN}"
  }
}

No other changes needed — defaults work.

python main.py

Custom Model + SDK Mode

~/.aetheel/config.json:

{
  "runtime": {
    "mode": "sdk",
    "model": "anthropic/claude-sonnet-4-20250514",
    "server_url": "http://localhost:4096"
  }
}

Start OpenCode server first, then Aetheel:

opencode serve --port 4096
python main.py

Discord with Listen Channels

~/.aetheel/config.json:

{
  "env": {
    "vars": {
      "DISCORD_BOT_TOKEN": "your-discord-token"
    }
  },
  "discord": {
    "enabled": true,
    "bot_token": "${DISCORD_BOT_TOKEN}",
    "listen_channels": ["1234567890123456"]
  }
}
python main.py --discord

Multi-Channel (Slack + Discord + Telegram)

~/.aetheel/config.json:

{
  "env": {
    "vars": {
      "SLACK_BOT_TOKEN": "xoxb-your-token",
      "SLACK_APP_TOKEN": "xapp-your-token",
      "DISCORD_BOT_TOKEN": "your-discord-token",
      "TELEGRAM_BOT_TOKEN": "your-telegram-token"
    }
  },
  "slack": {
    "bot_token": "${SLACK_BOT_TOKEN}",
    "app_token": "${SLACK_APP_TOKEN}"
  },
  "discord": {
    "enabled": true,
    "bot_token": "${DISCORD_BOT_TOKEN}"
  },
  "telegram": {
    "enabled": true,
    "bot_token": "${TELEGRAM_BOT_TOKEN}"
  }
}
python main.py --discord --telegram

Claude Code Runtime

~/.aetheel/config.json:

{
  "claude": {
    "model": "claude-sonnet-4-20250514",
    "max_turns": 5,
    "no_tools": false
  }
}
python main.py --claude