# Run a deterministic cron agent by scheduling a recorded replay

A scheduled agent that re-reasons every wake-up pays the model bill on every single tick — the heartbeat tax. A deterministic cron agent inverts that: instead of re-deriving the workflow, the schedule fires a replay of a run that already worked. The deterministic core replays at 0 tokens, and `reelier diff` turns each scheduled run into a standing, dated gate that exits non-zero the moment the upstream drifts.

This guide wires that up two ways with the reelier CLI: on GitHub Actions cron and on plain system cron. Every command below is real and every output is verbatim from an actual run on 2026-07-22.

Markdown twin of https://www.reelier.com/guides/deterministic-cron-agents — a step-by-step Reelier how-to. Every command below is a real Reelier command and every output block is verbatim, never a mockup.

## Before you start

- Reelier installed — `npm i -g reelier` (or use `npx -y reelier@latest` in place of `reelier` in every command below).
- One compiled, read-only `SKILL.md` you want to run on a schedule. Don't have one yet? Compile it first — a scheduled job runs a `SKILL.md` file, never a prompt.
- For the GitHub Actions path: the skill committed to a repo. For system cron: a machine with `cron` and Node.

## 01 · Pick a skill that's safe to run unattended

The thing you schedule is a compiled `SKILL.md`, and the one property that matters for an unattended schedule is: it must be read-only. Nobody is watching a 3 a.m. cron run, so it must not re-fire writes. Reelier replay is read-only by default — a recorded write step never re-executes unless you pass `--allow-writes` (which unlocks idempotent writes only; a destructive step still needs `--yes`). On a schedule, you pass neither.

`reelier from-session` (and `reelier scan`) tell you this up front — look for `✓ all N steps are read-only — safe to replay repeatedly`. If it warns that steps are side-effectful instead, that skill is a poor scheduling target; pick a read-only data-pull or health check. (Only about 1.1% of scanned sessions are read-only end-to-end, so this is also where you learn which workflows are schedulable at all.)

How you produce the `SKILL.md` from a recorded session is its own how-to; this guide picks up once you have one.

```bash
reelier from-session daily-health-check.jsonl
```

```
Scanned daily-health-check.jsonl: 1 replayable call(s) found.


Wrote daily-health-check.skill.md
  steps:   1
  asserts: 1
  binds:   0

✓ all 1 steps are read-only — safe to replay repeatedly

Open questions (1):
  - Step 1: no narration — describe what this step is for.
```

## 02 · Prove the replay locally before you schedule it

Run it once by hand. `reelier run` re-executes the recorded tool calls and checks each assertion. At the default `--max-level 0`, an LLM client is never even constructed — so the deterministic core replays at 0 tokens, and the run prints no `LLM tokens` line precisely because there were none to report.

That 0 is the deterministic core only — a step that needs judgment needs a model, the opt-in `--max-level 1|2` BYOK escalation. You never escalate an unattended job; keep the schedule at Level 0: same steps, every run, no bill.

The compiler wrote one assertion here — `status == 200`, the clean recorded result — and it asserts a shape, not a brittle value pin, so a fresh response body on each honest run does not count as drift.

```bash
reelier run daily-health-check.skill.md
```

```
✓ Step 1 — Call http.get [passed] 163ms

PASSED: 1/1 steps ok, 0 failed, 163ms total
```

## 03 · Make the scheduled run a gate: reelier diff

A cron job that only runs the skill tells you it ran — not whether it still does the same thing. Run the skill a second time, then `reelier diff <name>` compares the last two runs in `.reelier/runs/<name>.jsonl` and reports `SAME` (exit 0) or `DRIFTED` (exit 1), step by step. That exit code is the whole point: it is what upgrades a scheduled replay from a heartbeat into a standing gate.

On an ordinary day the verdict is `SAME` and the schedule stays green. On the day the upstream changes — a field disappears, a `200` becomes a `500` — the diff flips to `DRIFTED`, exits 1, and whatever scheduled it surfaces the failure instead of a false green.

```bash
reelier diff daily-health-check
```

```
  = Step 1 "Call http.get": passed, unchanged.

✓ SAME — 1 step(s), every outcome and escalation level identical to the baseline.
```

## 04 · Schedule it on GitHub Actions cron

The only cron-specific line is the trigger. Add a `schedule` block with a cron expression — `- cron: "0 8 * * *"` runs daily at 08:00 (GitHub cron is always UTC) — and keep a `workflow_dispatch: {}` alongside it for a manual "Run workflow" button. The job itself is the published action: `uses: seldonframe/reelier@v1` with `skill:` pointed at your `.skill.md` and `max-level: "0"` for the 0-token deterministic replay.

The action sets the job's pass/fail from the replay's own exit code, so a red check on a public repo is a genuine, dated drift signal. The full workflow anatomy — job summary, receipt push, the matrix pattern for several skills — is the CI guide's job; wire the schedule here and hand off there.

## 05 · Or schedule it on plain system cron

No CI runner? A single crontab entry (`crontab -e`) runs the same two commands on the same schedule. Chain them with `&&` so the diff gate only runs after a passing replay — then any non-zero exit, whether the replay failed or `diff` found drift, is your one signal to act on. Pipe that into your alerting (`|| curl ...your-webhook...`) so a drift pages you instead of dying silently in the cron log.

cron sends stdout and stderr to the crontab user's mail spool (or syslog) by default, so the receipt and any `DRIFTED` verdict land there unless you redirect them somewhere you'll see. Same two reelier commands as the CI path — only the scheduler differs.

```bash
0 8 * * * cd /srv/agent-checks && npx -y reelier@latest run daily-health-check.skill.md && npx -y reelier@latest diff daily-health-check
```

## The receipt

```
$ reelier run daily-health-check.skill.md
✓ Step 1 — Call http.get [passed] 163ms

PASSED: 1/1 steps ok, 0 failed, 163ms total

$ reelier run daily-health-check.skill.md
✓ Step 1 — Call http.get [passed] 137ms

PASSED: 1/1 steps ok, 0 failed, 137ms total

$ reelier diff daily-health-check
  = Step 1 "Call http.get": passed, unchanged.

✓ SAME — 1 step(s), every outcome and escalation level identical to the baseline.
```

What each scheduled tick runs, verbatim: the compiled `daily-health-check` skill replayed twice, then diffed. Produced on 2026-07-22 with `reelier` v0.14.0 in a scratch directory; the skill was compiled by `reelier from-session` from a one-call Claude Code-style transcript (a single `http.get` to the npm registry, shaped per `reelier/test/session.test.ts`). No model was constructed at any point; `reelier diff` exited 0 (a drift would exit 1 and fail the schedule).

## FAQ

**Doesn't running the agent on a cron still cost tokens every time?**

Not the deterministic core. At `--max-level 0` (the default) no LLM client is constructed, so a scheduled replay is 0 tokens — the run prints no token line because there were none. Judgment steps need a model, but that is the opt-in `--max-level 1|2` BYOK escalation, which you don't turn on for an unattended job.

**What happens on the day the upstream actually changes?**

`reelier diff` flips from `SAME` to `DRIFTED` and exits 1, so GitHub Actions or cron surfaces a failure instead of a silent green. Update the baseline: edit the failing assertion in the `SKILL.md` (it's markdown in your repo), or re-record and recompile — Reelier only builds steps from a run that actually happened, never from instruction text.

**Can I schedule a skill that writes data?**

Replay is read-only by default, so recorded write steps do not re-fire on a schedule unless you pass `--allow-writes` (idempotent writes only; a destructive step still needs `--yes`). For an unattended cron, prefer a read-only skill and pass neither flag — `reelier from-session`/`scan` tell you which of your runs are read-only up front.

## Related

- [Headless agent — the unattended, no-human-in-the-loop pattern a cron replay runs, defined](https://www.reelier.com/learn/headless-agent)
- [Heartbeat economics — the token bill a re-reasoning scheduled agent pays on every tick](https://www.reelier.com/blog/heartbeat-economics)
- [Reelier for GitHub Actions — the full replay-as-a-check setup this schedule plugs into](https://www.reelier.com/for/github-actions)
- [Guide: run an agent replay as a GitHub Actions check](https://www.reelier.com/guides/github-action-agent-replay)
- [Guide: diff two agent runs and fail on drift — the gate this schedule leans on](https://www.reelier.com/guides/diff-two-agent-runs)

## Sources

- [seldonframe/reelier — the open-source (AGPL-3.0) CLI every command here runs](https://github.com/seldonframe/reelier)
- [GitHub Actions: schedule events — the cron trigger (always UTC) the workflow step uses](https://docs.github.com/actions/using-workflows/events-that-trigger-workflows#schedule)
- [crontab.guru — build and read the cron expression for the system-cron path](https://crontab.guru/)

Other guides: [How to snapshot-test a Claude Code workflow](https://www.reelier.com/guides/snapshot-test-claude-code-workflows) · [How to record MCP tool calls with a wrap proxy](https://www.reelier.com/guides/record-mcp-tool-calls) · [How to replay an agent workflow at 0 LLM tokens](https://www.reelier.com/guides/replay-agent-workflow-zero-tokens) · [How to gate CI on agent drift](https://www.reelier.com/guides/ci-gate-for-agent-drift) · [Replay an agent skill on every PR with the Reelier GitHub Action](https://www.reelier.com/guides/github-action-agent-replay) · [Test your MCP server for regressions by replaying a golden run](https://www.reelier.com/guides/test-mcp-servers) · [Build an agent audit trail from Reelier run receipts](https://www.reelier.com/guides/agent-audit-trail-receipts) · [Configure BYOK escalation levels: L0, L1, and L2](https://www.reelier.com/guides/byok-escalation-levels) · [Compile a skill from an existing session with reelier from-session](https://www.reelier.com/guides/from-session-compile) · [Diff two agent runs with reelier diff (exit 1 on drift)](https://www.reelier.com/guides/diff-two-agent-runs) · [Convert an LLM agent skill into a deterministic, assertion-checked skill](https://www.reelier.com/guides/convert-agent-skill-deterministic)
