- 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
12 KiB
Configuration Guide
How Aetheel loads its settings — config file, secrets, and CLI overrides.
Table of Contents
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 (includingenv.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