Skip to content
kazma.
Search Kazma Documentation & Site
ع

Migration (cross-machine)

Move a full Kazma installation — config, secrets, memory, chat history, snapshots, scheduled jobs, assets — from one machine to another (WSL→Windows, Linux→Mac, server→laptop) without the silent breakage of a naive copy-paste.

A naive file copy breaks in three specific ways: the encrypted vault becomes undecryptable (wrong key), embedded file paths point at a dead /home/user/..., and Postgres-backed data (chat history, settings, checkpoints) is missed entirely. kazma migrate prevents all three.


On the source machine:

Terminal window
kazma migrate export --out my-kazma.zip

On the target machine (after copying my-kazma.zip over):

Terminal window
kazma migrate verify my-kazma.zip
kazma migrate import my-kazma.zip --workspace /path/to/kazma --dry-run # preview first
kazma migrate import my-kazma.zip --workspace /path/to/kazma # real import

That’s it for a SQLite-backed install. For Postgres sources, see § Postgres migration below.


A .zip archive with:

FileContents
manifest.jsonVersion, source OS/host, per-file sha256, vault-key fingerprint, table counts
meta.envKAZMA_VAULT_KEY + KAZMA_PUBLIC_URL (needed for vault decryption + OAuth)
config.yamlFull ConfigStore settings (secrets are vault:// refs, not plaintext)
data/vault.dbEncrypted secrets store (29+ secrets) — travels WITH the vault key
data/*.dbAll SQLite databases (snapshots, memory, cron, chat, checkpoints, etc.)
data/documents.dbDocument Intelligence metadata (versions, ACL, audit, and the jobs when they are on SQLite) when the platform store is present
document-store/ (bundle tree)Content-addressed blobs + manifests the document catalog references (documents.db, or the Postgres catalog in data/postgres.dump; immutable SHA-256 tree). Restored into the target documents.storage_root
data/postgres.dumpPostgres dump (only when source is Postgres-backed)
data/workspaces.dbWorkspace table (root paths rewritten on import)
assets/Everything else in the data dir that travels: the swarm’s worker registry and templates, chat attachments, documents, exports, images, the default workspace, and any file you (or the agent) keep there. Not carried: the install’s identity (install_id), its backups, logs, and what is rebuilt (vectors, code indexes)
pathmap.jsonSource workspace root + data dir (for path translation)

Document store notes: documents.db has no embedded absolute paths that need rewrite (content is content-addressed). Export snapshots the DB first, then copies the blobs and manifests it references; a referenced blob that is missing marks the bundle incomplete, and verify (and so import) refuses it. Import copies the target’s existing documents.db into the pre-import backup (best effort), installs the staged one, and adds the staged content files the target lacks (files already there are kept), into the document-store root resolved after config import, so a custom documents.storage_root is honored; documents.db is installed only when the target’s catalog is on SQLite. A document store that fails to restore fails the import (ok: false; since 2026-10-13 — it was a warning beside a successful import). See Document processing ops.

A Postgres document store travels as catalog plus files (since 2026-10-13). With document metadata on Postgres (KAZMA_DOCUMENTS_METADATA_BACKEND=postgres, or auto/unset on a Postgres install), the catalog is inside data/postgres.dump, taken first, and the exporter copies every content file and manifest it references from the storage root (until then it read the empty local documents.db and carried no content files). The CLI decides by the configured backends, since it has no running service. A Postgres catalog beside a SQLite queue (a fallback) is refused by name. An install with SQLite document metadata beside Postgres jobs carries documents.db and its content (the promotions read from the queue) and its jobs inside data/postgres.dump, taken at different moments. To move a store at ONE point in time instead, use the coordinated document backup (python scripts/document_pg_backup.py backup on the source; on the target, after kazma migrate import, restore ... --replace-document-tables): see Document processing → Backup.


These are the silent-breakage modes kazma migrate prevents:

A. Vault pairing — vault.db + KAZMA_VAULT_KEY travel together

Section titled “A. Vault pairing — vault.db + KAZMA_VAULT_KEY travel together”

The vault’s encryption salt lives inside vault.db, so the DB is undecryptable without its matching key. The bundle carries both. On import:

  • Keys match → vault.db is installed, secrets decrypt. ✅
  • Target key is empty → the bundle’s key is written to the target .env. ✅
  • Keys differ → import aborts unless you pass --reset-vault-key (which backs up the target’s existing vault.db first, then writes the bundle’s key).
Terminal window
kazma migrate import my-kazma.zip --workspace /path --reset-vault-key

B. Path translation — embedded paths rewritten automatically

Section titled “B. Path translation — embedded paths rewritten automatically”

A source install at /home/user/kazma has that path baked into workspaces.root_path, snapshots.state_json (full SupervisorState blobs), chat messages, memory episodes, and cron prompts. The importer rewrites them all to the target path, across OS separator conventions.

\Users\me\kazma
# Linux source → Windows target
kazma migrate import my-kazma.zip --workspace "C:\Users\me\kazma"

The rewrite is byte-level substring (not a JSON parse) so it handles the 300+ MB snapshots.db efficiently. Path-prefix ordering prevents partial rewrites (/home/u/kazma won’t corrupt /home/u/kazma-repos/ShipX).

C. Atomic import — staging → backup → swap

Section titled “C. Atomic import — staging → backup → swap”

Import never touches live data mid-flight:

  1. Stage — extract the bundle to kazma-data/.migrate-staging-<ts>/
  2. Path-rewrite the staged copies
  3. Backup live DBs to kazma-data/.migrate-backup-<ts>/
  4. Swap staging → live (WAL-safe, one file at a time)

A failure before the swap leaves live data untouched. The staging dir is preserved on failure for inspection. To roll back: copy the .db files from the backup dir back over the live ones.


When the source is Postgres-backed, the bundle also includes a data/postgres.dump produced by pg_dump -Fc (custom format — handles bytea blobs natively, ~7× smaller than plain text).

The migration engine finds the binaries automatically:

  1. pg_dump / pg_restore on PATH
  2. docker exec ${KAZMA_DB_CONTAINER:-kazma-db} <bin> — the common Docker-deployment default
  3. Clear error with install hint if neither is available

Override the container name:

Terminal window
KAZMA_DB_CONTAINER=my-postgres kazma migrate export --out my-kazma.zip

The target must have KAZMA_DB_BACKEND=postgres + KAZMA_DATABASE_URL set:

Terminal window
export KAZMA_DB_BACKEND=postgres
export KAZMA_DATABASE_URL=postgresql://kazma:kazma_change_me@127.0.0.1:5433/kazma
export KAZMA_DB_CONTAINER=kazma-db-win
kazma migrate import my-kazma.zip --workspace /path/to/kazma --reset-vault-key

pg_restore --clean --if-exists recreates the schema (target DB can be empty), then loads data. The SQLite files (vault, memory, snapshots) are restored alongside.

When pg_dump / pg_restore run via docker exec, they execute inside the container, where Postgres listens on localhost:5432 — not the host’s forwarded port (e.g. 5433). The engine detects the Docker case and overrides automatically. Override the internal port if your container listens elsewhere:

Terminal window
KAZMA_DB_INTERNAL_PORT=5432 # default; change only if your container differs

Terminal window
kazma migrate export [--out PATH] [--no-assets]
FlagDefaultPurpose
--out PATHkazma-bundle-<timestamp>.zipOutput bundle path
--no-assets(assets included)Leave the data dir’s files out (a smaller bundle of settings and databases). The swarm’s worker registry and templates still travel
Terminal window
kazma migrate verify BUNDLE [--no-hash]

Checks bundle integrity: structure, manifest compatibility, per-file sha256, vault-key fingerprint, table row counts. --no-hash skips the (slow) hash re-hash for a quick structural check.

Terminal window
kazma migrate import BUNDLE [--workspace PATH] [--reset-vault-key] [--dry-run]
FlagDefaultPurpose
--workspace PATHcurrent directoryTarget workspace root (paths are rewritten to this)
--reset-vault-key(abort on mismatch)Overwrite target’s vault key with the bundle’s (backs up existing vault.db first)
--dry-run(real import)Verify + plan only; no writes

VariableDefaultPurpose
KAZMA_DB_CONTAINERkazma-dbDocker container name for pg_dump / pg_restore discovery
KAZMA_DB_INTERNAL_PORT5432Container-internal Postgres port (when running via docker exec)

See also: Environment variables · Portability · Disaster recovery


Every import creates a pre-import backup at kazma-data/.migrate-backup-<ts>/: the databases it replaced, and under files/ every file of the target’s that the bundle replaced with a different one. To roll back:

Terminal window
# Stop Kazma, then copy the backup .db files back over the live ones
cp kazma-data/.migrate-backup-<ts>/*.db kazma-data/
# and the replaced files, if any
cp -r kazma-data/.migrate-backup-<ts>/files/. kazma-data/

An export that cannot read a file names it in the manifest, and verify (and so import) refuses that bundle. An import that cannot restore a file reports it, does not report success, and leaves the bundle’s copy in its staging folder.

For Postgres, pg_restore --clean --if-exists is idempotent — re-running the import restores from the bundle cleanly.