Skip to content

hermes profile update merges shipped cron jobs instead of replacing the store (#120823, salvage #120910) - #121264

Merged
kshitijk4poor merged 7 commits into
NousResearch:mainfrom
kshitijk4poor:salvage/120823-profile-distribution-cron-merge
Sep 24, 2026
Merged

kshitijk4poor merged 7 commits into
NousResearch:mainfrom
kshitijk4poor:salvage/120823-profile-distribution-cron-merge

Conversation

@kshitijk4poor

Copy link
Copy Markdown

hermes profile update no longer replaces the profile's whole cron store with the shipped one (#120823). This salvages #120910 by @JoaoMarcos44.

Why

A profile distribution that shipped cron/jobs.json was copied over the target profile's store on install and on every update. Jobs the user created locally were wiped. Shipped jobs lost their scheduler state and started unpaused on install. The live cron/runtime.lock sibling was copied as well.

Changes

  • Shipped job definitions are merged into the target store by job id, under the cron store lock (@JoaoMarcos44, fix(profiles): merge distributed cron jobs without replacing runtime state #120910). Local jobs survive, and shipped jobs keep their scheduler state across updates.
  • New shipped jobs install paused. Runtime siblings such as cron/runtime.lock are never copied.
  • The job-definition schema (JOB_DEFINITION_FIELDS, merge_job_definition, import_job_definitions) lives in cron/job_definition.py.
  • A schedule change goes through the scheduler's own update path: next run recomputed, quota hold cleared, pending slot dropped.
  • A shipped job the scheduler cannot take (a past one-shot or an unparseable string) is rejected with a job-labelled DistributionError before any file is replaced. Previously the update stopped halfway with SOUL.md already overwritten. String schedules are parsed up front, so cron resume can't hit a raw string later.
  • A corrupt target jobs.json also becomes a DistributionError instead of a traceback.

Validation

probe case main this PR
local job survives an update wiped kept
new shipped job installs paused runs paused
cron/runtime.lock sibling copied skipped
shipped definition refreshed; SOUL/skills replaced (control) yes yes
past one-shot shipped schedule half-applied update, raw ValueError refused before any write
  • tests/hermes_cli/test_profile_distribution.py together with tests/cron: 1412 passed, 10 skipped. 12-file review map: 343 passed, 8 skipped.
  • Reverting the up-front merge or the job-labelled error turns the extended kept test red.

Credit

Supersedes #120910, #120824.

JoaoMarcos44 and others added 7 commits September 24, 2026 14:46
…tion.py

cron/jobs.py is already ~3400 lines; JOB_DEFINITION_FIELDS and
merge_job_definition are only used by importers of a foreign cron store
(profile distributions), so they live in a small dedicated module instead
of growing the scheduler file. profile_distribution imports the new module
directly; jobs.py is left byte-identical to main (no re-export shim).

Refs NousResearch#120823

Co-authored-by: John Paul Soliva <soliva.johnpaul@icloud.com>
Co-authored-by: JoaoMarcos44 <joaomarcosdias444@gmail.com>
load_jobs raises RuntimeError for an unreadable or unrepairable jobs.json;
that is the only failure worth relabelling as DistributionError. Catching
OSError/ValueError too hid the profile's own permission/disk errors behind
"Could not merge cron jobs" and double-wrapped invalid-schedule ValueErrors
the CLI already reports on its own.

Refs NousResearch#120823

Co-authored-by: JoaoMarcos44 <joaomarcosdias444@gmail.com>
…le is replaced

A shipped job whose authored schedule was a past one-shot (or an unparseable
string) raised ValueError from _apply_schedule_update in the middle of
_copy_dist_payload: SOUL.md and skills were already replaced, the cron store
was not, and the message named no job. `hermes profile update` ended half-applied.

- _copy_dist_payload merges the cron store first, so the one step that can
  reject shipped content runs while the profile is still whole; the merge
  itself only writes after every record merged.
- merge_job_definition normalises string schedules via parse_schedule (a
  hand-authored store previously persisted the raw string, and `cron resume`
  then crashed on `.get`), and passes schedule_display only when the authored
  record has one so the helper's display fallback applies.
- _merge_cron_store wraps the ValueError into a DistributionError naming the job.
- Paused/created stamps use hermes_time.now() like every other cron record.

The kept update test now covers the past-one-shot rejection (SOUL.md untouched,
local schedule kept) and a corrupt target store surfacing as DistributionError,
so dropping the error wrapper goes red.
hermes_cli still reached into cron's private _jobs_lock and hand-built the
"created paused" record one function away from the module created so callers
never duplicate cron's schema. import_job_definitions() now holds the lock,
loads, merges and saves, and labels a merge ValueError with the job name;
_merge_cron_store keeps only the temp-store parse and the DistributionError
wrap, and takes the profile home instead of deriving it from dest.parent.parent.

Also drops the dead `and key != "repeat"` (repeat is reassigned right after)
and the comment that restated the module docstring.
Use the scheduler's own runnable predicate instead of a hand-rolled
enabled/state check, so a contradictory half-paused record is treated the same
way the scheduler treats it.
@alt-glitch alt-glitch added type/bug Something isn't working P1 High — major feature broken, no workaround comp/cli CLI entry point, hermes_cli/, setup wizard comp/cron Cron scheduler and job management area/profiles Multi-profile isolation, HERMES_HOME scoping area/install-update Installer, updater, packaging, wheels, doctor sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Sep 24, 2026
@kshitijk4poor
kshitijk4poor merged commit b0aefcc into NousResearch:main Sep 24, 2026
68 of 70 checks passed
teknium1 added a commit that referenced this pull request Sep 24, 2026
The merge keeps any job it cannot prove was shipped, so a retired shipped job may stay in the
store; the invariant is that it is never left running unless the user enabled it.
teknium1 added a commit that referenced this pull request Sep 24, 2026
The merge keeps any job it cannot prove was shipped, so a retired shipped job may stay in the
store; the invariant is that it is never left running unless the user enabled it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/install-update Installer, updater, packaging, wheels, doctor area/profiles Multi-profile isolation, HERMES_HOME scoping comp/cli CLI entry point, hermes_cli/, setup wizard comp/cron Cron scheduler and job management P1 High — major feature broken, no workaround sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants