feat: config-driven architecture, install wizard, live runtime switching, usage tracking, auto-failover

Major changes:
- Config-driven adapters: all channels (Slack, Discord, Telegram, WebChat, Webhooks) controlled via config.json with enabled flags and token auto-detection, no CLI flags required
- Runtime engine field: runtime.engine selects opencode/claude from config
- Interactive install script: 8-phase setup wizard with AI runtime detection/installation, token setup, identity file personalization (personality presets), aetheel CLI command, background service (launchd/systemd)
- Live runtime switching: /engine, /model, /provider commands hot-swap the AI runtime from chat without restart, changes persisted to config.json
- Usage tracking: per-request cost extraction from Claude Code JSON output, cumulative stats via /usage command
- Auto-failover: rate limit detection on both runtimes, automatic switch to other engine on quota errors with user notification
- Chat commands work without / prefix (Slack intercepts / in channels), commands: engine, model, provider, config, usage, reload, cron, subagents, status, help
- /config set for editing config.json from chat with dotted key notation
- Security audit saved to docs/security-audit.md
- Full command reference in docs/commands.md
- Future changes doc with NanoClaw agent teams analysis
- Logo added to README and WebChat UI
- README fully rewritten with all features documented
This commit is contained in:
2026-02-18 01:07:12 -05:00
parent 41b2f9a593
commit 6d73f74e0b
41 changed files with 11363 additions and 437 deletions

237
docs/Openclaw deep dive.md Normal file
View File

@@ -0,0 +1,237 @@
# OpenClaw Architecture Deep Dive
## What is OpenClaw?
OpenClaw is an open source AI assistant created by Peter Steinberger (founder of PSP PDF kit) that gained 100,000 GitHub stars in 3 days - one of the fastest growing repositories in GitHub history.
**Technical Definition:** An agent runtime with a gateway in front of it.
Despite viral stories of agents calling owners at 3am, texting people's wives autonomously, and browsing Twitter overnight, OpenClaw isn't sentient. It's elegant event-driven engineering.
## Core Architecture
### The Gateway
- Long-running process on your machine
- Constantly accepts connections from messaging apps (WhatsApp, Telegram, Discord, iMessage, Slack)
- Routes messages to AI agents
- **Doesn't think, reason, or decide** - only accepts inputs and routes them
### The Agent Runtime
- Processes events from the queue
- Executes actions using available tools
- Has deep system access: shell commands, file operations, browser control
### State Persistence
- Memory stored as local markdown files
- Includes preferences, conversation history, context from previous sessions
- Agent "remembers" by reading these files on each wake-up
- Not real-time learning - just file reading
### The Event Loop
All events enter a queue → Queue gets processed → Agents execute → State persists → Loop continues
## The Five Input Types
### 1. Messages (Human Input)
**How it works:**
- You send text via WhatsApp, iMessage, or Slack
- Gateway receives and routes to agent
- Agent responds
**Key details:**
- Sessions are per-channel (WhatsApp and Slack are separate contexts)
- Multiple requests queue up and process in order
- No jumbled responses - finishes one thought before moving to next
### 2. Heartbeats (Timer Events)
**How it works:**
- Timer fires at regular intervals (default: every 30 minutes)
- Gateway schedules an agent turn with a preconfigured prompt
- Agent responds to instructions like "Check inbox for urgent items" or "Review calendar"
**Key details:**
- Configurable interval, prompt, and active hours
- If nothing urgent: agent returns `heartbeat_okay` token (suppressed from user)
- If something urgent: you get a ping
- **This is the secret sauce** - makes OpenClaw feel proactive
**Example prompts:**
- "Check my inbox for anything urgent"
- "Review my calendar"
- "Look for overdue tasks"
### 3. Cron Jobs (Scheduled Events)
**How it works:**
- More control than heartbeats
- Specify exact timing and custom instructions
- When time hits, event fires and prompt sent to agent
**Examples:**
- 9am daily: "Check email and flag anything urgent"
- Every Monday 3pm: "Review calendar for the week and remind me of conflicts"
- Midnight: "Browse my Twitter feed and save interesting posts"
- 8am: "Text wife good morning"
- 10pm: "Text wife good night"
**Real example:** The viral story of agent texting someone's wife was just cron jobs firing at scheduled times. Agent wasn't deciding - it was responding to scheduled prompts.
### 4. Hooks (Internal State Changes)
**How it works:**
- System itself triggers these events
- Event-driven development pattern
**Types:**
- Gateway startup → fires hook
- Agent begins task → fires hook
- Stop command issued → fires hook
**Purpose:**
- Save memory on reset
- Run setup instructions on startup
- Modify context before agent runs
- Self-management
### 5. Webhooks (External System Events)
**How it works:**
- External systems notify OpenClaw of events
- Agent responds to entire digital life
**Examples:**
- Email hits inbox → webhook fires → agent processes
- Slack reaction → webhook fires → agent responds
- Jira ticket created → webhook fires → agent researches
- GitHub event → webhook fires → agent acts
- Calendar event approaches → webhook fires → agent reminds
**Supported integrations:** Slack, Discord, GitHub, and basically anything with webhook support
### Bonus: Agent-to-Agent Messaging
**How it works:**
- Multi-agent setups with isolated workspaces
- Agents pass messages between each other
- Each agent has different profile/specialization
**Example:**
- Research Agent finishes gathering info
- Queues up work for Writing Agent
- Writing Agent processes and produces output
**Reality:** Looks like collaboration, but it's just messages entering queues
## Why It Feels Alive
The combination creates an illusion of autonomy:
**Time** (heartbeats, crons) → **Events****Queue****Agent Execution****State Persistence****Loop**
### The 3am Phone Call Example
**What it looked like:**
- Agent autonomously decided to get phone number
- Agent decided to call owner
- Agent waited until 3am to execute
**What actually happened:**
1. Some event fired (cron or heartbeat) - exact configuration unknown
2. Event entered queue
3. Agent processed with available tools and instructions
4. Agent acquired Twilio phone number
5. Agent made the call
6. Owner didn't ask in the moment, but behavior was enabled in setup
**Key insight:** Nothing was thinking overnight. Nothing was deciding. Time produced event → Event kicked off agent → Agent followed instructions.
## The Complete Event Flow
**Event Sources:**
- Time creates events (heartbeats, crons)
- Humans create events (messages)
- External systems create events (webhooks)
- Internal state creates events (hooks)
- Agents create events for other agents
**Processing:**
All events → Enter queue → Queue processed → Agents execute → State persists → Loop continues
**Memory:**
- Stored in local markdown files
- Agent reads on wake-up
- Remembers previous conversations
- Not learning - just reading files you could open in text editor
## Security Concerns
### The Analysis
Cisco's security team analyzed OpenClaw ecosystem:
- 31,000 available skills examined
- 26% contain at least one vulnerability
- Called it "a security nightmare"
### Why It's Risky
OpenClaw has deep system access:
- Run shell commands
- Read and write files
- Execute scripts
- Control browser
### Specific Risks
1. **Prompt injection** through emails or documents
2. **Malicious skills** in marketplace
3. **Credential exposure**
4. **Command misinterpretation** that deletes unintended files
### OpenClaw's Own Warning
Documentation states: "There's no perfectly secure setup"
### Mitigation Strategies
- Run on secondary machine
- Use isolated accounts
- Limit enabled skills
- Monitor logs actively
- Use Railway's one-click deployment (runs in isolated container)
## Key Architectural Takeaways
### The Four Components
1. **Time** that produces events
2. **Events** that trigger agents
3. **State** that persists across interactions
4. **Loop** that keeps processing
### Building Your Own
You don't need OpenClaw specifically. You need:
- Event scheduling mechanism
- Queue system
- LLM for processing
- State persistence layer
### The Pattern
This architecture will appear everywhere. Every AI agent framework that "feels alive" uses some version of:
- Heartbeats
- Cron jobs
- Webhooks
- Event loops
- Persistent state
### Understanding vs Hype
Understanding this architecture means you can:
- Evaluate agent tools intelligently
- Build your own implementations
- Avoid getting caught up in viral hype
- Recognize the pattern in new frameworks
## The Bottom Line
OpenClaw isn't magic. It's not sentient. It doesn't think or reason.
**It's inputs, queues, and a loop.**
The "alive" feeling comes from well-designed event-driven architecture that makes a reactive system appear proactive. Time becomes an input. External systems become inputs. Internal state becomes inputs. All processed through the same queue with persistent memory.
Elegant engineering, not artificial consciousness.
## Further Resources
- OpenClaw documentation
- Clairvo's original thread (inspiration for this breakdown)
- Cisco security research on OpenClaw ecosystem

View File

@@ -1,3 +1,16 @@
completed
config instead of env
edit its own files and config as well as add skills
start command for all instead of flags use config
customize opencode/claudecode setup like llms and providers during setup, agent creation/modify for claudecode and opencode
install script starts server and adds the aetheel command
llm usage stats
logo
Not complete
agent to agent and agent orchestration
better UI
human in the loop
security
browse plugins and skills from claude marketplace or opencode plugins

140
docs/aetheel-vs-nanoclaw.md Normal file
View File

@@ -0,0 +1,140 @@
# Aetheel vs Nanoclaw: Feature Comparison & OpenCode Assessment
Aetheel is a solid reimplementation of the core nanoclaw concept in Python, but there are meaningful gaps. Here's what maps, what's missing, and where the opencode integration could be improved.
---
## What Aetheel Has (Maps Well to Nanoclaw)
| Feature | Nanoclaw | Aetheel | Status |
|---|---|---|---|
| Multi-channel adapters | WhatsApp (baileys) | Slack + Telegram | ✅ Good — cleaner abstraction via `BaseAdapter` |
| Session isolation | Per-group sessions | Per-thread sessions via `SessionStore` | ✅ Good |
| Dual runtime support | Claude Code SDK only | OpenCode (CLI+SDK) + Claude Code CLI | ✅ Good — more flexible |
| Scheduled tasks | Cron + interval + once via MCP tool | Cron + one-shot via APScheduler | ✅ Good |
| Subagent spawning | SDK `Task`/`TeamCreate` tools | Background threads via `SubagentManager` | ✅ Basic |
| Memory system | CLAUDE.md files per group | SOUL.md + USER.md + MEMORY.md + hybrid search | ✅ Better — vector + BM25 search |
| Skills system | `.claude/skills/` with SKILL.md | `skills/<name>/SKILL.md` with trigger matching | ✅ Good |
| Action tags | MCP tools (send_message, schedule_task) | Regex-parsed `[ACTION:remind\|...]` tags | ✅ Different approach, works |
---
## What's Missing from Aetheel
### 1. Container Isolation
Nanoclaw's biggest architectural feature. Every agent runs in an isolated Apple Container (or Docker) with controlled volume mounts, secret injection via stdin, and per-group IPC namespaces. Aetheel runs everything in the same process. This means:
- No sandboxing of agent tool use (bash, file writes)
- No mount-based security boundaries between groups
- Secrets are in the process environment, not isolated
### 2. MCP Server Integration
Nanoclaw runs a custom MCP server (`ipc-mcp-stdio.ts`) inside the container that gives the agent tools like `send_message`, `schedule_task`, `register_group`. Aetheel uses regex-parsed action tags instead, which is fragile — the AI has to format tags perfectly, and there's no validation or structured tool calling.
### 3. Multi-Group Support
Nanoclaw has per-group folders, per-group memory (CLAUDE.md), per-group IPC, and a global memory layer. Aetheel has a single workspace with shared memory files. No group isolation.
### 4. Persistent Conversation Sessions on Disk
Nanoclaw stores sessions as JSONL files in `data/sessions/{group}/.claude/` and can resume at a specific assistant message UUID. Aetheel's `SessionStore` is in-memory only — sessions are lost on restart.
### 5. IPC Message Streaming
Nanoclaw's agent runner uses a `MessageStream` (AsyncIterable) to pipe follow-up messages into an active agent query. The host can send new messages to a running agent via IPC files. Aetheel's runtime is request-response only — one message in, one response out.
### 6. Transcript Archiving
Nanoclaw archives full conversation transcripts to markdown before context compaction via a `PreCompact` hook. Aetheel logs sessions to daily files but doesn't handle compaction.
### 7. Group Registration
Nanoclaw lets the main agent register new groups dynamically via an MCP tool. Aetheel has no equivalent.
### 8. Idle Timeout / Session Lifecycle
Nanoclaw has a 30-minute idle timeout that closes the container stdin, ending the session gracefully. Aetheel has session TTL cleanup but no active lifecycle management.
---
## OpenCode Integration Assessment
The opencode runtime implementation in `agent/opencode_runtime.py` is well-structured. Here's what's correct and what needs attention.
### What's Done Well
- Dual mode (CLI + SDK) with graceful fallback from SDK to CLI
- Binary auto-discovery across common install paths
- JSONL event parsing for `opencode run --format json` output
- Session ID extraction from event stream
- System prompt injection via XML tags (correct workaround since `opencode run` doesn't have `--system-prompt`)
- Config from environment variables
### Issues / Improvements Needed
#### 1. SDK Client API Mismatch
The code calls `self._sdk_client.session.chat(session_id, **chat_kwargs)` but the opencode Python SDK uses `client.session.prompt()` not `.chat()`. The correct call is:
```python
response = self._sdk_client.session.prompt(
path={"id": session_id},
body={"parts": parts, "model": model_config}
)
```
#### 2. SDK Client Initialization
The code uses `from opencode_ai import Opencode` but the actual SDK package is `@opencode-ai/sdk` (JS/TS) or `opencode-sdk-python` (Python). The Python SDK uses `createOpencodeClient` pattern. Verify the actual Python SDK import path — it may be `from opencode import Client` or similar depending on the package version.
#### 3. No `--continue` Flag Validation
The CLI mode passes `--continue` and `--session` for session continuity, but `opencode run` may not support `--continue` the same way as the TUI. The `opencode run` command is designed for single-shot execution. For session continuity in CLI mode, you'd need to use the SDK mode with `opencode serve`.
#### 4. Missing `--system` Flag
The code injects system prompts as XML in the message body. This works but is a workaround. The SDK mode's `client.session.prompt()` supports a `system` parameter in the body, which would be cleaner.
#### 5. No Structured Output Support
Opencode's SDK supports `format: { type: "json_schema", schema: {...} }` for structured responses. This could replace the fragile `[ACTION:...]` regex parsing with proper tool calls.
#### 6. No Plugin/Hook Integration
Opencode has a plugin system (`tool.execute.before`, `tool.execute.after`, `experimental.session.compacting`) that could replace the action tag parsing. You could create an opencode plugin that exposes `send_message` and `schedule_task` as custom tools, similar to nanoclaw's MCP approach.
#### 7. Session Persistence
`SessionStore` is in-memory. Opencode's server persists sessions natively, so in SDK mode you could rely on the server's session storage and just map `conversation_id → opencode_session_id` in a SQLite table.
---
## Architectural Gap Summary
The biggest architectural gap isn't about opencode specifically — it's that Aetheel runs the agent in-process without isolation, while nanoclaw's container model is what makes it safe to give the agent bash access and file write tools.
To close that gap, options include:
- **Containerize the opencode runtime** — run `opencode serve` inside a Docker container with controlled mounts
- **Use opencode's permission system** — configure all dangerous tools to `"ask"` or `"deny"` per agent
- **Add an MCP server** — replace action tag regex parsing with proper MCP tools for `send_message`, `schedule_task`, etc.
- **Persist sessions to SQLite** — survive restarts and enable resume-at-message functionality
---
## Nanoclaw Features → Opencode Equivalents
| Nanoclaw (Claude Code SDK) | Opencode Equivalent | Gap Level |
|---|---|---|
| `query()` async iterable | HTTP server + SDK `client.session.prompt()` | 🔴 Architecture change needed |
| `resume` + `resumeSessionAt` | `POST /session/:id/message` | 🟡 No resume-at-UUID equivalent |
| Streaming message types (system/init, assistant, result) | SSE events via `GET /event` | 🟡 Different event schema |
| `PreCompact` hook | `experimental.session.compacting` plugin | 🟢 Similar concept, different API |
| `PreToolUse` hook (bash sanitization) | `tool.execute.before` plugin | 🟢 Similar concept, different API |
| `bypassPermissions` | Per-tool permission config set to `"allow"` | 🟢 Direct mapping |
| `isSingleUserTurn: false` via AsyncIterable | `prompt_async` endpoint | 🟡 Needs verification |
| CLAUDE.md auto-loading via `settingSources` | AGENTS.md convention | 🟢 Rename files |
| Secrets via `env` param on `query()` | `shell.env` plugin hook | 🟡 Different isolation model |
| MCP servers in `query()` config | `opencode.json` mcp config or `POST /mcp` | 🟢 Direct mapping |

177
docs/commands.md Normal file
View File

@@ -0,0 +1,177 @@
# Aetheel Commands Reference
All commands work across every adapter (Slack, Discord, Telegram, WebChat).
## Chat Commands
Type these as regular messages in any channel or DM. No `/` prefix needed — just send the word as a message. The `/` prefix also works in DMs and on platforms that don't intercept it (Discord, Telegram, WebChat).
> In Slack channels, Slack intercepts `/` as a native slash command and blocks it. Use the prefix-free form instead (e.g. `engine claude` not `/engine claude`). In Slack DMs with the bot, both forms work.
### General
| Command | Description |
|---|---|
| `status` | Show bot status, engine, model, sessions |
| `help` | Show all available commands |
| `time` | Current server time |
| `sessions` | Active session count + cleanup stale |
| `reload` | Reload config.json and skills |
| `subagents` | List active background tasks |
### Runtime Switching (live, no restart needed)
| Command | Description |
|---|---|
| `engine` | Show current engine (opencode/claude) |
| `engine opencode` | Switch to OpenCode runtime |
| `engine claude` | Switch to Claude Code runtime |
| `model` | Show current model |
| `model <name>` | Switch model (e.g. `model anthropic/claude-sonnet-4-20250514`) |
| `provider` | Show current provider (OpenCode only) |
| `provider <name>` | Switch provider (e.g. `provider anthropic`, `provider openai`) |
| `usage` | Show LLM usage stats, costs, and rate limit history |
Engine, model, and provider changes take effect immediately and are persisted to `config.json` so they survive restarts.
#### Examples
```
engine claude
model claude-sonnet-4-20250514
engine opencode
model anthropic/claude-sonnet-4-20250514
provider anthropic
model openai/gpt-4o
provider openai
model google/gemini-2.5-pro
provider google
```
### Configuration
| Command | Description |
|---|---|
| `config` | Show config summary (engine, model, adapters) |
| `config show` | Dump full config.json |
| `config set <key> <value>` | Edit a config value (dotted notation) |
#### Config Set Examples
```
config set runtime.timeout_seconds 300
config set claude.max_turns 5
config set webchat.enabled true
config set discord.enabled true
config set webhooks.token my-secret-token
config set heartbeat.silent true
```
After `config set`, run `reload` to apply changes that don't auto-apply (adapter changes require a restart).
### Scheduler
| Command | Description |
|---|---|
| `cron list` | List all scheduled jobs |
| `cron remove <id>` | Remove a scheduled job by ID |
### AI Chat
Any message that isn't a command is sent to the AI. The AI can also trigger actions by including tags in its response:
| AI Action Tag | What It Does |
|---|---|
| `[ACTION:remind\|<minutes>\|<message>]` | Schedule a one-shot reminder |
| `[ACTION:cron\|<cron_expr>\|<prompt>]` | Schedule a recurring cron job |
| `[ACTION:spawn\|<task>]` | Spawn a background subagent |
---
## Terminal Commands
After installation, the `aetheel` command is available in your shell.
### Service Management
| Command | Description |
|---|---|
| `aetheel start` | Start Aetheel in the foreground |
| `aetheel stop` | Stop the background service |
| `aetheel restart` | Restart the background service |
| `aetheel status` | Show install and service status |
| `aetheel logs` | Tail the live log file |
### Setup & Maintenance
| Command | Description |
|---|---|
| `aetheel setup` | Re-run the interactive setup wizard |
| `aetheel update` | Pull latest code + update dependencies |
| `aetheel doctor` | Run diagnostics (config, tokens, runtimes) |
| `aetheel config` | Open config.json in your `$EDITOR` |
### Pass-Through
Any other arguments are passed directly to `main.py`:
```bash
aetheel --test # Echo mode, no AI
aetheel --claude # Override: use Claude engine
aetheel --model gpt-4o # Override: specific model
aetheel --log DEBUG # Override: debug logging
```
These flags are optional overrides. All settings come from `~/.aetheel/config.json` by default.
---
## Config File Reference
All features are controlled by `~/.aetheel/config.json`. No flags required.
```jsonc
{
"runtime": {
"engine": "opencode", // "opencode" or "claude"
"mode": "cli", // "cli" or "sdk"
"model": null, // e.g. "anthropic/claude-sonnet-4-20250514"
"provider": null, // e.g. "anthropic", "openai", "google"
"timeout_seconds": 120
},
"claude": {
"model": null, // e.g. "claude-sonnet-4-20250514"
"max_turns": 3,
"no_tools": false
},
"slack": { "enabled": true },
"telegram": { "enabled": false },
"discord": { "enabled": false, "listen_channels": [] },
"webchat": { "enabled": false, "port": 8080 },
"webhooks": { "enabled": false, "port": 8090, "token": "" },
"heartbeat": { "enabled": true },
"hooks": { "enabled": true }
}
```
Adapters auto-enable when their token is set in `.env`, even if `enabled` is `false` in config.
---
## Auto-Failover & Rate Limit Handling
When a rate limit or quota error is detected from the active engine:
1. Aetheel notifies you in the channel with the error details
2. Automatically attempts to failover to the other engine (claude → opencode or vice versa)
3. If failover succeeds, the response is delivered and the active engine is switched
4. If failover also fails, both errors are reported
Rate limit detection covers: HTTP 429, "rate limit", "quota exceeded", "too many requests", "usage limit", "credit balance", "overloaded", and similar patterns from both Claude Code and OpenCode.
Use `usage` to see cumulative stats including rate limit hits and failover count.
Note: Claude Code's `--output-format json` returns `cost_usd` per request. OpenCode does not currently expose per-request cost in its CLI output, so cost tracking is only available for the Claude engine.

326
docs/configuration.md Normal file
View File

@@ -0,0 +1,326 @@
# 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)](#secrets)
4. [CLI Overrides](#cli-overrides)
5. [Priority Order](#priority-order)
6. [Reference](#reference)
7. [Examples](#examples)
---
## Overview
Aetheel uses a two-file configuration approach:
| File | Location | Purpose |
|------|----------|---------|
| `config.json` | `~/.aetheel/config.json` | All non-secret settings (model, timeouts, channels, paths) |
| `.env` | Project root | Secrets only (tokens, passwords, API keys) |
On first run, Aetheel auto-creates `~/.aetheel/config.json` with sensible defaults. You only need to edit what you want to change.
---
## Config File
Located at `~/.aetheel/config.json`. Created automatically on first run.
### Full Default Config
```json
{
"log_level": "INFO",
"runtime": {
"mode": "cli",
"model": null,
"timeout_seconds": 120,
"server_url": "http://localhost:4096",
"format": "json"
},
"claude": {
"model": null,
"timeout_seconds": 120,
"max_turns": 3,
"no_tools": true
},
"discord": {
"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"` |
### 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 |
### Top-level
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `log_level` | string | `"INFO"` | `"DEBUG"`, `"INFO"`, `"WARNING"`, `"ERROR"` |
---
## Secrets
Secrets live in `.env` in the project root. These are never written to `config.json`.
Copy the template:
```bash
cp .env.example .env
```
### 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) |
### 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` |
| `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 > Environment variables (.env) > config.json > 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 | `~/.aetheel/config.json` | No |
| Secrets | `<project>/.env` | No (in .gitignore) |
| 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)
`.env`:
```env
SLACK_BOT_TOKEN=xoxb-your-token
SLACK_APP_TOKEN=xapp-your-token
```
No config.json 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
{
"discord": {
"listen_channels": ["1234567890123456"]
}
}
```
`.env`:
```env
DISCORD_BOT_TOKEN=your-discord-token
```
```bash
python main.py --discord
```
### Multi-Channel (Slack + Discord + Telegram)
`.env`:
```env
SLACK_BOT_TOKEN=xoxb-your-token
SLACK_APP_TOKEN=xapp-your-token
DISCORD_BOT_TOKEN=your-discord-token
TELEGRAM_BOT_TOKEN=your-telegram-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
```

307
docs/discord-setup.md Normal file
View File

@@ -0,0 +1,307 @@
# Discord Bot Setup Guide
> Complete guide to creating a Discord bot and connecting it to Aetheel.
---
## Table of Contents
1. [Overview](#overview)
2. [Create a Discord Application](#step-1-create-a-discord-application)
3. [Create the Bot](#step-2-create-the-bot)
4. [Enable Privileged Intents](#step-3-enable-privileged-intents)
5. [Invite the Bot to Your Server](#step-4-invite-the-bot-to-your-server)
6. [Configure Aetheel](#step-5-configure-aetheel)
7. [Run and Test](#step-6-run-and-test)
8. [Troubleshooting](#troubleshooting)
9. [Architecture Reference](#architecture-reference)
---
## Overview
Aetheel connects to Discord using the **Gateway API** via `discord.py`, which means:
-**No public URL needed** — works behind firewalls and NAT
-**No webhook setup** — Discord pushes events via WebSocket
-**Real-time** — instant message delivery
-**DMs + @mentions** — responds in both
### What You'll Need
| Item | Description |
|------|-------------|
| **Discord Account** | With access to a server where you have Manage Server permissions |
| **Bot Token** | From the Discord Developer Portal |
| **Python 3.14+** | Runtime for the Aetheel service |
---
## Step 1: Create a Discord Application
1. Go to [https://discord.com/developers/applications](https://discord.com/developers/applications)
2. Click **"New Application"**
3. Enter a name: `Aetheel` (or any name you prefer)
4. Accept the Terms of Service
5. Click **"Create"**
You'll be taken to your application's **General Information** page.
---
## Step 2: Create the Bot
1. Navigate to **Bot** in the left sidebar
2. The bot user is created automatically with your application
3. Click **"Reset Token"** to generate a new bot token
4. **⚠️ Copy the token now!** You won't be able to see it again.
- Save it somewhere safe — this is your `DISCORD_BOT_TOKEN`
### Optional: Bot Settings
On the same Bot page, you can configure:
| Setting | Recommended | Why |
|---------|-------------|-----|
| **Public Bot** | OFF | Only you can invite it to servers |
| **Requires OAuth2 Code Grant** | OFF | Simpler invite flow |
---
## Step 3: Enable Privileged Intents
Still on the **Bot** page, scroll down to **Privileged Gateway Intents**.
Enable the following:
| Intent | Required | Purpose |
|--------|----------|---------|
| **Message Content Intent** | ✅ Yes | Read the text content of messages |
| **Server Members Intent** | Optional | Resolve member display names |
| **Presence Intent** | No | Not needed |
> **Important:** The Message Content Intent is required. Without it, the bot will receive empty message content for guild messages.
Click **"Save Changes"**.
---
## Step 4: Invite the Bot to Your Server
1. Navigate to **OAuth2****URL Generator** in the left sidebar
2. Under **Scopes**, check: `bot`
3. Under **Bot Permissions**, check:
| Permission | Purpose |
|------------|---------|
| **Send Messages** | Reply to users |
| **Read Message History** | Context for conversations |
| **View Channels** | See channels the bot is in |
4. Copy the **Generated URL** at the bottom
5. Open the URL in your browser
6. Select the server you want to add the bot to
7. Click **"Authorize"**
The bot should now appear in your server's member list (offline until you start Aetheel).
---
## Step 5: Configure Aetheel
### Option A: Using `.env` file (recommended)
Edit your `.env` file and add:
```env
DISCORD_BOT_TOKEN=your-discord-bot-token-here
```
### Option B: Export environment variable
```bash
export DISCORD_BOT_TOKEN="your-discord-bot-token-here"
```
---
## Step 6: Run and Test
### Install dependencies
```bash
uv sync
# or: pip install -r requirements.txt
```
This will install `discord.py` along with the other dependencies.
### Run the bot
```bash
# Discord only
uv run python main.py --discord
# Discord + Slack together
uv run python main.py --discord
# Discord with Claude Code runtime
uv run python main.py --discord --claude
# Test mode (echo handler, no AI)
uv run python main.py --discord --test
# Debug logging
uv run python main.py --discord --log DEBUG
```
### Verify it's working
1. Check the console — you should see:
```
Aetheel Discord Adapter
Bot: @Aetheel (123456789)
Guilds: My Server
```
2. In Discord, go to a channel where the bot is present
3. Type `@Aetheel help` — you should see the help response
4. Type `@Aetheel status` — you should see the bot's status
5. Send a DM to the bot — it should respond directly
### How the bot responds
| Context | Trigger | Session Isolation |
|---------|---------|-------------------|
| **Guild channel** | @mention only | Per-channel |
| **DM** | Any message | Per-DM channel |
---
## Troubleshooting
### ❌ "Discord bot token is required"
**Problem:** `DISCORD_BOT_TOKEN` is not set or empty.
**Fix:**
1. Check your `.env` file contains the token
2. Make sure there are no extra spaces or quotes
3. Verify the token is from the Bot page, not the application client secret
### ❌ Bot comes online but doesn't respond to messages
**Problem:** Message Content Intent is not enabled.
**Fix:**
1. Go to [Developer Portal](https://discord.com/developers/applications) → your app → **Bot**
2. Scroll to **Privileged Gateway Intents**
3. Enable **Message Content Intent**
4. Save and restart Aetheel
### ❌ Bot doesn't respond in guild channels
**Problem:** You're not @mentioning the bot.
**Fix:**
- In guild channels, the bot only responds to @mentions: `@Aetheel hello`
- In DMs, the bot responds to any message — no @mention needed
### ❌ "Improper token has been passed"
**Problem:** The token is malformed or from the wrong place.
**Fix:**
1. Go to **Bot** page in the Developer Portal
2. Click **"Reset Token"** to generate a fresh one
3. Copy the full token (it's a long string)
4. Make sure you're using the Bot token, not the Client ID or Client Secret
### ❌ "discord.errors.PrivilegedIntentsRequired"
**Problem:** You're requesting intents that aren't enabled in the portal.
**Fix:**
1. Go to **Bot** → **Privileged Gateway Intents**
2. Enable **Message Content Intent**
3. Save changes and restart
### ❌ Bot is offline in the member list
**Problem:** Aetheel isn't running, or the token is wrong.
**Fix:**
1. Start Aetheel with `--discord` flag
2. Check the console for connection errors
3. Verify the token in `.env` matches the one in the Developer Portal
### ❌ "Missing Permissions" when sending messages
**Problem:** The bot doesn't have Send Messages permission in that channel.
**Fix:**
1. Check the channel's permission overrides for the bot role
2. Re-invite the bot with the correct permissions (see Step 4)
3. Or manually grant the bot's role Send Messages in Server Settings → Roles
---
## Architecture Reference
### How It Works
```
┌──────────────────────┐
│ Your Discord │
│ Server │
│ │
│ #general │
│ #random │
│ DMs │
└──────┬───────────────┘
│ WebSocket (Gateway API)
┌──────▼───────────────┐
│ Aetheel Discord │
│ Adapter │
│ │
│ • Token resolution │
│ • @mention filter │
│ • DM handling │
│ • Message chunking │
│ (2000 char limit) │
│ • Async dispatch │
│ via to_thread() │
└──────┬───────────────┘
│ Callback
┌──────▼───────────────┐
│ Message Handler │
│ │
│ • Echo (test) │
│ • AI (OpenCode / │
│ Claude Code) │
│ • Memory + Skills │
│ • Action tags │
└──────────────────────┘
```
### Key Files
| File | Purpose |
|------|---------|
| `adapters/discord_adapter.py` | Core Discord adapter (Gateway, send/receive) |
| `adapters/base.py` | Abstract base class all adapters implement |
| `main.py` | Entry point — `--discord` flag enables this adapter |
| `.env` | Your Discord token (not committed to git) |
### Comparison with Other Adapters
| Feature | Slack | Telegram | Discord |
|---------|-------|----------|---------|
| **Library** | `slack_bolt` | `python-telegram-bot` | `discord.py` |
| **Connection** | Socket Mode (WebSocket) | Long polling | Gateway (WebSocket) |
| **Auth** | Bot Token + App Token | Single Bot Token | Single Bot Token |
| **Trigger (channels)** | @mention | @mention | @mention |
| **Trigger (DMs)** | Any message | Any message | Any message |
| **Text Limit** | 4000 chars | 4096 chars | 2000 chars |
| **Threading** | Native threads | Reply-to-message | N/A (per-channel) |
| **Session Isolation** | Per-thread | Per-chat | Per-channel |

916
docs/features-guide.md Normal file
View File

@@ -0,0 +1,916 @@
# Aetheel Features Guide
Complete reference for all Aetheel features, how to use them, and how to test them.
---
## Table of Contents
1. [Dual AI Runtimes](#1-dual-ai-runtimes)
2. [Tool Enablement & MCP Servers](#2-tool-enablement--mcp-servers)
3. [Multi-Channel Adapters](#3-multi-channel-adapters)
4. [WebChat Browser Interface](#4-webchat-browser-interface)
5. [Persistent Memory System](#5-persistent-memory-system)
6. [Skills System](#6-skills-system)
7. [Scheduler & Action Tags](#7-scheduler--action-tags)
8. [Heartbeat / Proactive System](#8-heartbeat--proactive-system)
9. [Subagents & Agent-to-Agent Communication](#9-subagents--agent-to-agent-communication)
10. [Self-Modification](#10-self-modification)
11. [Lifecycle Hooks](#11-lifecycle-hooks)
12. [Webhooks (External Event Receiver)](#12-webhooks-external-event-receiver)
13. [CLI Interface](#13-cli-interface)
14. [Configuration](#14-configuration)
15. [Running Tests](#15-running-tests)
13. [Running Tests](#13-running-tests)
---
## 1. Dual AI Runtimes
Aetheel supports two AI backends that share the same `AgentResponse` interface. Switch between them with a single flag.
### OpenCode (default)
Uses the [OpenCode](https://opencode.ai) CLI. Supports CLI mode (subprocess per request) and SDK mode (persistent server connection).
```bash
# CLI mode (default)
python main.py
# SDK mode (requires `opencode serve` running)
python main.py --sdk
# Custom model
python main.py --model anthropic/claude-sonnet-4-20250514
```
### Claude Code
Uses the [Claude Code](https://docs.anthropic.com/en/docs/claude-code) CLI with native `--system-prompt` support.
```bash
python main.py --claude
python main.py --claude --model claude-sonnet-4-20250514
```
### Key differences
| Feature | OpenCode | Claude Code |
|---------|----------|-------------|
| System prompt | XML-injected into message | Native `--system-prompt` flag |
| Session continuity | `--continue --session <id>` | `--continue --session-id <id>` |
| Tool access | Enabled by default | Controlled via `--allowedTools` |
| Output format | JSONL events | JSON result object |
### How to test
```bash
# Echo mode (no AI runtime needed)
python main.py --test
# Verify runtime detection
python cli.py doctor
```
---
## 2. Tool Enablement & MCP Servers
### Tool access
Claude Code runtime now has tools enabled by default. The `allowed_tools` list controls which tools the agent can use:
```
Bash, Read, Write, Edit, Glob, Grep, WebSearch, WebFetch,
Task, TaskOutput, TaskStop, Skill, TeamCreate, TeamDelete, SendMessage
```
To disable tools (pure conversation mode), set in `~/.aetheel/config.json`:
```json
{
"claude": {
"no_tools": true
}
}
```
To customize the tool list:
```json
{
"claude": {
"no_tools": false,
"allowed_tools": ["Bash", "Read", "Write", "WebSearch"]
}
}
```
### MCP server configuration
Add external tool servers via config. Aetheel writes the appropriate config file (`.mcp.json` for Claude, `opencode.json` for OpenCode) to the workspace before launching the runtime.
```json
{
"mcp": {
"servers": {
"my-tool": {
"command": "uvx",
"args": ["my-mcp-server@latest"],
"env": { "API_KEY": "..." }
}
}
}
}
```
### How to test
```bash
# Run MCP config writer tests
cd Aetheel
python -m pytest tests/test_mcp_config.py -v
```
---
## 3. Multi-Channel Adapters
Aetheel connects to messaging platforms via adapters. Each adapter converts platform events into a channel-agnostic `IncomingMessage` and routes responses back.
### Slack (default)
Requires `SLACK_BOT_TOKEN` and `SLACK_APP_TOKEN` in `.env`. Starts automatically when tokens are present.
```bash
python main.py
```
### Telegram
```bash
# Set TELEGRAM_BOT_TOKEN in .env first
python main.py --telegram
```
### Discord
```bash
# Set DISCORD_BOT_TOKEN in .env first
python main.py --discord
```
### Multiple adapters
```bash
python main.py --telegram --discord --webchat
```
When multiple adapters run, all but the last start in background threads. The last one runs blocking.
### How to test
```bash
python -m pytest tests/test_base_adapter.py -v
```
---
## 4. WebChat Browser Interface
A browser-based chat UI served via aiohttp with WebSocket support. No Slack/Discord/Telegram needed.
### Starting WebChat
```bash
# Via CLI flag
python main.py --webchat
# Or enable in config permanently
# Set "webchat": {"enabled": true} in ~/.aetheel/config.json
```
Then open `http://127.0.0.1:8080` in your browser.
### Configuration
```json
{
"webchat": {
"enabled": false,
"port": 8080,
"host": "127.0.0.1"
}
}
```
### Features
- Dark-themed chat UI at `GET /`
- WebSocket endpoint at `/ws` for real-time messaging
- Per-connection session isolation (each browser tab gets its own conversation)
- Auto-reconnect on disconnect (3 second delay)
- Basic markdown rendering (bold, code blocks, bullet points)
- Connection status indicator (green/red/gray)
### Architecture
```
Browser <--> WebSocket (/ws) <--> WebChatAdapter <--> ai_handler <--> Runtime
|
GET / serves static/chat.html
```
### How to test
```bash
# Start with echo handler + webchat
python main.py --test --webchat
# Open http://127.0.0.1:8080 and send a message
# You should see the echo response with message metadata
```
---
## 5. Persistent Memory System
Aetheel maintains persistent memory across sessions using local embeddings and SQLite.
### Identity files
Located in `~/.aetheel/workspace/`:
| File | Purpose |
|------|---------|
| `SOUL.md` | Personality, values, communication style |
| `USER.md` | User preferences, context, background |
| `MEMORY.md` | Long-term notes, facts to remember |
Edit these files directly. Changes are picked up automatically via file watching.
### How it works
1. All `.md` files in the workspace are chunked and embedded using `fastembed` (BAAI/bge-small-en-v1.5, 384-dim, runs locally)
2. Chunks are stored in SQLite with FTS5 full-text search
3. On each message, Aetheel searches memory using hybrid scoring (0.7 vector + 0.3 BM25)
4. Relevant results are injected into the system prompt as context
5. Conversations are logged to `daily/YYYY-MM-DD.md`
### CLI memory commands
```bash
# Search memory
python cli.py memory search "python projects"
# Force re-index
python cli.py memory sync
```
---
## 6. Skills System
Skills are markdown files that teach the agent how to handle specific types of requests. They're loaded at startup and injected into the system prompt when trigger words match.
### Creating a skill
Create `~/.aetheel/workspace/skills/<name>/SKILL.md`:
```markdown
---
name: weather
description: Check weather for any city
triggers: [weather, forecast, temperature, rain]
---
# Weather Skill
When the user asks about weather, use web search to find current conditions...
```
### How it works
1. Skills are discovered from `~/.aetheel/workspace/skills/*/SKILL.md`
2. YAML frontmatter defines `name`, `description`, and `triggers`
3. When a message contains a trigger word, the skill's body is injected into the system prompt
4. A summary of all available skills is always included
### Hot reload
Send `reload` or `/reload` in chat to reload skills without restarting.
### How to test
```bash
python -m pytest tests/test_skills.py -v
```
---
## 7. Scheduler & Action Tags
APScheduler-based system with SQLite persistence for one-shot and recurring jobs.
### Action tags
The AI can include action tags in its responses. The system strips them from the visible reply and executes the action.
| Tag | Effect |
|-----|--------|
| `[ACTION:remind\|5\|Drink water!]` | Sends "Drink water!" to the channel in 5 minutes |
| `[ACTION:cron\|0 9 * * *\|Good morning!]` | Sends "Good morning!" every day at 9 AM |
| `[ACTION:spawn\|Research Python 3.14]` | Spawns a background subagent for the task |
### Managing jobs
In chat:
- `/cron list` — list all scheduled jobs
- `/cron remove <id>` — remove a job
Via CLI:
```bash
python cli.py cron list
python cli.py cron remove <job_id>
```
### How to test
```bash
# Scheduler tests require apscheduler installed
python -m pytest tests/test_scheduler.py -v
```
---
## 8. Heartbeat / Proactive System
The heartbeat system runs periodic tasks automatically by parsing a user-editable `HEARTBEAT.md` file.
### How it works
1. At startup, `HeartbeatRunner` reads `~/.aetheel/workspace/HEARTBEAT.md`
2. Each `## ` heading defines a schedule (natural language)
3. Bullet points under each heading are task prompts
4. Tasks are registered as cron jobs with the Scheduler
5. When a task fires, it creates a synthetic message routed through `ai_handler`
### HEARTBEAT.md format
```markdown
# Heartbeat Tasks
## Every 30 minutes
- Check if any scheduled reminders need attention
- Review recent session logs for anything worth remembering
## Every morning (9:00 AM)
- Summarize yesterday's conversations
- Check for any pending follow-ups in MEMORY.md
## Every evening (6:00 PM)
- Update MEMORY.md with today's key learnings
```
### Supported schedule patterns
| Pattern | Cron equivalent |
|---------|----------------|
| `Every 30 minutes` | `*/30 * * * *` |
| `Every hour` | `0 * * * *` |
| `Every 2 hours` | `0 */2 * * *` |
| `Every morning (9:00 AM)` | `0 9 * * *` |
| `Every evening (6:00 PM)` | `0 18 * * *` |
### Configuration
```json
{
"heartbeat": {
"enabled": true,
"default_channel": "slack",
"default_channel_id": "C123456",
"silent": false
}
}
```
Set `enabled` to `false` to disable heartbeat entirely. If `HEARTBEAT.md` doesn't exist, a default one is created automatically.
### How to test
```bash
# Verify heartbeat parsing works
python -c "
from heartbeat.heartbeat import HeartbeatRunner
print(HeartbeatRunner._parse_schedule_header('Every 30 minutes')) # */30 * * * *
print(HeartbeatRunner._parse_schedule_header('Every morning (9:00 AM)')) # 0 9 * * *
print(HeartbeatRunner._parse_schedule_header('Every evening (6:00 PM)')) # 0 18 * * *
"
```
---
## 9. Subagents & Agent-to-Agent Communication
### Subagent spawning
The AI can spawn background tasks that run in separate threads with their own runtime instances.
```
[ACTION:spawn|Research the latest Python 3.14 features and summarize them]
```
The subagent runs asynchronously and sends results back to the originating channel when done.
### Managing subagents
In chat:
- `/subagents` — list active subagent tasks with IDs and status
### SubagentBus
A thread-safe pub/sub message bus for inter-subagent communication:
```python
from agent.subagent import SubagentManager
mgr = SubagentManager(runtime_factory=..., send_fn=...)
# Subscribe to a channel
mgr.bus.subscribe("results", lambda msg, sender: print(f"{sender}: {msg}"))
# Publish from a subagent
mgr.bus.publish("results", "Task complete!", "subagent-abc123")
```
### Claude Code team tools
When using Claude Code runtime, the agent has access to team coordination tools:
- `TeamCreate`, `TeamDelete` — create/delete agent teams
- `SendMessage` — send messages between agents in a team
- `Task`, `TaskOutput`, `TaskStop` — spawn and manage subagent tasks
### How to test
```bash
python -m pytest tests/test_subagent_bus.py -v
```
---
## 10. Self-Modification
The AI agent knows it can modify its own files. The system prompt tells it about:
- `~/.aetheel/config.json` — edit configuration
- `~/.aetheel/workspace/skills/<name>/SKILL.md` — create new skills
- `~/.aetheel/workspace/SOUL.md` — update personality
- `~/.aetheel/workspace/USER.md` — update user profile
- `~/.aetheel/workspace/MEMORY.md` — update long-term memory
- `~/.aetheel/workspace/HEARTBEAT.md` — modify periodic tasks
### Hot reload
After the agent edits config or skills, send `reload` or `/reload` in chat to apply changes without restarting:
```
You: /reload
Aetheel: 🔄 Config and skills reloaded.
```
### How to test
```bash
# Verify the system prompt contains self-modification instructions
python -c "
from agent.opencode_runtime import build_aetheel_system_prompt
prompt = build_aetheel_system_prompt()
assert 'Self-Modification' in prompt
assert 'config.json' in prompt
assert '/reload' in prompt
print('Self-modification prompt sections present ✅')
"
```
---
## 11. Lifecycle Hooks
Event-driven lifecycle hooks inspired by OpenClaw's internal hook system. Hooks fire on gateway/agent lifecycle events and let you run custom Python code at those moments.
### Supported events
| Event | When it fires |
|---|---|
| `gateway:startup` | Gateway process starts (after adapters connect) |
| `gateway:shutdown` | Gateway process is shutting down |
| `command:reload` | User sends `/reload` |
| `command:new` | User starts a fresh session |
| `agent:bootstrap` | Before workspace files are injected into context |
| `agent:response` | After the agent produces a response |
### Creating a hook
Create a directory in `~/.aetheel/workspace/hooks/<name>/` with two files:
`HOOK.md` — metadata in YAML frontmatter:
```markdown
---
name: session-logger
description: Log session starts to a file
events: [gateway:startup, command:reload]
enabled: true
---
# Session Logger Hook
Logs gateway lifecycle events for debugging.
```
`handler.py` — Python handler with a `handle(event)` function:
```python
def handle(event):
"""Called when a matching event fires."""
print(f"Hook fired: {event.event_key}")
# Push messages back to the user
event.messages.append("Hook executed!")
```
### Hook discovery locations
Hooks are discovered from two directories (in order):
1. `~/.aetheel/workspace/hooks/<name>/HOOK.md` — workspace hooks (per-project)
2. `~/.aetheel/hooks/<name>/HOOK.md` — managed hooks (shared across workspaces)
### Programmatic hooks
You can also register hooks in Python code:
```python
from hooks import HookManager, HookEvent
mgr = HookManager(workspace_dir="~/.aetheel/workspace")
mgr.register("gateway:startup", lambda e: print("Gateway started!"))
mgr.trigger(HookEvent(type="gateway", action="startup"))
```
### Configuration
```json
{
"hooks": {
"enabled": true
}
}
```
Set `enabled` to `false` to disable all hook discovery and execution.
### How to test
```bash
python -m pytest tests/test_hooks.py -v
```
---
## 12. Webhooks (External Event Receiver)
HTTP endpoints that accept POST requests from external systems (GitHub, Jira, email services, custom scripts) and route them through the AI handler as synthetic messages. Inspired by OpenClaw's `/hooks/*` gateway endpoints.
### Endpoints
| Endpoint | Method | Auth | Description |
|---|---|---|---|
| `/hooks/health` | GET | No | Health check |
| `/hooks/wake` | POST | Yes | Wake the agent with a text prompt |
| `/hooks/agent` | POST | Yes | Send a message to a specific agent session |
### Enabling webhooks
```json
{
"webhooks": {
"enabled": true,
"port": 8090,
"host": "127.0.0.1",
"token": "your-secret-token"
}
}
```
The webhook server starts automatically when `webhooks.enabled` is `true`.
### POST /hooks/wake
Wake the agent with a text prompt. The agent processes it and returns the response.
```bash
curl -X POST http://127.0.0.1:8090/hooks/wake \
-H "Authorization: Bearer your-secret-token" \
-H "Content-Type: application/json" \
-d '{"text": "Check my email for urgent items"}'
```
Response:
```json
{
"status": "ok",
"response": "I checked your inbox and found 2 urgent items..."
}
```
Optionally deliver the response to a messaging channel:
```bash
curl -X POST http://127.0.0.1:8090/hooks/wake \
-H "Authorization: Bearer your-secret-token" \
-H "Content-Type: application/json" \
-d '{
"text": "Summarize today'\''s calendar",
"channel": "slack",
"channel_id": "C123456"
}'
```
### POST /hooks/agent
Send a message to a specific agent session with channel delivery:
```bash
curl -X POST http://127.0.0.1:8090/hooks/agent \
-H "Authorization: Bearer your-secret-token" \
-H "Content-Type: application/json" \
-d '{
"message": "New GitHub issue: Fix login bug #42",
"channel": "slack",
"channel_id": "C123456",
"sender": "GitHub"
}'
```
### Use cases
- GitHub webhook → POST to `/hooks/agent` → agent triages the issue
- Email service → POST to `/hooks/wake` → agent summarizes new emails
- Cron script → POST to `/hooks/wake` → agent runs a daily report
- IoT sensor → POST to `/hooks/agent` → agent alerts on anomalies
### Authentication
All POST endpoints require a bearer token. Pass it via:
- `Authorization: Bearer <token>` header
- `?token=<token>` query parameter (fallback)
If no token is configured (`"token": ""`), endpoints are open (dev mode only).
### How to test
```bash
python -m pytest tests/test_webhooks.py -v
```
---
## 13. CLI Interface
Aetheel includes a Click-based CLI with subcommands for all major operations.
### Installation
After installing with `pip install -e .` or `uv sync`, the `aetheel` command is available:
```bash
aetheel # Same as `aetheel start`
aetheel start # Start with default adapters
aetheel --help # Show all commands
```
Or run directly:
```bash
python cli.py start --discord --webchat
python cli.py chat "What is Python?"
python cli.py doctor
```
### Commands
| Command | Description |
|---------|-------------|
| `aetheel` / `aetheel start` | Start with configured adapters |
| `aetheel start --discord` | Start with Discord adapter |
| `aetheel start --telegram` | Start with Telegram adapter |
| `aetheel start --webchat` | Start with WebChat adapter |
| `aetheel start --claude` | Use Claude Code runtime |
| `aetheel start --test` | Echo handler (no AI) |
| `aetheel chat "message"` | One-shot AI query (prints to stdout) |
| `aetheel status` | Show runtime status |
| `aetheel doctor` | Run diagnostics (check runtimes, tokens, workspace) |
| `aetheel config show` | Print current config.json |
| `aetheel config edit` | Open config in $EDITOR |
| `aetheel config init` | Reset config to defaults |
| `aetheel cron list` | List scheduled jobs |
| `aetheel cron remove <id>` | Remove a scheduled job |
| `aetheel memory search "query"` | Search memory |
| `aetheel memory sync` | Force memory re-index |
### How to test
```bash
# Verify CLI structure
python cli.py --help
python cli.py start --help
python cli.py config --help
python cli.py cron --help
python cli.py memory --help
# Run diagnostics
python cli.py doctor
```
---
## 14. Configuration
All configuration lives in `~/.aetheel/config.json`. Secrets (tokens) stay in `.env`.
### Config hierarchy (highest priority wins)
1. CLI arguments (`--model`, `--claude`, etc.)
2. Environment variables
3. `~/.aetheel/config.json`
4. Dataclass defaults
### Full config.json example
```json
{
"log_level": "INFO",
"runtime": {
"mode": "cli",
"model": null,
"timeout_seconds": 120,
"server_url": "http://localhost:4096",
"format": "json"
},
"claude": {
"model": null,
"timeout_seconds": 120,
"max_turns": 3,
"no_tools": false,
"allowed_tools": [
"Bash", "Read", "Write", "Edit", "Glob", "Grep",
"WebSearch", "WebFetch",
"Task", "TaskOutput", "TaskStop", "Skill",
"TeamCreate", "TeamDelete", "SendMessage"
]
},
"discord": {
"listen_channels": []
},
"memory": {
"workspace": "~/.aetheel/workspace",
"db_path": "~/.aetheel/memory.db"
},
"scheduler": {
"db_path": "~/.aetheel/scheduler.db"
},
"heartbeat": {
"enabled": true,
"default_channel": "slack",
"default_channel_id": "",
"silent": false
},
"webchat": {
"enabled": false,
"port": 8080,
"host": "127.0.0.1"
},
"mcp": {
"servers": {}
},
"hooks": {
"enabled": true
},
"webhooks": {
"enabled": false,
"port": 8090,
"host": "127.0.0.1",
"token": ""
}
}
```
### Environment variables (.env)
```bash
# Slack (required for Slack adapter)
SLACK_BOT_TOKEN=xoxb-...
SLACK_APP_TOKEN=xapp-...
# Telegram (required for --telegram)
TELEGRAM_BOT_TOKEN=...
# Discord (required for --discord)
DISCORD_BOT_TOKEN=...
# Runtime overrides
OPENCODE_MODEL=anthropic/claude-sonnet-4-20250514
CLAUDE_MODEL=claude-sonnet-4-20250514
LOG_LEVEL=DEBUG
```
---
## 15. Running Tests
### Prerequisites
```bash
cd Aetheel
pip install -e ".[test]"
# or
uv sync --extra test
```
### Run all tests
```bash
python -m pytest tests/ -v --ignore=tests/test_scheduler.py
```
> Note: `test_scheduler.py` requires `apscheduler` installed. If you have it, run the full suite:
> ```bash
> python -m pytest tests/ -v
> ```
### Run specific test files
```bash
# Base adapter tests
python -m pytest tests/test_base_adapter.py -v
# Skills system tests
python -m pytest tests/test_skills.py -v
# MCP config writer tests
python -m pytest tests/test_mcp_config.py -v
# SubagentBus pub/sub tests
python -m pytest tests/test_subagent_bus.py -v
# Scheduler tests (requires apscheduler)
python -m pytest tests/test_scheduler.py -v
# Hook system tests
python -m pytest tests/test_hooks.py -v
# Webhook receiver tests
python -m pytest tests/test_webhooks.py -v
```
### Test summary
| Test file | What it covers | Count |
|-----------|---------------|-------|
| `test_base_adapter.py` | BaseAdapter dispatch, handler registration, error handling | 9 |
| `test_skills.py` | Skill loading, trigger matching, frontmatter parsing, context building | 21 |
| `test_mcp_config.py` | MCP config writer (Claude/OpenCode formats, round-trip, edge cases) | 8 |
| `test_subagent_bus.py` | SubagentBus subscribe/publish, isolation, error resilience, thread safety | 10 + 2 |
| `test_hooks.py` | Hook discovery, trigger, programmatic hooks, error resilience, messages | 14 |
| `test_webhooks.py` | Webhook endpoints (wake, agent, health), auth, channel delivery | 10 |
| `test_scheduler.py` | Scheduler one-shot/cron jobs, persistence, removal | varies |
### Quick smoke tests
```bash
# Verify config loads correctly
python -c "from config import load_config; c = load_config(); print(f'Tools enabled: {not c.claude.no_tools}, Tools: {len(c.claude.allowed_tools)}')"
# Verify system prompt has new sections
python -c "
from agent.opencode_runtime import build_aetheel_system_prompt
p = build_aetheel_system_prompt()
for section in ['Your Tools', 'Self-Modification', 'Subagents & Teams']:
assert section in p, f'Missing: {section}'
print(f' ✅ {section}')
"
# Verify heartbeat parser
python -c "
from heartbeat.heartbeat import HeartbeatRunner
tests = [('Every 30 minutes', '*/30 * * * *'), ('Every morning (9:00 AM)', '0 9 * * *'), ('Every evening (6:00 PM)', '0 18 * * *')]
for header, expected in tests:
result = HeartbeatRunner._parse_schedule_header(header)
assert result == expected, f'{header}: got {result}, expected {expected}'
print(f' ✅ {header} -> {result}')
"
# Verify CLI commands exist
python cli.py --help
```

214
docs/future-changes.md Normal file
View File

@@ -0,0 +1,214 @@
# Future Changes
Planned features and architectural improvements for Aetheel.
---
## Agent Teams & Delegation
The current subagent system spawns generic background workers that share the main agent's identity. The goal is to evolve this into a proper agent team architecture where specialized agents with their own roles, tools, and identity files can be created and coordinated.
### Vision
A main agent (e.g. "CTO") talks to the user and delegates tasks to specialist agents (e.g. "Programmer", "Designer") that each have their own personality, tool access, and domain expertise. Results flow back through the main agent or directly to the channel.
```
User ──► Main Agent (CTO)
├──► Programmer Agent
│ • Own SOUL.md (code-focused personality)
│ • Tools: Bash, Read, Write, Edit, Grep
│ • Model: claude-sonnet for speed
├──► Designer Agent
│ • Own SOUL.md (design-focused personality)
│ • Tools: WebSearch, WebFetch, Read
│ • Model: claude-opus for creativity
└──► Researcher Agent
• Own SOUL.md (thorough, citation-heavy)
• Tools: WebSearch, WebFetch
• Model: gemini-2.5-pro for large context
```
### What Exists Today
- `SubagentManager` spawns background tasks via `[ACTION:spawn|<task>]`
- `SubagentBus` provides pub/sub messaging between subagents
- Claude Code's `Task`, `TaskOutput`, `TeamCreate`, `SendMessage` tools are already in the allowed tools list
- All subagents currently share the same identity files (SOUL.md, USER.md, MEMORY.md)
### How NanoClaw Does It
NanoClaw (the inspiration project) takes a fundamentally different approach — container-based isolation with per-group identity:
**Architecture:**
- Each chat group gets its own folder (`groups/{name}/`) with its own `CLAUDE.md` (identity/memory)
- A `groups/global/CLAUDE.md` provides shared context readable by all groups
- Each agent runs inside an Apple Container (lightweight Linux VM) with only its own folder mounted
- The main channel has elevated privileges (can manage groups, write global memory, schedule tasks for any group)
- Non-main groups can only access their own folder + read-only global memory
**Agent Teams (Swarm):**
- Claude Code has native `TeamCreate`, `SendMessage` tools for multi-agent orchestration
- NanoClaw enables this via `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` env var in the container
- The lead agent creates teammates using Claude's built-in team tools
- Each teammate gets instructions to use `send_message` with a `sender` parameter
- On Telegram, each sender gets a dedicated bot identity (pool of pre-created bots renamed dynamically)
- The lead agent coordinates but doesn't relay every teammate message — users see them directly
**Key design decisions:**
- Identity is per-group, not per-agent — a "programmer" agent in one group is different from a "programmer" in another
- The AI itself decides when to create teams and how to structure them — it's not pre-configured
- Container isolation means agents can't read each other's files or sessions
- IPC is file-based (JSON files in a watched directory), not in-memory pub/sub
- Secrets are passed via stdin, never mounted as files
### What Needs to Be Built
#### 1. Agent Definitions
A new `agents/` directory in the workspace where each agent gets its own folder:
```
~/.aetheel/workspace/agents/
├── programmer/
│ ├── AGENT.md # Role, description, triggers
│ ├── SOUL.md # Agent-specific personality
│ └── SKILL.md # Optional: domain-specific skills
├── designer/
│ ├── AGENT.md
│ ├── SOUL.md
│ └── SKILL.md
└── researcher/
├── AGENT.md
└── SOUL.md
```
`AGENT.md` frontmatter:
```yaml
---
name: programmer
description: Senior software engineer specializing in Python and system design
triggers: [code, implement, build, fix, debug, refactor, PR]
model: anthropic/claude-sonnet-4-20250514
engine: claude
tools: [Bash, Read, Write, Edit, Glob, Grep]
max_turns: 5
---
```
#### 2. Agent-Aware Routing
The main agent decides which specialist to delegate to based on:
- Explicit user request ("ask the programmer to...")
- Trigger word matching from AGENT.md
- AI-driven routing (the main agent's system prompt tells it about available agents)
New action tag: `[ACTION:delegate|<agent_name>|<task>]`
#### 3. Per-Agent Identity
Each agent gets its own system prompt built from:
1. Its own `SOUL.md` (personality/role)
2. Shared `USER.md` (user context is universal)
3. Shared `MEMORY.md` (long-term memory is universal)
4. Its own skills from `SKILL.md`
5. Its tool restrictions from `AGENT.md`
#### 4. Agent-to-Agent Communication
Extend `SubagentBus` to support:
- Named channels per agent (not just task IDs)
- Request/response patterns (agent A asks agent B, waits for reply)
- Broadcast messages (main agent sends context to all agents)
- Result aggregation (main agent collects results from multiple agents)
#### 5. Team Orchestration Patterns
- **Sequential**: Programmer writes code → Designer reviews UI → Main agent summarizes
- **Parallel**: Programmer and Designer work simultaneously, main agent merges results
- **Supervisory**: Main agent reviews each agent's output before sending to user
- **Collaborative**: Agents can message each other directly via the bus
#### 6. Chat Commands
```
team list # List defined agents
team status # Show which agents are active
team create <name> # Interactive agent creation
team remove <name> # Remove an agent definition
delegate <agent> <task> # Manually delegate to a specific agent
```
#### 7. Claude Code Native Teams
Claude Code already has `TeamCreate`, `TeamDelete`, `SendMessage` tools. These allow the AI itself to create and manage teams during a session. The integration path:
- When using Claude Code engine, the AI can use these tools natively
- Aetheel wraps the results and routes messages back to the right channel
- Agent definitions in `AGENT.md` pre-configure teams that get created on startup
### Implementation Priority
There are two paths — pick based on complexity appetite:
**Path A: Lightweight (extend current SubagentManager)**
1. Agent definition format (`AGENT.md`) and discovery
2. Per-agent identity file loading (own SOUL.md, shared USER.md/MEMORY.md)
3. `[ACTION:delegate|agent|task]` tag parsing
4. Agent-specific system prompt building with tool restrictions
5. Chat commands for team management
6. Agent-to-agent communication via extended bus
**Path B: Full isolation (NanoClaw-style containers)**
1. Enable `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` for Claude Code runtime
2. Per-group workspace folders with own CLAUDE.md
3. Container-based agent execution (Docker or Apple Container)
4. File-based IPC between host and containers
5. Mount security (allowlist, per-group isolation)
6. Bot pool for Telegram/Discord (each agent gets its own bot identity)
7. Lead agent orchestration via Claude's native team tools
Path A is simpler and works today. Path B is more secure and scalable but requires container infrastructure.
### NanoClaw Reference Files
Key files to study in `inspirations/nanoclaw/`:
- `src/container-runner.ts` — Container spawning, volume mounts, IPC, per-group isolation
- `src/types.ts``RegisteredGroup`, `ContainerConfig`, `AdditionalMount` types
- `groups/global/CLAUDE.md` — Global identity shared across all groups
- `groups/main/CLAUDE.md` — Main channel with elevated privileges
- `.claude/skills/add-telegram-swarm/SKILL.md` — Full agent swarm implementation for Telegram
- `docs/SPEC.md` — Complete architecture spec
- `docs/SECURITY.md` — Security model for container isolation
---
## Other Planned Changes
### Security Fixes (from security-audit.md)
- Path containment check in `memory/manager.py` `read_file()`
- Mandatory webhook auth when enabled
- Input schema validation on webhook POST bodies
- Stricter cron expression validation
- Rate limiting on HTTP endpoints
- WebSocket authentication for WebChat
- Dependency version pinning
### Persistent Usage Stats
Currently usage stats reset on restart. Persist to SQLite so `usage` command shows lifetime stats, daily/weekly/monthly breakdowns, and cost trends.
### Multi-Model Routing
Route different types of requests to different models automatically:
- Quick questions → fast/cheap model (sonnet, gpt-4o-mini)
- Complex reasoning → powerful model (opus, o1)
- Large context → big-context model (gemini-2.5-pro)
### Conversation Branching
Allow users to fork a conversation into a new thread with a different model or agent, then merge results back.

172
docs/security-audit.md Normal file
View File

@@ -0,0 +1,172 @@
# Aetheel Security Audit
**Date:** February 17, 2026
**Scope:** Full codebase review of all modules
---
## CRITICAL
### 1. Path Traversal in `memory/manager.py` → `read_file()`
The method accepts absolute paths and resolves them with `os.path.realpath()` but never validates the result is within the workspace directory. An attacker (or the AI itself) could read arbitrary files:
```python
# Current code — no containment check
if os.path.isabs(raw):
abs_path = os.path.realpath(raw)
```
**Fix:** Add a check like `if not abs_path.startswith(self._workspace_dir): raise ValueError("path outside workspace")`
### 2. Arbitrary Code Execution via Hook `handler.py` Loading
`hooks/hooks.py``_load_handler` uses `importlib.util.spec_from_file_location` to dynamically load and execute arbitrary Python from `handler.py` files found in the workspace. If an attacker can write a file to `~/.aetheel/workspace/hooks/<name>/handler.py`, they get full code execution. There's no sandboxing, signature verification, or allowlisting.
### 3. Webhook Auth Defaults to Open Access
`webhooks/receiver.py``_check_auth`:
```python
if not self._config.token:
return True # No token configured = open access
```
If the webhook receiver is enabled without a token, anyone on the network can trigger AI actions. The default config writes `"token": ""` which means open access.
### 4. AI-Controlled Action Tags Execute Without Validation
`main.py``_process_action_tags` parses the AI's response text for action tags like `[ACTION:cron|...]`, `[ACTION:spawn|...]`, and `[ACTION:remind|...]`. The AI can:
- Schedule arbitrary cron jobs with any expression
- Spawn unlimited subagent tasks
- Set reminders with any delay
There's no validation that the AI was asked to do this, no user confirmation, and no rate limiting. A prompt injection attack via any adapter could trigger these.
---
## HIGH
### 5. No Input Validation on Webhook POST Bodies
`webhooks/receiver.py` — JSON payloads are parsed but never schema-validated. Fields like `channel_id`, `sender`, `channel` are passed through directly. The `body` dict is stored in `raw_event` and could contain arbitrarily large data.
### 6. No Request Size Limits on HTTP Endpoints
Neither the webhook receiver nor the WebChat adapter set `client_max_size` on the aiohttp `Application`. Default is 2MB but there's no explicit limit, and no per-request timeout.
### 7. WebSocket Has No Authentication
`adapters/webchat_adapter.py` — Anyone who can reach the WebSocket endpoint at `/ws` can interact with the AI. No token, no session cookie, no origin check. If the host is changed from `127.0.0.1` to `0.0.0.0`, this becomes remotely exploitable.
### 8. No Rate Limiting Anywhere
No rate limiting on:
- Webhook endpoints
- WebSocket messages
- Adapter message handlers
- Subagent spawning (only a concurrent limit of 3, but no cooldown)
- Scheduler job creation
### 9. Cron Expression Not Validated Before APScheduler
`scheduler/scheduler.py``_register_cron_job` only checks `len(parts) != 5`. Malformed values within fields (e.g., `999 999 999 999 999`) are passed directly to `CronTrigger`, which could cause unexpected behavior or exceptions.
### 10. Webhook Token in Query Parameter
`webhooks/receiver.py`:
```python
if request.query.get("token") == self._config.token:
return True
```
Query parameters are logged in web server access logs, browser history, and proxy logs. This leaks the auth token.
---
## MEDIUM
### 11. SQLite Databases Created with Default Permissions
`sessions.db`, `scheduler.db`, and `memory.db` are all created under `~/.aetheel/` with default umask permissions. On multi-user systems, these could be world-readable.
### 12. Webhook Token Stored in `config.json`
The `webhooks.token` field in `config.py` is read from and written to `config.json`, which is a plaintext file. Secrets should only live in `.env`.
### 13. No HTTPS on Any HTTP Endpoint
Both WebChat (port 8080) and webhooks (port 8090) run plain HTTP. Even on localhost, this is vulnerable to local network sniffing.
### 14. Full Environment Passed to Subprocesses
`_build_cli_env()` in both runtimes copies `os.environ` entirely to the subprocess, which may include sensitive variables beyond what the CLI needs.
### 15. Session Logs Contain Full Conversations in Plaintext
`memory/manager.py``log_session()` writes unencrypted markdown files to `~/.aetheel/workspace/daily/`. No access control, no encryption, no retention policy.
### 16. XSS Partially Mitigated in `chat.html` but Fragile
The `renderMarkdown()` function escapes `<`, `>`, `&` first, then applies regex-based markdown rendering. User messages use `textContent` (safe). AI messages use `innerHTML` with the escaped+rendered output. The escaping happens before markdown processing, which is the right order, but the regex-based approach is fragile — edge cases in the markdown regexes could potentially bypass the escaping.
### 17. No CORS Headers on WebChat
The aiohttp app doesn't configure CORS. If exposed beyond localhost, cross-origin requests could interact with the WebSocket.
---
## LOW
### 18. Loose Dependency Version Constraints
`pyproject.toml`:
- `python-telegram-bot>=21.0` — no upper bound
- `discord.py>=2.4.0` — no upper bound
- `fastembed>=0.7.4` — no upper bound
These could pull in breaking or vulnerable versions on fresh installs.
### 19. No Security Scanning in CI/Test Pipeline
No `bandit`, `safety`, `pip-audit`, or similar tools in the test suite or project config.
### 20. `config edit` Uses `$EDITOR` Without Sanitization
`cli.py`:
```python
editor = os.environ.get("EDITOR", "nano")
subprocess.run([editor, CONFIG_PATH], check=True)
```
If `$EDITOR` contains spaces or special characters, this could behave unexpectedly (though `subprocess.run` with a list is safe from shell injection).
### 21. No Data Retention/Cleanup for Session Logs
Session logs accumulate indefinitely in `daily/`. No automatic pruning.
### 22. `SubagentBus` Has No Authentication
The pub/sub bus allows any code in the process to publish/subscribe to any channel. No isolation between subagents.
---
## Recommended Priority Fixes
The most impactful changes to make first:
1. **Add path containment check in `read_file()`** — one-line fix, prevents file system escape
2. **Make webhook auth mandatory** when `webhooks.enabled = true` — refuse to start without a token
3. **Add input schema validation** on webhook POST bodies
4. **Validate cron expressions** more strictly before passing to APScheduler
5. **Add rate limiting** to webhook and WebSocket endpoints (e.g., aiohttp middleware)
6. **Move `webhooks.token` to `.env` only**, remove from `config.json`
7. **Add WebSocket origin checking or token auth** to WebChat
8. **Set explicit `client_max_size`** on aiohttp apps
9. **Pin dependency upper bounds** in `pyproject.toml`
10. **Add `bandit`** to the test pipeline

841
docs/setup.md Normal file
View File

@@ -0,0 +1,841 @@
# Aetheel Server Setup Guide
Step-by-step guide to deploy Aetheel on a Linux or macOS machine.
---
## Quick Start (Recommended)
The interactive installer handles everything automatically:
```bash
# One-line install
curl -fsSL http://10.0.0.59:3051/tanmay/Aetheel/raw/branch/main/install.sh | bash
```
Or if you already have the repo cloned:
```bash
./install.sh
```
The installer will:
- Check prerequisites (Python 3.12+, uv)
- Install dependencies
- Detect or install AI runtimes (OpenCode / Claude Code)
- Walk you through token configuration
- Install the `aetheel` command
- Optionally set up a background service (launchd/systemd)
After install, use the `aetheel` command:
```bash
aetheel start # Start the bot
aetheel stop # Stop the background service
aetheel restart # Restart the service
aetheel status # Check status
aetheel logs # Tail live logs
aetheel setup # Re-run setup wizard
aetheel update # Pull latest + update deps
aetheel doctor # Run diagnostics
aetheel config # Edit config.json
```
For flags and options: `aetheel help`
---
## Manual Setup
If you prefer to set things up manually, follow the steps below.
---
## Table of Contents
1. [Prerequisites](#1-prerequisites)
2. [Install System Dependencies](#2-install-system-dependencies)
3. [Install uv (Python Package Manager)](#3-install-uv)
4. [Install an AI Runtime](#4-install-an-ai-runtime)
5. [Clone the Repository](#5-clone-the-repository)
6. [Install Python Dependencies](#6-install-python-dependencies)
7. [Configure Secrets (.env)](#7-configure-secrets)
8. [Configure Settings (config.json)](#8-configure-settings)
9. [Set Up Messaging Channels](#9-set-up-messaging-channels)
10. [Run the Test Suite](#10-run-the-test-suite)
11. [Start Aetheel](#11-start-aetheel)
12. [Run as a systemd Service](#12-run-as-a-systemd-service)
13. [Verify Everything Works](#13-verify-everything-works)
14. [Optional: Enable WebChat](#14-optional-enable-webchat)
15. [Optional: Enable Webhooks](#15-optional-enable-webhooks)
16. [Optional: Configure Hooks](#16-optional-configure-hooks)
17. [Optional: Configure Heartbeat](#17-optional-configure-heartbeat)
18. [Optional: Add MCP Servers](#18-optional-add-mcp-servers)
19. [Optional: Create Skills](#19-optional-create-skills)
20. [Updating](#20-updating)
21. [Troubleshooting](#21-troubleshooting)
---
## 1. Prerequisites
| Requirement | Minimum | Recommended |
|---|---|---|
| OS | Ubuntu 20.04 / Debian 11 | Ubuntu 22.04+ / Debian 12+ |
| Python | 3.12 | 3.13 |
| RAM | 512 MB | 1 GB+ (fastembed uses ~300 MB for embeddings) |
| Disk | 500 MB | 1 GB+ |
| Network | Outbound HTTPS | Outbound HTTPS |
You also need at least one of:
- Slack workspace with admin access (for Slack adapter)
- Telegram bot token (for Telegram adapter)
- Discord bot token (for Discord adapter)
- Nothing (for WebChat-only mode)
And at least one AI runtime:
- [OpenCode](https://opencode.ai) CLI
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code) CLI
- An API key for the LLM provider (Anthropic, OpenAI, etc.)
---
## 2. Install System Dependencies
```bash
# Update packages
sudo apt update && sudo apt upgrade -y
# Install essentials
sudo apt install -y git curl build-essential
# Verify
git --version
curl --version
```
---
## 3. Install uv
[uv](https://docs.astral.sh/uv/) is the recommended Python package manager. It handles Python versions, virtual environments, and dependencies.
```bash
# Install uv
curl -LsSf https://astral.sh/uv/install.sh | sh
# Add to PATH (restart shell or source profile)
source ~/.bashrc # or ~/.zshrc
# Verify
uv --version
```
uv will automatically install the correct Python version (3.13) when you run `uv sync`.
---
## 4. Install an AI Runtime
You need at least one AI runtime. Pick one (or both):
### Option A: OpenCode (recommended)
```bash
curl -fsSL https://opencode.ai/install | bash
# Verify
opencode --version
# Configure a provider
opencode auth login
```
### Option B: Claude Code
```bash
# Requires Node.js
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
# Install Claude Code
npm install -g @anthropic-ai/claude-code
# Verify
claude --version
```
### Set your API key
Whichever runtime you use, you need an API key for the LLM provider:
```bash
# For Anthropic (used by both OpenCode and Claude Code)
export ANTHROPIC_API_KEY="sk-ant-..."
# For OpenAI
export OPENAI_API_KEY="sk-..."
```
Add these to your `.env` file later (step 7).
---
## 5. Clone the Repository
```bash
# Clone
cd ~
git clone http://10.0.0.59:3051/tanmay/Aetheel.git
cd Aetheel
# Verify
ls pyproject.toml # Should exist
```
---
## 6. Install Python Dependencies
```bash
# Install project + test dependencies
uv sync --extra test
# Verify Python version
uv run python --version # Should be 3.13.x
# Verify key packages
uv run python -c "import click; import aiohttp; import apscheduler; print('All packages OK')"
```
---
## 7. Configure Secrets
Secrets (tokens, API keys) go in the `.env` file. This file is gitignored.
```bash
# Create from template
cp .env.example .env
# Edit with your tokens
nano .env
```
Fill in the tokens you need:
```bash
# Required for Slack
SLACK_BOT_TOKEN=xoxb-your-bot-token
SLACK_APP_TOKEN=xapp-your-app-token
# Required for Telegram (if using --telegram)
TELEGRAM_BOT_TOKEN=your-telegram-token
# Required for Discord (if using --discord)
DISCORD_BOT_TOKEN=your-discord-token
# AI provider API key
ANTHROPIC_API_KEY=sk-ant-your-key
# Optional overrides
# OPENCODE_MODEL=anthropic/claude-sonnet-4-20250514
# LOG_LEVEL=DEBUG
```
See [docs/slack-setup.md](slack-setup.md) and [docs/discord-setup.md](discord-setup.md) for how to get these tokens.
---
## 8. Configure Settings
Non-secret settings go in `~/.aetheel/config.json`. A default is created on first run, but you can create it now:
```bash
# Create the config directory
mkdir -p ~/.aetheel/workspace
# Generate default config
uv run python -c "from config import save_default_config; save_default_config()"
# View it
cat ~/.aetheel/config.json
```
Edit if needed:
```bash
nano ~/.aetheel/config.json
```
Key settings to review:
```json
{
"runtime": {
"mode": "cli",
"model": null
},
"claude": {
"no_tools": false,
"allowed_tools": ["Bash", "Read", "Write", "..."]
},
"heartbeat": {
"enabled": true,
"default_channel": "slack",
"default_channel_id": ""
},
"webchat": {
"enabled": false,
"port": 8080
},
"webhooks": {
"enabled": false,
"port": 8090,
"token": ""
}
}
```
---
## 9. Set Up Messaging Channels
### Slack (see [docs/slack-setup.md](slack-setup.md))
1. Create a Slack app at https://api.slack.com/apps
2. Add bot scopes: `app_mentions:read`, `chat:write`, `im:history`, `im:read`, `im:write`, `channels:history`, `users:read`
3. Enable Socket Mode, generate an app-level token (`xapp-...`)
4. Install to workspace, copy bot token (`xoxb-...`)
5. Invite bot to channels: `/invite @Aetheel`
### Discord (see [docs/discord-setup.md](discord-setup.md))
1. Create app at https://discord.com/developers/applications
2. Create bot, copy token
3. Enable Message Content Intent
4. Invite with `bot` scope + Send Messages + Read Message History
### Telegram
1. Message @BotFather on Telegram
2. `/newbot` → follow prompts → copy token
3. Set `TELEGRAM_BOT_TOKEN` in `.env`
---
## 10. Run the Test Suite
Before starting, verify everything works:
```bash
cd ~/Aetheel
# Run the full test + smoke check script
uv run python test_all.py
```
You should see all checks pass:
```
━━━ RESULTS ━━━
Total: 45 checks
Passed: 45
Failed: 0
Skipped: 0
All checks passed! 🎉
```
If anything fails, fix it before proceeding. Common issues:
- Missing packages → `uv sync --extra test`
- Wrong directory → `cd ~/Aetheel`
You can also run just the pytest suite:
```bash
uv run python -m pytest tests/ -v --ignore=tests/test_scheduler.py
```
---
## 11. Start Aetheel
### Basic start (Slack only)
```bash
uv run python main.py
```
### With additional adapters
```bash
# Slack + Discord
uv run python main.py --discord
# Slack + Telegram
uv run python main.py --telegram
# Slack + Discord + WebChat
uv run python main.py --discord --webchat
# All adapters
uv run python main.py --discord --telegram --webchat
```
### With Claude Code runtime
```bash
uv run python main.py --claude
```
### Test mode (no AI, echo handler)
```bash
uv run python main.py --test
```
### Using the CLI
```bash
# Same as above but via the click CLI
uv run python cli.py start --discord --webchat
# One-shot chat (no adapters needed)
uv run python cli.py chat "What is Python?"
# Diagnostics
uv run python cli.py doctor
```
---
## 12. Run as a systemd Service
To keep Aetheel running after you disconnect from SSH:
### Create the service file
```bash
sudo nano /etc/systemd/system/aetheel.service
```
Paste (adjust paths to match your setup):
```ini
[Unit]
Description=Aetheel AI Assistant
After=network.target
[Service]
Type=simple
User=your-username
WorkingDirectory=/home/your-username/Aetheel
ExecStart=/home/your-username/.local/bin/uv run python main.py
Restart=on-failure
RestartSec=10
Environment=PATH=/home/your-username/.local/bin:/usr/local/bin:/usr/bin:/bin
# Load .env file
EnvironmentFile=/home/your-username/Aetheel/.env
# Optional: add more adapters
# ExecStart=/home/your-username/.local/bin/uv run python main.py --discord --webchat
[Install]
WantedBy=multi-user.target
```
### Enable and start
```bash
# Reload systemd
sudo systemctl daemon-reload
# Enable on boot
sudo systemctl enable aetheel
# Start now
sudo systemctl start aetheel
# Check status
sudo systemctl status aetheel
# View logs
sudo journalctl -u aetheel -f
```
### Manage the service
```bash
sudo systemctl stop aetheel # Stop
sudo systemctl restart aetheel # Restart
sudo systemctl disable aetheel # Disable on boot
```
---
## 13. Verify Everything Works
### Check the startup log
```bash
sudo journalctl -u aetheel --no-pager | tail -20
```
You should see:
```
============================================================
Aetheel Starting
============================================================
Config: /home/user/.aetheel/config.json
Runtime: opencode/cli, model=default
Channels: slack
Skills: 0
Scheduler: ✅
Heartbeat: ✅ 3 tasks
Subagents: ✅
Hooks: ✅ 0 hooks
Webhooks: ❌
============================================================
```
### Test from Slack/Discord/Telegram
1. Send `status` → should show runtime info
2. Send `help` → should show command list
3. Send any question → should get an AI response
4. Send `/reload` → should confirm reload
### Test from CLI
```bash
uv run python cli.py doctor
uv run python cli.py status
```
---
## 14. Optional: Enable WebChat
Browser-based chat at `http://your-server:8080`.
### Via config
Edit `~/.aetheel/config.json`:
```json
{
"webchat": {
"enabled": true,
"port": 8080,
"host": "0.0.0.0"
}
}
```
Set `host` to `0.0.0.0` to accept connections from other machines (not just localhost).
### Via CLI flag
```bash
uv run python main.py --webchat
```
### Firewall
If using `ufw`:
```bash
sudo ufw allow 8080/tcp
```
Then open `http://your-server-ip:8080` in a browser.
---
## 15. Optional: Enable Webhooks
HTTP endpoints for external systems to trigger the agent.
### Configure
Edit `~/.aetheel/config.json`:
```json
{
"webhooks": {
"enabled": true,
"port": 8090,
"host": "0.0.0.0",
"token": "your-secret-webhook-token"
}
}
```
Generate a random token:
```bash
python3 -c "import secrets; print(secrets.token_urlsafe(32))"
```
### Firewall
```bash
sudo ufw allow 8090/tcp
```
### Test
```bash
# Health check (no auth)
curl http://localhost:8090/hooks/health
# Wake the agent
curl -X POST http://localhost:8090/hooks/wake \
-H "Authorization: Bearer your-secret-webhook-token" \
-H "Content-Type: application/json" \
-d '{"text": "What time is it?"}'
# Send to a specific channel
curl -X POST http://localhost:8090/hooks/agent \
-H "Authorization: Bearer your-secret-webhook-token" \
-H "Content-Type: application/json" \
-d '{
"message": "New alert from monitoring",
"channel": "slack",
"channel_id": "C123456"
}'
```
---
## 16. Optional: Configure Hooks
Lifecycle hooks run custom code on gateway events.
### Create a hook
```bash
mkdir -p ~/.aetheel/workspace/hooks/startup-logger
```
Create `~/.aetheel/workspace/hooks/startup-logger/HOOK.md`:
```markdown
---
name: startup-logger
description: Log when the gateway starts
events: [gateway:startup]
enabled: true
---
# Startup Logger
Logs a message when Aetheel starts.
```
Create `~/.aetheel/workspace/hooks/startup-logger/handler.py`:
```python
import logging
logger = logging.getLogger("aetheel.hooks.startup-logger")
def handle(event):
logger.info("Gateway started — startup hook fired!")
```
Hooks are discovered automatically on startup.
---
## 17. Optional: Configure Heartbeat
The heartbeat system runs periodic tasks automatically.
### Edit HEARTBEAT.md
```bash
nano ~/.aetheel/workspace/HEARTBEAT.md
```
Example:
```markdown
# Heartbeat Tasks
## Every 30 minutes
- Check if any scheduled reminders need attention
## Every morning (9:00 AM)
- Summarize yesterday's conversations
- Check for any pending follow-ups
## Every evening (6:00 PM)
- Update MEMORY.md with today's key learnings
```
### Configure where heartbeat responses go
Edit `~/.aetheel/config.json`:
```json
{
"heartbeat": {
"enabled": true,
"default_channel": "slack",
"default_channel_id": "C123456"
}
}
```
Set `default_channel_id` to the Slack channel ID where heartbeat responses should be sent.
---
## 18. Optional: Add MCP Servers
External tool servers that extend the agent's capabilities.
Edit `~/.aetheel/config.json`:
```json
{
"mcp": {
"servers": {
"brave-search": {
"command": "uvx",
"args": ["brave-search-mcp@latest"],
"env": {
"BRAVE_API_KEY": "your-key"
}
}
}
}
}
```
Aetheel writes the appropriate config file (`.mcp.json` or `opencode.json`) to the workspace before launching the runtime.
---
## 19. Optional: Create Skills
Skills teach the agent how to handle specific types of requests.
```bash
mkdir -p ~/.aetheel/workspace/skills/weather
```
Create `~/.aetheel/workspace/skills/weather/SKILL.md`:
```markdown
---
name: weather
description: Check weather for any city
triggers: [weather, forecast, temperature, rain]
---
# Weather Skill
When the user asks about weather:
1. Use web search to find current conditions
2. Include temperature, conditions, and forecast
3. Format the response with emoji
```
Skills are loaded at startup and can be reloaded with `/reload`.
---
## 20. Updating
```bash
cd ~/Aetheel
# Pull latest
git pull
# Update dependencies
uv sync --extra test
# Run tests
uv run python test_all.py
# Restart service
sudo systemctl restart aetheel
```
---
## 21. Troubleshooting
### "No channel adapters initialized!"
No messaging tokens are set. Check your `.env` file has at least one of:
- `SLACK_BOT_TOKEN` + `SLACK_APP_TOKEN`
- `TELEGRAM_BOT_TOKEN` (with `--telegram` flag)
- `DISCORD_BOT_TOKEN` (with `--discord` flag)
- `--webchat` flag (no tokens needed)
### "AI runtime not initialized"
The AI CLI (opencode or claude) isn't installed or isn't in PATH.
```bash
# Check
which opencode
which claude
# Install OpenCode
curl -fsSL https://opencode.ai/install | bash
```
### "Request timed out"
The AI took too long. Increase timeout in config:
```json
{
"runtime": { "timeout_seconds": 300 },
"claude": { "timeout_seconds": 300 }
}
```
### Memory system init failed
Usually means fastembed can't download the embedding model on first run. Check internet access and disk space.
```bash
# Test manually
uv run python -c "from fastembed import TextEmbedding; e = TextEmbedding(); print('OK')"
```
### Port already in use (WebChat/Webhooks)
Another process is using the port. Change it in config or find the process:
```bash
sudo lsof -i :8080 # Find what's using port 8080
```
### systemd service won't start
Check logs:
```bash
sudo journalctl -u aetheel -n 50 --no-pager
```
Common issues:
- Wrong `WorkingDirectory` path
- Wrong `User`
- `.env` file not found (check `EnvironmentFile` path)
- uv not in PATH (check `Environment=PATH=...`)
### Run diagnostics
```bash
uv run python cli.py doctor
```
This checks config validity, workspace, runtime CLIs, tokens, and memory DB.

View File

@@ -0,0 +1,560 @@
# Aetheel Phase 3 Feature Spec
> Specification for implementing heartbeat, CLI, webchat, self-modification, agent-to-agent communication, and tool/MCP/web search integration.
---
## Table of Contents
1. [Runtime Capabilities Audit](#1-runtime-capabilities-audit)
2. [Tool System, MCP & Web Search](#2-tool-system-mcp--web-search)
3. [Heartbeat / Proactive System](#3-heartbeat--proactive-system)
4. [CLI Interface](#4-cli-interface)
5. [WebChat Interface](#5-webchat-interface)
6. [Self-Modification](#6-self-modification)
7. [Agent-to-Agent Communication](#7-agent-to-agent-communication)
8. [Implementation Order](#8-implementation-order)
---
## 1. Runtime Capabilities Audit
Before building anything, here's what OpenCode and Claude Code already provide natively that Aetheel can leverage without custom implementation.
### OpenCode Built-in Tools
OpenCode ships with these tools enabled by default (no config needed):
| Tool | Description | Aetheel Status |
|------|-------------|----------------|
| `bash` | Execute shell commands | ✅ Available via runtime |
| `read` | Read file contents | ✅ Available via runtime |
| `write` | Create/overwrite files | ✅ Available via runtime |
| `edit` | Modify files via string replacement | ✅ Available via runtime |
| `grep` | Regex search across files | ✅ Available via runtime |
| `glob` | Find files by pattern | ✅ Available via runtime |
| `list` | List directory contents | ✅ Available via runtime |
| `websearch` | Web search via Exa AI (no API key needed) | ✅ Available — no setup required |
| `webfetch` | Fetch and read web pages | ✅ Available via runtime |
| `skill` | Load SKILL.md files | ✅ Available via runtime |
| `todowrite` / `todoread` | Task tracking | ✅ Available via runtime |
| `lsp` | Code intelligence (experimental) | ✅ Available via runtime |
| `patch` | Apply diffs | ✅ Available via runtime |
| Custom tools | User-defined JS/TS tools | 🟡 Available but not wired |
| MCP servers | External tool servers | 🟡 Available but not configured |
### Claude Code Built-in Tools
Claude Code provides these tools natively:
| Tool | Description | Aetheel Status |
|------|-------------|----------------|
| `Bash` | Shell execution | ✅ Available via runtime |
| `Read`, `Write`, `Edit` | File operations | ✅ Available via runtime |
| `Glob`, `Grep` | File search | ✅ Available via runtime |
| `WebSearch`, `WebFetch` | Web access | ✅ Available via runtime |
| `Task`, `TaskOutput`, `TaskStop` | Subagent spawning | ✅ Available via runtime |
| `TeamCreate`, `TeamDelete`, `SendMessage` | Agent teams | ✅ Available via runtime |
| `Skill` | Load skills | ✅ Available via runtime |
| MCP servers | Via `.mcp.json` config | 🟡 Available but not configured |
### Key Insight
Both runtimes already have web search, file ops, bash, and subagent tools built in. Aetheel doesn't need to implement these — it just needs to stop blocking them. Currently Aetheel's system prompt doesn't tell the agent about these capabilities, and Claude Code's `--allowedTools ""` (no_tools=true) actively disables them.
---
## 2. Tool System, MCP & Web Search
### What Needs to Change
**Problem**: Aetheel currently runs both runtimes in "pure chat" mode — tools are disabled or not mentioned in the system prompt.
**Solution**: Enable tool access and configure MCP servers.
### 2.1 Enable Runtime Tools
For OpenCode (CLI mode), tools are enabled by default. No change needed.
For OpenCode (SDK mode), tools are enabled by default on the server. No change needed.
For Claude Code, change `no_tools` default from `true` to `false` in config, and set a sensible `allowedTools` list:
```python
# config.py — ClaudeConfig
@dataclass
class ClaudeConfig:
no_tools: bool = False # Changed from True
allowed_tools: list[str] = field(default_factory=lambda: [
"Bash", "Read", "Write", "Edit", "Glob", "Grep",
"WebSearch", "WebFetch",
"Task", "Skill",
])
```
### 2.2 Update System Prompt
Add tool awareness to `build_aetheel_system_prompt()`:
```
# Your Tools
- You have access to shell commands, file operations, and web search
- Use web search to look up current information when needed
- You can read and write files in the workspace (~/.aetheel/workspace/)
- You can execute shell commands for system tasks
```
### 2.3 MCP Server Configuration
Create an `opencode.json` in the workspace for OpenCode MCP config, or a `.mcp.json` for Claude Code. This lets users add external tool servers.
Add to `~/.aetheel/config.json`:
```json
{
"mcp": {
"servers": {
"example": {
"command": "uvx",
"args": ["some-mcp-server"],
"env": {}
}
}
}
}
```
Aetheel writes this to the appropriate config file (`opencode.json` or `.mcp.json`) in the workspace directory before launching the runtime.
### 2.4 Implementation Tasks
1. Change `ClaudeConfig.no_tools` default to `False`
2. Add `allowed_tools` to `ClaudeConfig` with sensible defaults
3. Update `_build_cli_args()` in `claude_runtime.py` to pass `--allowedTools` from config
4. Update `build_aetheel_system_prompt()` to mention available tools
5. Add `mcp` section to `config.py` and `config.json`
6. Write MCP config files to workspace before runtime launch
7. Add `TOOLS.md` to workspace identity files (like PicoClaw/OpenClaw)
### Inspiration
- **NanoClaw**: Passes `allowedTools` list to `query()` call, runs custom MCP server for IPC
- **PicoClaw**: Built-in tool registry in `pkg/tools/`, `TOOLS.md` describes tools to agent
- **OpenClaw**: Tool registry with streaming, `TOOLS.md` in workspace
---
## 3. Heartbeat / Proactive System
### How Inspiration Repos Do It
**PicoClaw**: Reads `HEARTBEAT.md` every 30 minutes. Quick tasks run directly, long tasks spawn subagents. The heartbeat file contains prompts like "Check if any cron jobs need attention" or "Review MEMORY.md for stale entries."
**Nanobot**: Has a `heartbeat/` module that triggers periodic agent runs.
**NanoClaw**: Uses scheduled tasks via MCP tools — the agent itself schedules recurring tasks using `schedule_task` with cron expressions. No separate heartbeat file.
**OpenClaw**: Cron system + wakeups. Heartbeat runs are suppressed from WebChat broadcast when `showOk: false`.
### Design for Aetheel
Combine PicoClaw's `HEARTBEAT.md` approach (simple, file-based) with Aetheel's existing scheduler:
### 3.1 HEARTBEAT.md
Create `~/.aetheel/workspace/HEARTBEAT.md` as a user-editable file:
```markdown
# Heartbeat Tasks
Tasks that run periodically. Each section is a task prompt.
## Every 30 minutes
- Check if any scheduled reminders need attention
- Review recent session logs for anything worth remembering
## Every morning (9:00 AM)
- Summarize yesterday's conversations
- Check for any pending follow-ups in MEMORY.md
## Every evening (6:00 PM)
- Update MEMORY.md with today's key learnings
```
### 3.2 Heartbeat Runner
New module: `heartbeat/heartbeat.py`
```python
class HeartbeatRunner:
def __init__(self, runtime, send_fn, workspace_dir, scheduler):
...
def start(self):
"""Register heartbeat tasks with the scheduler."""
# Parse HEARTBEAT.md
# Register cron jobs for each section
# Jobs route through ai_handler with synthetic messages
def _parse_heartbeat_md(self) -> list[HeartbeatTask]:
"""Parse HEARTBEAT.md into structured tasks."""
...
def _on_heartbeat(self, task: HeartbeatTask):
"""Called when a heartbeat fires. Routes to AI handler."""
...
```
### 3.3 Integration
- Parse `HEARTBEAT.md` at startup, register tasks with existing `Scheduler`
- Heartbeat tasks create synthetic `IncomingMessage` with `source="heartbeat"`
- Results can be sent to a configured channel or logged silently
- Add `heartbeat` section to config:
```json
{
"heartbeat": {
"enabled": true,
"default_channel": "slack",
"default_channel_id": "C123456",
"silent": false
}
}
```
### 3.4 Implementation Tasks
1. Create `heartbeat/` module with `HeartbeatRunner`
2. Create default `HEARTBEAT.md` in workspace (like SOUL.md bootstrap)
3. Parse markdown sections into cron expressions + prompts
4. Register with existing `Scheduler` at startup
5. Add heartbeat config section
6. Wire into `main.py` initialization
---
## 4. CLI Interface
### How Inspiration Repos Do It
**Nanobot**: `nanobot agent -m "..."`, `nanobot gateway`, `nanobot status`, `nanobot cron add/list/remove`
**OpenClaw**: `openclaw gateway`, `openclaw agent --message "..."`, `openclaw doctor`, `openclaw pairing approve`
**PicoClaw**: Binary with subcommands via Go's `cobra` library
### Design for Aetheel
Use Python's `click` library for a clean CLI with subcommands.
### 4.1 CLI Structure
```
aetheel # Start with default adapters (same as current main.py)
aetheel start # Start with all configured adapters
aetheel start --discord # Start with specific adapters
aetheel chat "message" # One-shot chat (no adapter, just AI)
aetheel status # Show runtime status
aetheel cron list # List scheduled jobs
aetheel cron remove <id> # Remove a job
aetheel sessions list # List active sessions
aetheel sessions clear # Clear stale sessions
aetheel config show # Print current config
aetheel config edit # Open config in $EDITOR
aetheel config init # Reset to defaults
aetheel memory search "q" # Search memory
aetheel memory sync # Force memory re-index
aetheel doctor # Diagnostics (check runtime, tokens, etc.)
```
### 4.2 Implementation
New file: `cli.py`
```python
import click
@click.group()
def cli():
"""Aetheel — AI-Powered Personal Assistant"""
pass
@cli.command()
@click.option("--discord", is_flag=True)
@click.option("--telegram", is_flag=True)
@click.option("--claude", is_flag=True)
@click.option("--model", default=None)
@click.option("--test", is_flag=True)
@click.option("--log", default="INFO")
def start(discord, telegram, claude, model, test, log):
"""Start Aetheel with configured adapters."""
# Current main() logic moves here
...
@cli.command()
@click.argument("message")
def chat(message):
"""One-shot chat with the AI (no adapter needed)."""
...
@cli.group()
def cron():
"""Manage scheduled jobs."""
pass
@cron.command("list")
def cron_list():
...
@cron.command("remove")
@click.argument("job_id")
def cron_remove(job_id):
...
# ... etc
```
### 4.3 Entry Point
Add to `pyproject.toml`:
```toml
[project.scripts]
aetheel = "cli:cli"
```
### 4.4 Implementation Tasks
1. Add `click` dependency to `pyproject.toml`
2. Create `cli.py` with command groups
3. Move `main()` logic into `start` command
4. Add `chat`, `status`, `cron`, `sessions`, `config`, `memory`, `doctor` commands
5. Add entry point to `pyproject.toml`
6. Keep `main.py` as backward-compatible wrapper
---
## 5. WebChat Interface
### How Inspiration Repos Do It
**OpenClaw**: Full WebSocket-based WebChat as an internal channel. Messages routed through the gateway with `messageChannel: "webchat"`. Has a web UI built with React.
**Nanobot/NanoClaw/PicoClaw**: No webchat.
### Design for Aetheel
A lightweight HTTP server with WebSocket support, served as a new adapter.
### 5.1 Architecture
```
Browser ←→ WebSocket ←→ WebChatAdapter ←→ ai_handler ←→ Runtime
Static HTML/JS
```
### 5.2 Implementation
New adapter: `adapters/webchat_adapter.py`
Use `aiohttp` for both the static file server and WebSocket handler:
- `GET /` — Serves the chat UI (single HTML file with embedded JS/CSS)
- `WS /ws` — WebSocket for real-time chat
- Each WebSocket connection = one conversation (session isolation)
- Messages flow through the same `BaseAdapter._dispatch``ai_handler` path
### 5.3 Chat UI
Single self-contained HTML file at `static/chat.html`:
- Minimal chat interface (message list + input box)
- WebSocket connection to the local server
- Markdown rendering for AI responses
- No build step, no npm, no framework — just vanilla HTML/JS/CSS
### 5.4 Config
```json
{
"webchat": {
"enabled": false,
"port": 8080,
"host": "127.0.0.1"
}
}
```
### 5.5 Implementation Tasks
1. Add `aiohttp` dependency
2. Create `adapters/webchat_adapter.py` extending `BaseAdapter`
3. Create `static/chat.html` — self-contained chat UI
4. Add webchat config section
5. Wire into `main.py` / CLI `start` command with `--webchat` flag
6. Add to `_adapters` dict like other adapters
---
## 6. Self-Modification
### Can the Runtime Do This?
**Yes.** Both OpenCode and Claude Code have `Write`, `Edit`, and `Bash` tools that can modify any file the process has access to. The agent can already:
- Edit `~/.aetheel/config.json` (via file write tools)
- Create new skill files in `~/.aetheel/workspace/skills/<name>/SKILL.md`
- Update `SOUL.md`, `USER.md`, `MEMORY.md`
- Modify `HEARTBEAT.md` to add/remove periodic tasks
### What Aetheel Needs to Do
The agent just needs to be told it can do this. Update the system prompt:
```
# Self-Modification
- You can edit your own config at ~/.aetheel/config.json
- You can create new skills by writing SKILL.md files to ~/.aetheel/workspace/skills/<name>/SKILL.md
- You can update your identity files (SOUL.md, USER.md, MEMORY.md)
- You can modify HEARTBEAT.md to change your periodic tasks
- After editing config, tell the user to restart for changes to take effect
- After adding a skill, it will be available on next restart (or use /reload if implemented)
```
### 6.1 Hot Reload (Optional Enhancement)
Add a `/reload` command that re-reads config and skills without restart:
```python
if text_lower in ("reload", "/reload"):
cfg = load_config()
_skills.reload()
return "🔄 Config and skills reloaded."
```
### 6.2 Implementation Tasks
1. Update system prompt to mention self-modification capabilities
2. Ensure tools are enabled (see section 2)
3. Add `/reload` command to `ai_handler`
4. Add workspace path to system prompt so agent knows where files are
---
## 7. Agent-to-Agent Communication
### Can the Runtime Do This?
**OpenCode**: Has `Task` tool for spawning subagents. Subagents run in child sessions. No direct inter-session messaging.
**Claude Code**: Has `Task`, `TaskOutput`, `TaskStop` for subagent management, plus `TeamCreate`, `TeamDelete`, `SendMessage` for agent teams. `SendMessage` allows agents to communicate within a team.
### How Inspiration Repos Do It
**NanoClaw**: Uses Claude Code's `TeamCreate` + `SendMessage` tools. The `allowedTools` list includes both. Agent teams allow multiple agents to work together with message passing.
**OpenClaw**: `sessions_send` tool allows one session to send a message to another session. Supports fire-and-forget or wait-for-reply modes. Visibility config controls which sessions can see each other.
### Design for Aetheel
Aetheel already has `SubagentManager` for spawning background tasks. What's missing is the ability for subagents to communicate back to the main agent or to each other.
### 7.1 Approach: Leverage Runtime's Native Tools
Since both OpenCode and Claude Code have subagent/team tools built in, the simplest approach is to enable them:
For Claude Code, add to `allowed_tools`:
```python
["Task", "TaskOutput", "TaskStop", "TeamCreate", "TeamDelete", "SendMessage"]
```
For OpenCode, these are enabled by default.
### 7.2 Aetheel-Level Inter-Subagent Messaging
For Aetheel's own `SubagentManager`, add a message bus:
```python
class SubagentBus:
"""Simple pub/sub for subagent communication."""
def __init__(self):
self._channels: dict[str, list[Callable]] = {}
def subscribe(self, channel: str, callback: Callable):
self._channels.setdefault(channel, []).append(callback)
def publish(self, channel: str, message: str, sender: str):
for cb in self._channels.get(channel, []):
cb(message, sender)
```
Wire into `SubagentManager` so subagents can:
- Publish results to a channel
- Subscribe to messages from other subagents
- Send messages to the originating user via `_send_fn`
### 7.3 Implementation Tasks
1. Enable `TeamCreate`, `SendMessage`, `Task` tools in Claude Code config
2. Update system prompt to mention subagent capabilities
3. Add `SubagentBus` to `agent/subagent.py`
4. Wire bus into `SubagentManager.spawn()` so subagents can communicate
5. Add `/subagents` command to list active subagents and their status
---
## 8. Implementation Order
Ordered by dependency and impact:
### Phase 3A: Enable What Already Exists (1-2 days)
1. **Tool enablement** — Change `no_tools` default, update allowed tools, update system prompt
2. **Self-modification** — Just system prompt changes + `/reload` command
3. **Agent-to-agent** — Enable runtime tools + system prompt
### Phase 3B: New Modules (3-5 days)
4. **CLI interface**`cli.py` with click, move main() logic
5. **Heartbeat system**`heartbeat/` module, `HEARTBEAT.md`, scheduler integration
### Phase 3C: WebChat (3-5 days)
6. **WebChat adapter** — aiohttp server, WebSocket handler, static HTML UI
### Dependencies
```
Tool enablement ──→ Self-modification (needs tools enabled)
──→ Agent-to-agent (needs tools enabled)
──→ Heartbeat (agent needs tools for heartbeat tasks)
CLI ──→ WebChat (webchat flag in CLI)
Heartbeat ──→ (standalone, uses existing scheduler)
```
### New Dependencies to Add
```toml
[project.dependencies]
# ... existing ...
click = ">=8.1.0"
aiohttp = ">=3.9.0"
```
### New Files
```
Aetheel/
├── cli.py # CLI entry point
├── heartbeat/
│ ├── __init__.py
│ └── heartbeat.py # HeartbeatRunner
├── adapters/
│ └── webchat_adapter.py # WebChat adapter
├── static/
│ └── chat.html # WebChat UI
└── ~/.aetheel/workspace/
├── HEARTBEAT.md # Periodic task prompts
└── TOOLS.md # Tool descriptions for agent
```