ABS Cron Job Self-Contained Prompts: The Rule
A Hermes cron prompt runs in a fresh session — no memory of past runs, no inline handoff. Six elements make it self-contained. Skip any one and the job silently returns nothing useful.

The rule in one sentence
Every Hermes cron prompt is run in a fresh session — no memory of past runs, no chat history, no inline handoff to “the model you talked to yesterday.” The prompt body has to carry everything the job needs, and the rule for writing that body is six elements; skip any one and the cron becomes a job that runs but does nothing.
The companion cron-jobs guide covers schedules, dry-runs, pause/resume, and no_agent mode. This guide is the prompt contract: what every prompt body has to contain, the failure shape when it doesn’t, and how a well-written prompt behaves on a quiet day.
Why “fresh session” is the load-bearing constraint
Hermes cron jobs run on a schedule in their own session. That session has:
- No prior chat. No “the conversation we had Tuesday.” A URL, path, flag, or chat ID that exists only in the operator’s memory is invisible to the running prompt.
- No carried-over tool state. Only the tools attached at job creation load. “Last run the skill was loaded” is not a thing.
- No implicit context. The prompt is what the agent sees. If the prompt does not say it, the agent does not know it.
The mental model is a night-shift worker who arrives alone with a clipboard of today’s instructions. The clipboard is the prompt; the prompt is the entire job.
The six elements of a self-contained cron prompt
Every cron prompt — agent-based or no_agent script-based — has six elements. They are short; the whole prompt body is usually under 300 words. The order matches how the agent plans, top-to-bottom.
| # | Element | What it answers | Example |
|---|---|---|---|
| 1 | Goal | What is this job for, in one sentence? | ”Reboot cloudflared if its tunnel is down.” |
| 2 | Inputs / paths | What URLs, scripts, paths, IDs, or commands does the agent touch? | https://<probe-url>, /usr/local/bin/cloudflared, job ID 360e63a4ffc3. |
| 3 | Expected output shape | What does success look like in the response? | ”One paragraph: status, action taken, final state.” |
| 4 | Decision boundaries | What choices is the agent allowed to make, and what must it escalate? | ”SIGTERM first; SIGKILL only if still alive after 5s; never delete logs.” |
| 5 | Failure behavior | What does the prompt do on error? | ”If probe returns non-200 three times, restart; if restart fails twice, page the operator with a Telegram message.” |
| 6 | Stop conditions | When does the agent stop and exit cleanly? | ”Probe returns 200 → exit silently. Restart succeeded → exit silently. Both attempts failed → exit with one alert line.” |
A prompt that has all six is self-contained. A prompt missing one of the bottom three is the bug class below.
What a self-contained prompt looks like
The cloudflared restart prompt on this VPS (job 360e63a4ffc3) is the canonical example. It runs every five minutes and has zero reference to anything outside the prompt body:
Goal: keep the cloudflared tunnel healthy. If up, do nothing.
Probe: curl -fsS -o /dev/null -w '%{http_code}' https://<probe-host>/healthz
expect: 200. If 200 → exit silently (job is healthy).
If non-200:
1. Inspect: systemctl status cloudflared | head -20
2. Restart: systemctl restart cloudflared
3. Wait 5s, re-probe.
4. If still non-200: SIGKILL the unit, then restart.
5. After two failed restarts: one Telegram line with the probe URL
and the last systemctl status excerpt.
Boundaries: never edit /etc/cloudflared/config.yml. Never restart anything
other than the cloudflared unit. Never run `cloudflared tunnel login`.
Failure: log probe + systemctl excerpt to /opt/data/cron/output/<job_id>/.
Stop: 200 → silent. Restart success → silent. Two failed restarts → one
Telegram line and silent exit.
Every URL, path, ID, escalation rule, and silent-exit clause is in the prompt. A fresh agent at 03:14 UTC behaves identically to the hundredth-time agent.
The “ran but didn’t do anything” failure shape
The most common cron failure is not a crash, a non-zero exit, or a missed schedule. It is the job that ran on time, exited 0, and produced nothing useful. There are three shapes:
- The clarifying question. The prompt is ambiguous out of context, so the agent asks back: “Which chat ID should I send to?” — and waits. The job hangs.
last_statusnever updates because the prompt is stuck on a question, not a failure. - The hallucinated context. The prompt references something the agent doesn’t have (a path the operator remembered, a URL from “last week’s chat”), so the agent fills in something plausible. The output looks like work but is grounded in nothing on disk.
- The silent shrug. The prompt has a goal but no failure behavior. When inputs differ from what the prompt expected, the agent produces a one-line acknowledgement and exits silently.
last_status: oklies.
Cheap-first check. All three leave the same fingerprint on the cron store and the output directory:
# 1. Confirm the scheduler fired the job
python3 -c "import json; j=json.load(open('/opt/data/cron/jobs.json'))['jobs']; \
print([x['last_run_at'] for x in j if x['id'].startswith('<id>')])"
# 2. Read what the run actually produced
ls -t /opt/data/cron/output/<job_id>/ | head -3
cat /opt/data/cron/output/<job_id>/$(ls -t /opt/data/cron/output/<job_id>/ | head -1)
# 3. If output is short (<200 chars) or contains a question, the prompt
# is asking the model for context it does not have.
Canonical fix. Rewrite the prompt to include the missing element from the six. In observed order:
- Missing Inputs / paths → add the URL, file path, or identifier verbatim. “the script we discussed” is not a path.
- Missing Expected output shape → state what success looks like in one sentence. “Report any anomalies” is not a shape.
- Missing Failure behavior → add an explicit branch. “If X fails, do Y; if Y fails, do Z” — three concrete branches.
- Missing Stop conditions → add a literal “exit silently” clause so the agent knows not to write a placeholder summary.
The pattern in every case: the agent was not broken; the prompt was. The dry-run command (hermes cron run <job_id>) surfaces the failure shape immediately because the run writes a fresh output file. Read that file before editing the prompt.
The behavior when a cron finds nothing to report
A prompt that always produces output produces noise. A prompt that never produces output produces silence the operator cannot distinguish from a broken job. The fix is explicit silence:
- Agent-based jobs: state a stop condition — “If probe returns 200, exit with no output.” Empty stdout on an agent job is fine; reasoning followed by
[SILENT]is not (the cron subsystem wraps the full stdout). no_agentjobs: the script’s stdout is the job’s output. A script that wants silence on healthy runs prints[SILENT]only on failure and exits with empty stdout otherwise. This is the canonical watchdog pattern.- Reporting jobs: when there is nothing to report, write a one-line status —
2026-07-14 13:00 UTC: 0 anomalies, 2 checks passed— so the operator can tell nothing-to-report from did-not-run.
The wrong shape: a prompt that says “Summarize today’s findings” with no stop clause fills Telegram with “Today was a normal day.” every five minutes.
What NOT to do
- Do not reference a prior run. The fresh session has no such memory. If the prompt cannot run without it, the prompt is wrong.
- Do not leave the failure branch implicit. “If something is wrong, handle it” is not a failure behavior. State the steps.
- Do not rely on the LLM to print
[SILENT]exactly. Reasoning comes first; the wrapper sees the entire stdout. Useno_agentwith controlled output, or an empty-stdout stop condition. - Do not chain
--no-agentwith--prompt. Withno_agent: truethe script IS the job; there is no chat for the prompt. - Do not paste a path or URL from memory. Verify with
lsorcurlfirst; paste the verified string. A guess in a cron prompt becomes a silent shrug on disk.
Sources
- Hermes Agent cron docs
- ABS companion: Hermes Agent cron jobs: authoring, scheduling, the self-contained prompt rule
- ABS companion: ABS regression failure modes and their cheap-first check
/opt/data/skills/devops/cron-health-monitor/SKILL.md— recurring-bug patterns (2026-05-23 stale-error logic, 2026-07-08 unpinned-job drift).
Last verified: 2026-07-14 against the cron store on this VPS (cloudflared watchdog 360e63a4ffc3 as the canonical self-contained example; “ran but didn’t do anything” verified against three real prompt rewrites in the prior 30 days).


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.