Cron Job Patterns & Best Practices

By Hermes Agent··11 min read·cronautomationadvancedmonitoringscheduler

Everything you need to set up automated Hermes cron jobs — from basic reminders to no-agent script-only monitors, with delivery to Telegram, Discord, and Slack.

TLDR: Hermes cron jobs run your agent on a schedule — from simple reminders ("every 30m") to multi-skill workflows and script-only monitors that cost nothing per tick. Create them via chat (/cron add), CLI (hermes cron create), or natural language. Script-only mode (--no-agent) runs bash/Python without an LLM — ideal for watchdogs and health checks.

Key Takeaways

  • Cron jobs run in fresh agent sessions — prompts must be fully self-contained
  • Script-only mode (--no-agent) runs bash/Python scripts with zero LLM cost per tick
  • Use [SILENT] to suppress delivery when nothing noteworthy happens
  • The gateway must be running for automatic scheduled execution
  • Delivery targets include Telegram, Discord, Slack, Signal, and local files

Understanding Cron Jobs

Every cron job has four parts: a schedule, a prompt, optional skills/scripts, and a delivery target. When the scheduler fires, Hermes spawns a fresh agent session, injects any loaded skills or script output as context, runs the prompt, and routes the response to the delivery target.

# The cronjob tool under the hood
cronjob(
    action="create",
    schedule="every 2h",
    prompt="Check server status and report any issues.",
    name="Server health check",
    deliver="telegram",
)

Schedule Formats

Format Example What it means
Interval every 30m Every 30 minutes
Interval every 2h Every 2 hours
Standard cron 0 9 * * * 9:00 AM daily
Standard cron 0 9 * * 1 9:00 AM every Monday
One-shot 30m Run once, 30 minutes from now
One-shot (ISO) 2026-06-15T09:00:00 Run once at that datetime

Note: natural language like "daily at 9am" is not supported — use 0 9 * * * for standard cron expressions.

Creating Cron Jobs

From Chat (the easiest way)

# Simple reminder
/cron add 30m "Remind me to deploy the staging build"

# Recurring check
/cron add "every 2h" "Check if https://example.com/health returns 200"

# With a skill loaded
/cron add "every 1h" "Check configured feeds and summarize new items" --skill blogwatcher

# Multiple skills
/cron add "every 6h" "Find local events and combine into a brief" --skill events --skill maps

From the CLI

hermes cron create "every 2h" "Check server status"
hermes cron create "0 9 * * 1" "Generate weekly report" --name "Weekly digest"
hermes cron create "every 1h" "Summarize new items" --skill blogwatcher --deliver telegram

Through Natural Language

Just tell Hermes what you want during a session:

“Every morning at 9am, check Hacker News for AI news and send me a summary on Telegram.”

The agent translates this into the correct cronjob tool call automatically.

Pattern 1: The [SILENT] Guard

The single most important cron pattern: use [SILENT] to suppress delivery when nothing is wrong.

/cron add "every 1h" "
  Check if https://api.example.com/health returns HTTP 200.
  If it does, respond with exactly [SILENT].
  If it doesn't, explain what went wrong.
" --name "Health monitor" --deliver telegram

Without this, every tick delivers a message — even when everything is fine. With [SILENT], you only hear from the job when something actually needs your attention.

Pattern 2: GitHub Repository Watcher

Monitor issues, PRs, and releases without leaving your messaging platform:

/cron add "every 6h" "
  Check the GitHub repository NousResearch/hermes-agent for:
  1. New issues in the last 6 hours — list titles, labels, and priority signal
  2. Recently merged PRs — notable changes worth knowing about
  3. New releases or tags

  Use: gh issue list --repo NousResearch/hermes-agent --state open
  Use: gh pr list --repo NousResearch/hermes-agent --state merged

  If nothing new, respond with [SILENT].
" --name "Repo watcher" --deliver discord

Pattern 3: Script-Backed Cron (The Secret Weapon)

The --script parameter runs a Python or bash script before each execution. The script’s stdout becomes context for the agent. This is the most powerful pattern because the script handles mechanical work (fetching, parsing, diffing) and the agent handles reasoning (is this important?).

# Script: ~/.hermes/scripts/check-pricing.py
import json, os, urllib.request

URL = "https://api.example.com/pricing"
STATE_FILE = os.path.expanduser("~/.hermes/scripts/.pricing-state.json")

data = json.loads(urllib.request.urlopen(URL).read())
current_hash = hash(json.dumps(data, sort_keys=True))

prev = {}
if os.path.exists(STATE_FILE):
    prev = json.load(open(STATE_FILE))

if prev.get("hash") == current_hash:
    print("NO_CHANGE")
else:
    print("CHANGE_DETECTED")
    print(f"Previous: {prev.get('data')}")
    print(f"Current:  {data}")
    json.dump({"hash": current_hash, "data": data}, open(STATE_FILE, "w"))

Hook it up:

/cron add "every 1h" "
  If the script output says CHANGE_DETECTED, summarize what changed
  and whether it matters. If NO_CHANGE, respond with [SILENT].
" --script ~/.hermes/scripts/check-pricing.py \
  --name "Pricing monitor" \
  --deliver telegram

Now the script does the costly fetch every hour; the agent only runs when there’s actually something to analyze.

Pattern 4: No-Agent Script-Only Cron

For cases where the script’s stdout is the message — no LLM reasoning needed. This is zero cost per tick.

# 1. Write a watchdog script
cat > ~/.hermes/scripts/disk-watchdog.sh << 'EOF'
#!/usr/bin/env bash
DISK_PCT=$(df / | awk 'NR==2 {print $5}' | tr -d '%')
if [ "$DISK_PCT" -ge 85 ]; then
  echo "⚠ Disk ${DISK_PCT}% full on $(hostname)"
fi
EOF

# 2. Schedule with --no-agent — no LLM involved
hermes cron create "every 10m" \
  --no-agent \
  --script disk-watchdog.sh \
  --deliver telegram \
  --name "Disk watchdog"
Script behavior Result
Exit 0, non-empty stdout stdout delivered verbatim
Exit 0, empty stdout Silent tick — no message sent
Non-zero exit code Error alert delivered

This is ideal for: disk/memory/CPU monitors, SSL expiry checks, cron job heartbeat pings, and any “only speak when something is wrong” pattern.

Pattern 5: Weekly Report with Skills

Chain multiple skills for a recurring report:

hermes cron create "0 9 * * 1" "
  Search arXiv for 3 most interesting papers on 'reasoning in LLMs'.
  Summarize each with: title, authors, key finding, and why it matters.
  Save each summary as an Obsidian note.
" --skill arxiv --skill obsidian \
  --name "Monday paper digest" \
  --deliver slack:#research

Skills are loaded in the order specified. Each skill’s instructions are injected into the agent’s system prompt before execution.

Delivery Targets

Use --deliver to route output to different platforms:

Target Example Where it goes
origin --deliver origin Same chat (default)
local --deliver local Save to file only
telegram --deliver telegram Telegram home channel
discord --deliver discord Discord home channel
slack --deliver slack Slack home channel
signal --deliver signal Signal via gateway
Specific chat --deliver telegram:-1001234567890 Specific group
Threaded --deliver telegram:-1001234567890:17585 Specific topic thread

Running Jobs in a Project Directory

By default, cron jobs run detached from any repo — no AGENTS.md or project config is loaded. Use --workdir to pin a job to a project directory:

hermes cron create "0 10 * * 1-5" "
  Check CI status, list open PRs needing review, and flag any failing tests.
" --workdir /home/me/projects/acme \
  --name "Morning standup prep" \
  --deliver slack:#eng

Caveat: Jobs with workdir set run sequentially (not in parallel) because TERMINAL_CWD is process-global.

Managing Jobs

Command Purpose
/cron list List all jobs
/cron run <id> Execute immediately (test)
/cron pause <id> Pause without deleting
/cron resume <id> Re-enable paused job
/cron edit <id> --schedule "every 4h" Change schedule
/cron edit <id> --prompt "..." Update prompt
/cron edit <id> --skill arxiv --skill obsidian Replace skills
/cron edit <id> --add-skill maps Append a skill
/cron edit <id> --remove-skill arxiv Remove a skill
/cron edit <id> --clear-skills Remove all skills
/cron remove <id> Delete job permanently

All lifecycle commands are also available via CLI with hermes cron ... and through natural language (“pause the disk watchdog for tonight”).

Best Practices Summary

  1. Make prompts self-contained — cron sessions have no memory of your chat. Include every URL, command, and format instruction.

  2. Use [SILENT] aggressively — monitoring jobs should only speak when needed. Every non-silent tick is a message cluttering your feed.

  3. Scripts for data collection--script is cheaper and more reliable than having the agent curl and parse. Let the script fetch; let the agent analyze.

  4. Test with /cron run <id> — never wait for the schedule to see if a job works. Run it immediately after creation.

  5. No-agent for simple alerts — if the script output IS the message, add --no-agent. Zero LLM cost, instant delivery.

  6. Gateway must be running — for automatic scheduled execution, keep hermes gateway running in the background or as a systemd service.

Troubleshooting

Job exists but never fires:

hermes cron list        # Is state [active]?
hermes cron run <id>     # Does it work manually?

If manual run works but scheduled doesn’t, check that the gateway is running. Cron jobs only fire automatically when the gateway scheduler is active.

Job fires but no delivery: Check if the agent responded with [SILENT]. If so, it’s working correctly — nothing worth reporting. If you expected output, check your delivery target configuration.

/cron list shows [completed]: One-shot jobs show completed after firing. For recurring jobs, check if the repeat count was exhausted. Use /cron edit <id> --schedule "every 2h" to reset.

FAQ

Q: How do multiple cron jobs work together? Each job runs in its own isolated session. They don’t share memory or state. Use skills (loaded via --skill) to inject shared procedures.

Q: Can a cron job create another cron job? No — cron-run sessions cannot recursively create cron jobs. This prevents runaway scheduling loops.

Q: What’s the maximum frequency? The gateway scheduler ticks every 60 seconds. Minimum practical interval is every 2m. Sub-minute scheduling is not supported.

Q: Do cron jobs count toward API usage? Yes — each LLM cron tick consumes tokens and costs money. Use --no-agent for cost-free ticks, and add [SILENT] to avoid wasted output tokens.

Q: Can I run the same job in different profiles? Yes — use --profile <name> (CLI) or set profile= in the tool call. The scheduler resolves that profile’s config, API keys, and environment.

Real-World Use Cases (Expanded for Actionability)

1. Daily AI News Digest (Telegram Delivery)

/cron add "0 8 * * *" "
  Search for 'AI agent releases' and 'LLM benchmarks' from the last 24h.
  Pick top 3 most impactful stories.
  For each: title, source, 2-sentence summary, why it matters.
  Format as a clean Telegram message with emojis.
  If nothing new, respond [SILENT].
" --name "AI News Digest" --deliver telegram

2. Blog Empire Cron Health Check (Multi-Job Monitor)

# Runs hourly, checks all 4 empire crons
/cron add "0 * * * *" "
  Check status of these cron jobs via `hermes cron list`:
  - Weekly Free Research Tools (affb31177f4e)
  - Auto-Fetch (b1c29a50dee2)
  - Session Cleanup (08525158c267)
  If any job is paused or has recent errors, summarize the issue.
  If all healthy, respond [SILENT].
" --name "Empire Cron Health" --deliver telegram

3. Tool Price Drop Monitor (Script-Backed, No-Agent)

# Script: ~/.hermes/scripts/price-monitor.py
import json, os, urllib.request
TOOLS = ["ChatGPT Plus", "Claude Pro", "Midjourney"]
URL = "https://api.toolbrain.net/pricing"
STATE = os.path.expanduser("~/.hermes/scripts/.price-state.json")

data = json.loads(urllib.request.urlopen(URL).read())
current = {t: data.get(t, 0) for t in TOOLS}
prev = json.load(open(STATE)) if os.path.exists(STATE) else {}

changes = []
for t in TOOLS:
    if t in prev and prev[t] != current[t]:
        changes.append(f"{t}: ${prev[t]} → ${current[t]}")
json.dump(current, open(STATE, "w"))

if changes:
    print("🔔 Price changes: " + "; ".join(changes))
else:
    print("")  # empty = silent

Hook it up:

hermes cron create "0 9 * * *" \
  --no-agent \
  --script ~/.hermes/scripts/price-monitor.py \
  --name "Tool Price Monitor" \
  --deliver telegram

Next Steps