| --- |
| sidebar_position: 5 |
| title: "Scheduled Tasks (Cron)" |
| description: "Schedule automated tasks with natural language, manage them with one cron tool, and attach one or more skills" |
| --- |
| |
| # Scheduled Tasks (Cron) |
|
|
| Schedule tasks to run automatically with natural language or cron expressions. Hermes exposes cron management through a single `cronjob` tool with action-style operations instead of separate schedule/list/remove tools. |
|
|
| ## What cron can do now |
|
|
| Cron jobs can: |
|
|
| - schedule one-shot or recurring tasks |
| - pause, resume, edit, trigger, and remove jobs |
| - attach zero, one, or multiple skills to a job |
| - deliver results back to the origin chat, local files, or configured platform targets |
| - run in fresh agent sessions with the normal static tool list |
|
|
| :::warning |
| Cron-run sessions cannot recursively create more cron jobs. Hermes disables cron management tools inside cron executions to prevent runaway scheduling loops. |
| ::: |
|
|
| ## Creating scheduled tasks |
|
|
| ### In chat with `/cron` |
|
|
| ```bash |
| /cron add 30m "Remind me to check the build" |
| /cron add "every 2h" "Check server status" |
| /cron add "every 1h" "Summarize new feed items" --skill blogwatcher |
| /cron add "every 1h" "Use both skills and combine the result" --skill blogwatcher --skill maps |
| ``` |
|
|
| ### From the standalone CLI |
|
|
| ```bash |
| hermes cron create "every 2h" "Check server status" |
| hermes cron create "every 1h" "Summarize new feed items" --skill blogwatcher |
| hermes cron create "every 1h" "Use both skills and combine the result" \ |
| --skill blogwatcher \ |
| --skill maps \ |
| --name "Skill combo" |
| ``` |
|
|
| ### Through natural conversation |
|
|
| Ask Hermes normally: |
|
|
| ```text |
| Every morning at 9am, check Hacker News for AI news and send me a summary on Telegram. |
| ``` |
|
|
| Hermes will use the unified `cronjob` tool internally. |
|
|
| ## Skill-backed cron jobs |
|
|
| A cron job can load one or more skills before it runs the prompt. |
|
|
| ### Single skill |
|
|
| ```python |
| cronjob( |
| action="create", |
| skill="blogwatcher", |
| prompt="Check the configured feeds and summarize anything new.", |
| schedule="0 9 * * *", |
| name="Morning feeds", |
| ) |
| ``` |
|
|
| ### Multiple skills |
|
|
| Skills are loaded in order. The prompt becomes the task instruction layered on top of those skills. |
|
|
| ```python |
| cronjob( |
| action="create", |
| skills=["blogwatcher", "maps"], |
| prompt="Look for new local events and interesting nearby places, then combine them into one short brief.", |
| schedule="every 6h", |
| name="Local brief", |
| ) |
| ``` |
|
|
| This is useful when you want a scheduled agent to inherit reusable workflows without stuffing the full skill text into the cron prompt itself. |
|
|
| ## Editing jobs |
|
|
| You do not need to delete and recreate jobs just to change them. |
|
|
| ### Chat |
|
|
| ```bash |
| /cron edit <job_id> --schedule "every 4h" |
| /cron edit <job_id> --prompt "Use the revised task" |
| /cron edit <job_id> --skill blogwatcher --skill maps |
| /cron edit <job_id> --remove-skill blogwatcher |
| /cron edit <job_id> --clear-skills |
| ``` |
|
|
| ### Standalone CLI |
|
|
| ```bash |
| hermes cron edit <job_id> --schedule "every 4h" |
| hermes cron edit <job_id> --prompt "Use the revised task" |
| hermes cron edit <job_id> --skill blogwatcher --skill maps |
| hermes cron edit <job_id> --add-skill maps |
| hermes cron edit <job_id> --remove-skill blogwatcher |
| hermes cron edit <job_id> --clear-skills |
| ``` |
|
|
| Notes: |
|
|
| - repeated `--skill` replaces the job's attached skill list |
| - `--add-skill` appends to the existing list without replacing it |
| - `--remove-skill` removes specific attached skills |
| - `--clear-skills` removes all attached skills |
|
|
| ## Lifecycle actions |
|
|
| Cron jobs now have a fuller lifecycle than just create/remove. |
|
|
| ### Chat |
|
|
| ```bash |
| /cron list |
| /cron pause <job_id> |
| /cron resume <job_id> |
| /cron run <job_id> |
| /cron remove <job_id> |
| ``` |
|
|
| ### Standalone CLI |
|
|
| ```bash |
| hermes cron list |
| hermes cron pause <job_id> |
| hermes cron resume <job_id> |
| hermes cron run <job_id> |
| hermes cron remove <job_id> |
| hermes cron status |
| hermes cron tick |
| ``` |
|
|
| What they do: |
|
|
| - `pause` β keep the job but stop scheduling it |
| - `resume` β re-enable the job and compute the next future run |
| - `run` β trigger the job on the next scheduler tick |
| - `remove` β delete it entirely |
|
|
| ## How it works |
|
|
| **Cron execution is handled by the gateway daemon.** The gateway ticks the scheduler every 60 seconds, running any due jobs in isolated agent sessions. |
|
|
| ```bash |
| hermes gateway install # Install as a user service |
| sudo hermes gateway install --system # Linux: boot-time system service for servers |
| hermes gateway # Or run in foreground |
| |
| hermes cron list |
| hermes cron status |
| ``` |
|
|
| ### Gateway scheduler behavior |
|
|
| On each tick Hermes: |
|
|
| 1. loads jobs from `~/.hermes/cron/jobs.json` |
| 2. checks `next_run_at` against the current time |
| 3. starts a fresh `AIAgent` session for each due job |
| 4. optionally injects one or more attached skills into that fresh session |
| 5. runs the prompt to completion |
| 6. delivers the final response |
| 7. updates run metadata and the next scheduled time |
|
|
| A file lock at `~/.hermes/cron/.tick.lock` prevents overlapping scheduler ticks from double-running the same job batch. |
|
|
| ## Delivery options |
|
|
| When scheduling jobs, you specify where the output goes: |
|
|
| | Option | Description | Example | |
| |--------|-------------|---------| |
| | `"origin"` | Back to where the job was created | Default on messaging platforms | |
| | `"local"` | Save to local files only (`~/.hermes/cron/output/`) | Default on CLI | |
| | `"telegram"` | Telegram home channel | Uses `TELEGRAM_HOME_CHANNEL` | |
| | `"telegram:123456"` | Specific Telegram chat by ID | Direct delivery | |
| | `"telegram:-100123:17585"` | Specific Telegram topic | `chat_id:thread_id` format | |
| | `"discord"` | Discord home channel | Uses `DISCORD_HOME_CHANNEL` | |
| | `"discord:#engineering"` | Specific Discord channel | By channel name | |
| | `"slack"` | Slack home channel | | |
| | `"whatsapp"` | WhatsApp home | | |
| | `"signal"` | Signal | | |
| | `"matrix"` | Matrix home room | | |
| | `"mattermost"` | Mattermost home channel | | |
| | `"email"` | Email | | |
| | `"sms"` | SMS via Twilio | | |
| | `"homeassistant"` | Home Assistant | | |
| | `"dingtalk"` | DingTalk | | |
| | `"feishu"` | Feishu/Lark | | |
| | `"wecom"` | WeCom | | |
| | `"weixin"` | Weixin (WeChat) | | |
| | `"bluebubbles"` | BlueBubbles (iMessage) | | |
| | `"qqbot"` | QQ Bot (Tencent QQ) | | |
|
|
| The agent's final response is automatically delivered. You do not need to call `send_message` in the cron prompt. |
|
|
| ### Response wrapping |
|
|
| By default, delivered cron output is wrapped with a header and footer so the recipient knows it came from a scheduled task: |
|
|
| ``` |
| Cronjob Response: Morning feeds |
| ------------- |
| |
| <agent output here> |
| |
| Note: The agent cannot see this message, and therefore cannot respond to it. |
| ``` |
|
|
| To deliver the raw agent output without the wrapper, set `cron.wrap_response` to `false`: |
|
|
| ```yaml |
| # ~/.hermes/config.yaml |
| cron: |
| wrap_response: false |
| ``` |
|
|
| ### Silent suppression |
|
|
| If the agent's final response starts with `[SILENT]`, delivery is suppressed entirely. The output is still saved locally for audit (in `~/.hermes/cron/output/`), but no message is sent to the delivery target. |
|
|
| This is useful for monitoring jobs that should only report when something is wrong: |
|
|
| ```text |
| Check if nginx is running. If everything is healthy, respond with only [SILENT]. |
| Otherwise, report the issue. |
| ``` |
|
|
| Failed jobs always deliver regardless of the `[SILENT]` marker β only successful runs can be silenced. |
|
|
| ## Script timeout |
|
|
| Pre-run scripts (attached via the `script` parameter) have a default timeout of 120 seconds. If your scripts need longer β for example, to include randomized delays that avoid bot-like timing patterns β you can increase this: |
|
|
| ```yaml |
| # ~/.hermes/config.yaml |
| cron: |
| script_timeout_seconds: 300 # 5 minutes |
| ``` |
|
|
| Or set the `HERMES_CRON_SCRIPT_TIMEOUT` environment variable. The resolution order is: env var β config.yaml β 120s default. |
|
|
| ## Provider recovery |
|
|
| Cron jobs inherit your configured fallback providers and credential pool rotation. If the primary API key is rate-limited or the provider returns an error, the cron agent can: |
|
|
| - **Fall back to an alternate provider** if you have `fallback_providers` (or the legacy `fallback_model`) configured in `config.yaml` |
| - **Rotate to the next credential** in your [credential pool](/docs/user-guide/configuration#credential-pool-strategies) for the same provider |
|
|
| This means cron jobs that run at high frequency or during peak hours are more resilient β a single rate-limited key won't fail the entire run. |
|
|
| ## Schedule formats |
|
|
| The agent's final response is automatically delivered β you do **not** need to include `send_message` in the cron prompt for that same destination. If a cron run calls `send_message` to the exact target the scheduler will already deliver to, Hermes skips that duplicate send and tells the model to put the user-facing content in the final response instead. Use `send_message` only for additional or different targets. |
|
|
| ### Relative delays (one-shot) |
|
|
| ```text |
| 30m β Run once in 30 minutes |
| 2h β Run once in 2 hours |
| 1d β Run once in 1 day |
| ``` |
|
|
| ### Intervals (recurring) |
|
|
| ```text |
| every 30m β Every 30 minutes |
| every 2h β Every 2 hours |
| every 1d β Every day |
| ``` |
|
|
| ### Cron expressions |
|
|
| ```text |
| 0 9 * * * β Daily at 9:00 AM |
| 0 9 * * 1-5 β Weekdays at 9:00 AM |
| 0 */6 * * * β Every 6 hours |
| 30 8 1 * * β First of every month at 8:30 AM |
| 0 0 * * 0 β Every Sunday at midnight |
| ``` |
|
|
| ### ISO timestamps |
|
|
| ```text |
| 2026-03-15T09:00:00 β One-time at March 15, 2026 9:00 AM |
| ``` |
|
|
| ## Repeat behavior |
|
|
| | Schedule type | Default repeat | Behavior | |
| |--------------|----------------|----------| |
| | One-shot (`30m`, timestamp) | 1 | Runs once | |
| | Interval (`every 2h`) | forever | Runs until removed | |
| | Cron expression | forever | Runs until removed | |
|
|
| You can override it: |
|
|
| ```python |
| cronjob( |
| action="create", |
| prompt="...", |
| schedule="every 2h", |
| repeat=5, |
| ) |
| ``` |
|
|
| ## Managing jobs programmatically |
|
|
| The agent-facing API is one tool: |
|
|
| ```python |
| cronjob(action="create", ...) |
| cronjob(action="list") |
| cronjob(action="update", job_id="...") |
| cronjob(action="pause", job_id="...") |
| cronjob(action="resume", job_id="...") |
| cronjob(action="run", job_id="...") |
| cronjob(action="remove", job_id="...") |
| ``` |
|
|
| For `update`, pass `skills=[]` to remove all attached skills. |
|
|
| ## Job storage |
|
|
| Jobs are stored in `~/.hermes/cron/jobs.json`. Output from job runs is saved to `~/.hermes/cron/output/{job_id}/{timestamp}.md`. |
|
|
| Jobs may store `model` and `provider` as `null`. When those fields are omitted, Hermes resolves them at execution time from the global configuration. They only appear in the job record when a per-job override is set. |
|
|
| The storage uses atomic file writes so interrupted writes do not leave a partially written job file behind. |
|
|
| ## Self-contained prompts still matter |
|
|
| :::warning Important |
| Cron jobs run in a completely fresh agent session. The prompt must contain everything the agent needs that is not already provided by attached skills. |
| ::: |
|
|
| **BAD:** `"Check on that server issue"` |
|
|
| **GOOD:** `"SSH into server 192.168.1.100 as user 'deploy', check if nginx is running with 'systemctl status nginx', and verify https://example.com returns HTTP 200."` |
|
|
| ## Security |
|
|
| Scheduled task prompts are scanned for prompt-injection and credential-exfiltration patterns at creation and update time. Prompts containing invisible Unicode tricks, SSH backdoor attempts, or obvious secret-exfiltration payloads are blocked. |
|
|