Skip to content

Instantly share code, notes, and snippets.

@yanji84
Created February 22, 2026 18:58
Show Gist options
  • Select an option

  • Save yanji84/ebc72e9b02553786418c2c24829752c7 to your computer and use it in GitHub Desktop.

Select an option

Save yanji84/ebc72e9b02553786418c2c24829752c7 to your computer and use it in GitHub Desktop.
ZeroClaw Migration Assessment - Technical analysis of migrating from OpenClaw to ZeroClaw for AI agent hosting

ZeroClaw Migration Assessment (Public)

Date: 2026-02-22 Server: Cloud VPS (4 vCPU, 8GB RAM, 80GB disk, Ubuntu 24.04) ZeroClaw version tested: v0.1.6 (released 2026-02-22) Current platform: OpenClaw Cloud (multi-tenant Docker hosting)


Table of Contents

  1. Background & Motivation
  2. The "Claws" Landscape
  3. Candidate Comparison
  4. Why ZeroClaw
  5. Deep Architecture Analysis
  6. Phase 0 Validation (Live Server)
  7. Migration Architecture Design
  8. Risk Assessment
  9. Go/No-Go Decision
  10. Next Steps

1. Background & Motivation

Simon Willison reported on Andrej Karpathy's discussion of "Claws" — an emerging category of AI agents that run on personal hardware, use messaging protocols, and can both respond to instructions and schedule autonomous tasks. Karpathy described OpenClaw as a "400K lines of vibe-coded monster" and pointed to several lighter alternatives.

Current Resource Problem

Our OpenClaw Cloud platform runs on a single cloud VPS (8GB RAM). Each customer bot consumes ~420-440MB RAM in an always-running Docker container capped at 1GB. With 4 active bots plus the admin bot and api-router, we're using 3.6GB of 7.6GB available — leaving room for only 1-2 more bots before hitting limits.

Component Actual RAM Usage
openclaw-bot-1 418 MB
openclaw-bot-2 419 MB
openclaw-bot-3 422 MB
openclaw-bot-4 437 MB
api-router 36 MB
Total containers ~1.7 GB
OS + portal + cron + admin bot ~1.9 GB
Grand total ~3.6 GB

MAX_BOTS is set to 6, but in practice we can run 4-5 before the server becomes memory-constrained.

What Makes It Heavy

The heaviness is NOT our platform code — portal (3 npm deps, 2700 lines), api-router (zero deps, 460 lines), cron (750 lines), and manage.sh (2000 lines) are all lean. The heavy part is the OpenClaw SDK itself: a 2.48GB Docker image running Node.js 22 with the full openclaw gateway, which idles at ~420MB per bot.


2. The "Claws" Landscape

Five alternatives were identified from Karpathy's discussion and community coverage:

Project Language Core LOC RAM/bot WhatsApp Telegram Channels Security Model
NanoClaw TypeScript ~3,900 ~50MB host + shared containers Yes (Baileys) Yes (skill) 2-3 OS containers + mount allowlist
nanobot Python ~4,000 ~100MB Yes Yes 8+ Application-level
ZeroClaw Rust <5MB Cloud API + Web (feature flag) Yes 17+ Workspace scoping + ChaCha20
IronClaw (NEAR) Rust unknown No Yes (WASM) 5 WASM sandbox + credential injection
PicoClaw Go <10MB No Yes 6 Minimal

Elimination

  • IronClaw: No WhatsApp support. Eliminated.
  • PicoClaw: No WhatsApp support. Eliminated.
  • nanobot: Python, academic project, no container isolation, no group privacy. Weaker candidate.

Finalists: NanoClaw vs ZeroClaw

Both support WhatsApp and Telegram. The key differentiators:

Factor NanoClaw ZeroClaw
Same language as our platform Yes (TypeScript/Node.js) No (Rust)
Memory search (vector + FTS5) No (file-only, grep) Yes (built-in, same 70/30 hybrid)
Resource efficiency ~50MB/bot <5MB/bot
WhatsApp library Baileys (proven) wa-rs (3 days old)
Config approach Source code modification TOML config (hot-reload)
Workspace file conventions Different Same as OpenClaw
Migration command No zeroclaw migrate openclaw
Plugin system None (modify source) None (recompile Rust)

3. Candidate Comparison

NanoClaw Deep Dive

Source: github.com/qwibitai/nanoclaw

Architecture: Single Node.js host process orchestrating ephemeral Docker containers. One container spawned per conversation turn (not always-running). Claude Agent SDK runs inside containers. Max 5 concurrent containers via GroupQueue.

Process model:

WhatsApp (Baileys) → storeMessage() → SQLite → Poll (2s)
  → GroupQueue → docker run → agent-runner/index.ts
  → Claude Agent SDK query() → stdout → host → WhatsApp sendMessage()

Memory: File-based only (CLAUDE.md per group). No vector search, no embeddings, no FTS5. Agent can grep its own group folder but has no semantic search.

WhatsApp: Baileys 7.0-rc9 in the host process. Known issues: no exponential backoff on reconnect (#183), no connection state machine, text + captions only (no media #184), QR hang on Node 20 (#157).

Key strength: ~15 source files, 9 runtime dependencies, auditable in an afternoon. Same language as our platform.

Key weakness: No memory search. Bots with 50+ memory files lose the "remember when I said..." experience entirely. This is our biggest UX sacrifice.

Multi-tenant implications: NanoClaw is single-bot. Multi-tenant requires N separate NanoClaw instances, each with its own Baileys WhatsApp connection, SQLite database, and process. No built-in HTTP API — would need to add Express server for portal integration.

ZeroClaw Deep Dive

Source: github.com/zeroclaw-labs/zeroclaw (17k stars, 2k forks, 27+ contributors)

Architecture: Single Rust binary (~16MB). Three modes: agent (CLI), gateway (HTTP), daemon (gateway + channels + heartbeat + scheduler). One process per bot, single-threaded async (Tokio).

Trait-driven modules: Eight swappable traits:

Trait Implementations
Provider 22+: Anthropic, OpenAI, OpenRouter, Ollama, Groq, Mistral, DeepSeek, etc.
Channel 9+: Telegram, Discord, Slack, WhatsApp (Cloud + Web), Matrix, IRC, iMessage, Email
Memory SQLite (hybrid), Markdown, PostgreSQL, Lucid, None
Tool Shell, file_read/write, memory ops, browser, content_search, cron
Observer Noop, Log, Prometheus, OpenTelemetry
Runtime Native, Docker (sandboxed)
Security Autonomy levels (readonly/supervised/full)
Tunnel Cloudflare, Tailscale, ngrok

Memory system: Built-in hybrid search — 70% vector (cosine similarity) + 30% FTS5 (BM25 keyword). Same weights as our memory-privacy plugin. Embedding via OpenAI text-embedding-3-small or custom endpoint. SQLite storage with embedding cache (LRU, 10k entries). No external dependencies.

WhatsApp: Dual-mode. Cloud API (official, requires Meta Business Account) or Web mode (wa-rs native Rust client, requires --features whatsapp-web build flag). Web mode merged 2026-02-19, QR rendering bug fixed 2026-02-22.

Config: TOML-based, stored at ~/.zeroclaw/config.toml. Hot-reloadable for provider, model, API key. Full JSON Schema available via zeroclaw config schema.

Security: Five layers:

  1. Network: 127.0.0.1 bind, rate limiting, 64KB body limit
  2. Auth: 6-digit OTP pairing, channel allowlists, HMAC webhook validation
  3. Authorization: Three autonomy levels, workspace scoping, command allowlist
  4. Runtime: Optional Docker sandbox, read-only rootfs, memory/CPU limits
  5. Data: ChaCha20-Poly1305 AEAD encryption at rest

Scheduling: HEARTBEAT.md (identical concept to ours) + cron tool (zeroclaw cron add "0 9 * * *" --tz "Asia/Shanghai" "task").

Maturity concerns: v0.1.6, pre-1.0. 261 .unwrap() calls across 80 files (panic risk). std::sync::Mutex in async contexts (6 modules — potential deadlocks). Only 3 integration tests. Releases every 1-2 days.


4. Why ZeroClaw

ZeroClaw was selected as the primary candidate over NanoClaw for three reasons:

  1. Memory search preservation: ZeroClaw has built-in hybrid vector + FTS5 search with the exact same 70/30 weighting we use. NanoClaw has no search at all — only file-based grep.

  2. Resource efficiency: 28x lighter than OpenClaw (15MB vs 420MB actual, measured). Potential to run 50-100 bots on the same server instead of 4-5.

  3. OpenClaw compatibility: identity.format = "openclaw" reads the same workspace files (SOUL.md, AGENTS.md, etc.). zeroclaw migrate openclaw command exists and works. Same HEARTBEAT.md concept.

NanoClaw remains the fallback if ZeroClaw's WhatsApp Web support proves unworkable — same language as our platform makes it easier to modify.


5. Deep Architecture Analysis

Current OpenClaw Cloud Architecture

Internet → Proxy (:80/:443) → Portal Express (:3000) → Docker Daemon
                                    |                     ├── api-router (:9090, 36MB)
                                    |                     ├── openclaw-bot1 (:19000, ~420MB)
                                    |                     ├── openclaw-bot2 (:19001, ~420MB)
                                    |                     └── ...
                                    |
                                    +→ cron.js (every 1m)
                                    +→ admin API endpoints

Admin Bot (host, :18000, ~940MB) → exec tool → manage.sh → containers

Proposed ZeroClaw Architecture

Internet → Proxy (:80/:443) → Portal Express (:3000) → systemd
                                    |                     ├── api-router (:9090, native)
                                    |                     ├── zeroclaw@bot1 (:19000, ~15MB)
                                    |                     ├── zeroclaw@bot2 (:19001, ~15MB)
                                    |                     └── ...
                                    |
                                    +→ cron.js (every 1m)
                                    +→ admin API endpoints

zeroclaw-admin (host, :18000, ~15MB) → same workspace

Component Mapping

Current Component Lines Decision ZeroClaw Equivalent
api-router/server.js 460 KEEP unchanged Move from container to native process. Change api-router:9090127.0.0.1:9090
portal/server.js 2700 ADAPT Replace docker composesystemctl, docker execzeroclaw CLI
portal/cron.js 750 ADAPT Replace docker compose pssystemctl list-units, adapt session capture
portal/public/admin.html 2900 ADAPT File paths change, JSON → TOML config format
manage.sh 2000 ADAPT Abstract Docker calls to functions, add systemd backend
templates/ all ADAPT Add config.toml.template, keep workspace templates
docker-compose.yml 220 REPLACE systemd unit template zeroclaw@.service
Dockerfile 35 DROP Pre-built binary, no image needed
Plugins (datetime) 104 DEFER ZeroClaw may have built-in equivalent
Plugins (bot-relay) 924 DEFER No hook system in ZeroClaw, needs upstream Rust PR or sidecar
Plugins (memory-privacy) 638 DEFER No plugin system, would need upstream contribution

systemd Unit Template

Replaces per-bot docker-compose entries:

# /etc/systemd/system/zeroclaw@.service
[Unit]
Description=ZeroClaw bot %i
After=network.target

[Service]
Type=simple
User=zeroclaw
Group=zeroclaw
WorkingDirectory=/opt/zeroclaw/customers/%i
ExecStart=/usr/local/bin/zeroclaw daemon --config-dir /opt/zeroclaw/customers/%i
EnvironmentFile=/opt/zeroclaw/customers/%i/gateway.env
Restart=on-failure
RestartSec=5
MemoryMax=64M
CPUQuota=50%
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
NoNewPrivileges=true
ReadWritePaths=/opt/zeroclaw/customers/%i

[Install]
WantedBy=multi-user.target

Portal Command Mapping

Current (Docker) Proposed (systemd)
docker compose up -d openclaw-<name> systemctl start zeroclaw@<name>
docker compose stop openclaw-<name> systemctl stop zeroclaw@<name>
docker compose restart openclaw-<name> systemctl restart zeroclaw@<name>
docker logs -f openclaw-<name> journalctl -u zeroclaw@<name> -f
docker inspect --format '{{.State.Health}}' curl http://localhost:<port>/health
unbuffer docker exec -t ... openclaw channels login unbuffer zeroclaw channel add whatsapp '...'
docker compose ps --format '{{.Name}}' systemctl list-units zeroclaw@* --state=active

API Key Isolation

Keep centralized proxy (recommended). The api-router is battle-tested, tiny (36MB actual), and provides tamper-proof cost tracking. The only change:

# Bot's config.toml — point to api-router
[models.anthropic]
api_url = "http://127.0.0.1:9090"    # was http://api-router:9090 (Docker bridge)
api_key = "irt_<hex>"                  # same internal token scheme

ZeroClaw has api_url and api_key top-level config fields that override provider defaults. The api-router validates the irt_ token, swaps for the real Anthropic key, proxies the request, and extracts cost from the response. No changes to api-router itself.

Cron Adaptation

Responsibility Still Needed? Changes Required
Owner channel capture Yes Major: ZeroClaw has no sessions.json. Need alternative data source.
Federation heartbeat Yes None — reads federation.json, independent of bot runtime.
New bot detection Yes Replace docker compose ps with systemctl list-units.
Bot dropoff Yes Same detection logic, different liveness check.
Trial enforcement Yes Replace docker compose stop with systemctl stop.

Memory System Comparison

Aspect OpenClaw (current) ZeroClaw
Storage SQLite main.sqlite SQLite brain.db
Vector Gemini embeddings via api-router OpenAI/custom embeddings
FTS5 Built-in Built-in
Hybrid weights 70% vector / 30% keyword 70% vector / 30% keyword (configurable)
Chunking 400 tokens, 80 overlap 512 max tokens
Privacy filtering memory-privacy plugin Not implemented
Config memorySearch.remote.{baseUrl,apiKey} memory.embedding_provider, memory.embedding_model

6. Phase 0 Validation (Live Server)

All tests performed on the production server (Cloud VPS) on 2026-02-22.

6.1 Installation

gh release download v0.1.6 --repo zeroclaw-labs/zeroclaw \
  --pattern 'zeroclaw-x86_64-unknown-linux-gnu.tar.gz' --dir /tmp/
tar xzf /tmp/zeroclaw-x86_64-unknown-linux-gnu.tar.gz
cp zeroclaw /usr/local/bin/zeroclaw
  • Binary size: 16 MB
  • Version: zeroclaw 0.1.6
  • Auto-initialized: Created ~/.zeroclaw/config.toml (311 lines, full defaults)

6.2 Doctor Diagnostics

zeroclaw doctor

Results: 21 ok, 4 warnings, 1 error.

  • Config file found and parsed
  • Provider "openrouter" valid, no API key set (expected)
  • No channels configured (expected)
  • Detected: git 2.43.0, Python 3.12.3, Node 22.22.0, Docker 29.2.1
  • 8 CLI tools discovered

6.3 Gateway Server

Started with zeroclaw gateway -p 42617:

🦀 ZeroClaw Gateway listening on http://127.0.0.1:42617
  🌐 Web Dashboard: http://127.0.0.1:42617/
  POST /pair      — pair a new client
  POST /webhook   — {"message": "your prompt"}
  GET  /api/*     — REST API (bearer token required)
  GET  /ws/chat   — WebSocket agent chat
  GET  /health    — health check
  GET  /metrics   — Prometheus metrics

Health endpoint (GET /health):

{
  "status": "ok",
  "paired": false,
  "runtime": {
    "pid": 4191084,
    "uptime_seconds": 1,
    "components": {
      "gateway": {
        "status": "ok",
        "restart_count": 0,
        "last_ok": "2026-02-22T18:34:18Z"
      }
    }
  }
}

Webhook endpoint (POST /webhook):

  • Accepts {"message": "..."} JSON
  • Returns {"error": "LLM request failed"} without API key (expected)
  • Confirms the endpoint is functional

Web Dashboard: Embedded React SPA served at /. Built-in, no separate install.

6.4 Config Format

TOML only. No JSON config support. The zeroclaw config schema command outputs full JSON Schema (useful for validation tooling but the config file must be TOML).

Key config sections relevant to our platform:

# Provider routing
default_provider = "anthropic"
default_model = "anthropic/claude-sonnet-4-20250514"
api_url = "http://127.0.0.1:9090"      # API router
api_key = "irt_<hex>"                    # Internal token

# Gateway
[gateway]
port = 42617
host = "127.0.0.1"
require_pairing = false

# Memory (hybrid search)
[memory]
backend = "sqlite"
embedding_provider = "none"              # or "openai", "custom:URL"
vector_weight = 0.7
keyword_weight = 0.3

# Identity (OpenClaw compatible)
[identity]
format = "openclaw"

# Heartbeat
[heartbeat]
enabled = true
interval_minutes = 30

# Cost tracking (self-reported)
[cost]
enabled = true
daily_limit_usd = 10.0
monthly_limit_usd = 100.0

# Channels
[channels_config.whatsapp]
session_path = "~/.zeroclaw/state/wa-session.db"   # Triggers Web mode
allowed_numbers = ["*"]

[channels_config.telegram]
bot_token = "123456:ABC..."
allowed_users = ["*"]

6.5 WhatsApp Web Support

BLOCKER: The pre-built binary does NOT include WhatsApp Web.

When configuring [channels_config.whatsapp] with session_path (Web mode trigger):

WARN zeroclaw::channels: WhatsApp Web backend requires 'whatsapp-web' feature.
     Enable with: cargo build --features whatsapp-web

The WhatsApp Web channel was merged on 2026-02-19 (PR #859), hardened in PR #1059, and had a QR rendering bug fixed on 2026-02-22 (PR #1371). However:

  • Not in default build: Cargo.toml default features are empty. The CI release builds do not include --features whatsapp-web.
  • GitHub issue #1301: Open, requesting features be included in Docker/release builds. Zero comments as of 2026-02-22.
  • Library: Uses wa-rs v0.2 (native Rust), NOT Baileys. Custom SQLite storage backend for Signal Protocol keys.
  • No Rust on server: Would need to install Rust toolchain and compile from source (~15-30 min, 2-4GB RAM during build).
  • Known gaps: No media attachments (PR #1267 open), LID-to-phone normalization issues (PR #1295 open).

WhatsApp Cloud API (alternative mode): Works in pre-built binary. Set phone_number_id and access_token instead of session_path. But requires Meta Business Account — not compatible with our personal-number QR pairing flow.

6.6 Migration Test

Tested with real customer data:

zeroclaw migrate openclaw \
  --config-dir /tmp/zeroclaw-migrate-test \
  --source /path/to/openclaw/workspace \
  --dry-run

Dry run output:

🔎 Dry run: OpenClaw migration preview
  Source: /path/to/openclaw/workspace
  Target: /tmp/zeroclaw-migrate-test/workspace
  Candidates: 59
    - from sqlite:   0
    - from markdown: 59

Actual migration:

✅ OpenClaw memory migration complete
  Imported:          59
  Skipped unchanged: 0
  Renamed conflicts: 0

Migrated database (brain.db):

Table Rows Purpose
memories 59 Main store (id, key, content, category, embedding, timestamps)
memories_fts 59 FTS5 full-text index
embedding_cache 0 Empty (no embedding provider configured)

Sample migrated memory:

[core] openclaw_openclaw_core_3: 出生日期:2026-02-19
[core] openclaw_openclaw_core_8: **每日HN总结**:已设置凌晨1点自动发送前一天Hacker News热门总结
[core] openclaw_openclaw_core_11: **个性调整**:从"witty companion"切换至"efficient assistant"模式

Memory CLI works:

zeroclaw memory stats → 59 total (48 daily, 11 core)
zeroclaw memory list  → All entries listed with categories

Migration imports memory only — does not copy SOUL.md, AGENTS.md, etc. Workspace files would need separate copying during provisioning.

6.7 Resource Usage

Measured on live server with ps and docker stats:

Metric OpenClaw (per bot) ZeroClaw (gateway) Ratio
RSS Memory 418-437 MB 15 MB 28x lighter
Virtual Memory 426 MB (mapped, not resident)
Peak RSS 15 MB
Threads ~20+ 7 3x fewer
Binary/Image ~2.5 GB Docker image 16 MB binary 156x smaller

Capacity projection:

Scenario OpenClaw ZeroClaw
RAM per bot (idle) 420 MB 15 MB
Available RAM for bots ~4 GB ~4 GB
Max bots (theoretical) ~9 ~266
Max bots (practical, with overhead) 4-5 50-100

6.8 Session Storage

ZeroClaw does NOT have a sessions.json equivalent. Conversation history is kept in-memory (up to max_history_messages: 50) and optionally in runtime-trace.jsonl (observability mode). There is no persistent session transcript file that maps conversation participants to their channel identities.

This means our cron's captureOwnerChannels() function — which reads OpenClaw's sessions.json to discover the owner's Telegram ID from their first DM — has no data source in ZeroClaw.

State files found:

  • daemon_state.json: Component health, PID, uptime. No user/session data.
  • workspace/state/memory_hygiene_state.json: Memory cleanup timestamps.
  • workspace/state/runtime-trace.jsonl: Empty until observability enabled.

7. Migration Architecture Design

What We Keep (unchanged or minimal adaptation)

  • api-router (460 lines, zero deps) — tamper-proof cost tracking, token isolation
  • Template system — same {{PLACEHOLDER}} substitution, same personality directories
  • Workspace files — SOUL.md, AGENTS.md, HEARTBEAT.md, TOOLS.md, USER.md, MEMORY.md
  • Identity system — identity.json schema, trust levels, cron mutations, sync views
  • Trial system — trial.json, cost limits, goodbye messages via usage.json
  • Invite/purge/trash — same business logic, different process commands
  • LUKS encryption — orthogonal to process management
  • Reverse proxy — unchanged

What We Replace

Current Replacement
docker compose up/stop/restart systemctl start/stop/restart zeroclaw@<name>
docker logs journalctl -u zeroclaw@<name>
docker exec ... openclaw Direct zeroclaw CLI calls
docker inspect health checks curl localhost:<port>/health + systemctl is-active
docker-compose.yml (append/remove entries) systemd unit files (enable/disable)
openclaw.json per bot config.toml per bot
Container security (cap_drop, read_only, non-root) systemd sandboxing (ProtectSystem, NoNewPrivileges)
Docker bridge network localhost networking

What We Drop

  • Dockerfile — no image builds
  • docker-compose.yml — replaced by systemd units
  • OpenClaw SDK (2.48GB image) — replaced by 16MB binary
  • 1GB/bot memory allocation — down to ~15MB actual

Config Template

New templates/config.toml.template replacing templates/openclaw.json.template:

default_provider = "anthropic"
default_model = "anthropic/claude-sonnet-4-20250514"
default_temperature = 0.7
api_url = "http://127.0.0.1:9090"
api_key = "{{INTERNAL_TOKEN}}"

[gateway]
port = {{GATEWAY_PORT}}
host = "127.0.0.1"
require_pairing = true

[heartbeat]
enabled = true
interval_minutes = 30

[memory]
backend = "sqlite"
embedding_provider = "custom:http://127.0.0.1:9090/gemini/v1beta"
vector_weight = 0.7
keyword_weight = 0.3

[identity]
format = "openclaw"

[cost]
enabled = true
daily_limit_usd = {{COST_LIMIT}}

[channels_config.whatsapp]
session_path = "state/wa-session.db"
allowed_numbers = []

[channels_config.telegram]
bot_token = "{{TELEGRAM_BOT_TOKEN}}"
allowed_users = []

[autonomy]
level = "supervised"
workspace_only = true
allowed_commands = ["git", "npm", "ls", "cat", "grep", "find", "echo", "pwd", "wc", "head", "tail", "date"]

Phased Migration Plan

Phase 0: Validate (completed — this document)

Phase 1: Parallel Infrastructure (2-3 days)

  1. Create systemd unit template zeroclaw@.service
  2. Adapt manage.sh: abstract Docker commands behind functions, add systemd backend
  3. Create config.toml.template
  4. Move api-router to native process (or --network host)
  5. Both Docker and systemd bots coexist

Phase 2: Portal Adaptation (2-3 days)

  1. Update server.js: docker composesystemctl, docker execzeroclaw CLI
  2. Update cron.js: process detection, session capture adaptation
  3. Update admin backend: file paths, JSON → TOML config reads/writes
  4. Test full onboarding flow end-to-end

Phase 3: First Live Migration (1 day)

  1. Pick least-active bot for testing
  2. Stop Docker container → zeroclaw migrate openclaw → start systemd service
  3. Coordinate WhatsApp re-pairing with owner (credentials are device-bound, cannot migrate)
  4. Monitor 24 hours

**Phase 4: Gradual Rollout

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment