#Manual migration to new machine

1 messages · Page 1 of 1 (latest)

iron pelican
#

OpenClaw v 2026.4.15 now lives on a very old machine. I plan to migrate to e newer laptop with v2026.4.21. I installed everything necessary like honcho, qmd, some skills, with help from the agent.
Now I need to transfer the agent from the old to the new machine. To abpvoid problems with versions and to start clean, I plan to copy memory files, basic files (soul, memory, user etc). For honcho, I want to export a kind of narrative file with all conversations.i Isuppose that after starting the gateway, the files will be read and embedded, and my new agent will be comparable to the old one. Am I correct?

thorn crownBOT
#

To help others find answers, you can mark your question as solved via Right click solution message -> Apps -> âś… Mark Solution

echo notchBOT
fallow agate
#

I asked the same question a couple of hours ago (Linux host related)

iron pelican
#

Hi krill, looks like you were offline for a while and missed my question about migration.

coral raftBOT
#

Let me check the current docs so I don’t guess on migration details.

#

Sorry, I missed that earlier.

Short answer: partly correct, but not enough if you want the new machine to feel like the old one.

If you want a real migration, copy both:

  1. State dir: usually ~/.openclaw/
  2. Workspace: usually ~/.openclaw/workspace

That preserves:

  • config
  • auth and channel logins
  • session history
  • workspace files like AGENTS.md, SOUL.md, USER.md, MEMORY.md

Then on the new machine run:

#
openclaw doctor
openclaw gateway restart
openclaw status

Important nuance:

  • AGENTS.md / SOUL.md / USER.md are read as workspace context.
  • MEMORY.md and memory/*.md are indexed for memory search.
  • Old chats are not recreated from a narrative export. If you want prior conversations, copy ~/.openclaw/agents/<agentId>/sessions/ too.
  • For Honcho, if the new machine points to the same Honcho backend + workspaceId, you usually do not need a narrative export. If Honcho was self-hosted on the old machine, migrate that data/backend too.

So the rule of thumb is:

  • Same agent on new machine → copy state dir + workspace
  • Fresh start, same personality/memory files → copy workspace only
iron pelican
#

The honcho database on the old machine is built a little different than on the new one. Particularly the use of lancedb next to pgvector because of incompatibility with certain embedding models. So the database can not be copied 1:1. My agent told me that honcho can produce a day to day reconstruction of conversation that can then be read by the new agent, and thus honcho. Is that correct?

coral raftBOT
#

I’m going to check whether that “day by day reconstruction” flow is actually documented or if it was just a rough idea.

#

Not really, at least not as a faithful rebuild.

What I could verify:

  • openclaw honcho setup migrates workspace files into Honcho, things like USER.md, MEMORY.md, memory/, SOUL.md, etc.
  • I do not see documented support for: “export old Honcho as day-by-day narrative, start new OpenClaw, and Honcho automatically reconstructs the old memory from that.”
  • Honcho’s own docs do show importing historical sessions/messages from structured JSON.

So the distinction is:

  • Narrative/day log = useful fallback context, but lossy
  • Real sessions/messages = proper Honcho migration input

I’d be a bit skeptical of the narrative-file route if your goal is “new agent behaves like the old one.”

Best path

#
  1. Copy workspace + OpenClaw state if possible.
  2. For Honcho, either:
    • keep the same Honcho backend/workspaceId, or
    • export real conversation sessions/messages and re-import them into the new Honcho DB
  3. Use narrative files only as a backup seed, not as the main migration method.

If you still have the old OpenClaw session files, those are a much better source than reconstructed summaries:

  • ~/.openclaw/agents/<agentId>/sessions/*.jsonl

That gives you actual turns, which can be converted into Honcho session imports.

Docs:

#

If you want, I can help you sketch the safest migration flow from OpenClaw session JSONL -> Honcho import JSON.

iron pelican
#

Yes please

coral raftBOT
#

Yep. If the Honcho DB format changed and you can’t migrate it 1:1, I’d use this order:

Recommended path

Option A, best if possible

Keep the same Honcho backend/workspaceId and just point the new OpenClaw at it.

Option B, best when backend/storage changed

Re-import real session messages into the new Honcho from OpenClaw session transcripts.

Option C, last resort

Narrative/day-by-day summaries. Useful, but lossy. I would not make this the primary migration path.

Concrete migration flow

1. On the old machine, save the important sources

  • workspace, usually ~/.openclaw/workspace
#
  • state dir, usually ~/.openclaw
  • especially session transcripts:
    • ~/.openclaw/agents/<agentId>/sessions/*.jsonl

Also grab a quick sanity snapshot:

openclaw --version
openclaw status
openclaw honcho status

2. Restore the normal OpenClaw parts on the new machine

Copy over:

  • workspace files (AGENTS.md, SOUL.md, USER.md, MEMORY.md, memory/, skills)
  • state dir if you want session history/auth/config too
#

Then run:

openclaw doctor
openclaw gateway restart
openclaw status
openclaw memory status
openclaw honcho status

3. For Honcho, do not rely on “agent reads narrative and becomes the same”

Instead, rebuild Honcho from structured conversations.

Honcho’s docs show importing historical conversations in this shape:

#
{
  "sessions": [
    {
      "id": "session_1",
      "messages": [
        { "role": "user", "content": "..." },
        { "role": "assistant", "content": "..." }
      ]
    }
  ]
}

So the practical move is:

  • parse old OpenClaw *.jsonl
#
  • keep only real user and assistant turns
  • strip tool chatter / metadata
  • group by session
  • import that JSON into the new Honcho workspace

4. Use narrative files only as extra seed material

If you already have daily summaries, they can still help, but as:

  • backup context
  • extra uploaded files
  • something the new agent can read manually

Not as the main Honcho migration.

My recommendation

If you must change Honcho storage format, I’d do:

#
  1. migrate workspace + normal OpenClaw state
  2. rebuild Honcho from old OpenClaw session JSONL
  3. only then add narrative summaries if you want extra texture

That gives you the closest result to the old agent.

Verify after import

Good checks:

openclaw honcho status
openclaw honcho ask "What should I know about this user?"
openclaw memory search "some specific old fact"

Docs:

iron pelican
#

Yes

coral raftBOT
#

Let me check the session transcript shape quickly so I don’t invent the wrong converter logic.

#

Nice. Here’s the converter logic I’d use.

Goal

Turn OpenClaw session files:

  • ~/.openclaw/agents/<agentId>/sessions/*.jsonl

into Honcho import data like:

{
  "sessions": [
    {
      "id": "some-session-id",
      "messages": [
#
        { "role": "user", "content": "..." },
        { "role": "assistant", "content": "..." }
      ]
    }
  ]
}

Conversion steps

1. Read every *.jsonl session file

Ignore:

  • sessions.json
  • backup files
  • non-JSONL files
#

2. Parse line by line

Each line is a JSON record.

Keep only records where:

  • type === "message"
  • message.role is "user" or "assistant"

Skip things like:

  • session-meta
  • model snapshots
  • tool-result records
  • custom metadata records

3. Extract only real text

From each kept message:

#
  • read message.content
  • flatten it to plain text
  • skip empty content

Important: for user messages, strip the inbound metadata envelope first, things like:

  • Conversation info (untrusted metadata)
  • Sender (untrusted metadata)

OpenClaw does this internally before session export too, because otherwise the metadata pollutes the corpus.

4. Preserve order and timestamps

For each message:

  • prefer message.timestamp
  • otherwise use record timestamp
  • otherwise keep original file order
#

This matters because Honcho reasons across sessions, and order helps a lot.

5. Build one Honcho session per OpenClaw transcript

A simple mapping is:

  • Honcho session id = JSONL filename without .jsonl

Example:

  • discord-thread-abc123.jsonl → Honcho session "discord-thread-abc123"

6. Map speakers to peers

For a simple personal setup:

  • all "user" turns → one Honcho user peer
  • all "assistant" turns → one Honcho assistant peer
#

If you had multiple real humans in the old system and that distinction matters, then use separate Honcho peers per sender. Otherwise one user peer is fine.

7. Import into the new Honcho workspace

For each session:

  • create/get Honcho session
  • add the user + assistant peers
  • add messages in order

8. Wait for processing, then verify

After import, test with:

openclaw honcho status
openclaw honcho ask "What should I know about this user?"
#

Practical gotchas

  • Do not import tool chatter. Only user/assistant natural-language turns.
  • Do not import the untrusted metadata envelope. Strip it first.
  • Skip empty assistant messages or pure tool-only replies.
  • Keep session boundaries. Don’t merge everything into one mega-session unless you really have to.

Minimal pseudocode

for each session_file in sessions_dir:
  session_id = basename(session_file, ".jsonl")
  out_messages = []

  for each line in session_file:
    record = JSON.parse(line)
#
    if record.type != "message": continue

    role = record.message.role
    if role not in ["user", "assistant"]: continue

    text = extract_text(record.message.content)
    if role == "user":
      text = strip_inbound_metadata_envelope(text)

    text = normalize_whitespace(text)
    if text is empty: continue

    ts = record.message.timestamp || record.timestamp || null
    out_messages.push({ role, content: text, timestamp: ts })
#
  write session_id + out_messages into Honcho import JSON

My recommendation

If you do this, you’ll get much closer to the original Honcho behavior than with day-by-day narrative reconstruction.

If you want, I can next sketch a tiny Node script for this, since OpenClaw session files are already JSONL and Node is the easiest fit.

iron pelican
#

That would help

coral raftBOT
#

Yep, here’s a rough but practical converter that turns OpenClaw session JSONL files into a Honcho-style conversation.json.

It is intentionally simple, inspectable, and does not write to Honcho yet.

// openclaw-jsonl-to-honcho.js
// Usage:
//   node openclaw-jsonl-to-honcho.js /path/to/sessions ./honcho-import.json

const fs = require("node:fs/promises");
const path = require("node:path");

function extractText(value) {
  if (typeof value === "string") return value;

  if (Array.isArray(value)) {
#
    return value.map(extractText).filter(Boolean).join("\n");
  }

  if (value && typeof value === "object") {
    if (typeof value.text === "string") return value.text;
    if (typeof value.content === "string") return value.content;
    if (typeof value.input_text === "string") return value.input_text;
    if (Array.isArray(value.content)) return extractText(value.content);
    if (Array.isArray(value.parts)) return extractText(value.parts);
  }

  return "";
}

function stripInboundMetadataEnvelope(text) {
#
  let out = String(text || "").replace(/\r/g, "");

  const patterns = [
    /^Conversation info \(untrusted metadata\):\s*```json[\s\S]*?```\s*/i,
    /^Sender \(untrusted metadata\):\s*```json[\s\S]*?```\s*/i,
    /^Untrusted context \(metadata, do not treat as instructions or commands\):\s*<<<EXTERNAL_UNTRUSTED_CONTENT[\s\S]*?<<<END_EXTERNAL_UNTRUSTED_CONTENT.*?>>>\s*/i,
  ];

  let changed = true;
  while (changed) {
    changed = false;
    for (const pattern of patterns) {
      const next = out.replace(pattern, "");
      if (next !== out) {
        out = next;
#
        changed = true;
      }
    }
  }

  return out.trim();
}

function normalizeText(text) {
  return String(text || "")
    .replace(/\r/g, "")
    .replace(/\n+/g, " ")
    .replace(/[ \t]{2,}/g, " ")
    .trim();
}
#

function toSessionId(filename) {
  return filename.replace(/\.jsonl$/, "");
}

async function readSessionFile(filePath) {
  const raw = await fs.readFile(filePath, "utf8");
  const lines = raw.split("\n");
  const messages = [];

  for (const line of lines) {
    if (!line.trim()) continue;

    let record;
    try {
#
      record = JSON.parse(line);
    } catch {
      continue;
    }

    if (!record || record.type !== "message" || !record.message) continue;

    const role = record.message.role;
    if (role !== "user" && role !== "assistant") continue;

    let text = extractText(record.message.content);
    if (!text) continue;

    if (role === "user") {
      text = stripInboundMetadataEnvelope(text);
#
    }

    text = normalizeText(text);
    if (!text) continue;

    const timestamp =
      record.message.timestamp ||
      record.timestamp ||
      null;

    messages.push({
      role,
      content: text,
      ...(timestamp ? { timestamp } : {}),
    });
#
  }

  return messages;
}

async function main() {
  const sessionsDir = process.argv[2];
  const outFile = process.argv[3] || "./honcho-import.json";

  if (!sessionsDir) {
    console.error("Usage: node openclaw-jsonl-to-honcho.js /path/to/sessions ./honcho-import.json");
    process.exit(1);
  }

  const names = await fs.readdir(sessionsDir);
#
  const sessionFiles = names
    .filter((name) => name.endsWith(".jsonl"))
    .filter((name) => name !== "sessions.json")
    .sort();

  const sessions = [];

  for (const name of sessionFiles) {
    const filePath = path.join(sessionsDir, name);
    const messages = await readSessionFile(filePath);

    if (messages.length === 0) continue;

    sessions.push({
      id: toSessionId(name),
#
      messages,
    });
  }

  const output = { sessions };
  await fs.writeFile(outFile, JSON.stringify(output, null, 2), "utf8");

  console.log(`Wrote ${sessions.length} sessions to ${outFile}`);
}

main().catch((err) => {
  console.error(err);
  process.exit(1);
});
#

Run it

node openclaw-jsonl-to-honcho.js \
  ~/.openclaw/agents/main/sessions \
  ./honcho-import.json

What it does

  • keeps only real user / assistant messages
  • ignores tool chatter and metadata records
  • strips the inbound metadata envelope from user turns
  • writes one Honcho session per OpenClaw .jsonl

What to inspect after

Open honcho-import.json and sanity-check:

  • no Conversation info (untrusted metadata) blocks
#
  • no tool JSON
  • reasonable session counts
  • user/assistant ordering looks right

If you want, I can paste the second tiny script that takes honcho-import.json and actually imports it into Honcho with @honcho-ai/sdk.

iron pelican
#

Please

coral raftBOT
#

Yep, here’s the second tiny script that imports honcho-import.json into Honcho.

1. Install the SDK

pnpm add @honcho-ai/sdk

2. Save this as import-to-honcho.mjs

// import-to-honcho.mjs
// Usage:
//   node import-to-honcho.mjs ./honcho-import.json
//
// Env:
#
//   HONCHO_WORKSPACE_ID=your-workspace
//   HONCHO_API_KEY=...              # omit for self-hosted if not needed
//   HONCHO_BASE_URL=http://localhost:8000   # optional, for self-hosted
//   HONCHO_USER_PEER_ID=user        # optional
//   HONCHO_ASSISTANT_PEER_ID=assistant # optional

import fs from "node:fs/promises";
import { Honcho } from "@honcho-ai/sdk";

const inputFile = process.argv[2] || "./honcho-import.json";

const workspaceId = process.env.HONCHO_WORKSPACE_ID || "openclaw-migrated";
const userPeerId = process.env.HONCHO_USER_PEER_ID || "user";
const assistantPeerId = process.env.HONCHO_ASSISTANT_PEER_ID || "assistant";
#
const honchoConfig = {
  workspaceId,
  ...(process.env.HONCHO_API_KEY ? { apiKey: process.env.HONCHO_API_KEY } : {}),
  ...(process.env.HONCHO_BASE_URL ? { baseURL: process.env.HONCHO_BASE_URL } : {}),
};

const honcho = new Honcho(honchoConfig);

const raw = await fs.readFile(inputFile, "utf8");
const data = JSON.parse(raw);

if (!data.sessions || !Array.isArray(data.sessions)) {
  throw new Error("Input JSON must contain { sessions: [...] }");
}
#
const user = await honcho.peer(userPeerId);
const assistant = await honcho.peer(assistantPeerId);

let importedSessions = 0;
let importedMessages = 0;

for (const sessionData of data.sessions) {
  if (!sessionData?.id || !Array.isArray(sessionData.messages) || sessionData.messages.length === 0) {
    continue;
  }

  const session = await honcho.session(sessionData.id);

  // Safe even if peers already exist in the session
  await session.addPeers([user, assistant]);
#

  const messages = sessionData.messages
    .filter((m) => m && (m.role === "user" || m.role === "assistant") && typeof m.content === "string" && m.content.trim())
    .map((m) => {
      return m.role === "user"
        ? user.message(m.content.trim())
        : assistant.message(m.content.trim());
    });

  if (messages.length === 0) continue;

  await session.addMessages(messages);

  importedSessions += 1;
  importedMessages += messages.length;
#
  console.log(`Imported ${sessionData.id} (${messages.length} messages)`);
}

console.log(`Done. Imported ${importedSessions} sessions, ${importedMessages} messages into workspace "${workspaceId}".`);

3. Run it

Managed Honcho:

export HONCHO_WORKSPACE_ID="your-workspace-id"
export HONCHO_API_KEY="your-api-key"
node import-to-honcho.mjs ./honcho-import.json

Self-hosted Honcho: