Migration (cross-machine)
Cross-machine migration (kazma migrate)
Section titled “Cross-machine migration (kazma migrate)”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.
Quick start
Section titled “Quick start”On the source machine:
kazma migrate export --out my-kazma.zipOn the target machine (after copying my-kazma.zip over):
kazma migrate verify my-kazma.zipkazma migrate import my-kazma.zip --workspace /path/to/kazma --dry-run # preview firstkazma migrate import my-kazma.zip --workspace /path/to/kazma # real importThat’s it for a SQLite-backed install. For Postgres sources, see § Postgres migration below.
What the bundle contains
Section titled “What the bundle contains”A .zip archive with:
| File | Contents |
|---|---|
manifest.json | Version, source OS/host, per-file sha256, vault-key fingerprint, table counts |
meta.env | KAZMA_VAULT_KEY + KAZMA_PUBLIC_URL (needed for vault decryption + OAuth) |
config.yaml | Full ConfigStore settings (secrets are vault:// refs, not plaintext) |
data/vault.db | Encrypted secrets store (29+ secrets) — travels WITH the vault key |
data/*.db | All SQLite databases (snapshots, memory, cron, chat, checkpoints, etc.) |
data/documents.db | Document 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.dump | Postgres dump (only when source is Postgres-backed) |
data/workspaces.db | Workspace 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.json | Source 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.
The three invariants
Section titled “The three invariants”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).
kazma migrate import my-kazma.zip --workspace /path --reset-vault-keyB. 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.
# Linux source → Windows targetkazma 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:
- Stage — extract the bundle to
kazma-data/.migrate-staging-<ts>/ - Path-rewrite the staged copies
- Backup live DBs to
kazma-data/.migrate-backup-<ts>/ - 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.
Postgres migration
Section titled “Postgres migration”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).
Discovery — pg_dump / pg_restore
Section titled “Discovery — pg_dump / pg_restore”The migration engine finds the binaries automatically:
pg_dump/pg_restoreonPATHdocker exec ${KAZMA_DB_CONTAINER:-kazma-db} <bin>— the common Docker-deployment default- Clear error with install hint if neither is available
Override the container name:
KAZMA_DB_CONTAINER=my-postgres kazma migrate export --out my-kazma.zipImport into a Postgres target
Section titled “Import into a Postgres target”The target must have KAZMA_DB_BACKEND=postgres + KAZMA_DATABASE_URL set:
export KAZMA_DB_BACKEND=postgresexport KAZMA_DATABASE_URL=postgresql://kazma:kazma_change_me@127.0.0.1:5433/kazmaexport KAZMA_DB_CONTAINER=kazma-db-winkazma migrate import my-kazma.zip --workspace /path/to/kazma --reset-vault-keypg_restore --clean --if-exists recreates the schema (target DB can be empty), then loads data. The SQLite files (vault, memory, snapshots) are restored alongside.
Docker-internal port
Section titled “Docker-internal port”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:
KAZMA_DB_INTERNAL_PORT=5432 # default; change only if your container differsCommands reference
Section titled “Commands reference”kazma migrate export
Section titled “kazma migrate export”kazma migrate export [--out PATH] [--no-assets]| Flag | Default | Purpose |
|---|---|---|
--out PATH | kazma-bundle-<timestamp>.zip | Output 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 |
kazma migrate verify
Section titled “kazma migrate verify”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.
kazma migrate import
Section titled “kazma migrate import”kazma migrate import BUNDLE [--workspace PATH] [--reset-vault-key] [--dry-run]| Flag | Default | Purpose |
|---|---|---|
--workspace PATH | current directory | Target 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 |
Environment variables
Section titled “Environment variables”| Variable | Default | Purpose |
|---|---|---|
KAZMA_DB_CONTAINER | kazma-db | Docker container name for pg_dump / pg_restore discovery |
KAZMA_DB_INTERNAL_PORT | 5432 | Container-internal Postgres port (when running via docker exec) |
See also: Environment variables · Portability · Disaster recovery
Rollback
Section titled “Rollback”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:
# Stop Kazma, then copy the backup .db files back over the live onescp kazma-data/.migrate-backup-<ts>/*.db kazma-data/# and the replaced files, if anycp -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.

