hermes-agent/website/docs/user-guide/features/cron.md
Teknium ea0bd81b84 feat(skills): consolidate find-nearby into maps as a single location skill
find-nearby and the (new) maps optional skill both used OpenStreetMap's
Overpass + Nominatim to answer the same question — 'what's near this
location?' — so shipping both would be duplicate code for overlapping
capability. Consolidate into one active-by-default skill at
skills/productivity/maps/ that is a strict superset of find-nearby.

Moves + deletions:
- optional-skills/productivity/maps/ → skills/productivity/maps/ (active,
  no install step needed)
- skills/leisure/find-nearby/ → DELETED (fully superseded)

Upgrades to maps_client.py so it covers everything find-nearby did:
- Overpass server failover — tries overpass-api.de then
  overpass.kumi.systems so a single-mirror outage doesn't break the skill
  (new overpass_query helper, used by both nearby and bbox)
- nearby now accepts --near "<address>" as a shortcut that auto-geocodes,
  so one command replaces the old 'search → copy coords → nearby' chain
- nearby now accepts --category (repeatable) for multi-type queries in
  one call (e.g. --category restaurant --category bar), results merged
  and deduped by (osm_type, osm_id), sorted by distance, capped at --limit
- Each nearby result now includes maps_url (clickable Google Maps search
  link) and directions_url (Google Maps directions from the search point
  — only when a ref point is known)
- Promoted commonly-useful OSM tags to top-level fields on each result:
  cuisine, hours (opening_hours), phone, website — instead of forcing
  callers to dig into the raw tags dict

SKILL.md:
- Version bumped 1.1.0 → 1.2.0, description rewritten to lead with
  capability surface
- New 'Working With Telegram Location Pins' section replacing
  find-nearby's equivalent workflow
- metadata.hermes.supersedes: [find-nearby] so tooling can flag any
  lingering references to the old skill

External references updated:
- optional-skills/productivity/telephony/SKILL.md — related_skills
  find-nearby → maps
- website/docs/reference/skills-catalog.md — removed the (now-empty)
  'leisure' section, added 'maps' row under productivity
- website/docs/user-guide/features/cron.md — find-nearby example
  usages swapped to maps
- tests/tools/test_cronjob_tools.py, tests/hermes_cli/test_cron.py,
  tests/cron/test_scheduler.py — fixture string values swapped
- cli.py:5290 — /cron help-hint example swapped

Not touched:
- RELEASE_v0.2.0.md — historical record, left intact

E2E-verified live (Nominatim + Overpass, one query each):
- nearby --near "Times Square" --category restaurant --category bar → 3 results,
  sorted by distance, all with maps_url, directions_url, cuisine, phone, website
  where OSM had the tags

All 111 targeted tests pass across tests/cron/, tests/tools/, tests/hermes_cli/.
2026-04-19 05:19:22 -07:00

11 KiB

sidebar_position title description
5 Scheduled Tasks (Cron) 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

/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

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:

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

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.

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

/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

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

/cron list
/cron pause <job_id>
/cron resume <job_id>
/cron run <job_id>
/cron remove <job_id>

Standalone CLI

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.

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:

# ~/.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:

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:

# ~/.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 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)

30m     → Run once in 30 minutes
2h      → Run once in 2 hours
1d      → Run once in 1 day

Intervals (recurring)

every 30m    → Every 30 minutes
every 2h     → Every 2 hours
every 1d     → Every day

Cron expressions

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

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:

cronjob(
    action="create",
    prompt="...",
    schedule="every 2h",
    repeat=5,
)

Managing jobs programmatically

The agent-facing API is one tool:

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.

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.