feat(backup): scheduled auto-backups with retention + hermes backup --list - #43058
feat(backup): scheduled auto-backups with retention + hermes backup --list#43058tgmerritt wants to merge 1 commit into
Conversation
|
Verification review — clean ✅ Reviewed the full diff (~550 lines across 4 files + 286-line test file). Well-structured anacron-style auto-backup feature gated by What I checked:
No issues found. Clean feature implementation. |
teknium1
left a comment
There was a problem hiding this comment.
Thanks for the focused scheduled-backup implementation and accompanying tests. The feature is still needed on current main, but two path-scope issues need correction before it is safe to salvage.
Problems
hermes_cli/backup.py:1113and:1179useget_default_hermes_root(). ForHERMES_HOME=<root>/profiles/<name>, that helper deliberately returns<root>(hermes_constants.py:123-150), whileload_config()reads the active profile config (hermes_cli/config.py:747-749). A named profile can enable the feature yet write/list root-store archives.hermes_cli/backup.py:1117permits a custom destination under HERMES_HOME._write_full_zip_backup()only excludes static directory names (hermes_cli/backup.py:1157-1172), so a destination such asHERMES_HOME/scheduled-backupsis included on the next run. Existing regression coverage explicitly protects against this growth class forbackups/(tests/hermes_cli/test_backup.py:1908-1923).
Suggested changes
- Scope scheduled-backup source, state, default destination, and listing to
get_hermes_home(); add a named-profile test. - Reject an in-source custom destination or exclude its subtree during the zip walk; test two runs.
- Add the new top-level
backupdefaults tohermes_cli/config.py::DEFAULT_CONFIG.
Automated hermes-sweeper review.
| if not _auto_backup_enabled(cfg): | ||
| return None | ||
|
|
||
| hermes_root = hermes_home or get_default_hermes_root() |
There was a problem hiding this comment.
get_default_hermes_root() intentionally climbs from HERMES_HOME=<root>/profiles/<name> to <root> (hermes_constants.py:123-150), but this feature reads the active profile's config. Use get_hermes_home() here so a named gateway archives its own state rather than the shared root.
| if not hermes_root.is_dir(): | ||
| return None | ||
|
|
||
| backup_dir = _auto_backup_dir(cfg, hermes_root) |
There was a problem hiding this comment.
A custom backup.dir can be an unexcluded child of hermes_root; _write_full_zip_backup() will include prior archives from that directory on the next run. Reject an in-source destination or pass it as an excluded subtree, and add a two-run regression test.
| files. Includes the configured ``backup.dir`` when it differs from the | ||
| default location. | ||
| """ | ||
| home = hermes_home or get_default_hermes_root() |
There was a problem hiding this comment.
Keep archive listing profile-scoped as well. With a named HERMES_HOME, this resolves to the machine root and exposes archives outside the active profile.
|
Hi @tgmerritt — thanks for putting this together. I'm interested in the scheduled backup feature, especially being able to configure the destination (for example, a mounted drive or cloud-synced folder) and retain a defined number of backups. I saw the automated hermes-sweeper review from July 14 covering profile scoping, preventing an in-source backup directory from being recursively archived, and adding the defaults to If you are no longer able to work on it, would you be comfortable with me preparing focused follow-up commits that address those review items while preserving your original commit history and authorship? I'd prefer to collaborate with you rather than open a competing PR. Thanks! |
|
Absolutely please work on it if you have the cycles. I'm slammed, but I do
try to contribute to the project when I can.
…On Wed, Jul 29, 2026 at 11:36 AM pxxD1998 ***@***.***> wrote:
*pxxD1998* left a comment (NousResearch/hermes-agent#43058)
<#43058 (comment)>
Hi @tgmerritt <https://github.com/tgmerritt> — thanks for putting this
together. I'm interested in the scheduled backup feature, especially being
able to configure the destination (for example, a mounted drive or
cloud-synced folder) and retain a defined number of backups.
I saw the automated hermes-sweeper review from July 14 covering profile
scoping, preventing an in-source backup directory from being recursively
archived, and adding the defaults to DEFAULT_CONFIG. Are you still
planning to continue this PR?
If you are no longer able to work on it, would you be comfortable with me
preparing focused follow-up commits that address those review items while
preserving your original commit history and authorship? I'd prefer to
collaborate with you rather than open a competing PR. Thanks!
—
Reply to this email directly, view it on GitHub
<#43058?email_source=notifications&email_token=AAJQFKD4SUHPOG2LH7I6EWT5HIRXRA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKMJSGA3TEOBYGQ22M4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJLDGN5XXIZLSL5RWY2LDNM#issuecomment-5120728845>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAJQFKAU7ABIRR2HCNMZTL35HIRXRAVCNFSNUABGKJSXA33TNF2G64TZHMYTAMRUGU2TIMRWG45US43TOVSTWNBWGI2TEMBYGE4DJILWAI>
.
Triage notifications, keep track of coding agent tasks and review pull
requests on the go with GitHub Mobile for iOS
<https://github.com/notifications/mobile/ios/AAJQFKG52O2NJ3343SUDPTD5HIRXRA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKMJSGA3TEOBYGQ22M4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJKTGN5XXIZLSL5UW64Y>
and Android
<https://github.com/notifications/mobile/android/AAJQFKABVXOJQQ4OORFEK5D5HIRXRA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKMJSGA3TEOBYGQ22M4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJLTGN5XXIZLSL5QW4ZDSN5UWI>.
Download it today!
You are receiving this because you were mentioned.Message ID:
***@***.***>
--
Tyler Merritt
古池や、蛙飛び込む、水の音
|
You are asking whether this PR is still active and how to continue its reviewed work without creating a competing implementation. Case context, measured live from our triage graph (2026-07-29T17:57:02+00:00):
If you want to move this one along: keep the diff scoped and rebase onto current |
|
Hi @tgmerritt — thank you again for giving me the go-ahead to help with this. I prepared a candidate branch rebased onto current upstream
Candidate branch: Comparison against current upstream Verification on WSL2 / Ubuntu 24.04:
No replacement PR has been opened. If you would like to keep #43058 as the active PR, you or a maintainer can use this candidate as a tested rebased reference for updating its branch. If a replacement PR would be easier, I can prepare one that clearly credits and builds on #43058—but I will not open one unless you or a maintainer asks me to. |
69936b9 to
9eadc26
Compare
…-list
The backup building blocks already exist (hermes backup, hermes import,
--quick snapshots, pre-update archives), but the "automatic" half of
job, which is exactly the barrier the issue calls out for non-developer
users. A single disk failure still wipes months of accumulated skills
and memory for anyone who never set that up.
Add an anacron-style scheduled backup, off by default and driven by a
new config block:
backup:
enabled: true # default false — opt-in
schedule: daily # hourly | daily | weekly | <hours as integer>
keep_last: 7 # auto archives to retain (oldest pruned first)
dir: ~/backups # optional override (default: ~/.hermes/backups)
maybe_create_auto_backup() mirrors the curator's gating pattern: cheap
when disabled or not due (one config read + one JSON stat), real cadence
enforced by a last_run_at stamp in backups/.auto_backup_state.json. The
gateway cron ticker polls it hourly alongside the curator, so any
long-running gateway gets periodic snapshots with zero user setup — no
OS cron, works the same on Linux/macOS/Windows. Archives reuse
_write_full_zip_backup (same exclusions, same WAL-safe SQLite copies)
and restore with the existing hermes import.
Details:
- Failures stamp last_run_at too, so a persistently failing destination
retries once per interval instead of walking the full tree every poll.
- Pruning only touches auto-*.zip; pre-update/pre-migration/manual
archives in the same directory are never deleted. keep_last floors at
1 for the same reason as _prune_pre_update_backups.
- hermes backup --list shows every archive (auto, pre-update,
pre-migration, manual) with date/size/path across the default and
configured directories.
- backup.dir lets users point at a mounted drive or cloud-synced folder
for off-machine copies without a cloud-provider integration.
Partially addresses NousResearch#12238 (the backup acceptance criteria; per-skill
history/rollback and memory diff are a version-control subsystem better
served by a separate PR).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
9eadc26 to
cc8c8ab
Compare
|
Rebased onto current |
What does this PR do?
Implements the automatic half of #12238 (Built-in Automatic Backup & Version Control).
The backup building blocks already shipped —
hermes backup(full zip),hermes import(restore),--quickstate snapshots, pre-update archives — but getting periodic backups today still means hand-wiring a cron job, which is exactly the barrier the issue calls out for non-developer users. A single disk failure still wipes months of accumulated skills and memory for anyone who never set that up.This adds an anacron-style scheduled backup, off by default, driven by the config block the issue proposed:
maybe_create_auto_backup()mirrors the curator's gating pattern exactly: cheap when disabled or not due (one config read + one small JSON stat), with the real cadence enforced by alast_run_atstamp inbackups/.auto_backup_state.json. The gateway cron ticker polls it hourly alongside the curator, so any long-running gateway gets periodic snapshots with zero user setup — no OS cron, identical behavior on Linux/macOS/Windows. Archives reuse_write_full_zip_backup()(same exclusions, same WAL-safe SQLite copies) and restore with the existinghermes import.Design decisions worth flagging for review:
last_run_attoo — a persistently failing destination (e.g. unmounted drive) retries once per interval instead of walking the full tree on every hourly poll.auto-*.zip— pre-update/pre-migration/manual archives in the sharedbackups/directory are never deleted, andkeep_lastfloors at 1 (same rationale as_prune_pre_update_backups).backup.dirinstead of cloud destinations. Pointing it at a mounted drive or a cloud-synced folder (Dropbox/Syncthing) covers the off-machine need without a provider integration; S3/R2 targets from the issue would be a separate, much larger PR.keep_lastpruning, restore,--list). Per-skillhistory/rollbackandmemory diffare a version-control subsystem, not a backup mechanism — better served by a dedicated follow-up PR if there's appetite.I had Claude Fable 5 do this work - specifically to test it's capabilities when analyzing a request on Github, an unknown codebase (to me), and to use some of my Claude-time to give back to the open source community. This may or may not be in the style or format that the maintainers wish - and it's my highest goal NOT to actually get involved in the software but rather push Fable itself to see how effective it can be with limited context - if the maintainers accept this PR, that's an implicit nod of approval to Fable as I gave it very very little to work from
Related Issue
Partially addresses #12238 (backup acceptance criteria — see scope note above)
Type of Change
Changes Made
hermes_cli/backup.py— new "Scheduled auto-backup" section:maybe_create_auto_backup(),backup:config parsing (enabled/schedule/keep_last/dir),_prune_auto_backups(), state persistence, pluslist_backup_archives()/run_backup_list()for--list.gateway/run.py—_start_cron_ticker()pollsmaybe_create_auto_backup()hourly (AUTO_BACKUP_EVERY), same pattern and error-handling as the curator tick.hermes_cli/subcommands/backup.py+hermes_cli/main.py—--listflag and dispatch.website/docs/reference/cli-commands.md—--listoption row and a "Scheduled auto-backups" section documenting the config block and the gateway-driven cadence.tests/hermes_cli/test_auto_backup.py— 25 new tests: schedule parsing (named/numeric/garbage), disabled-by-default, interval gating (incl. naive-timestamp and unparseable-state recovery), archive creation +hermes importvalidation compatibility,keep_lastpruning that spares non-auto archives, failure stamping (no poll-hammering), custom dir,--listclassification/ordering/output.How to Test
scripts/run_tests.sh tests/hermes_cli/test_auto_backup.py— 25/25 pass.scripts/run_tests.sh tests/hermes_cli/test_backup.py tests/hermes_cli/test_subcommands_batch.py— all pass exceptTestProfileRestoration::test_import_creates_profile_wrappers, which fails identically on unmodifiedmainin my environment (the test's alias-collision check consults the realPATH, and my machine has an unrelated~/.local/bin/researcherbinary). Pre-existing, unrelated to this change.HERMES_HOME): set the config block withschedule: hourly, callmaybe_create_auto_backup()→auto-<timestamp>.zipcreated with config/skills/.env inside; immediate second call returnsNone(gated);hermes backup --listshows the archive with kind/date/size.Checklist
Code
fix(scope):,feat(scope):, etc.)scripts/run_tests.sh(see How to Test, incl. one pre-existing environmental failure on main)Documentation & Housekeeping
docs/, docstrings) — or N/Acli-config.yaml.exampleif I added/changed config keys — N/A (the example file doesn't carry optional feature blocks likeapprovals:/updates:; documented inwebsite/docs/reference/cli-commands.md)CONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — N/A🤖 Generated with Claude Code