Lessons from real cron jobs that went wrong. Apply these when authoring or editing cron job prompts via cronjob(action='create') or cronjob(action='update').
LLM agents generating files will pattern-match existing directory structures and "helpfully" create files where they shouldn't. If a cron prompt says "write to auto/" but the agent sees a sibling curated/ directory with similar files, it may create files in curated/ too — even though the prompt never asked for it.
LLMs default to verbose prose unless constrained. A cron prompt asking for a "summary" will produce 2,000–3,000 char narrative blocks unless you pin the format explicitly.
Rule: Pin format AND length in the prompt. State the output shape (bullets, table, one-liner) and a character/line budget. "Summarize in 8–12 bullet points, ≤600 chars total" is actionable. "Summarize the key points" is not.This applies to every cron output that humans skim: digests, briefings, retros, reports. The agent has no reader in mind — you must encode the reader's attention budget.
Wherever the prompt tells the agent to write a file, add an explicit "DO NOT" immediately after:
3. Write the auto-weekly retro to retros/auto/YYYY/YYYY-WXX.md ...
⚠️ DO NOT write anything to retros/curated/. That directory is reserved for Joseph's hand-written retros only.
1. Name the exact output path — not a directory, the full file pattern (auto/YYYY/YYYY-WXX.md).
2. Add a ⚠️ DO NOT guard for every sibling directory the agent could see but should never write to.
3. State who owns the forbidden path — "reserved for Joseph's hand-written X" is stronger than "don't write there."
4. Review the prompt from the agent's perspective — if you removed the negative constraint, would the agent still only write to the right place? If not, the guard is necessary.
Bad:
> Write the weekly retro to retros/auto/YYYY/YYYY-WXX.md.
Good:
> Write the weekly retro to retros/auto/YYYY/YYYY-WXX.md.
>
> ⚠️ DO NOT write anything to retros/curated/. That directory is reserved for Joseph's hand-written retros only.
script field must be a bare filename — no arguments, no interpreter prefix. The cron engine resolves the script field as a literal file path under HERMES_HOME/scripts/. It does NOT parse shell arguments: hermes-file-evals.sh --suite daily resolves to a file literally named hermes-file-evals.sh --suite daily, which doesn't exist → Script not found error. Fix: put ONLY the filename in script (e.g. hermes-file-evals.sh), and if arguments are needed, encode them in the prompt or in a wrapper script. Also never include python3 or bash as a prefix — the engine auto-detects the interpreter by extension (.sh/.bash → bash, else Python).HERMES_HOME/scripts/ are rejected. The cron engine has a traversal guard that resolves the script path and checks it stays inside HERMES_HOME/scripts/. A symlink pointing outside that directory (even to the real script elsewhere) fails with Script not found. Fix: copy the script into HERMES_HOME/scripts/ — do not symlink. If you maintain the canonical copy elsewhere, keep both in sync or use a build step.deliver value MUST be platform:chat_id (e.g., slack:C014MMH2CTH), not a bare channel ID. A bare ID causes silent blocked_config delivery failures — the script runs, wiki files get written, but the Slack summary never arrives. Always verify delivery format when creating or debugging cron jobs.monthly/, quarterly/, yearly/ subdirectories and decides to generate those too, even if the prompt only asked for weekly. Add: "Generate ONLY the weekly file. Do not create monthly, quarterly, or yearly files."