Skip to content

feat(coding-agents): spread page refreshes with hashed cron fields (H) - #4208

Merged
nicoloboschi merged 1 commit into
mainfrom
feat-page-trigger-hashed-cron
Sep 8, 2026
Merged

feat(coding-agents): spread page refreshes with hashed cron fields (H)#4208
nicoloboschi merged 1 commit into
mainfrom
feat-page-trigger-hashed-cron

Conversation

@nicoloboschi

Copy link
Copy Markdown
Collaborator

Alternative to #4176, addressing the same problem with a different interface.

The problem (same as #4176)

One pageTriggerCron is shared by every page in every bank running this plugin. A literal
"0 3 * * *" therefore does not schedule a refresh at 03:00 — it schedules all of them at
03:00, ~5 page syntheses per bank, on the worker pool that also serves retain. A session ingesting
at 03:0x queues behind the pile. Moving the hour moves the pile.

The interface

#4176 adds a pageTriggerType member, "daily-staggered". That spells the period in the type name,
so the next ask (hourly, or nightly-only) is another enum member, another branch, another docs row —
and "daily-staggered" cannot express "spread within 03:00" at all.

Spreading is a property of the schedule, so put it in the expression. A cron field written H is
replaced, per page, by a value hashed from bank + page name:

pageTriggerCron what each page gets
"H H * * *" once a day, at its own minute and hour (= #4176)
"H * * * *" once an hour, at its own minute
"H 3 * * *" daily at 03:MM — spread inside the hour you chose
"H H(0-5) * * *" daily, spread across the night only
"0 3 * * *" unchanged — no H, no hashing

H is Jenkins' syntax for exactly
this problem, borrowed rather than invented so it is recognisable. No new config field, no new enum
member.

Implementation notes

  • H never leaves the package. expandCronHash resolves it to an ordinary 5-field expression, so
    the API parses plain cron and the control plane shows an editable schedule.
  • Resolution happens at page creation (seedPages, captureInitiative) rather than in
    buildPageTrigger, because that is the only point where the page's identity exists — the trigger
    is built once per session for all of them.
  • The field index is hashed alongside the seed, so H H * * * is worth 1440 slots rather than 60
    correlated ones (a test asserts all 24 hours are reached across 200 banks).
  • A malformed H is refused at config resolution with a fallback to auto-refresh, matching how a
    missing pageTriggerCron is already handled. expandCronHash deliberately leaves an expression it
    cannot read alone rather than inventing a time nobody asked for.
  • Hashing distributes approximately; two pages can still collide, just not all of them.

Not included

#4176's second change — reconciliation patching only tags_match so a hand-edited schedule survives
— is an independent fix and is left to that PR.

Validation

892 tests pass (67 files), package build, tsc --noEmit, and ./scripts/hooks/lint.sh all green.
skill/SKILL.md, the docs page and the docs-skill reference were regenerated from the README after
lint, per CLAUDE.md.

One `pageTriggerCron` is shared by every page in every bank running this
plugin, so a literal `"0 3 * * *"` does not schedule a refresh at 03:00 — it
schedules all of them at 03:00, ~5 page syntheses per bank, on the worker pool
that also serves retain. Moving the hour moves the pile.

Borrow Jenkins' `H`: a cron field written `H` is replaced, per page, by a value
hashed from bank + page name, so each page keeps its own stable slot.

    "H H * * *"       daily, at this page's own minute and hour
    "H * * * *"       hourly, at its own minute
    "H 3 * * *"       daily at 03:MM — spread inside the chosen hour
    "H H(0-5) * * *"  daily, spread across the night only
    "0 3 * * *"       unchanged

Spreading is a property of the schedule, so it lives in the expression rather
than in a new `pageTriggerType` member per period (`daily-staggered`, then
`hourly-staggered`, …), each of which would spell the period in the type name
and cost a config value, a branch and a docs row.

`H` never leaves the package: `expandCronHash` resolves it to an ordinary
5-field expression at page creation — the only point where the page's identity
exists — so the server parses plain cron and the control plane shows an
editable schedule. The field index is hashed alongside the seed so `H H * * *`
is worth 1440 slots rather than 60 correlated ones. A malformed `H` is refused
at config resolution, since `expandCronHash` deliberately leaves an expression
it cannot read alone rather than inventing a time nobody asked for.
@nicoloboschi
nicoloboschi merged commit 2643186 into main Sep 8, 2026
94 checks passed
@nicoloboschi
nicoloboschi deleted the feat-page-trigger-hashed-cron branch September 8, 2026 09:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant