Files
bot/docs/install/migrate-from-openclaw.md
zeekayandHanzo Dev 7d4a2293f1 migrate openclaw holds OpenClaw's approvals under /exec and a merge's exec to who gets in
OpenClaw applies its exec approvals policy after a session's /exec. A /exec in
Hanzo Bot replaces tools.exec, so the import also writes the policy into
~/.bot/exec-approvals.json, the floor Hanzo Bot applies to gateway and node
exec after /exec: each agent gets the stricter of what that file held it to
and what the policy holds it to, nothing else in the file changes, and the
old file is kept as .bak. With BOT_STATE_DIR elsewhere Hanzo Bot would not
read it from the target, so the import stops (RED-EXECPARITY-2).

A merge now bounds exec by who reaches each agent: each channel (with
channels.defaults and its pairing entries), pairing entries of a channel no
config names, and hooks. An audience that admits and routes exactly as the
import alone keeps OpenClaw's exec, one that does so as the existing bot.json
keeps its exec (unset is deny), one that only admits as an install gets that
install's strictest agent, and one that admits as neither denies exec to every
agent it reaches, in tools.exec and, for /exec, in the host floor. A channel
turned off admits no one. The same bound runs on a fresh bot.json when the
target already holds pairing entries (RED-EXECPARITY-1).

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-27 17:36:42 -07:00

37 KiB

summary, read_when, title
summary read_when title
Move an OpenClaw install to Hanzo Bot with one command: what moves, what does not, and what to do by hand
You run OpenClaw and want to switch to Hanzo Bot
You ran `hanzo-bot migrate openclaw` and want to know what it did
You need the OpenClaw to Hanzo Bot command, config or env var mapping
Migrate from OpenClaw

Migrate from OpenClaw

Hanzo Bot is a fork of OpenClaw (formerly Warelay, Clawdbot and Moltbot; MIT). If you run OpenClaw today, one command copies your config, credentials, workspace, sessions, skills and cron jobs into Hanzo Bot. OpenClaw's own files are never changed, so you can go back at any time.

This page was checked against OpenClaw 2026.9.6 (commit eb377ac, npm [email protected]), a real install made with openclaw onboard, and the Hanzo Bot source.

The one command

npm install -g @hanzo/bot         # Node 22 or newer
hanzo-bot migrate openclaw        # shows what would move; writes nothing
hanzo-bot migrate openclaw --apply
hanzo-bot doctor

If hanzo-bot migrate --help says the command is unknown, update first: npm install -g @hanzo/bot@latest.

  • The source is $OPENCLAW_STATE_DIR, else ~/.openclaw, else a ~/.clawdbot from the Clawdbot days that OpenClaw has not moved yet. Point it elsewhere with --from <dir>; an openclaw --profile work install lives in ~/.openclaw-work.
  • The target is Hanzo Bot's state dir: $BOT_STATE_DIR, else ~/.bot. Hanzo Bot never uses ~/.clawdbot: it belongs to OpenClaw, which leaves it as a link to ~/.openclaw.
  • --json prints the plan as JSON (names and counts only, never a secret value).

Without --apply the command reads and prints; nothing is written anywhere. With --apply it performs exactly what the plan listed. Run it again at any time: what is already in place shows as unchanged, and nothing that exists in Hanzo Bot is replaced.

Stop OpenClaw's gateway before starting Hanzo Bot's. Both use port 18789, and a WhatsApp login only stays connected on one of them:

openclaw gateway stop          # `openclaw gateway uninstall` also removes its service
hanzo-bot gateway install      # Hanzo Bot's gateway as a launchd/systemd service
hanzo-bot gateway run          # or in the foreground

What moves

OpenClaw Hanzo Bot Notes
openclaw.json (JSON5, $include resolved) bot.json Converted key by key; see config keys. Merged beneath an existing bot.json: see an existing bot.json.
.env .env OPENCLAW_* keys become BOT_*; values are copied as written, never printed. Keys you already set are kept.
workspace/, workspace-<agent>/ same names Every file, with its mode. A symlink keeps pointing where it pointed; one into ~/.openclaw points at the same place in ~/.bot. A dir that is itself a link elsewhere is copied as files, so the two installs stop sharing it. A link to a dir that is not there (an unmounted drive) is listed, not copied.
skills/, hooks/, tools/ same names Managed skills, installed hooks and tools, as files.
Skill Workshop skills (agents/<id>/agent/workshop-skills/) <agent workspace>/.agents/skills/ Each stays with its agent. One that a managed, workspace or personal skill of the same name hid in OpenClaw, or that a skill already in ~/.bot/skills would hide, stays behind.
credentials/ credentials/ Channel logins (WhatsApp) and pairing allowlists.
Auth profiles (state/openclaw.sqlite, agents/<id>/agent/openclaw-agent.sqlite, or auth-profiles.json) agents/<id>/agent/auth-profiles.json API keys and OAuth tokens, mode 0600. Usage statistics and cooldowns start fresh.
Sessions (session_nodes, transcript_events, or sessions.json + *.jsonl) agents/<id>/sessions/ The session index and every transcript. Earlier generations of a session become <id>.jsonl.reset.<time> archives.
Your cron jobs (cron_jobs or cron/jobs.json) cron/jobs.json Schedule, payload and delivery. Run history starts fresh.
Pairing allowlists (channel_pairing_allow_entries) credentials/<channel>-<account>-allowFrom.json Who may DM your bot. An allowlist file OpenClaw has not folded into its database yet is merged in, as OpenClaw's doctor would.
An agent's heartbeat checklist (cron_job_scratch, formerly HEARTBEAT.md) HEARTBEAT.md in the agent's workspace OpenClaw moved it into its database; Hanzo Bot reads it from the workspace.
hooks.internal.installs bot.json hooks.internal.installs

OpenClaw 2026.5 and later keeps most state in SQLite (state/openclaw.sqlite and one openclaw-agent.sqlite per agent). The importer reads copies of those databases, WAL included, and writes Hanzo Bot's plain files. Older OpenClaw installs keep the same state in files; both layouts are read.

Everything is written inside the Hanzo Bot state dir: bot.json, .env, credentials/ and agents/ are owner-only (0600 files, 0700 directories); copied files keep their mode. Nothing is written into a dir OpenClaw uses: not ~/.openclaw, not a dir it reaches through a link, not a workspace it keeps elsewhere. An agent whose workspace is outside ~/.bot gets its heartbeat checklist and workshop skills in ~/.bot/agents/<id>/from-openclaw/, and the plan says where to move them. So do an agent's workshop skills when its workspace keeps .agents/ as a link into such a dir (your dotfiles, say). A config path spelled through Clawdbot's ~/.clawdbot, or through any other link into ~/.openclaw, is read as the ~/.openclaw path it reaches, so it becomes the ~/.bot copy. A workspace in a part of ~/.openclaw the import does not copy stays where it is, and both installs use it.

What does not move

The plan lists each of these under Not carried, with the reason.

What Why What to do
OpenClaw plugins (extensions/, npm/, git/, plugins.installs, plugins.load.paths) Built for OpenClaw's plugin SDK. A load path that holds a bot.plugin.json is kept. hanzo-bot plugins list shows what Hanzo Bot ships.
Secrets in OpenClaw's secret store ({ "source": "store" } refs) Encrypted with OpenClaw's key. The setting that used it stays, without the value. Enter the value again, in bot.json or ~/.bot/.env.
credentials/auth-profiles/ Encrypted with OpenClaw's key. Sign in again: hanzo-bot models auth or hanzo-bot configure.
Cron jobs OpenClaw created itself (heartbeat, memory dreaming, skill review) They carry a declarationKey; Hanzo Bot schedules its own heartbeat. Nothing.
Paired devices and nodes (devices/, identity/, nodes/) Pairing is per gateway. hanzo-bot qr, then pair again.
Approved commands and the approvals socket (exec-approvals.json) Per gateway. The policy in it moves: see Exec policy. Approve again when asked.
Search indexes (memory/, qmd/), caches, media, logs, tmp/, backups Rebuilt or runtime-only. Nothing.
Config keys Hanzo Bot does not have Listed one by one. See what OpenClaw has that Hanzo Bot does not.

Commands

The CLI is hanzo-bot (also installed as bot). Most commands keep their OpenClaw name.

OpenClaw Hanzo Bot
openclaw onboard hanzo-bot onboard
openclaw gateway run | install | start | stop | status | uninstall hanzo-bot gateway run | install | start | stop | status | uninstall
openclaw channels add | login | status hanzo-bot channels add | login | status
openclaw models status | set hanzo-bot models status | set
openclaw agent, openclaw message send hanzo-bot agent, hanzo-bot message send
openclaw cron, openclaw automations hanzo-bot cron
openclaw exec-approvals, openclaw approvals hanzo-bot approvals
openclaw chat, openclaw terminal hanzo-bot tui (connects to the running gateway)
openclaw skills install <slug> npx clawhub install <slug> --workdir ~/.bot/workspace
openclaw skills list | info | check hanzo-bot skills list | info | check
openclaw doctor, status, health, logs, config, configure, sessions, memory, hooks, plugins, secrets, security, sandbox, pairing, devices, nodes, node, qr, update, uninstall the same name under hanzo-bot
openclaw migrate (imports into OpenClaw) hanzo-bot migrate openclaw (imports OpenClaw into Hanzo Bot)

These OpenClaw commands have no Hanzo Bot counterpart: attach, audit, backup, capability/infer, connect, database, exec-policy, file-transfer, fleet, mcp, promos, proxy, resume, tasks, telemetry, transcripts, triage, users, worker, worktrees.

Config keys

Most of openclaw.json means the same thing in bot.json and is copied as it is. Settings OpenClaw moved after Hanzo Bot forked from it go back to where Hanzo Bot reads them:

openclaw.json bot.json
agents.entries.<id> agents.list[] with id: "<id>": see Agents
agents.defaults.modelPolicy.allow agents.defaults.models (the allowlist), keeping each model's alias and params
agents.defaults.models with no modelPolicy.allow dropped: in OpenClaw it is a catalog, in Hanzo Bot a non-empty map restricts models
agents.defaults.embeddedAgent, agents.list[].embeddedAgent …embeddedPi
memory.search, agents.list[].memory.search agents.defaults.memorySearch, agents.list[].memorySearch
tts messages.tts
attachments media
gateway.nodes.commands.allow, .deny gateway.nodes.allowCommands, .denyCommands
plugins.entries.canvas.config.host canvasHost
plugins.entries.brave.config.webSearch.apiKey tools.web.search.apiKey
plugins.entries.{perplexity,google,xai,moonshot}.config.webSearch tools.web.search.{perplexity,gemini,grok,kimi}
browser.profiles.<name>.driver: "openclaw" driver: "clawd"; a profile with no color gets one from Hanzo Bot's palette
tools.exec.mode, agents.list[].tools.exec.mode tools.exec.security and .ask: see Exec policy
"${OPENCLAW_X}" anywhere in a value "${BOT_X}"
~/.openclaw/... anywhere in a value ~/.bot/...
meta dropped (OpenClaw's write stamp)

A setting that already exists at its new place wins over the old one. Keys Hanzo Bot does not have are dropped and listed. The result is checked with Hanzo Bot's own validator (schema and plugin checks) before it is written, so bot.json always loads; a value it rejects is dropped and listed rather than written.

Agents

A message no binding matches goes to one agent. The import keeps it the agent OpenClaw gave it:

  • A legacy agents.list (no agents.entries, no agents.ownership: "explicit") is read as every OpenClaw before 2026.9 read it, and as Hanzo Bot reads it: the first default: true agent, else the first agent.
  • With agents.entries, each agent is the entry under its key; an agents.list beside it is not read. With agents.ownership: "explicit", the agent agents.defaults.systemAgent.agentId names gets default: true, else a sole agent answers. Otherwise the one default: true entry, else a sole agent.
  • OpenClaw 2026.9 gives no agent at all to such a message when several agents are configured and none is named, and it does not load a roster its schema rejects (an id inside an entry, agents.list beside agents.entries, two default: true entries, a system agent or binding naming an agent the roster lacks). Hanzo Bot always has a default agent, so the import stops, writes nothing, and names the setting to fix.
  • The import also stops when a binding would be dropped, or would name an agent bot.json lacks, since its messages would then go to the default agent.

Exec policy

Hanzo Bot has no tools.exec.mode. The import writes the security and ask each mode stands for in OpenClaw, at the root and in each agent:

tools.exec.mode tools.exec.security tools.exec.ask
deny deny off
allowlist allowlist off
ask, auto allowlist on-miss
full full off
  • As in OpenClaw, a mode wins over a security or ask set beside it. Those are dropped and listed.
  • Hanzo Bot has no model reviewer, so a command auto would have sent to one asks you instead.
  • A mode OpenClaw does not accept (OpenClaw would not load that config) imports as deny. Do by hand names it. So does a security, ask or host it does not accept (with no mode), and a tools or tools.exec that is not an object: that agent, or the root, imports as deny.
  • OpenClaw runs exec with no security set as full; Hanzo Bot denies host exec with none. The plan names each agent this leaves without exec under Do by hand; set tools.exec.security to allow it. hanzo-bot doctor names them too.

OpenClaw also bounds exec with the policy in its exec approvals: exec-approvals.json, or the exec_approvals_config table of state/openclaw.sqlite in newer versions. The import folds that policy's security and ask (its defaults, its "*" entry, and each agent's own entry) into tools.exec, the way OpenClaw applies it to commands run on the gateway or a node: each agent gets the stricter of its config and the policy's value for it. It never loosens a setting, and Renamed lists each change. Where the root is tightened for the agents the policy's "*" and defaults govern, an agent whose own entry OpenClaw let run more keeps that value on its own entry. OpenClaw ran no exec with a policy it could not read, or with an exec-approvals.json (or openclaw doctor --fix's claim on it, even a link to nothing) that doctor had not yet moved into its database. For either, the import sets security to deny and says so under Do by hand. Approved commands are not carried; approve them again when asked.

OpenClaw applies that policy after a session's /exec, so no /exec lifts it. A /exec in Hanzo Bot replaces tools.exec, so the import also writes the policy into ~/.bot/exec-approvals.json, which Hanzo Bot applies to gateway and node exec after /exec, exactly where OpenClaw applies its own. Each agent there gets the stricter of what that file already held it to and what the policy holds it to; nothing else in the file changes, and the old file is kept as exec-approvals.json.bak. Hanzo Bot reads that file from ~/.bot whatever its state dir, so with BOT_STATE_DIR set elsewhere the import stops rather than leave the policy behind: import with it unset.

Elevated exec (/elevated full) never raises tools.exec.security: as in OpenClaw, it skips approvals only where the policy is already full with ask: "off", and deny stays deny. An agent with no security on the default host is denied with elevated as without.

An existing bot.json

When ~/.bot/bot.json exists, the converted config is merged beneath it and the old file is kept as bot.json.bak:

  • A value you set wins. The plan names each key where your value differs from OpenClaw's, under Do by hand, instead of listing it as moved.
  • Except where it would let a sender run exec that neither install gave them. OpenClaw's channels and bindings are added to yours, so each agent's tools.exec security and ask are held to no looser than the import alone gives that agent. Where your agents.list, bindings or default agent send messages elsewhere than OpenClaw did, the default agent and the agents your bindings name are held to the strictest agent OpenClaw ran. And for each channel (with its pairing allowlists) and your hooks: senders it lets in exactly as OpenClaw did keep OpenClaw's exec, senders it lets in exactly as your bot.json did keep yours, and a channel that now lets in senders neither did (your open Telegram beside OpenClaw's owner-only exec, say) denies exec to every agent it reaches, in bot.json and in ~/.bot/exec-approvals.json, so a /exec cannot lift it either. An agent either install ran with host: "sandbox" gave its senders no exec on the host, so where the merge moves it to another host its exec is denied. Do by hand names each tools.exec this changed; loosen it by hand if you mean to.
  • Values Hanzo Bot's first run wrote (from running hanzo-bot before the import) give way to OpenClaw's: the workspace, the gateway mode and bind, and, when the import brings your own Anthropic key (in any agent's auth profiles, in .env, or in the config), the route that sent Anthropic calls through api.hanzo.ai. The sign-in token the first run stored for that route is then removed from every agent, so it never goes to Anthropic.
  • A route through api.hanzo.ai that you changed stays; the plan then says your imported key goes there too.
  • The merged file is checked again. An imported key that would stop it loading next to your settings is dropped and listed.

Environment variables

Every OPENCLAW_<NAME> in ~/.openclaw/.env is written as BOT_<NAME> in ~/.bot/.env. OPENCLAW_STATE_DIR, OPENCLAW_CONFIG_PATH, OPENCLAW_HOME and OPENCLAW_PROFILE point at the OpenClaw install and are not carried. Provider keys such as ANTHROPIC_API_KEY, OPENAI_API_KEY or TELEGRAM_BOT_TOKEN keep their names.

OpenClaw Hanzo Bot
OPENCLAW_STATE_DIR (~/.openclaw) BOT_STATE_DIR (~/.bot)
OPENCLAW_CONFIG_PATH (~/.openclaw/openclaw.json) BOT_CONFIG_PATH (~/.bot/bot.json)
openclaw --profile <name> (~/.openclaw-<name>) hanzo-bot --profile <name> (~/.bot-<name>)
OPENCLAW_HOME BOT_HOME
OPENCLAW_GATEWAY_TOKEN, OPENCLAW_GATEWAY_PORT BOT_GATEWAY_TOKEN, BOT_GATEWAY_PORT
OPENCLAW_LOG_LEVEL, OPENCLAW_HIDE_BANNER, OPENCLAW_SKIP_CHANNELS BOT_LOG_LEVEL, BOT_HIDE_BANNER, BOT_SKIP_CHANNELS

Variables you export in your shell profile or service unit are not touched; rename them there the same way. If bot.json reads a ${BOT_*} that no .env sets, the plan says which.

Channels

Channel After the import
WhatsApp The login in credentials/whatsapp/<account>/ moves as it is (both use Baileys 7). Keep one gateway on it: WhatsApp logs out the other.
Telegram, Discord, Slack, Google Chat, Microsoft Teams, Mattermost, LINE, Feishu, Zalo, IRC, Twitch, Nostr, Nextcloud Talk, Synology Chat, Tlon Tokens in bot.json or .env move. A token that was in OpenClaw's secret store is entered again.
Signal signal-cli keeps its keys in its own data dir, which both installs use. Nothing to do; run one gateway at a time.
iMessage, BlueBubbles Configuration only; nothing to re-pair.
Matrix, Zalo Personal From an OpenClaw 2026.5+ install their session was in OpenClaw's database: sign in again with hanzo-bot channels login. From an older install the session files move.
OpenClaw-only channels (SMS, FaceTime, Google Meet, Zoom and Teams meetings) Not in Hanzo Bot; listed as not carried.

Check them all with hanzo-bot channels status --probe.

Skills

Skills are folders with a SKILL.md, the same format in both. Your workspace skills (workspace/skills/) and managed skills (skills/) move as files, so they load in Hanzo Bot the same way. Skill Workshop skills belong to one agent: each goes to that agent's workspace as a project skill (.agents/skills/), which only that agent loads. A skill of yours with the same name as a Hanzo Bot bundled skill overrides it, as it did in OpenClaw.

Skills installed from ClawHub keep their .clawhub/ origin and lock files, so the ClawHub CLI still updates them in their new place:

npx clawhub update --all --workdir ~/.bot/workspace
npx clawhub install <slug> --workdir ~/.bot/workspace

Hanzo Bot reads a skill's gating (requires.bins, requires.env, os) from metadata.bot, metadata.hanzo-bot, or ClawHub's metadata.openclaw, so ClawHub skills are checked the same way they were.

Models and keys

Your provider keys and your model choice move unchanged: an OpenClaw install on anthropic/claude-opus-5 with an Anthropic key is a Hanzo Bot install on the same model with the same key. If you ran hanzo-bot before importing, its first-run route for Anthropic through api.hanzo.ai gives way to your key, wherever OpenClaw kept it (see an existing bot.json). hanzo-bot models status shows what it resolved.

Hanzo Bot can also route every model through the Hanzo gateway at api.hanzo.ai: one key for many providers, billed to your Hanzo account. Create a key (see API keys), then:

echo 'HANZO_API_KEY=<your key>' >> ~/.bot/.env
hanzo-bot models set hanzo/enso

hanzo/<model> (and zen/<model>) refer to models served through the gateway. A fresh install with no config asks you to sign in with a Hanzo account on first run.

Hanzo account

OpenClaw has no account. Hanzo Bot works without one too, with your own keys. A Hanzo account (hanzo.id) adds the model gateway above. Running hanzo-bot with no arguments signs you in and opens a launch menu; its Run Locally starts the gateway on the bot.json you have, such as the one the import wrote, without rewriting it and with that config's own auth, bind and Tailscale settings, as hanzo-bot gateway run would.

On a machine with an OpenClaw install and no bot.json, the first hanzo-bot command shows the import commands and asks before setting up a fresh install; without a terminal to ask on, it stops there.

What Hanzo Bot adds

  • The Hanzo model gateway: hanzo/* and zen/* models through api.hanzo.ai.
  • Sign-in with a Hanzo account (hanzo.id).
  • hanzo-bot social: post and schedule through Hanzo Social.
  • The flow plugin: typed workflows with resumable approvals.

What OpenClaw has that Hanzo Bot does not

  • The commands listed under Commands as having no counterpart.
  • Config blocks: mcp (MCP servers), accessGroups, cloudWorkers, desktop, proxy, security, surfaces, telemetry, transcripts, worktreeRoot, worktreeAcceleration. The import drops them and lists each one.
  • OpenClaw's plugin SDK and its plugin catalog; the provider plugins it split out (Anthropic, OpenAI and the rest are built into Hanzo Bot, so plugins.entries.anthropic and the like are dropped).
  • The SMS, FaceTime and meeting channels.
  • An encrypted secret store; Hanzo Bot reads secrets from bot.json, .env, the environment, or a file or command (secrets in bot.json).

Going back

Nothing in ~/.openclaw, or in any dir OpenClaw reaches from it, was changed. hanzo-bot doctor lists OpenClaw's gateway service, even one still labelled com.clawdbot.gateway from the Clawdbot days, and never removes it. Stop Hanzo Bot's gateway and start OpenClaw's:

hanzo-bot gateway stop
openclaw gateway start         # `openclaw gateway install` if you uninstalled its service

To remove the import, delete ~/.bot (or only what the plan created).

Troubleshooting

  • no OpenClaw install at …: pass the right dir with --from, for example hanzo-bot migrate openclaw --from ~/.openclaw-work.
  • A conflict line in the plan: that file already exists in Hanzo Bot with different content. Hanzo Bot's copy is kept; the plan names each file. Move yours by hand if you want it.
  • Port 18789 in use: OpenClaw's gateway is still running. openclaw gateway stop, or uninstall its service with openclaw gateway uninstall.
  • WhatsApp logged out: two gateways were connected to the same account. Stop one, then hanzo-bot channels login --channel whatsapp if needed.
  • A channel says it has no token: it was in OpenClaw's secret store. Set it again.
  • … leads into …, which OpenClaw uses, through the link …: a dir in ~/.bot is a link into ~/.openclaw or another dir OpenClaw uses (a shared workspace, say), so the import would change OpenClaw's files. The message names the link: replace it with a plain dir (a copy of what it points at), then run again.
  • OpenClaw would not load … or OpenClaw answers a message no binding matches with no agent: fix the named setting in openclaw.json (openclaw doctor --fix repairs most, or mark the agent that should answer with default: true), then run again. Nothing was written.
  • the import would send messages to other agents than OpenClaw did: a binding Hanzo Bot would drop or cannot route. Fix it in openclaw.json, then run again.
  • Exec denied after the move: with no sandbox configured, Hanzo Bot runs exec on the host only as tools.exec.security allows, and denies it when that is unset. Set it to "allowlist" or "full"; hanzo-bot doctor names each agent. OpenClaw's exec approvals policy also holds in ~/.bot/exec-approvals.json: loosen it there too if you mean to.
  • OpenClaw's exec approvals policy has to go to ~/.bot/exec-approvals.json: BOT_STATE_DIR points elsewhere. Run the import with it unset.
  • bot.json would not load after the merge: your existing bot.json and OpenClaw's config clash in a way no single imported key explains. Move bot.json aside, import, then copy your settings back.
  • compressed transcript events need Node 22.15 or newer: OpenClaw stores large transcript events zstd-compressed; run the import with Node 22.15+.
  • hanzo-bot doctor notices an OpenClaw install and prints the commands above, with or without a bot.json; it is safe to ignore once you have moved.