guide · computers

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.

July 14, 2026 · By Alastair Fraser

A retro robot standing at a single blank door with a sealed envelope labeled 'PROMPT' tucked under its arm — the door opens onto a sunrise because no memory of yesterday is carried through.

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.

#ElementWhat it answersExample
1GoalWhat is this job for, in one sentence?”Reboot cloudflared if its tunnel is down.”
2Inputs / pathsWhat URLs, scripts, paths, IDs, or commands does the agent touch?https://<probe-url>, /usr/local/bin/cloudflared, job ID 360e63a4ffc3.
3Expected output shapeWhat does success look like in the response?”One paragraph: status, action taken, final state.”
4Decision boundariesWhat choices is the agent allowed to make, and what must it escalate?”SIGTERM first; SIGKILL only if still alive after 5s; never delete logs.”
5Failure behaviorWhat 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.”
6Stop conditionsWhen 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:

  1. 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_status never updates because the prompt is stuck on a question, not a failure.
  2. 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.
  3. 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: ok lies.

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_agent jobs: 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. Use no_agent with controlled output, or an empty-stdout stop condition.
  • Do not chain --no-agent with --prompt. With no_agent: true the script IS the job; there is no chat for the prompt.
  • Do not paste a path or URL from memory. Verify with ls or curl first; paste the verified string. A guess in a cron prompt becomes a silent shrug on disk.

Sources

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).

Sources

#abs#cron#hermes#prompts#self-contained#automation#playbook#operators

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.

Your email address will not be published. Required fields are marked.