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