Skip to content
meirlabs

loops

Standing agent loops that watch, scribe, and escalate — never act

v0.1.0

How to stand up a scheduled agent that watches a system between sessions: it wakes on a cadence, reads the state of record, checks live reality against thresholds a human already wrote down, records what changed, and drafts escalations for a human to act on. One archetype (watcher-scribe), a spec-before-schedule rule, and a dry-run-first pilot protocol with a quantitative promote-or-stop bar. Distilled from a real pilot that passed its dry run, launched, and taught every gotcha in here.

npx @meir-labs/skill-loops

What it does

  • gates every loop behind a written spec: hard rules, numeric thresholds, cadence justification, pilot bar
  • enforces the watcher-scribe boundary — read state, update the ops file, draft escalations, never operate
  • pilots small: hand dry-run first, ~20 scheduled wakes, promote only on zero unsafe actions and ≤1 false positive
  • encodes the anti-noise rules — no-change wakes write nothing, known gaps never escalate, absolute dates only

The file

This is the actual skill — the whole thing is the markdown you see here. Copy it into .claude/skills/ or install with the command above.

skills/loops/SKILL.md
---
name: loops
description: Design, pilot, and run scheduled watcher-scribe agent loops that escalate to a human, never act. Use when the user says "set up a loop", "monitor X on a schedule", "check this every few hours", "recurring agent", or "routine".
---

# Loops

How to stand up a standing agent loop: a scheduled agent that wakes on a cadence,
reads the state of record, checks live reality against thresholds a human already
wrote down, records what changed, and drafts escalations for a human to act on.
The loop is a watcher and a scribe. It is never an operator.

## 0. When a loop is worth having (and when it is not)

A loop earns its keep when all three are true:

1. **The watched system moves between your sessions.** Deadlines attached to
   calendar dates, SLAs measured in hours, external state that changes without
   you (deliverability rates, a queue, a ramp schedule). If state only changes
   when you act, a loop just confirms nothing happened.
2. **The rules are already written down as numbers.** A loop enforces
   thresholds; it must never invent them. If you cannot point at a doc that
   says "X must stay below Y after date Z", write that doc first (the meirlabs
   convention: a Verification section in the domain's ops skill).
3. **Catching a breach hours earlier changes the outcome.** Compliance clocks,
   reputation damage that compounds, ramps with due dates. If nothing bad
   happens by waiting for your next session, wait for your next session.

Do not build a loop as a substitute for reading state when you start working —
sessions still read the state of record first. And do not build one before the
system it watches is live: a loop watching static state is a safety shakedown
at best, noise at worst.

## 1. The one archetype: watcher-scribe

Every loop starts life as a watcher-scribe. It has exactly three permitted
outputs, and a hard rule above them:

**HARD RULE, stated verbatim in the prompt, no exceptions:** the loop NEVER
performs the domain's real actions (send, launch, approve, suppress, change
DNS or settings, spend money). If any action other than the three below seems
required, it STOPS and escalates instead.

1. **Read live state** (read-only APIs, DNS, files).
2. **Update the state of record** — the domain's OPS file. Correcting drift
   between the file and reality is the scribe's job, not an escalation.
3. **Draft escalations** for the human. Format, always: what tripped, the
   measured number, the threshold it crossed, the suggested human action.
   Specific and quantitative; never "something looks off".

Promoting a loop from watcher to operator (letting it fix things) is a
separate, later decision made only after a clean pilot — and even then, one
narrow action at a time.

## 2. Write the loop spec before the loop

A loop is born as a spec document, not a schedule. The spec (keep it in
`ops/tasks/<loop-name>.md`; worked example in the meirlabs homebase:
`ops/tasks/loops-pilot-outreach.md`) carries:

- **Objective** — one paragraph: what it watches, what it writes, what it never does.
- **The exact prompt** — self-contained, because a scheduled agent starts with
  zero context. Include the hard rule, the read order, each check with its
  numeric threshold, and the output rules.
- **Read order, non-negotiable:** (1) the state-of-record file, (2) the rules
  doc with the thresholds, (3) only then live state — so the loop interprets
  numbers against rules instead of guessing. If either file is unreadable, the
  loop does nothing and reports that it is blind. Acting on partial state is
  the cardinal sin.
- **Cadence + why** — justified against how fast the watched state actually
  moves and what the tightest real SLA is. Cold-email state moves in hours →
  4 wakes/day. A nightly report → 1. Complement existing loops; never
  duplicate one.
- **Escalation conditions** — the enumerated list of "notify, do not act" cases.
- **Pilot plan and pass/fail bar** (section 4).
- **Prerequisites checklist** — every file, credential, and access path the
  loop needs, each independently verified before launch.

## 3. Where the loop runs (decide with eyes open)

**Cloud routine** (claude.ai/code/routines, created via the /schedule skill):
survives your machine being off. But it is a sandbox:
- GitHub access needs the Claude GitHub App installed on the org, or a
  `/web-setup` token sync from a CLI that can already reach the repo. Verify
  with a throwaway run BEFORE trusting a schedule — the classic silent failure
  is a routine whose checkout never worked.
- No access to your machine: no Keychain, no local files, no local env.
  Secrets go in the routine's cloud-environment variables (UI-only, stored in
  plain text — judge accordingly) or the loop runs without them (see the
  known-gap rule, section 5).
- Writes default to `claude/*` branches + a PR, not pushes to main. For a
  pilot, keep that default: it contains an over-reaching loop at the cost of
  one merge click.
- The loop reads `origin/main`. Local sessions must push state-of-record
  changes promptly or every wake sees a stale file and "corrects" it backwards.
- Cron is UTC. A local-time cadence drifts by an hour across DST changes;
  note it in the spec.

**Local schedule** (cron on your machine): full repo, Keychain, and network
access, no sandbox friction — but wakes silently skip when the machine is off
or asleep. Fine for loops that tolerate missed wakes; wrong for SLA clocks.

**Secrets hygiene, wherever the loop runs:** any secret file on disk is `0600`,
never `0644` — make that the default, not a per-incident fix. Better still, keep
no long-lived plaintext secret files at all: pull on demand (`vercel env pull`),
use a manager (1Password CLI, `direnv` with an encrypted store), or the OS
keychain. Cloud-environment variables are stored in plain text — treat them as
the lowest tier and put only read-only, easily-rotated keys there.

## 4. Pilot protocol: prove it small, then decide

Never turn a loop on wide. In order:

1. **Dry run by hand.** Run the exact prompt once in a normal session with one
   modification: propose-only — everything it would have written goes to a
   scratch file, nothing touches the repo or any API mutably. Read the output.
   Two questions: did it try to do anything beyond its three permitted
   outputs, and would its writes have been correct and quiet?
2. **Short scheduled window.** ~20 wakes (about 5 days at 4/day). Watch for the
   two failure modes: **stalling** (a wake that produces nothing or silently
   errors) and **over-reaching** (editing more than its files, drafting noise,
   proposing to act).
3. **Iterate the prompt, not the cadence, first.** Noisy → tighten the
   no-change rule and the escalation bar. Only a loop that genuinely misses
   fast state earns a tighter interval.
4. **Usage review.** After the window, check cost per wake and total. A
   read-heavy monitor should be cheap; an expensive one is over-fetching or
   over-writing and needs trimming before it runs indefinitely.
5. **Decide, against the pre-written bar:** promote only if zero unsafe
   actions, at most 1 false-positive escalation, every real state change
   reflected within one wake, and cost acceptable. Otherwise stop and
   redesign. Widening scope or cadence is a separate later decision.

## 5. Output discipline (where loops die)

- **No-change wakes write nothing.** No branch, no PR, no "no change" log
  line. A one-line session summary is enough. Loops die by noise: four
  do-nothing PRs a day trains the human to ignore the loop, and then the real
  escalation is ignored too.
- **Encode known gaps explicitly.** If a credential or data source is
  deliberately absent (a key not provisioned yet), the prompt must say so and
  instruct the loop to skip those checks silently, noting them only in the
  session summary. Otherwise every wake escalates "I am blind" and the pilot
  fails on false positives through no fault of the design. Only an
  unexpectedly failing source is real blindness worth escalating.
- **Escalations are files, not vibes.** One dated file per escalation in an
  agreed place the human actually looks (e.g. `<domain>/escalations/`),
  delivered by PR, with a pointer line in the ops log. Agree on the landing
  place before launch; it is a spec prerequisite.
- **Absolute dates only** in everything the loop writes.

## Verification

Quantitative checks a session (or a meta-loop) can run against any loop built
with this skill:

- The loop's spec doc exists and contains: the hard rule verbatim, a numeric
  threshold for every check, a cadence justification, and a pilot pass/fail
  bar. A loop with an unwritten threshold is a fail.
- Dry run completed before the schedule existed, with zero unsafe actions and
  zero repo writes. No dry-run record = do not launch.
- Every threshold the prompt cites matches the domain rules doc exactly
  (numbers, dates, units). A drifted copy is a fail; the prompt should cite
  the doc, not fork it.
- Cloud checkout verified by an actual run before the first scheduled wake.
- Pilot window defined in wakes (default 20) with the promote bar: 0 unsafe
  actions, ≤1 false-positive escalation, every real state change reflected in
  the state of record within one wake.
- No-change wakes produced zero repo writes during the pilot. Any "no change"
  commit or PR is a fail.
- Post-pilot usage review done before the loop runs past its window.