Migrate from OpenClaw
Guide the user through migrating their OpenClaw installation to NanoClaw. This is a conversation, not a batch job. Read OpenClaw state, discuss it with the user, make judgment calls together about what to bring over and how.
Principle: Never silently copy data. Read it, explain it, discuss where it belongs in NanoClaw's architecture, show proposed changes before applying. Credentials must be masked when displayed (first 4 + ... + last 4 characters). Make judgment calls about what's core vs. reference material.
UX: Use AskUserQuestion for multiple-choice only. Use plain text for free-form input. Don't dump raw data — summarize and explain conversationally.
Migration State File
Create migration-state.md in the project root at the start of Phase 0. Update it after each phase completes. This file is the single source of truth for the migration — if context is compacted or lost, re-read it to recover all decisions and progress.
Before starting any phase, re-read migration-state.md to ensure you have current state.
Sections to maintain (add data as each phase completes):
- Progress — checkbox list of phases (Phase 0–7)
- Discovery — STATE_DIR, IDENTITY_NAME, channels, groups (with JID mappings), workspace files, cron job count, MCP servers
- Decisions — assistant_name, group_model (shared/separate/main-only), main_group (folder + jid)
- Registered Groups — table: folder, jid, channel, is_main
- Settings Migrated — timezone, anthropic_credential (masked), sender_allowlist (created/skipped)
- Identity & Memory — paths of files created, which CLAUDE.md was edited
- Channel Credentials — table: channel, status, env_var
- Scheduled Tasks — table: original_id, name, migrated/deferred
- Deferred / Not Applicable — unsupported channels, discussed customizations, OpenClaw-only features
Keep it factual and terse — this is for machine recovery after compaction, not human reading. Delete the file at the end of Phase 7 (or offer to keep it as a record).
Phase 0: Discovery
Run the discovery script to find and summarize the OpenClaw installation:
pnpm exec tsx ${CLAUDE_SKILL_DIR}/scripts/discover-openclaw.ts
If the user specifies a custom path, pass it: --state-dir <path>
Parse the status block. Key fields: STATUS, STATE_DIR, CHANNELS, WORKSPACE_FILES, DAILY_MEMORY_FILES, SKILL_COUNT, SKILLS, CRON_JOBS, MCP_SERVERS, IDENTITY_NAME, AGENT_COUNT, AGENT_IDS.
Sanity-check the output: The discovery script detects known structures but can silently miss data if OpenClaw's format has changed. Check CONFIG_TOP_KEYS and CONFIG_CHANNEL_KEYS — if you see keys the script didn't report on (e.g. a channel name not in CHANNELS, or a top-level section like integrations or plugins), read that section of the config directly with the Read tool. Also check STATE_DIR_CONTENTS for directories the script doesn't scan (e.g. unexpected folders alongside workspace/, agents/, cron/).
If STATUS=not_found: Tell the user no OpenClaw installation was detected at the standard locations (~/.openclaw, ~/.clawdbot). Ask if they have a custom path. If not, exit.
If STATUS=found: Present a human-readable summary:
- "I found your OpenClaw installation at
<STATE_DIR>." - Identity: name from IDENTITY.md (if found)
- Workspace files: which of SOUL.md, USER.md, MEMORY.md, IDENTITY.md exist
- Channels: list each, note which NanoClaw supports (whatsapp, telegram, slack, discord) and which it doesn't
- Daily memory files: count (if any)
- Skills: count and names (from workspace, shared, personal, project locations)
- Cron jobs: count and names
- MCP servers: count and names
- Agents: count (relevant for Phase 1 groups discussion)
Then explain the key architectural differences. Don't dump a table — paraphrase conversationally:
- Container isolation: NanoClaw runs each agent in an isolated Linux container (Docker or Apple Container). OpenClaw runs everything in one process. This means stronger isolation but also means each group is its own sandbox.
- Group-based memory: In OpenClaw, all groups under one agent share the same SOUL.md, MEMORY.md, and IDENTITY.md. In NanoClaw, each group has its own filesystem and CLAUDE.md. Shared state goes in
groups/global/CLAUDE.md(mounted read-only into all non-main containers). - Channel skills: In OpenClaw, channels are configured in
openclaw.json. In NanoClaw, channels are installed as code via skills (/add-telegram,/add-whatsapp, etc.) and configured through.envvariables. - Simpler config: NanoClaw has no config file — behavior is in the code and
CLAUDE.mdfiles. Credentials live in.envor the OneCLI vault.
AskUserQuestion: "Ready to start migrating? I'll go through each area one at a time."
- Yes, let's go — proceed to Phase 1
- Tell me more — explain more about any area they ask about
- Skip migration — exit
Phase 1: Groups and Architecture
This discussion must happen before identity/memory, because the shared-vs-isolated decision determines where files go.
If GROUP_COUNT > 0 or AGENT_COUNT > 1, this is a critical conversation. Even with just one group, explain the model difference so the user understands what they're getting into.
OpenClaw model: All groups routed to the same agent share one workspace — the same SOUL.md, MEMORY.md, IDENTITY.md, and tools. When you talk to the bot in your family chat or your work chat, it's the same agent with the same personality and memory. Only the session (conversation history) is separate per group.
NanoClaw model: Each group is a completely separate agent running in its own Linux container. Separate filesystem, separate memory, separate CLAUDE.md. The bot in your family chat and your work chat are different agents that don't know about each other — unless you explicitly share state via groups/global/CLAUDE.md, which is mounted read-only into all non-main containers.
Explain this conversationally. If the user only has one group, it's simple — just note the difference and move on. If they have multiple groups, discuss:
AskUserQuestion: "In OpenClaw, your groups shared the same personality and memory. In NanoClaw, each group is a fully separate agent. How would you like to handle this?"
- Shared personality (recommended if your groups had the same bot) — "I'll put the shared personality, identity, and user context in
groups/global/CLAUDE.md. Every group sees it. Each group can add its own customizations on top." - Fully separate — "Each group gets its own independent personality and memory. Complete isolation between groups."
- Just main group for now — "Set up one group now. We can add others later."
Remember this choice — it determines where identity and memory files go in the next phase.
Confirm assistant name
Before registering groups, confirm the assistant name — it's used for trigger patterns and CLAUDE.md templates.
IDENTITY_NAME from discovery gives the OpenClaw name. Ask the user: "Your OpenClaw assistant was named <IDENTITY_NAME>. Want to keep this name in NanoClaw?" If they want a different name, ask what it should be. If IDENTITY_NAME was empty, ask them to choose a name (default: "Andy").
The register step's --assistant-name flag writes ASSISTANT_NAME to .env and updates CLAUDE.md templates automatically — no manual .env write needed.
Registering groups
The discovery script provides detected groups in the GROUPS field (format: channel:id(name)=>nanoclaw_jid). These are extracted from OpenClaw's session store and channel config.
For each group the user wants to bring over, pre-register it:
pnpm exec tsx setup/index.ts --step register -- --jid "<nanoclaw_jid>" --name "<group_name>" --folder "<channel>_<slug>" --trigger "@<confirmed_name>" --channel <channel> --assistant-name "<confirmed_name>"
Only pass --assistant-name on the first registration (i