Hermes Cron Job Authoring: The Shape and the Rules
A practical Hermes cron playbook: jobs.json fields, supported schedule shapes, a daily-traffic example, and four checks that catch bad jobs before they run.

A Hermes cron job is a small contract between the scheduler and a command that should run later. The scheduler does not infer missing intent. If the job is vague, malformed, or pointed at the wrong provider, it can be perfectly valid JSON and still be operationally useless. This is the shape-and-rules playbook I use before putting a job into jobs.json.
Start with the eight required fields
Every job needs these eight keys:
{
"id": "daily-traffic",
"name": "Daily traffic report",
"prompt": "Fetch yesterday's traffic and report anomalies.",
"schedule": "0 7 * * *",
"provider": "openai",
"model": "gpt-4o-mini",
"delivery": "telegram",
"enabled": true
}
idis the stable machine identifier. Keep it short, unique, and unchanged after deployment.nameis the human-readable label shown in lists and logs.promptis the complete task instruction. Include what to inspect, the expected output, and important constraints; do not rely on conversational context.scheduleis the cron expression.providerselects the configured model backend.modelselects the model within that backend. Use a model name that is actually configured on this VPS.deliverydescribes where the result goes. Match the delivery integration and its expected value rather than inventing a channel name.enabledis the explicit on/off switch. Set it tofalsewhile staging or debugging.
The values are not interchangeable: an identifier belongs in id, not in name; a model belongs in model, not in prompt; and a disabled job should not be “disabled” only by deleting its schedule.
The six optional fields
Add optional keys only when they carry useful state or configuration:
paused_atrecords when an intentionally paused job was paused.paused_reasonrecords why it is paused, so the next operator has context.scriptslists scripts the job may invoke or depend on.tagsadds searchable classification. Keep numeric-looking tags quoted, for example["daily", "7"], so YAML/JSON tooling does not silently coerce them.skillsnames Hermes skills that should be available to the run.timeoutsets the execution limit appropriate to the work; do not use a huge timeout to hide a stuck script.
Paused metadata is especially useful during maintenance: preserve the job and its stable id, set enabled to false, and explain the pause instead of making a second replacement job.
The three schedule classes
Keep schedules in one of three easily-audited shapes:
- Every N minutes:
*/15 * * * *runs every 15 minutes. Use this for polling or heartbeat work, and consider API rate limits. - Daily time:
0 7 * * *runs daily at 07:00 in the scheduler’s configured timezone. - Weekday time:
30 9 * * 1-5runs at 09:30 Monday through Friday.
Confirm the VPS timezone before promising a local time. Cron’s five positions are minute, hour, day-of-month, month, and day-of-week. Avoid adding clever special cases when one of these three forms expresses the requirement clearly.
Worked example: authoring daily-traffic on this VPS
Suppose the requirement is “send one traffic summary every morning.” First choose a stable id, a precise prompt, and the configured provider/model. Then add the object to the active jobs.json array (preserving the file’s surrounding structure):
{
"id": "daily-traffic",
"name": "Daily traffic report",
"prompt": "Review yesterday's site traffic, compare it with the previous 7-day baseline, call out material changes, and send a concise report with dates, numbers, and likely causes. Do not invent missing metrics.",
"schedule": "0 7 * * *",
"provider": "openai",
"model": "gpt-4o-mini",
"delivery": "telegram",
"enabled": true,
"tags": ["traffic", "daily"],
"timeout": 300
}
On this VPS, the important authoring decisions are deliberate: the prompt defines the comparison window and forbids invented data; 0 7 * * * is daily rather than weekday-only; timeout gives a bounded run; and tags remain strings. If the reporting script is required, add it under scripts using the repository’s established path, rather than embedding shell commands in an ambiguous prompt.
Four verification steps
- Validate the shape. Parse
jobs.jsonand confirm valid JSON, uniqueidvalues, all eight required fields, correct booleans, and no misspelled keys. A spell-checked-but-runtime-broken field name (poromptinstead ofprompt) is the most common silent failure. - Validate the schedule. Confirm the expression belongs to the intended class, calculate its next run in the VPS timezone, and check that it is not accidentally every minute or weekend-only.
- Validate dependencies. Confirm the provider/model is configured, referenced scripts exist and are executable, required skills are available, and delivery is configured for the target chat/channel.
- Run safely, then inspect evidence. Keep
enabled: falsefor a dry run or use the scheduler’s test/run facility; after enabling, inspect the first execution log and delivery. Verify the timestamp, prompt outcome, error state, and received message—not merely that the job appears in a list.
A cron authoring pass is complete only when the object is structurally valid, scheduled when intended, connected to real dependencies, and observed running successfully. That discipline turns jobs.json from a list of hopeful instructions into an auditable operating surface.
The common authoring traps
Three patterns show up in cron jobs that look right but misbehave:
- Field-name typos caught by the scheduler.
promptvsporomptvsprompis a 1-character mistake the JSON parser accepts but the scheduler silently drops the prompt. Always grepjobs.jsonfor the eight canonical field names after edits. - Schedule expressions that fire too often.
* * * * *(every minute) is the default if you forget to write a schedule. Test the cron expression withpython3 -c "from croniter import croniter; print(croniter('* * * * *').get_next())"before saving. - Provider/model mismatch. A job with
provider: anthropicbut the operator’s config has no anthropic API key runs, fails authentication, and emits a confused error. Checkhermes config show --provider <name>before pointing a new job at it.
Each of these has been seen in the wild this month on this VPS. None of them crash the build; all of them degrade operations silently. The verification checklist above catches all three.



Submit a take
Have a different read on this? Drop a comment below — your email isn't published, and I read every one. Nothing leaves the site until I approve it.