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
This commit is contained in:
445
docs/project/configuration.md
Normal file
445
docs/project/configuration.md
Normal file
@@ -0,0 +1,445 @@
|
||||
# Configuration Guide
|
||||
|
||||
> How Aetheel loads its settings — config file, secrets, and CLI overrides.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Overview](#overview)
|
||||
2. [Config File](#config-file)
|
||||
3. [Secrets (env.vars)](#secrets)
|
||||
4. [CLI Overrides](#cli-overrides)
|
||||
5. [Priority Order](#priority-order)
|
||||
6. [Reference](#reference)
|
||||
7. [Examples](#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
|
||||
|
||||
```json
|
||||
{
|
||||
"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.
|
||||
|
||||
```json
|
||||
{
|
||||
"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
|
||||
|
||||
```json
|
||||
{
|
||||
"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.
|
||||
|
||||
```bash
|
||||
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`:
|
||||
```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.
|
||||
|
||||
```bash
|
||||
python main.py
|
||||
```
|
||||
|
||||
### Custom Model + SDK Mode
|
||||
|
||||
`~/.aetheel/config.json`:
|
||||
```json
|
||||
{
|
||||
"runtime": {
|
||||
"mode": "sdk",
|
||||
"model": "anthropic/claude-sonnet-4-20250514",
|
||||
"server_url": "http://localhost:4096"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Start OpenCode server first, then Aetheel:
|
||||
```bash
|
||||
opencode serve --port 4096
|
||||
python main.py
|
||||
```
|
||||
|
||||
### Discord with Listen Channels
|
||||
|
||||
`~/.aetheel/config.json`:
|
||||
```json
|
||||
{
|
||||
"env": {
|
||||
"vars": {
|
||||
"DISCORD_BOT_TOKEN": "your-discord-token"
|
||||
}
|
||||
},
|
||||
"discord": {
|
||||
"enabled": true,
|
||||
"bot_token": "${DISCORD_BOT_TOKEN}",
|
||||
"listen_channels": ["1234567890123456"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
python main.py --discord
|
||||
```
|
||||
|
||||
### Multi-Channel (Slack + Discord + Telegram)
|
||||
|
||||
`~/.aetheel/config.json`:
|
||||
```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}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
python main.py --discord --telegram
|
||||
```
|
||||
|
||||
### Claude Code Runtime
|
||||
|
||||
`~/.aetheel/config.json`:
|
||||
```json
|
||||
{
|
||||
"claude": {
|
||||
"model": "claude-sonnet-4-20250514",
|
||||
"max_turns": 5,
|
||||
"no_tools": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
python main.py --claude
|
||||
```
|
||||
Reference in New Issue
Block a user