Use when performing a full-system backup of a Hermes Agent server — survey, clean up caches, checkpoint databases, bundle repos and configs, compress, and write a manifest. Pairs with hermes-recovery for the restore side.
Use when performing a full-system backup of a Hermes Agent server — survey, clean up caches, checkpoint databases, bundle repos and configs, compress, and write a manifest. Pairs with hermes-recovery for the restore side.
Use git bundle for portable full-repo snapshots (all branches, all tags):
cd ~/hermes-cortex && git bundle create ~/backups/$BACKUP_DATE/repos/hermes-cortex-public.bundle --all
# ~/hermes-cortex-private no longer a git repo — backup via cp -r ~/hermes-cortex-private ~/backups/$BACKUP_DATE/private-data/cd ~/.hermes/hermes-agent && git bundle create ~/backups/$BACKUP_DATE/repos/hermes-agent.bundle --all
Do NOT back upnode_modules/, venv/, or build artifacts — the bundle captures only git-tracked objects. The full checkout is reconstructed from the bundle.
Restore:git clone hermes-*.bundle <dirname>
Completion criteria: All bundle files exist and have non-zero size.
Completion criteria: All DB files in place matching original sizes.
7. Copy Hermes Configuration
Core config:
cp ~/.hermes/config.yaml ~/backups/$BACKUP_DATE/configs/
cp -a ~/.hermes/skills ~/backups/$BACKUP_DATE/configs/
cp -a ~/.hermes/scripts ~/backups/$BACKUP_DATE/configs/
cp -a ~/.hermes/plugins ~/backups/$BACKUP_DATE/configs/ 2>/dev/null
cp -a ~/.hermes/cron ~/backups/$BACKUP_DATE/configs/
Root-level JSON state files:
mkdir -p ~/backups/$BACKUP_DATE/configs/hermes-root
for f in auth.json gateway_state.json channel_directory.json \
docker-daemon.json processes.json \
provider_models_cache.json ollama_cloud_models_cache.json; docp ~/.hermes/$f ~/backups/$BACKUP_DATE/configs/hermes-root/ 2>/dev/null
donecp ~/.hermes/models_dev_cache.json ~/backups/$BACKUP_DATE/configs/ 2>/dev/null
Completion criteria: Langfuse configs and any docker image archives present.
10. Write Manifest
Create MANIFEST.txt inside the backup directory. Include:
Date, hostname
Category breakdown with file counts and sizes
What was cleaned up (space reclaimed)
What was intentionally skipped (Ollama models, Python packages, docker volumes)
How to restore
Completion criteria: MANIFEST.txt exists, all backup files accounted for.
11. Compress the Archive
cd ~/backups
tar czf $BACKUP_DATE-hermes-backup.tar.gz $BACKUP_DATE/
Verify: tar tzf $BACKUP_DATE-hermes-backup.tar.gz | head -5
Log compression ratio (raw vs compressed size).
12. Record in Memory
Save the backup date, archive path, and size to memory so future sessions can find it.
Completion criteria: Memory updated with backup location.
Pitfalls
Pitfall
Symptom
Fix
state.db copied while gateway is writing
WAL not flushed, partial write
Checkpoint first (PRAGMA wal_checkpoint(TRUNCATE)), then copy. Better: use the sqlite backup API for a WAL-correct consistent snapshot of a LIVE db — python3 -c "import sqlite3; src=sqlite3.connect('~/.hermes/state.db'); dst=sqlite3.connect('backup.db'); src.backup(dst)" — handles WAL without stopping the gateway
git bundle fails: packed object corrupt
fatal: packed object ... is corrupt / pack has bad object at offset from git bundle create --all
The repo's .git packs are damaged (disk write errors). HEAD commit may still be intact in another pack (git cat-file -t HEAD works, git log works). Fallback: tar the worktree (the RUNNING code, no .git/venv/node_modules) + tar .git raw as hermes-agent-gitdir.tar.gz. Repair later: remove bad packs (git verify-pack to find them) + git fetch origin (works if repo is ≤1 commit behind origin)
git bundle from dirty working tree
Uncommitted changes excluded from bundle
git status --short first — report dirty state; bundle anyway for now
~/.git-credentials zeroed by gateway write protection
File exists but 0 bytes
Check with wc -c after copy; re-populate if zeroed
Nginx config permission denied
Can't read /etc/nginx/sites-enabled/*
Try without sudo; if blocked, skip and note in manifest
mycortex PGlite has lock files
Copy errors on .mycortex-lock/lock
Skip lock files — they're recreated on restart
Multiple backup dirs same day
Previous backup at same date path
Use $BACKUP_DATE-v2 suffix
Docker images NOT pre-exported
No ~/.hermes/docker-*.tar.gz
Skip step; note in manifest that images must pull from registry
Verification Checklist
Survey complete — disk space, repo state, docker status all known
Cleanup freed ≥ 500 MB (or justified as minimal)
SQLite databases checkpointed (WAL files zeroed)
All 3 git repos bundled (public, private, hermes-agent)
state.db, kanban.db, web-cache.db, mycortex PGlite all copied
config.yaml, skills/, scripts/, cron/, plugins/ all backed up
Root JSON files (auth, gateway, channel, processes, caches) backed up
Hermes subdirs (dashboard, LSP, offline) backed up
~/brain/ and ~/.brain/ both copied
nginx config copied (or skipped with reason in manifest)
.zshrc, crontab.txt, .git-credentials backed up
Langfuse config copied
Docker image archives copied (or noted absent)
MANIFEST.txt written — covers all included and excluded items
Archive compressed — verified with tar tzf
Final archive size ≤ 1 GB (or justified larger)
Backup location recorded in memory for future recovery