← All skills

better-loop

v2.0.0MITlong runs

A loop that fires on a clock re-sends the same unmet condition and the same six failing tasks turn after turn, and each fire re-bills the session's whole accumulated prefix — five of twelve heavy sessions did that and accounted for 91% of input between them. Nothing about a smaller context window stops a loop from restarting. So this arms a watcher instead of a schedule: it polls one deterministic probe command in the background, wakes the session only when the answer changes, sends the delta rather than the state, and goes progressively quieter about a failure it has already reported. A quiet system costs nothing at all, and a tick that needs no conversation can run detached, where there is no prefix to pay for.

Install
/plugin install better-loop@fledgeling-plugins

Needs the marketplace added first — how to do that.

Reach for it when

Arm recurring or event-driven work as a change-gated watcher this skill creates itself, so a tick fires only when something has actually changed.

Not for

NOT for a one-off task that finishes in a single turn.

What ships with it

Say any of this

  • loop on this
  • keep checking the deploy
  • monitor the benchmark runs and fix errors
  • create a loop prompt
  • harden this loop
  • what exactly is performing the loop, I expected to see a monitor

Taken from the skill’s own trigger description — these are the phrases it listens for. You do not have to match them exactly.

What comes backIllustrative — written from the skill’s documentation, not captured from a run

keep checking the deploy

Mechanism picked BEFORE anything is written.

  change-gated watcher  ← chosen
  5-minute cron         rejected: 120 wakes to find 2
                        changes, each re-billing the
                        whole session prefix
  better-goal           rejected: no finish line here

  probe   vercel ls --json | jq -r '.[0].state'
  ✓ deterministic — ran twice, identical

ARMED   Monitor, persistent. No cron, so no 7-day expiry.

  18:04  baseline   BUILDING
  18:06  (quiet)
  18:12  CHANGE     BUILDING → ERROR      ← woke the session
  18:31  repeat     ERROR seen ×3, suppressed until 19:01

polls 47 · wakes 2 · budget 12/h

Easily confused with

Why it exists

Two reasons, and the second is the expensive one.

/loop schedules a cron in the current session. That is the whole mechanism, and every edge of it is a way for a loop to look fine while doing nothing. It expires after seven days — the task fires one last time, deletes itself, and nothing warns you. It fires only while the session is idle. It never catches up a fire it missed. It dies on /clear. And since v2.1.196 a scheduled fire only invokes skills Claude may invoke on its own: built-in commands and anything marked disable-model-invocation (the bundled /verify and /code-review included) arrive as plain text, with no error. A loop whose whole job was "run /code-review every hour" ticks correctly all day and reviews nothing.

And a loop that fires on a schedule pays for every fire. Measured across the heaviest sessions on this machine:

5 of 12 of the heaviest sessions — 91% of input between them — re-sent the same unmet condition, the same six failing tasks, the same status poll turn after turn, paying the whole prefix each time.

A wake is not free and it is not cheap. It re-bills the accumulated session context, so a five-minute cron watching a deploy that changes twice in two hours buys 24 wakes to carry 2 pieces of news. Nothing about a shorter interval fixes that; the interval is the problem.

There is a third, softer reason. A loop has no surface. From the session that armed one:

"what exactly is performing the loop; i expected to see a monitor or script running, did i invoke it incorrectly?" "i expected /loop to be able to be left"

Nothing was wrong. A dynamic loop is a scheduled wakeup with no visible process, so a working loop and a dead one look identical.

What it does

It runs the probe outside the session

The loop is a Monitor running watch.sh, a plain shell script that polls a probe command on an interval and compares the answer to last time. Polling costs nothing — no model, no context, no tokens. Only a change produces a line, and only a line wakes the session.

18:04  baseline   BUILDING
18:06  (quiet)
18:12  CHANGE     BUILDING → ERROR      ← woke the session
18:31  repeat     ERROR seen ×3, suppressed until 19:01

polls 47 · wakes 2 · budget 12/h

Forty-seven polls, two wakes. A five-minute cron would have been 24 wakes for the same two pieces of news.

It refuses to re-send a failure you already have

This is the fix for the defect above, and it is mechanical rather than advisory. Every probe answer is fingerprinted into a known-state register. The first time a state appears it wakes the session. The second time it is suppressed and backed off; the third, further. A state seen three times is not reported again for half an hour, then an hour, capped at four.

A suppressed change is not a lost one — it goes to the ledger, so a quiet loop can prove it was working:

| 4 | 2026-08-09 18:55 | repeat | seen ×3 — suppressed until 19:25 [7f3e...] |

Four bounds sit on top of it, all in the state file: a wake budget (default 12 per rolling hour, after which changes go to the ledger only), repeat-after backoff, dry-stop after N unchanged polls, and stop-when, a command that ends the loop when it exits 0.

And when a wake does happen, it carries the delta — the lines that moved — not the whole probe output. The full state is one re-run away if the tick needs it.

The tick can skip the session entirely

For work that does not need the conversation, --tick-cmd dispatches a fresh detached claude -p on a change. The session is never woken and never billed for the prefix. It is the cheapest shape available, and the trade is real: a detached tick that fails fails quietly, so its log is worth reading.

It picks the mechanism before writing anything

/loop does not make this decision; it schedules whatever you typed. The question that settles it is what has to be true for the next tick to be worth running.

AnswerMechanismWhy
"Something changed" — a status, a queue, CI, a PRWatcher (watch.sh under Monitor)The change wakes the session. No wasted ticks, no expiry, and visible
"Something changed, and I don't need to see it"Watcher + detached tickThe cheapest tick there is: no wake, no prefix
"A line appeared in a stream"Monitor on the stream directlyNo probe needed; the event is already a line
"The work is finished"better-goalThat is a finish line, not a cadence. It routes rather than arming the wrong thing
"It's 9am" / "the first of the month"Fixed cronWall-clock is the one case where a schedule is genuinely the right answer
"It has to run whether or not my laptop is open"Routines, a Desktop task, or GitHub ActionsA session-bound loop needs the session open. Better to say so than arm something that will not survive

Most requests phrased "keep doing X until Y" are a goal wearing a loop's clothes.

The preflight blocks on a probe that lies

The headline check runs your probe twice and compares. A probe carrying a timestamp, a PID, a duration or an unsorted list changes on every poll, which turns a change-gated watcher back into a cron with extra steps — and it is invisible until the bill arrives.

The rest covers interval sanity, skills that would arrive as plain text, loops already armed in this repo, and — under --cron, for the composed case — the scheduler flags, a symlinked .claude, the 50-task cap, loop.md size and precedence, the seven-day expiry, and whether the interval maps to a clean cron at all. 7m does not; it gives uneven gaps at :56 to :00. 90m cron cannot express.

Important A watcher is only as good as its filter, and the failure is always the same shape: it watches for the success marker and goes quiet on a crash, which is indistinguishable from still running. Before arming one, ask whether it would emit anything if the process died right now. If not, widen it. Noise is cheaper than a missed failure.

Installing

/plugin marketplace add fledgeling-co/fledgeling-plugins
/plugin install better-loop@fledgeling-plugins

Using it

On its own:

/better-loop monitor the benchmark runs, resolve any harness or config errors,
and restart the benchmarks as needed

When a loop has gone quiet, or when you cannot tell whether it is running. It reads the state file, the ledger and the live tasks rather than guessing.

Composed with the built-in, where a wall-clock cadence is genuinely what you want:

/loop /better-loop keep checking the deploy and fix what breaks

scripts/status.sh answers "how's it going" without waking the loop or costing it a turn — and warns when the wake-to-poll ratio says the probe is too wide to be worth gating on. scripts/disarm.sh clears the state; TaskStop stops the monitor.

The rules that keep it useful

A wake must carry new information. Not a poll, not a repeat, not output already in the context. Everything else here follows from that.

Prefer a change to a schedule. A watcher that emits when something moves beats a cron asking every five minutes whether anything moved — on cost, on latency, and on ticks wasted.

Bound the loop — a wake budget, a repeat backoff, a dry-stop, and a stop-when. Four bounds, all in one file you can read.

One loop per concern. Two loops watching the same thing wake the session twice for one change.

Give every tick a no-op branch. Without one, a tick with nothing to do invents work to justify itself.

What's in the box

skills/better-loop/
  SKILL.md
  references/
    mechanics.md         how the watcher, Monitor and /loop actually work,
                         with the binary and doc citations
    mechanism-choice.md  the decision table, what a tick costs, worked cases
    failure-modes.md     the observed failures, each mapped to its fix
    templates.md         the brief, the state file, the ledger, the arming sequence
  scripts/
    preflight.sh         probe determinism first; --cron adds the scheduler checks
    arm.sh               writes per-slug state and prints the Monitor call
    watch.sh             the loop itself: polls, fingerprints, suppresses repeats,
                         emits CHANGE / DONE / ENDED / QUIET / GONE
    tick.sh              appends one ledger row per tick
    status.sh            reads state and ledger; flags a wake-heavy probe
    disarm.sh            <slug> | --all | --loop-md
    write-brief.sh       renders and size-checks .claude/loop.md, for the
                         composed case only
evals/evals.json         six cases plus the process evals
EVALS.md                 the measured result against the no-skill baseline

Does it earn its place

Measured the same way as its sibling: each case run twice from an identical fixture, once with the skill and once with nothing, graded blind. Across both, 32 of 33 assertions against the baseline's 12, and 8 preferences out of 8.

The two cases that carry it are the ones where the baseline was not merely thinner but wrong. Asked what performs a no-interval loop, it said there is no timer and the loop ended with the turn; dynamic pacing schedules a real pending wakeup, so acting on that answer means re-arming something already scheduled. Told to run /verify every 30 minutes, it concluded /verify does not exist, missing that it is a bundled skill a scheduled fire delivers as plain text.

Against that, one honest tie: on "don't arm a loop with a dead verifier" the baseline matched it 3/3. Those scores predate the rebuild around change-gated watchers, so they measure the earlier /loop-hardening skill. EVALS.md has the table, the tie, the three defects the run found in these skills, and what was never measured.

What it doesn't do

It can't keep a loop running without the session. A Monitor lives in the conversation, and --resume does not restore one. Backgrounding the session keeps it alive without a terminal, which is usually the fix; where the work genuinely has to run unattended, the skill routes you to Routines, a Desktop scheduled task, or GitHub Actions rather than arming something that will not last the night.

It doesn't survive /clear. The state file, the ledger and the brief stay on disk, so re-arming is one command, but the watcher itself is gone.

It can't make a tick correct. It can make a tick change-gated, bounded, and honest about whether it ran. What the tick does is still down to the protocol you wrote.