mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-01 16:31:56 +08:00
Covers ~60 merged PRs from Apr 15–29 that shipped user-visible behavior without docs coverage. No functional code changes; docs + static manifest regeneration only. Highlights: Stale / incorrect: - configuration.md: auxiliary auto-routing line was wrong since #11900; now correctly states auto routes to the main model, with a note on the cost trade-off and per-task override pattern. - integrations/providers.md + configuration.md compression intro: removed stale 'Gemini Flash via OpenRouter' claim. - website/static/api/model-catalog.json: rebuilt from hermes_cli/models.py so the live manifest picks up tencent/hy3-preview (and remains in sync for future model-catalog PRs). Platform messaging (#17417 #16997 #16193 #14315 #13151 #11794 #10610 #10283 #10246 #11564 #13178): - Signal: native formatting (bodyRanges), reply quotes, reactions. - Telegram: table rendering (bullets + code-block fallback), disable_link_previews, group_allowed_chats. - Slack: strict_mention config. - Discord: slash_commands disable, send_animation GIF, send_message native media attachments. - DingTalk: require_mention + allowed_users. CLI (#16052 #16539 #16566 #15841 #14798 #10043): - New 'hermes fallback' interactive manager. - New 'hermes update --check', '--backup' flag, and pre-update pairing snapshot behavior. - 'hermes gateway start/restart --all' multi-profile flag. - cron.md: 'hermes tools' as a platform, per-job enabled_toolsets, wakeAgent gate, context_from chaining. Config keys / env vars (#17305 #17026 #17000 #15077 #14557 #14227 #14166 #14730 #17008): - terminal.docker_run_as_host_user, display.runtime_metadata_footer, compression.hygiene_hard_message_limit, HINDSIGHT_TIMEOUT, skills.guard_agent_created, TAVILY_BASE_URL, security.allow_private_urls, agent.api_max_retries, gateway hot-reload of compression/context_length config edits. TUI / CLI UX (#17130 #17113 #17175 #17150 #16707 #12312 #12305 #12934 #14810 #14045 #17286 #17126): - HERMES_TUI_RESUME, HERMES_TUI_THEME, LaTeX rendering, busy-indicator styles, ctrl-x queued-message delete, git branch in status bar, per- prompt elapsed stopwatch, external-editor keybind, markdown stripping, TUI voice-mode parity, /agents overlay, /reload + /mouse. Gateway features (#16506 #15027 #13428 #12116): - Native multimodal image routing based on vision capability. - /usage account-limits section. - /steer slash command (added to reference + explanation in CLI). Plugins / hooks (#12929 #12972 #10763 #16364): - transform_tool_result, transform_terminal_output plugin hooks. - PluginContext.dispatch_tool() documented with slash-command example. - google_meet bundled plugin entry under built-in-plugins.md. Other (#16576 #16572 #16383 #15878 #15608 #15606 #14809 #14767 #14231 #14232 #14307 #13683 #12373 #11891 #11291 #10066): - hermes backup exclusions (WAL/SHM/journal + checkpoints/). - security.md hardline blocklist (floor below --yolo). - FHS install layout for root installs. - openssh-client + docker-cli baked into the Docker image. - MEDIA: tag supported extensions table (docs/office/archives/pdf). - Remote-to-host file sync on SSH/Modal/Daytona teardown. - 'hermes model' -> Configure Auxiliary Models interactive picker. - Podman support via HERMES_DOCKER_BINARY. Providers / STT / one-shot (#15045 #14473 #15704): - alibaba-coding-plan first-class provider entry. - xAI Grok STT as a 6th transcription option. - 'hermes -z' scripted one-shot mode + HERMES_INFERENCE_MODEL. Build: 'docusaurus build' succeeds. No new broken links/anchors; pre-existing warnings unchanged.
448 lines
15 KiB
Markdown
448 lines
15 KiB
Markdown
---
|
||
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.
|
||
|
||
## Running a job inside a project directory
|
||
|
||
Cron jobs default to running detached from any repo — no `AGENTS.md`, `CLAUDE.md`, or `.cursorrules` is loaded, and the terminal / file / code-exec tools run from whatever working directory the gateway started in. Pass `--workdir` (CLI) or `workdir=` (tool call) to change that:
|
||
|
||
```bash
|
||
# Standalone CLI
|
||
hermes cron create --schedule "every 1d at 09:00" \
|
||
--workdir /home/me/projects/acme \
|
||
--prompt "Audit open PRs, summarize CI health, and post to #eng"
|
||
```
|
||
|
||
```python
|
||
# From a chat, via the cronjob tool
|
||
cronjob(
|
||
action="create",
|
||
schedule="every 1d at 09:00",
|
||
workdir="/home/me/projects/acme",
|
||
prompt="Audit open PRs, summarize CI health, and post to #eng",
|
||
)
|
||
```
|
||
|
||
When `workdir` is set:
|
||
|
||
- `AGENTS.md`, `CLAUDE.md`, and `.cursorrules` from that directory are injected into the system prompt (same discovery order as the interactive CLI)
|
||
- `terminal`, `read_file`, `write_file`, `patch`, `search_files`, and `execute_code` all use that directory as their working directory (via `TERMINAL_CWD`)
|
||
- The path must be an absolute directory that exists — relative paths and missing directories are rejected at create / update time
|
||
- Pass `--workdir ""` (or `workdir=""` via the tool) on edit to clear it and restore the old behaviour
|
||
|
||
:::note Serialization
|
||
Jobs with a `workdir` run sequentially on the scheduler tick, not in the parallel pool. This is deliberate — `TERMINAL_CWD` is process-global, so two workdir jobs running at the same time would corrupt each other's cwd. Workdir-less jobs still run in parallel as before.
|
||
:::
|
||
|
||
## 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.
|
||
|
||
## Toolsets available to cron jobs
|
||
|
||
Cron runs each job in a fresh agent session with no chat platform attached. By default the cron agent gets **the toolset you configured for the `cron` platform in `hermes tools`** — not the CLI default, not everything under the sun.
|
||
|
||
```bash
|
||
hermes tools
|
||
# → pick the "cron" platform in the curses UI
|
||
# → toggle toolsets on/off just like you would for Telegram/Discord/etc.
|
||
```
|
||
|
||
Tighter per-job control is available via the `enabled_toolsets` field on `cronjob.create` (or on an existing job via `cronjob.update`):
|
||
|
||
```text
|
||
cronjob(action="create", name="weekly-news-summary",
|
||
schedule="every sunday 9am",
|
||
enabled_toolsets=["web", "file"], # just web + file, no terminal/browser/etc.
|
||
prompt="Summarize this week's AI news: ...")
|
||
```
|
||
|
||
When `enabled_toolsets` is set on a job it wins; otherwise the `hermes tools` cron-platform config wins; otherwise Hermes falls back to the built-in defaults. This matters for cost control: carrying `moa`, `browser`, `delegation` into every tiny "fetch news" job bloats the tool-schema prompt on every LLM call.
|
||
|
||
### Skipping the agent entirely: `wakeAgent`
|
||
|
||
If your cron job attaches a pre-check script (via `script=`), the script can decide at runtime whether Hermes should even invoke the agent. Emit a final stdout line of the form:
|
||
|
||
```text
|
||
{"wakeAgent": false}
|
||
```
|
||
|
||
…and cron skips the agent run entirely for this tick. Useful for frequent polls (every 1–5 min) that only need to wake the LLM when state actually changed — otherwise you pay for zero-content agent turns over and over.
|
||
|
||
```python
|
||
# pre-check script
|
||
import json, sys
|
||
latest = fetch_latest_issue_count()
|
||
prev = read_state("issue_count")
|
||
if latest == prev:
|
||
print(json.dumps({"wakeAgent": False})) # skip this tick
|
||
sys.exit(0)
|
||
write_state("issue_count", latest)
|
||
print(json.dumps({"wakeAgent": True, "context": {"new_issues": latest - prev}}))
|
||
```
|
||
|
||
When `wakeAgent` is omitted, the default is `true` (wake the agent as usual).
|
||
|
||
### Chaining jobs: `context_from`
|
||
|
||
A cron job can consume the most recent successful output of one or more other jobs by listing their names (or IDs) in `context_from`:
|
||
|
||
```text
|
||
cronjob(action="create", name="daily-digest",
|
||
schedule="every day 7am",
|
||
context_from=["ai-news-fetch", "github-prs-fetch"],
|
||
prompt="Write the daily digest using the outputs above.")
|
||
```
|
||
|
||
The referenced jobs' most recent completed outputs are injected above the prompt as context for this run. Each upstream entry must be a valid job ID or name (see `cronjob action="list"`). Note: chaining reads the *most recent completed* output — it does not wait for upstream jobs that are running in the same tick.
|
||
|
||
## 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.
|