From bfb6e0bb33e61cef064ab5b41f91716bc02a474b Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 18 Jun 2026 14:18:31 +1000 Subject: [PATCH] docs(cron): document CronScheduler provider + cron.provider key Phase 3.5. cron-internals.md gateway-integration section now describes the pluggable trigger (resolve_cron_scheduler, built-in default, plugins/cron discovery, the never-without-a-trigger fallback, and the trigger-vs-execution split). cli-commands.md notes cron.provider near the hermes cron entry. --- .../docs/developer-guide/cron-internals.md | 25 ++++++++++++++++++- website/docs/reference/cli-commands.md | 7 ++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/website/docs/developer-guide/cron-internals.md b/website/docs/developer-guide/cron-internals.md index bad59645dbc..c895d339b09 100644 --- a/website/docs/developer-guide/cron-internals.md +++ b/website/docs/developer-guide/cron-internals.md @@ -102,7 +102,30 @@ tick() ### Gateway Integration -In gateway mode, the scheduler runs in a dedicated background thread (`_start_cron_ticker` in `gateway/run.py`) that calls `scheduler.tick()` every 60 seconds alongside message handling. +In gateway mode, the cron **trigger** (the part that decides *when* a due job +fires — "Axis B") is selected through a pluggable `CronScheduler` provider. The +gateway calls `resolve_cron_scheduler()` (`cron/scheduler_provider.py`) and runs +the resolved provider's `start()` in a dedicated background thread, alongside a +separate gateway-housekeeping thread. + +The active provider is chosen by the `cron.provider` config key: + +- **empty (default)** → the built-in `InProcessCronScheduler`, which runs the + historical in-process loop calling `scheduler.tick()` every 60 seconds. This + is byte-identical to the pre-provider behavior. +- **a named provider** (e.g. `chronos`, a managed-cron provider for + scale-to-zero deployments) → discovered from `plugins/cron//` or + `$HERMES_HOME/plugins//`. + +If a named provider is missing, fails to load, or reports `is_available() == +False`, the resolver falls back to the built-in with a warning — **cron is +never left without a trigger.** The built-in provider lives in core +(`cron/scheduler_provider.py`), not in `plugins/`, so the fallback can't be +accidentally removed. + +What "firing" *means* (job execution + delivery) is unchanged and shared by all +providers — it stays in `scheduler.run_job()` / `scheduler._deliver_result()`. +A provider only controls the trigger, never execution. In CLI mode, cron jobs only fire when `hermes cron` commands are run or during active CLI sessions. diff --git a/website/docs/reference/cli-commands.md b/website/docs/reference/cli-commands.md index 3071ac0e5fc..f0fe67d4349 100644 --- a/website/docs/reference/cli-commands.md +++ b/website/docs/reference/cli-commands.md @@ -533,6 +533,13 @@ hermes cron | `status` | Check whether the cron scheduler is running. | | `tick` | Run due jobs once and exit. | +The cron **trigger** is pluggable via the `cron.provider` config key. Empty +(the default) uses the built-in in-process ticker. A named provider (e.g. +`chronos`, a managed-cron provider for scale-to-zero deployments) is discovered +from `plugins/cron//` or `$HERMES_HOME/plugins//`; an unknown or +unavailable provider falls back to the built-in, so cron is never left without +a trigger. See the [cron internals](../developer-guide/cron-internals.md#gateway-integration) doc. + ## `hermes kanban` ```bash