File size: 3,650 Bytes
9ec4919 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | # 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).
|