# Shell / Cron Loop Variant > Portable template. It depends only on a POSIX shell, coreutils, `cron`, and an agent CLI you already have. Use it when you want full control and no vendor runtime. A minimal cron wrapper that delegates one bounded task to an agent CLI, gates the result on a deterministic check, and records receipts. This is the Level 2 step on the [Loop Maturity Model](../../README.md#loop-maturity-model): a scheduled loop that reports, with state outside the model. ## When to use - You want the loop on your own machine or server, owning isolation and secrets yourself. - The runtime should be boring and inspectable: a script you can read end to end. - You are prototyping a loop before promoting it to a managed runtime. ## The wrapper ```sh #!/usr/bin/env bash # loop-wrapper.sh - run one bounded agent task, gate on a check, record receipts. set -u REPO="${REPO:-$PWD}" CHECK_CMD="${CHECK_CMD:-npm test}" AGENT_CMD="${AGENT_CMD:-claude -p}" # any non-interactive agent CLI RECEIPTS="${RECEIPTS:-$REPO/loop-receipts.log}" LOCK="${LOCK:-$REPO/.loop.lock}" # One run at a time: skip if a previous run is still going (missed-run guardrail). if ! mkdir "$LOCK" 2>/dev/null; then echo "$(date -u +%FT%TZ) skip: previous run still holds the lock" >> "$RECEIPTS" exit 0 fi trap 'rmdir "$LOCK"' EXIT cd "$REPO" || exit 1 # Gate first: if the check already passes, there is no work to do. if bash -c "$CHECK_CMD" >/tmp/loop-check.out 2>&1; then echo "$(date -u +%FT%TZ) ok: check already green, no action" >> "$RECEIPTS" exit 0 fi # Delegate exactly one bounded attempt to the agent. prompt="The check '$CHECK_CMD' is failing. Latest output: $(tail -n 80 /tmp/loop-check.out) Fix only the cause of this failure. Do not change CI config, add dependencies, or edit unrelated files. Append one line to loop-receipts.log describing the change." $AGENT_CMD "$prompt" # Re-gate: the deterministic check, not the agent, decides success. if bash -c "$CHECK_CMD" >/tmp/loop-check.out 2>&1; then echo "$(date -u +%FT%TZ) fixed: check green after agent run" >> "$RECEIPTS" else echo "$(date -u +%FT%TZ) blocked: still failing, escalating" >> "$RECEIPTS" # Escalation: open an issue, ping a channel, or just leave a receipt for a human. # gh issue create --title "loop blocked: $CHECK_CMD" --body-file /tmp/loop-check.out fi ``` Schedule it with cron (here, every weekday at 09:00): ```cron 0 9 * * 1-5 cd /path/to/repo && CHECK_CMD="npm test" AGENT_CMD="claude -p" ./loop-wrapper.sh ``` ## How it maps to the Loop Contract | Contract part | In this template | | ------------- | ---------------- | | Trigger | The cron schedule | | Intake | The failing check output, trimmed to the last 80 lines | | Verification | `CHECK_CMD` exit code, re-run after the agent, never the agent's opinion | | Durable state | `loop-receipts.log` | | Budget | One bounded attempt per run; the lock prevents overlap | | Escalation | A blocked receipt, optionally an issue or alert | ## Guardrails - Run against a dedicated branch, worktree, or a checkout you are willing to lose; the agent edits real files. - The lock directory is the missed-run guardrail: a slow run will not stack on top of the next scheduled one. - Scope the agent CLI's permissions (allowed tools, sandbox, read-only paths) before running unattended; see [Securing Unattended Loops](../../README.md#securing-unattended-loops). - For crash-proof state and guaranteed recovery, graduate to a [durable execution runtime](../../meta/RUNTIME_SELECTION.md). - The richer, multi-iteration version of this idea is [`test-repair-loop.sh`](test-repair-loop.sh).