NanoClaw Architecture Overview
nanoclaw
NanoClaw Architecture Overview
What NanoClaw Does
NanoClaw is a personal AI assistant that runs Claude in isolated Linux containers and makes it accessible through messaging platforms (WhatsApp, Telegram, Slack, Discord, Gmail). You message your assistant from any chat app; it thinks inside a sandboxed container, and replies.
It solves three problems simultaneously:
- Access — Talk to Claude from WhatsApp or Telegram instead of a web UI
- Autonomy — The agent can browse the web, run bash, read/write files, and schedule recurring tasks
- Security — All of the above happens inside OS-level containers, not on your host machine
Target user: Individual developers/power users who want a personal Claude assistant they fully control and can customize by editing code.
Tech Stack
| Layer | Technology | Why |
|---|---|---|
| Language | TypeScript (ES2022, strict) | Type safety, modern async/await, same language host and container |
| Runtime | Node.js 20+ | Single-process, event-loop fits the polling architecture |
| Database | SQLite via better-sqlite3 |
Zero-config, single-file, synchronous API simplifies state management |
| AI Runtime | @anthropic-ai/claude-agent-sdk |
Official SDK for running Claude as an autonomous agent with tool use |
| Container | Docker / Apple Container | OS-level isolation — agents can run bash safely because they're sandboxed |
| IPC Protocol | @modelcontextprotocol/sdk (MCP) |
Standard protocol for exposing tools (scheduling, messaging) to the agent |
| Scheduling | cron-parser |
Parse cron expressions for recurring tasks |
| Credentials | @onecli-sh/sdk |
Gateway-based secret injection — containers never see real API keys |
| Validation | zod (container only) |
Runtime type checking for MCP tool arguments |
Notable non-dependencies: No Express, no Redis, no message queue, no ORM. The entire host process is ~3500 lines across 15 files with 3 runtime dependencies.
Directory Structure
nanoclaw/
├── src/ # Host orchestrator (runs on your machine)
│ ├── index.ts # Main entry point, message loop, startup
│ ├── config.ts # Environment-based configuration
│ ├── db.ts # SQLite schema, queries, migrations
│ ├── container-runner.ts # Spawns containers, parses output streams
│ ├── group-queue.ts # Per-group concurrency and task queuing
│ ├── router.ts # Message formatting and outbound routing
│ ├── ipc.ts # Watches IPC directories for agent requests
│ ├── task-scheduler.ts # Cron/interval/one-time task execution
│ ├── mount-security.ts # Validates volume mounts against allowlist
│ ├── sender-allowlist.ts # Per-group message sender filtering
│ ├── container-runtime.ts # Docker/Apple Container abstraction
│ ├── remote-control.ts # Remote Claude Code session support
│ ├── logger.ts # Structured logging
│ ├── types.ts # Shared type definitions
│ └── channels/ # Channel plugins
│ ├── registry.ts # Plugin registration (Map-based)
│ └── index.ts # Barrel import that triggers self-registration
│
├── container/ # Runs INSIDE the Linux container
│ ├── Dockerfile # Node 22 + Chromium + agent tools
│ ├── build.sh # Container image build script
│ ├── agent-runner/
│ │ └── src/
│ │ ├── index.ts # Agent lifecycle: stdin→SDK→stdout
│ │ └── ipc-mcp-stdio.ts # MCP server exposing 8 tools to the agent
│ └── skills/ # Skills loaded into agent containers
│ ├── agent-browser/ # Web browsing via Chromium
│ ├── capabilities/ # Self-report installed capabilities
│ ├── slack-formatting/ # Slack mrkdwn syntax reference
│ └── status/ # Health check and diagnostics
│
├── groups/ # Per-group persistent storage
│ ├── main/CLAUDE.md # Main channel agent instructions
│ └── global/CLAUDE.md # Shared instructions for all groups
│
├── setup/ # Interactive setup modules
├── scripts/ # Migration runner
├── .claude/skills/ # 47+ installable skill definitions
├── config-examples/ # Example configuration files
└── docs/ # Documentation
System Architecture Diagram
┌─────────────────────────────────────────────────────────────────────┐
│ HOST PROCESS (Node.js) │
│ │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │ WhatsApp │ │ Telegram │ │ Slack │ │ Discord │ │
│ │ Channel │ │ Channel │ │ Channel │ │ Channel │ │
│ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ │
│ │ │ │ │ │
│ └───────────────┼───────────────┼───────────────┘ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ Channel Registry │ (self-registration at startup) │
│ └────────┬─────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ src/index.ts │ │
│ │ ┌──────────┐ ┌──────────┐ ┌───────────┐ │ │
│ │ │ Message │ │ State │ │ Session │ │ │
│ │ │ Loop │──│ (SQLite)│──│ Manager │ │ │
│ │ └────┬─────┘ └──────────┘ └───────────┘ │ │
│ └───────┼─────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ ┌────────────────┐ ┌──────────────────┐ │
│ │ GroupQueue │ │ Task Scheduler │ │ IPC Watcher │ │
│ │ (per-group │ │ (cron/interval │ │ (polls IPC dirs │ │
│ │ concurrency)│ │ /one-time) │ │ for agent cmds) │ │
│ └──────┬───────┘ └───────┬────────┘ └────────┬─────────┘ │
│ │ │ │ │
│ └────────────────────┼──────────────────────┘ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Container Runner │ │
│ │ (spawn, stream, │ │
│ │ timeout, logging) │ │
│ └─────────┬───────────┘ │
│ │ │
└─────────────────────────────┼──────────────────────────────────────┘
│ stdin (JSON) / stdout (markers)
│ + volume mounts + IPC files
▼
┌─────────────────────────────────────────────────────────────────────┐
│ LINUX CONTAINER (Docker/Apple) │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ agent-runner/index.ts │ │
│ │ │ │
│ │ stdin ──► Parse JSON ──► Claude Agent SDK query() ──► │ │
│ │ ▲ │ │ │
│ │ │ ┌───────┼────────┐ │ │
│ │ IPC input/ │ │ │ │ │
│ │ (follow-up ┌──────┴──┐ ┌──┴───┐ ┌──┴────┐ │ │
│ │ messages) │ Bash │ │ Read │ │ Write │ ... │ │
│ │ └─────────┘ └──────┘ └───────┘ │ │
│ │ │ │
│ │ ┌──────────────────────────────────┐ │ │
│ │ │ MCP Server (ipc-mcp-stdio.ts) │ │ │
│ │ │ ┌──────────────┬────────────┐ │ │ │
│ │ │ │ send_message │ schedule_ │ │ │ │
│ │ │ │ │ task │ │ │ │
│ │ │ ├──────────────┼────────────┤ │ │ │
│ │ │ │ list_tasks │ pause_task │ │ │ │
│ │ │ ├──────────────┼────────────┤ │ │ │
│ │ │ │ resume_task │cancel_task │ │ │ │
│ │ │ ├──────────────┼────────────┤ │ │ │
│ │ │ │ update_task │register_ │ │ │ │
│ │ │ │ │group │ │ │ │
│ │ │ └──────────────┴────────────┘ │ │ │
│ │ └──────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ writes JSON files │ │
│ │ /workspace/ipc/{messages,tasks}/ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ /workspace/group/ ← group's persistent storage (read-write) │
│ /workspace/global/ ← shared memory (read-only for non-main) │
│ /workspace/extra/* ← additional validated mounts │
│ /workspace/project/ ← project root (main only, read-only) │
│ Chromium + agent-browser ← web automation │
└─────────────────────────────────────────────────────────────────────┘
Core Data Flow (Happy Path)
User sends "@Andy what's the weather?"
│
▼
Channel SDK (e.g. baileys) receives message
│
▼
onMessage callback → storeMessage() → SQLite messages table
│
▼
Message loop (2s poll) → getNewMessages() → trigger pattern match
│
▼
GroupQueue.enqueueMessageCheck() or GroupQueue.sendMessage()
│ │
│ (no active container) │ (container already running)
▼ ▼
processGroupMessages() Write IPC file to input/
│ │
▼ ▼
runContainerAgent() Agent picks up via drainIpcInput()
│ spawn container
│ write JSON to stdin
│ stream stdout markers
│
▼
Agent SDK query() → Claude thinks → tool calls → result
│
▼
writeOutput() → ---NANOCLAW_OUTPUT_START--- JSON ---NANOCLAW_OUTPUT_END---
│
▼
Host parses marker → onOutput callback → channel.sendMessage()
│
▼
User receives response in chat
Key Design Principles
- Single process, no microservices — One Node.js process manages everything on the host
- Security through OS isolation — Agents run in containers; bash is safe because it's sandboxed
- File-based IPC — Agent communicates with host via JSON files, not network calls
- Polling over webhooks — Simple, reliable, no exposed ports needed
- Skills over features — New capabilities are added via git branch merges, not configuration
- AI-native development — Claude Code handles setup, debugging, and customization