# 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//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 |