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
917 lines
22 KiB
Markdown
917 lines
22 KiB
Markdown
# 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
|
|
```
|