Skip to content

feat(cron): Automation Blueprints — parameterized automation templates across every surface - #41309

Merged
teknium1 merged 7 commits into
mainfrom
feat/suggested-cron-jobs
Jun 11, 2026
Merged

feat(cron): Automation Blueprints — parameterized automation templates across every surface#41309
teknium1 merged 7 commits into
mainfrom
feat/suggested-cron-jobs

Conversation

@teknium1

@teknium1 teknium1 commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Pick an automation by name and Hermes asks you for what it needs — no cron syntax, no slot=value typing. One blueprint definition renders natively on every surface (dashboard form, CLI/TUI/messenger slash command, agent conversation, docs catalog), and every path ends at the same cron.jobs.create_job — there is no second job engine.

This PR has three layers:

  1. Suggested Cron Jobs — Hermes can propose automations and you accept them with one tap via /suggestions, instead of assembling cron jobs by hand. Sources: a curated catalog, blueprint-carrying skills, and (reserved) usage/integration detectors. Consent-first: nothing schedules without explicit acceptance; dismissals latch.

  2. Automation Blueprints — parameterized templates with typed slots (time/enum/text/weekdays). The slot schema (cron/blueprint_catalog.py) is the single source of truth; four renderers consume it. 14 curated blueprints spanning daily/weekly/general (morning brief, important-mail monitor, weekly review, news digest, meal plan, habit check-in, hydration nudge, learning drip, and more).

    • Dashboard / desktop app — a Blueprints tab renders each blueprint's slots as a form; submit → POST /api/cron/blueprints/instantiate fills + creates the job (no agent turn).
    • Desktop — a hermes://blueprint/<key>?slot=val deep-link routes into the chat composer pre-filled.
    • Docs — a generated catalog page with copy-paste commands.
  3. Conversational fill/blueprint <name> (alias /bp) name-matches a blueprint (exact → prefix → substring → fuzzy; ambiguous lists candidates) and seeds the agent to ask you for each value one at a time, then calls the existing cronjob tool. The slot=value one-liner remains as the deterministic dashboard/docs/power-user shortcut. No new tool.

How the conversational fill stays invariant-safe

The seed enters as a normal user turn — never a synthetic injection.

  • Shared handler returns BlueprintCommandResult{text, agent_seed}; match_blueprint() + build_blueprint_seed() are the shared pieces.
  • Gateway: dispatch rewrites event.text to the seed and falls through to the agent (the same pattern /steer uses).
  • CLI: handler sets a one-shot self._pending_agent_seed; the interactive loop consumes it right after process_command() and runs it as the next turn.

Changes (by surface)

  • Corecron/suggestions.py (store: add/list/accept/dismiss, dedup+latch, pending cap), cron/suggestion_catalog.py, cron/blueprint_catalog.py (AutomationBlueprint + BlueprintSlot, 14 blueprints, 4 renderers, fill_blueprint), tools/blueprints.py (blueprint↔job bridge), hermes_cli/suggestions_cmd.py + hermes_cli/blueprint_cmd.py (shared handlers); CommandDef + CLI dispatch + gateway dispatch.
  • Dashboard — blueprint endpoints, AutomationBlueprints.tsx gallery+form, CronPage sub-tab, api.ts.
  • Desktophermes:// deep-link end to end.
  • Docs — generated Automation Blueprints catalog page + sidebar entry; creating-skills.md blueprint section.

Naming note: this feature was developed as "Cron Recipes" and rebranded to Automation Blueprints before merge (final commit renames every identifier, route, deep-link, and doc — no behavior change).

Validation

Result
tests/cron/ (blueprints, suggestions, schedule, validation, renderers, handlers) pass
tests/tools/test_blueprints.py + blueprint web endpoints pass
Targeted suite post-rename (cron + blueprints + web_server + commands) 874 pass
E2E name-match variants → seed built → agent path; direct slot=valfill_blueprintcreate_job → persisted job (schedule/deliver/origin correct)

Rebased onto current main; linear history, contributor authorship preserved.

Infographic

automation-blueprints

@github-actions

github-actions Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

🔎 Lint report: feat/suggested-cron-jobs vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 10757 on HEAD, 10719 on base (🆕 +38)

🆕 New issues (20):

Rule Count
invalid-argument-type 10
not-subscriptable 3
unresolved-attribute 3
unresolved-import 3
unsupported-operator 1
First entries
tests/cron/test_suggestions.py:198: [not-subscriptable] not-subscriptable: Cannot subscript object of type `None` with no `__getitem__` method
cron/scripts/classify_items.py:215: [unresolved-attribute] unresolved-attribute: Attribute `get` is not defined on `None` in union `dict[str, Any] | None`
tests/cron/test_suggestions.py:198: [invalid-argument-type] invalid-argument-type: Method `__getitem__` of type `bound method str.__getitem__(key: SupportsIndex | slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None], /) -> str` cannot be called with key of type `Literal["monitor"]` on object of type `str`
tests/tools/test_blueprints.py:12: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
hermes_cli/cli_commands_mixin.py:1302: [unresolved-attribute] unresolved-attribute: Object of type `Self@_handle_blueprint_command` has no attribute `_console_print`
tests/cron/test_suggestions.py:198: [not-subscriptable] not-subscriptable: Cannot subscript object of type `float` with no `__getitem__` method
tests/cron/test_suggestions.py:198: [invalid-argument-type] invalid-argument-type: Method `__getitem__` of type `Overload[(i: SupportsIndex, /) -> Unknown, (s: slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None], /) -> list[Unknown]]` cannot be called with key of type `Literal["monitor"]` on object of type `list[Unknown]`
tests/cron/test_blueprint_catalog.py:14: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/cron/test_suggestions.py:197: [unsupported-operator] unsupported-operator: Operator `in` is not supported between objects of type `Literal["monitor"]` and `str | dict[Unknown, Unknown] | list[Unknown] | ... omitted 31 union elements`
tests/cron/test_suggestions.py:198: [invalid-argument-type] invalid-argument-type: Method `__getitem__` of type `Overload[(i: SupportsIndex, /) -> str, (s: slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None], /) -> list[str]]` cannot be called with key of type `Literal["monitor"]` on object of type `list[str]`
tests/cron/test_suggestions.py:198: [not-subscriptable] not-subscriptable: Cannot subscript object of type `int` with no `__getitem__` method
tests/cron/test_suggestions.py:198: [invalid-argument-type] invalid-argument-type: Method `__getitem__` of type `bound method str.__getitem__(key: SupportsIndex | slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None], /) -> str` cannot be called with key of type `Literal["provider"]` on object of type `str`
tests/cron/test_blueprint_catalog.py:145: [invalid-argument-type] invalid-argument-type: Argument to function `blueprint_deeplink` is incorrect: Expected `AutomationBlueprint`, found `AutomationBlueprint | None`
tests/cron/test_blueprint_catalog.py:150: [invalid-argument-type] invalid-argument-type: Argument to function `blueprint_catalog_entry` is incorrect: Expected `AutomationBlueprint`, found `AutomationBlueprint | None`
hermes_cli/cli_commands_mixin.py:1277: [unresolved-attribute] unresolved-attribute: Object of type `Self@_handle_suggestions_command` has no attribute `_console_print`
tests/cron/test_suggestions.py:13: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/cron/test_blueprint_catalog.py:140: [invalid-argument-type] invalid-argument-type: Argument to function `blueprint_slash_command` is incorrect: Expected `AutomationBlueprint`, found `AutomationBlueprint | None`
tests/cron/test_suggestions.py:198: [invalid-argument-type] invalid-argument-type: Method `__getitem__` of type `Overload[(i: SupportsIndex, /) -> Unknown, (s: slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None], /) -> list[Unknown]]` cannot be called with key of type `Literal["provider"]` on object of type `list[Unknown]`
tests/cron/test_blueprint_catalog.py:121: [invalid-argument-type] invalid-argument-type: Argument to function `fill_blueprint` is incorrect: Expected `AutomationBlueprint`, found `AutomationBlueprint | None`
tests/cron/test_blueprint_catalog.py:128: [invalid-argument-type] invalid-argument-type: Argument to function `blueprint_form_schema` is incorrect: Expected `AutomationBlueprint`, found `AutomationBlueprint | None`

✅ Fixed issues: none

Unchanged: 5611 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@daimon-nous daimon-nous Bot added type/feature New feature or request comp/cron Cron scheduler and job management comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery P3 Low — cosmetic, nice to have labels Jun 7, 2026
@teknium1
teknium1 force-pushed the feat/suggested-cron-jobs branch from 7b49a62 to 57b9901 Compare June 8, 2026 01:51
@teknium1
teknium1 requested a review from a team June 8, 2026 01:51
@teknium1 teknium1 changed the title feat(cron): Suggested Cron Jobs — one surface for proposed automations feat(cron): Cron Recipes — parameterized automation templates across every surface Jun 8, 2026
@teknium1
teknium1 force-pushed the feat/suggested-cron-jobs branch 4 times, most recently from f5e2859 to 9b08c71 Compare June 8, 2026 17:02
@teknium1
teknium1 enabled auto-merge (rebase) June 11, 2026 16:50
OutThisLife
OutThisLife previously approved these changes Jun 11, 2026
auto-merge was automatically disabled June 11, 2026 16:52

Rebase failed

teknium1 added 6 commits June 11, 2026 10:06
Hermes can propose automations and let the user accept them with one tap
via /suggestions, instead of making them assemble cron jobs by hand. Every
proposal — wherever it originates — flows through one surface.

Sources (the 'where suggestions come from'):
- catalog: curated starter automations (daily briefing, important-mail
  monitor, weekly review, workday-start reminder) via /suggestions catalog
- recipe: installing a skill that carries a metadata.hermes.recipe block
  registers a suggestion instead of auto-scheduling
- usage / integration: reserved for the background-review detector and
  account-connect triggers (sources defined; emitters land next)

Pieces:
- cron/suggestions.py — the store. add/list/accept/dismiss, dedup+latch by
  key (dismissed proposals never re-offered), pending cap so it can't become
  a nag wall. Accepting calls the existing cron.jobs.create_job — there is
  NO second job engine. Mirrors jobs.py storage (atomic writes, lock, 0600).
- cron/suggestion_catalog.py — the curated set. The important-mail monitor
  entry is where the old proactive-monitor poll->classify->surface engine
  lives now (cron/scripts/classify_items.py + the 'monitor' aux task), as ONE
  catalog automation rather than a standalone feature.
- tools/recipes.py — recipe<->job bridge; register_recipe_suggestion() makes
  a recipe source 'recipe' of this surface. recipe_to_job_spec() is the single
  translation both the direct and suggestion paths share.
- hermes_cli/suggestions_cmd.py — shared /suggestions handler (CLI + gateway
  never drift); /suggestions [accept N|dismiss N|catalog|clear].
- Wired: CommandDef + CLI dispatch (cli.py) + gateway dispatch (gateway/run.py)
  + aux 'monitor' task (config.py) + recipe-install hook (skills_hub.py).

Consent-first throughout: nothing auto-schedules; acceptance is always
explicit; dismissals latch.

Supersedes #41122 (proactive-monitor) and #41127 (recipes): both fold in here
as a catalog entry and a suggestion source respectively.

Tests: store (dedup/cap/accept/dismiss/latch), catalog seeding+idempotency,
recipe->suggestion bridge, command handler, aux config. E2E: recipe SKILL.md
-> parsed -> suggested -> accepted -> real cron job persisted to jobs.json.
…every surface

A 'recipe' is a one-place definition of an automation that every surface
renders natively. The slot schema (cron/recipe_catalog.py) is the single
source of truth; four renderers consume it, and all paths end at the same
cron.jobs.create_job — no second job engine.

Form where there's a screen, conversation where there's a chat line:
- Dashboard / GUI app: a Recipes sub-tab on the Cron page renders each
  recipe's typed slots as a form (time-picker, enum dropdown, free-text);
  submit POSTs /api/cron/recipes/instantiate which fills + creates the job.
- CLI / TUI / messengers: /cron-recipe lists the catalog, shows a recipe's
  fields, or fills + creates from a pasted 'key slot=val' command. The shared
  handler (hermes_cli/cron_recipe_cmd.py) names any missing/invalid slot so
  the agent can ask a targeted follow-up.
- Docs: a generated Cron Recipes catalog page (website, .mdx + React cards)
  shows each recipe with a copy-paste command and a 'Send to App' button.
- Desktop: a hermes:// URL scheme (Electron single-instance lock +
  setAsDefaultProtocolClient + open-url/second-instance) routes
  hermes://cron-recipe/<key>?slot=val into the chat composer pre-filled.

Typed slots (time/enum/text/weekdays) with defaults: users never type raw
cron — recipes parameterize time-of-day and weekday sets and translate to
cron expressions; a free-text 'schedule' slot is the full-flexibility escape
hatch. Consent-first throughout: nothing schedules without an explicit submit
or send.

Core:
- cron/recipe_catalog.py — CronRecipe + RecipeSlot, 5 curated recipes,
  recipe_form_schema / recipe_slash_command / recipe_deeplink /
  recipe_catalog_entry renderers, fill_recipe (validate + translate to
  create_job kwargs).
- hermes_cli/cron_recipe_cmd.py — shared /cron-recipe handler (CLI + TUI +
  gateway never drift). CommandDef + dispatch in commands.py / cli.py /
  gateway/run.py.

Dashboard: GET /api/cron/recipes + POST /api/cron/recipes/instantiate
(web_server.py), CronRecipes.tsx gallery+form, Segmented sub-tab on CronPage,
api.ts methods + types.

Desktop: hermes:// scheme end to end (main.cjs deep-link router + ready-queue,
preload onDeepLink/signalDeepLinkReady, global.d.ts types, desktop-controller
composer prefill, electron-builder protocols key).

Docs: extract-cron-recipes.py generator wired into prebuild.mjs,
cron-recipes-catalog.mdx + CronRecipesCatalog React component, sidebar entry.
Generated index json gitignored like skills.json.

Tests: 23 core (catalog/slots/schedule-resolution/validation/renderers/command
handler/generator) + 5 web_server endpoint tests. E2E verified end to end:
slot fill -> create_job -> persisted job with correct schedule/deliver/origin.
Reworks the chat-line UX: pick a recipe by name and the agent asks you for
what it needs, one question at a time, instead of forcing you to hand-type a
slot=val command line.

- /cron-recipe                  -> lists the catalog
- /cron-recipe <name>           -> forgiving name match (exact/prefix/substring/
                                   fuzzy; ambiguous lists candidates), then seeds
                                   the agent with a natural-language fill request
                                   built from the recipe's typed slots + schedule
                                   and prompt templates. The agent asks for each
                                   value one at a time and calls the EXISTING
                                   cronjob tool. No new tool.
- /cron-recipe <name> slot=val  -> unchanged deterministic path (fill_recipe ->
                                   create_job) for the dashboard/docs/power user.

Mechanism (no new plumbing, invariant-safe — the seed enters as a normal user
turn, never a synthetic injection):
- shared handler returns RecipeCommandResult{text, agent_seed}; match_recipe()
  and build_recipe_seed() are the new shared pieces.
- gateway: dispatch rewrites event.text to the seed and falls through to the
  agent (the same pattern /steer uses).
- CLI: handler sets a one-shot self._pending_agent_seed; the interactive loop
  consumes it right after process_command() and runs it as the next turn.

The typed-slot schema stays the single source of truth (still validates the
form/inline path via fill_recipe); the agent path just renders those slots into
the questions to ask. Docs updated to lead with the name-then-ask flow.
…ot names, surface-aware UX

Review fixes for the Cron Recipes stack before release:

- hydration-move: */90 in the cron minute field silently wraps to hourly
  (croniter-verified) — 90/120-minute options never fired at their stated
  cadence. Replaced with an hour-field step (0 9-17/2 * * 1-5) and an
  interval_hours slot whose options (1/2/3h) all fire as labeled.
- fill_recipe: reject unknown slot names. A typo'd 'tiem=07:15' used to
  silently create the job at the 08:00 default; now it 422s on the dashboard
  form and errors on the slash/deep-link paths with the valid slot list.
- deliver slot: non-strict enum (options are suggestions, scheduler
  validates downstream) so slack/whatsapp/etc. users aren't locked out;
  GET /api/cron/recipes rewrites its options from cron_delivery_targets()
  so the dashboard form only offers configured platforms; help text no
  longer claims dashboard-created jobs deliver to 'the chat you set this
  up from' (the endpoint strips origin — they go to the home channel).
- gateway: success/accept messages no longer point at /cron (cli_only);
  surface-aware hint instead. Conversational fill now sends the
  'Setting up X — I'll ask you a couple of things…' ack before the agent
  turn, matching the CLI experience.
- important-mail catalog entry: reference the urgency classifier by module
  path (python3 -m cron.scripts.classify_items) instead of baking an
  absolute host path into the job prompt — stale after relocation and
  nonexistent on remote terminal backends. cron/scripts is now a real
  package and ships in the wheel (pyproject packages.find).
- export_recipe: interval schedules round-trip again — parse_schedule
  stores 'minutes' but the renderer only read 'seconds', so every interval
  job exported as the silent '0 9 * * *' fallback.
- skills_hub install: say so when a recipe suggestion is dropped
  (latched dedup or pending cap) instead of printing nothing.

Targeted tests: 58 cron/recipe + 261 web_server pass; E2E-validated all
14 recipes fill+parse, hydration cadences via croniter, typo rejection on
slash + endpoint paths, surface-aware hints, and interval export round-trip.
…t the 50-cap

CI tests the PR merged with current main, where the new /memory canonical
command filled Slack's 50-slash cap: with btw/bg/reset all pinned ahead of
canonicals, the last canonical (/debug) got clamped and the Telegram-parity
test failed. Canonical commands must win slots over alias spellings — /new
keeps its native slot and 'reset' stays reachable via /hermes reset.

Also updates test_includes_aliases_as_first_class_slashes to assert the
pinned-alias contract (_SLACK_PRIORITY_ALIASES survive) instead of a
specific unpinned alias's survival, which was the same change-detector
pattern the docstring already warned about.
@teknium1
teknium1 force-pushed the feat/suggested-cron-jobs branch from 80123fd to 9c07e06 Compare June 11, 2026 17:07
@teknium1 teknium1 changed the title feat(cron): Cron Recipes — parameterized automation templates across every surface feat(cron): Automation Blueprints — parameterized automation templates across every surface Jun 11, 2026
Product rename across every surface: module/file names (blueprint_catalog,
tools/blueprints, blueprint_cmd), slash command /cron-recipe -> /blueprint
(alias /bp), dashboard API /api/cron/blueprints, desktop deep-link
hermes://blueprint/<key>, docs catalog page + extract script, and the
skill frontmatter block metadata.hermes.blueprint. No behavior change.
@teknium1
teknium1 merged commit cb29e8a into main Jun 11, 2026
32 checks passed
@teknium1
teknium1 deleted the feat/suggested-cron-jobs branch June 11, 2026 17:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard comp/cron Cron scheduler and job management comp/gateway Gateway runner, session dispatch, delivery P3 Low — cosmetic, nice to have type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants