Skip to main content
Claude Code ships three ways to make an agent repeat, right in the terminal (Anthropic’s loop taxonomy):
  • /goal — a goal-based loop in your live session: an evaluator checks your success criteria each iteration, until the goal is achieved or a maximum number of turns is hit.
  • /loop — a time-based loop on your machine: re-run a prompt on an interval, or let the model pace itself.
  • /schedule — the same idea in the cloud: agents (routines) that run on a cron.
For a developer driving their own session, these are the right tool: one line, zero setup, and you’re watching the work happen. loop.js exists for the day the loop stops being something you run and becomes something you ship — a system with a bar to meet, a budget to respect, state to keep, and no human watching. The distinction in one sentence: /goal, /loop, and /schedule are commands inside Claude Code; loop.js is the same convergence discipline as a framework — a typed engine you run from any terminal, install on a schedule, or embed via Loop.define / loop.run / typed events. Everything a loop needs to reach done — an independent verdict, durable state, declared guards, real scheduling — in one npm package, driving the same Claude agents underneath.

What each owns

/goal/loop/scheduleloop.js
Who judges “done”an evaluator in the same sessionthe working model itselfeach run ends when its agent stopsa separate, skeptical Verify agent — own model, read-only — and only its Verdict settles the Loop
Where memory livesthe session’s context windowthe session — stops when your machine doescarried by your prompt, run to runon disk — Record, journal, handoff notes; every Round starts with fresh context
Crash, restart, overlaptied to the live sessiontied to the live sessionfire-and-forgetthe Lock (CAS + heartbeat): a live owner refuses overlap, a dead one is taken over
What stops a runawaya maximum number of turnsyou cancel ityou cancel ittotal usd ledger (step-granular), Rounds cap, per-Round timeout — each a typed Exit
A finished goal, re-triggeredruns againruns againre-judged through the Verify gate — near-free while still ok, re-opens when stale
Surfacea command inside Claude Codea command inside Claude Codea command inside Claude Codea typed library — Loop.define / loop.run, an event stream, CLI exit codes
The nearest sibling is /goal — it, too, refuses to let the worker declare victory early. The differences, side by side:
/goalloop.js
The judgean evaluator inside your sessiona separate Verify session — own model, read-only permissions
Feedbacka mandatory reason on every verdict, fed to the next Round
An unmeetable goalruns to the turn capsettles as an explicit give-up (impossible)
Lifetimeends with your sessionresumes from disk — any machine, any trigger, weeks later

What the convergence machinery buys you

Only a verdict settles

A separate Verify agent — its own model, read-only permissions — judges every Round. Rounds, dollars, and timeouts are guards, never a definition of done.

State on disk

Fresh context every Round; memory read back from disk. The loop survives crashes, restarts, and weeks on a schedule — it’s as durable as your filesystem.

Declared guards

rounds, usd, and a per-Round timeout bound the loop. Every ending is a typed Exit — a wrapper branches on exit codes, not on parsed output.

One writer per Workspace

run() claims the Lock atomically and throws LoopBusy at a live owner. Any Trigger cadence is safe — a periodic schedule doubles as the crash watchdog.

Schedulers, not daemons

loop cron installs Entries into crontab, launchd, Task Scheduler, or Modal — and an Entry declares its own lifetime: --until settled removes itself at the first settle.

Embed it, too

An async-iterable Run handle and a self-describing, replayable event stream — the same engine the CLI drives, as a typed library inside your product.

The same agents, judged

loop.js is not a different agent — Execute and Verify run on the Claude Agent SDK, the engine behind Claude Code. What loop.js adds is the machinery around them:
import { Loop } from "@loop.js/core"

const loop = Loop.define({
  goal: "Make `bun test` pass on ./api",
  verify: { model: "claude-haiku-4-5" }, // the judge runs cheaper, read-only
  limits: { rounds: 10, usd: 5, timeout: "15m" },
})

const run = loop.run()
for await (const e of run) {
  if (e.type === "verdict") console.log(`round ${e.round}:`, e.ok, e.reason)
}

const exit = await run.done()
// { settled: true, verdict } — the judge said so, not the worker
npx loop cron add "*/30 * * * *" --until settled   # keeps trying, removes itself when the bar is met

When to keep /goal, /loop, and /schedule

Honestly: often. Watching a refactor converge in your own session (/goal), re-checking a deploy every five minutes (/loop), a personal daily routine (/schedule) — an interactive command beats a framework. Reach for loop.js at the first of: the loop outlives your session, someone else (or cron) triggers it, money is on the line, or “done” must be judged by an agent the worker can’t influence.

Quickstart

From empty directory to a settled Goal.

What is loop engineering

The practice loop.js is built for — and where it stands in it.