Skip to content

Backup System

Automated daily backup of the Hermes Agent state to a dedicated private GitHub repo (fducat18/hermes-backup). The engine is backup.sh, which uses a Grandfather-Father-Son (GFS) rotation: daily snapshots, promoted to weekly on Sundays and monthly on the 1st, with independent retention (7 daily / 4 weekly / 3 monthly).

Category Files Notes
Identity SOUL.md, CONTEXT.md, TODO.md Agent identity, glossary, task list
Memory memories/MEMORY.md, memories/USER.md Agent memory + user profile
Config config.yaml All settings
Skills skills/ Custom skills
Cron cron/jobs.json Scheduled job definitions
Scripts scripts/ Custom automation (backup, security audit, etc.)
Plugins plugins/ Installed plugins
Notes inbox notes-inbox/ Ad-hoc notes until Drive + PARA integration
Non-default profiles profiles/<name>/ Same selective structure: SOUL/CONTEXT/TODO, memories, config.yaml, profile.yaml, skills, scripts, plugins, cron/jobs.json
Item Reason
.env, .env.bak.*, auth.json Secrets β€” stored in Bitwarden, never on GitHub
state.db, sessions.db Session transcripts / runtime cache β€” too large for GitHub (>30 MB), recoverable from artifacts
agent.log* Ephemeral runtime logs
hermes-agent/ source Reinstallable via pipx; only custom patches are re-applied by scripts
docs/ Separate repository
cache/, logs/, node/, sandboxes/, pastes/, lsp/ Ephemeral or non-state

Repo: fducat18/hermes-backup (private)

Each snapshot is a live directory (no tar.gz), placed in exactly one tier folder:

hermes-backup/
β”œβ”€β”€ backup.sh # SSOT of the backup engine
β”œβ”€β”€ restore.sh
β”œβ”€β”€ .gitignore
└── backups/
β”œβ”€β”€ daily/ # Mon–Sat (7 retained)
β”‚ └── 20260821/
β”‚ β”œβ”€β”€ SOUL.md, CONTEXT.md, TODO.md
β”‚ β”œβ”€β”€ config.yaml
β”‚ β”œβ”€β”€ memories/ skills/ scripts/ plugins/
β”‚ β”œβ”€β”€ cron/jobs.json
β”‚ β”œβ”€β”€ notes-inbox/
β”‚ β”œβ”€β”€ profiles/<name>/…
β”‚ └── MANIFEST.txt
β”œβ”€β”€ weekly/ # Sunday (4 retained)
β”‚ └── 20260816/
└── monthly/ # 1st of month (3 retained)
└── …

Exclusive tiers: each snapshot lives in exactly one tier folder β€” there is no duplication across daily/, weekly/, monthly/. A snapshot on a Sunday lands only in weekly/; one on the 1st only in monthly/; every other day only in daily/.

Date-only naming: snapshot folders use YYYYMMDD (no timestamp). This enables same-day de-duplication β€” running the script twice in one day replaces that day’s snapshot instead of leaving duplicates β€” and keeps the rotation/retention logic simple.

Retention is enforced independently per tier:

Tier When created Retention
Daily Every day at 02:00 UTC 7 snapshots
Weekly Sunday (hard-link of the daily snapshot) 4 snapshots
Monthly 1st of month (hard-link of the latest weekly) 3 snapshots

Weekly and monthly snapshots are hard links (cp -al) to the underlying daily snapshot β€” they share the same inodes, so they consume almost no extra disk. Rotating out a daily snapshot that is also linked in weekly/monthly frees space only once all references are gone.

  • Cron: Hermes State Backup job, 0 2 * * * (02:00 UTC), no_agent: true
  • Script: backup.sh (engine, stdout is the deliverable)
  • Repo: ~/workspace/hermes-backup/backup.sh is the SSOT; a synced copy lives at ~/.hermes/scripts/backup.sh for the cron job. Keep both in sync when editing.
  • Delivery: deterministic 4-line summary to Slack (status icon, date/time, per-tier rotation counts, git pack size). No file listing is sent.

From the hermes-backup repo:

Terminal window
./restore.sh backups/daily/20260821
./restore.sh backups/weekly/20260816

NOT restored (by design): secrets (.env, auth.json) β€” recreated manually from Bitwarden.

After hermes update (which overwrites the hermes-agent source checkout):

  1. Reinstall: pipx install hermes-agent
  2. Restore the dashboard security patch (Host-header validation): python3 ~/.hermes/scripts/ensure_dashboard_security.py
  3. Restart the dashboard: systemctl --user restart hermes-dashboard

The dashboard security patch is also re-applied automatically by a watchdog cron job after upgrades.

  • Secrets stay out of git: .env and auth.json are excluded via the backup repo’s .gitignore, so nothing sensitive is ever committed.
  • Credentials are recreated from Bitwarden on restore β€” never stored in the backup repo.
  • The backup repo is private; access control is GitHub’s.
  • No bank data, no email, no sensitive personal data β€” just agent configuration and state.
  • The backup script treats ~/.hermes/ as read-only: it only copies from it, never modifies it.