Migrate From V1 logo

Migrate From V1

OrganizationPopular
nanocoai
migrate-from-v1

Finish migrating a NanoClaw v1 install into v2. Run after `bash migrate-v2.sh` completes. Seeds the owner, migrates legacy memory, reconciles container configs, and helps port custom v1 code. Triggers on "migrate from v1", "finish migration", "v1 migration".

Overview

Publishernanocoai
Repositorynanoclaw
Skill namemigrate-from-v1
Stars
30.9K
Forks
12.8K
Bundled files
Instructions only
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by nanocoai on GitHub. Read the source before you install it.

Installation

Install the Migrate From V1 AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/nanocoai/nanoclaw/tree/main/.claude/skills/migrate-from-v1
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/nanocoai/nanoclaw.git /tmp/nanoclaw
mkdir -p .claude/skills
cp -r /tmp/nanoclaw/.claude/skills/migrate-from-v1 .claude/skills/migrate-from-v1
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Migrate From V1 in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Migrate From V1 on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Migrate From V1 is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Finish v1 → v2 migration

bash migrate-v2.sh already ran the deterministic migration. It handled:

  • .env keys merged
  • v2 DB seeded (agent_groups, messaging_groups, wiring)
  • Group folders copied (v1 CLAUDE.md → v2 CLAUDE.local.md)
  • Session data copied with conversation continuity (incl. Claude Code memory + JSONL transcripts)
  • Scheduled tasks ported
  • Channel code installed and auth state copied (incl. WhatsApp Baileys keystore)
  • WhatsApp LIDs resolved from store/auth and aliased into messaging_groups
  • Container skills copied
  • Container image built

Your job is the parts that need human judgment: triage failed steps, seed the owner, run the shared-memory migration, reconcile configs, and port fork customizations.

Read logs/setup-migration/handoff.json first — it has overall_status, per-step results in steps, and a followups list.

Preflight: was the script run?

Before anything else, check that logs/setup-migration/handoff.json exists. If it doesn't, the user is invoking this skill before migrate-v2.sh ran. Stop and tell them, verbatim:

This skill finishes a migration that migrate-v2.sh started. Run that first, in your terminal — not from inside Claude:

bash
bash migrate-v2.sh

It needs interactive prompts (channel selection, service switchover) and runs Node/pnpm bootstrap, Docker, OneCLI setup, and a container build that don't fit inside a Claude session. When it finishes, it'll hand control back to Claude automatically — at which point this skill picks up.

Do not attempt to run the script yourself, simulate its effects, or pick up the migration mid-stream. The deterministic side has dependencies on a real interactive shell.

Once handoff.json exists, proceed to Phase 0.

Phase 0: Get v2 routing real messages

Before any deeper migration work, prove v2 actually answers messages on the user's real channels. v1 is paused, not touched — flipping back is a service restart.

0a — Fix blockers only

Walk handoff.steps. Fix only the failures that would stop the bot from routing one message; defer the rest to its later phase.

0b — Smoke test, then continue

Tell the user the switch is non-destructive (v1 is paused, not modified; reverting is one command). Help them stop v1's service unit and start v2's, tail the host log for a clean boot, and have them send a real test message. Use AskUserQuestion to confirm the bot responded.

If yes, continue to Phase 1. If no, diagnose from logs/nanoclaw.log and re-test — don't proceed to deeper work on a broken router.

Deferred failures

Re-visit anything you skipped in 0a before declaring the migration done. Most surface naturally in later phases (1c-groups ↔ Phase 2, 1e-tasks ↔ task verification).

Phase 1: Owner and access

v2 auto-creates a users row for every sender it sees (via extractAndUpsertUser in src/modules/permissions/index.ts). By the time this skill runs, the owner's row likely already exists — it just needs the owner role granted.

User ID format: always <channel_type>:<platform_handle>. Each channel populates this differently:

  • Telegram: telegram:<numeric_user_id> (e.g. telegram:6037840640)
  • Discord: discord:<snowflake_user_id> (e.g. discord:123456789012345678)
  • WhatsApp: whatsapp:<phone>@s.whatsapp.net (e.g. whatsapp:14155551234@s.whatsapp.net)
  • Slack: slack:<user_id> (e.g. slack:U04ABCDEF)
  • Others: <channel_type>:<platform_id>

Steps:

  1. Query users table: SELECT id, kind, display_name FROM users.
  2. If exactly one user exists, confirm: AskUserQuestion: "Is <display_name> (<id>) you?" — Yes / No, let me type it.
  3. If multiple users exist, present them as options in AskUserQuestion.
  4. If no users exist yet (service hasn't received a message), ask the user to send a test message first, then re-query.
  5. Once confirmed, check user_roles via getUserRoles(userId). If an owner row already exists, skip. Otherwise grant it with grantRole. grantRole inserts a new row per call, so the getUserRoles check keeps this re-runnable.

Use the DB helpers in src/modules/permissions/db/user-roles.ts (getUserRoles, grantRole). Init the DB first, then call the helpers:

ts
import { closeDb, initDb } from '../src/db/connection.js';
import { runMigrations } from '../src/db/migrations/index.js';
import { CENTRAL_DB_PATH } from '../src/config.js';
import { getUserRoles, grantRole } from '../src/modules/permissions/db/user-roles.js';

const db = await initDb(CENTRAL_DB_PATH);
try {
  await runMigrations(db); // idempotent

  const userId = '<user_id>';
  if (!(await getUserRoles(userId)).some((r) => r.role === 'owner')) {
    await grantRole({
      user_id: userId,
      role: 'owner',
      agent_group_id: null, // owner role must be global
      granted_by: null,
      granted_at: new Date().toISOString(),
    });
  }
} finally {
  await closeDb();
}

Access policy

After seeding the owner, discuss the access policy. v2's messaging_groups.unknown_sender_policy controls who can interact with the bot. migrate-v2.sh set it to public so the bot would respond during the switchover test, but the user may want to tighten it.

Present the options via AskUserQuestion:

  1. Public (public, current) — anyone can message the bot. Good for personal DM bots.
  2. Known users only (strict) — only users the access gate accepts (owner, admin, or agent_group_members) can trigger the bot. Others are silently dropped.
  3. Approval required (request_approval) — unknown senders trigger an approval request to the owner. Good for group chats where you want to vet new members.

The unknown_sender_policy column accepts exactly these three values; use the parenthesized value for <chosen_policy> below.

If the user picks option 2 or 3, seed the known users from v1's message history. The v1 database is at <handoff.v1_path>/store/messages.db. It has a messages table with sender and sender_name columns. For each group:

sql
-- v1: unique senders per chat (excluding bot messages)
SELECT DISTINCT sender, sender_name
FROM messages
WHERE chat_jid = '<v1_jid>' AND is_from_me = 0 AND sender IS NOT NULL

The sender value is a platform handle (e.g. 6037840640 for Telegram). Build the v2 user ID by inferring the channel type from the chat JID prefix (use parseJid from setup/migrate-v2/shared.ts) and combining: <channel_type>:<sender>.

For each sender:

  1. Upsert into users(id, kind, display_name) if not already present.
  2. Insert into agent_group_members(user_id, agent_group_id) for each agent group wired to that messaging group.

Show the user the list of senders being imported and let them deselect any they don't want.

Then update the messaging groups:

sql
UPDATE messaging_groups SET unknown_sender_policy = '<chosen_policy>'
WHERE id IN (SELECT id FROM messaging_groups WHERE channel_type IN (<migrated_channels>))

Phase 2: Migrate legacy memory

Run /migrate-memory for the imported groups. It quiesces each group, moves the v1 CLAUDE.local.md into the shared memory/ tree without reading it during staging, then has the invoking coding harness distill standing identity into instructions.prepend.md and durable facts into Core Memory or focused linked files before the NanoClaw group runs again.

Do not duplicate that migration logic here. Record each group's result in the handoff before continuing.

Phase 3: Container config

migrate-v2.sh writes container.json directly from v1's container_config (the additionalMounts shape is identical). If the v1 config was unparseable, it falls back to a .v1-container-config.json sidecar.

For each group, check:

  1. If container.json exists, read it and verify the additionalMounts host paths are still valid on this machine. Flag any that don't exist.
  2. If .v1-container-config.json exists (parse failure fallback), read it, discuss with the user, and write a proper container.json. Then delete the sidecar.
  3. Check for env or packages fields — env may overlap with OneCLI vault, packages (apt/npm) are portable.

Phase 4: Fork customizations

Check whether the user's v1 install was a customized fork.

bash
cd <v1_path>
git remote -v
git log --oneline <upstream>/main..HEAD 2>/dev/null

If no commits ahead of upstream: stock v1, skip this phase.

If there are commits:

  1. Show the commit list to the user.
  2. AskUserQuestion: "How do you want to handle your v1 customizations?"
    • Copy portable items (recommended) — copy container/skills/*, .claude/skills/*, docs/*. Grep each copied file for v1-only references that won't resolve in v2 and flag them to the user: workspace paths (/workspace/group/, /workspace/project/, /workspace/ipc/, /workspace/extra/), the v1 IPC mechanism, registered_groups / is_main, the v1 sender allowlist, and store/messages.db.
    • Full walkthrough — go commit by commit, decide together.
    • Reference only — stash to docs/v1-fork-reference/ for later.
  3. Source code (src/*, container/agent-runner/src/*) is NOT portable — v2's architecture is fundamentally different. Stash to docs/v1-fork-reference/ with a README explaining what each file did. Don't translate.

Principles

  • v1 checkout is read-only. Never modify files under handoff.v1_path.
  • Show before writing. Show diffs or proposed content before modifying standing instructions, memory, or container.json.
  • Mask credentials when displaying (first 4 + ... + last 4 characters).
  • handoff.json is the recovery point. If context gets compacted, re-read it and git status to recover state.

Setup steps you can run

The setup flow at setup/index.ts has individual steps you can invoke if something is missing or failed:

bash
pnpm exec tsx setup/index.ts --step <name>
StepWhen to use
onecliOneCLI not installed or not healthy
authNo Anthropic credential in vault
containerContainer image needs rebuild
serviceService not installed or not running
mountsMount allowlist missing
verifyEnd-to-end health check (run after everything else)
environmentSystem check (Node, dirs)

When done

  1. Run the verify step to confirm everything works:
    bash
    pnpm exec tsx setup/index.ts --step verify
  2. Delete logs/setup-migration/handoff.json — offer to save as docs/migration-<date>.md first.
  3. Restart the service if running so changes take effect. The v2 service label is install-specific (nanoclaw-v2-<slug> / com.nanoclaw-v2-<slug>), so derive it from src/install-slug.ts rather than guessing:
    bash
    # Linux
    UNIT=$(pnpm exec tsx -e "import{getSystemdUnit}from'./src/install-slug.js';console.log(getSystemdUnit())")
    systemctl --user restart "$UNIT"
    # macOS
    LABEL=$(pnpm exec tsx -e "import{getLaunchdLabel}from'./src/install-slug.js';console.log(getLaunchdLabel())")
    launchctl kickstart -k "gui/$(id -u)/$LABEL"

Frequently asked questions

What does the Migrate From V1 AI skill do?

Finish migrating a NanoClaw v1 install into v2. Run after `bash migrate-v2.sh` completes. Seeds the owner, migrates legacy memory, reconciles container configs, and helps port custom v1 code. Triggers on "migrate from v1", "finish migration", "v1 migration".

Why use Migrate From V1 on TypingMind?

Because you install it once and use it with any model. Migrate From V1 is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Migrate From V1 in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/nanocoai/nanoclaw/tree/main/.claude/skills/migrate-from-v1. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use Migrate From V1?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Migrate From V1?

As many as you like. As long as a model supports skills, you can use Migrate From V1 with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Migrate From V1 AI skill free?

Yes. It is published on GitHub by nanocoai under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇