Loop Js AI Agent
A loop engineering framework — state a Goal; Rounds run until a skeptical, read-only Verify agent settles it.
Loop Js is an open-source ai agent by loop-js, written mainly in TypeScript, with 136 GitHub stars and the Apache-2.0 license. Its code was last updated Aug 30, 2026. The latest npm release is loop 3.3.6, published Apr 19, 2021, with 2,086 downloads last week.
loop.js
Loop.js is the framework for loop engineering.
You state a goal and what "done" means. The engine runs an agent at it, Round after Round — fresh context every Round, memory read back from disk — until a separate, skeptical judge rules the bar met. Run it from a terminal, schedule it — on your machine or deployed to Modal's cloud — or embed it in a product: same engine, same guarantees.
import { Loop } from "@loop.js/core"
const loop = Loop.define({
goal: "Build a playable 2D platformer",
verify: "It builds clean, `bun test` passes, and the game boots to a controllable character",
limits: { rounds: 20, usd: 10 },
})
const exit = await loop.run().done()
// { settled: true, verdict: { ok: true, reason: "…" } } — the judge said done, not the worker
Loop engineering is designing the system that prompts an agent instead of prompting it turn by turn: a goal, a way to work, a way to verify, a stop discipline — the loop does the iterating. loop.js is that system as a framework: what is loop engineering →
Why loops need a framework
Running an agent in while (true) is one line. Whether that loop ever converges is
everything around it — and that is the part loop.js owns:
| The pain | The machinery |
|---|---|
| The agent says "done" when it isn't | A separate Verify agent — own session, own (cheaper, if you like) model, read-only by default — judges every Round. Only its verdict settles the Loop; the worker never grades its own homework. |
| Long sessions bloat and drift | Every Round starts with fresh context and reads its memory back from disk — the worker's own handoff notes. Round 40 starts as fresh as Round 1. |
| Retries that don't converge | A "not yet" verdict carries a mandatory reason, fed to the next Round. A goal that can never pass settles as an explicit give-up instead of burning the budget. |
| Money runs away | Declared guards: total usd (step-granular ledger), rounds, per-Round timeout — and they default tight ($1, 3 Rounds, 5m). Every ending is a typed Exit with its own process exit code. |
| Crashes, restarts, double triggers | All state lives on disk; loop run is idempotent and resumes from the cursor. The Lock (compare-and-set + heartbeat) refuses a live owner and takes over a dead one — any trigger cadence is overlap-safe. |
| Babysitting | loop cron installs into a real scheduler — crontab, launchd, Task Scheduler, or Modal in the cloud. No daemon, ever. |
What do you loop?
Build something until it's actually done. The verdict — not vibes — decides when to stop:
// loop.config.ts
export default Loop.define({
goal: "Build a playable 2D platformer — arrow keys, jump physics, win and lose states",
verify: "`bun test` passes and `bun run build` emits a bundle that boots without console errors",
limits: { rounds: 30, usd: 15, timeout: "20m" },
})
npx loop run # Rounds stream by until the judge says ok — or a guard fires, with its own exit code
Keep something true, on a schedule. A time-dependent goal goes stale by itself — each morning the settled Loop is re-judged: yesterday's brief no longer satisfies "today's brief exists", so the Loop re-opens and writes a new one. On days the bar still holds, the trigger costs one judge turn and re-settles.
export default Loop.define({
goal: "Today's brief on my watchlist (NVDA, TSLA, BTC) exists in ./briefs, named by date — overnight moves, headlines, one paragraph of context each",
verify: { model: "claude-haiku-4-5" }, // bar = the goal itself; judged by a cheaper model
permissions: "bypass", // gating off: headlines need the network — run this loop inside your own container
})
npx loop cron add "0 8 * * 1-5" --until forever # weekday mornings, until you remove it
Trade a strategy, audited every day. The worker trades through your broker's API; the judge audits every order against the strategy — and a breach settles as a give-up, so the loop never retries its way into more orders:
export default Loop.define({
goal: "Today's trades are executed and logged in ./trades, dated — follow ./strategy.md: check its signals, size positions within its caps, attach a stop-loss to every order",
verify: "Every order in today's log matches a strategy.md rule, respects its caps, carries a stop-loss, and reconciles with the broker's confirmations — any breach is a give-up, not a retry",
permissions: "bypass", // broker API + market data need the network — run contained
limits: { rounds: 3, usd: 2 },
})
npx loop cron add "30 9 * * 1-5" --until forever --backend modal # weekday mornings — deployed to Modal, no machine of yours stays on
The usd guard caps what the loop spends on the model, never what the strategy trades —
position caps live in strategy.md and in your broker account's own limits.
Chores that should stay done. Same shape, pointed at upkeep — schedule with
--until settled (the entry removes itself at the first settle) or --until forever:
- "Dependencies are current,
bun testis green, and the changelog has an entry" — weekly - "Every new issue has a triage label and a first response" — nightly
- "Every TODO in ./src links an issue or is deleted"
- "Test coverage ≥ 80%, no skipped tests"
- "Yesterday's ETL output exists in ./data and passes its sanity checks"
- "Every post in ./posts has an up-to-date Chinese translation"
One pass, no judging. Agent.define is the same Execute phase run bare — one ungraded
pass, no verdict, no convergence machinery:
import { Agent } from "@loop.js/core"
await Agent.define({ goal: "Summarize yesterday's git log into ./standup.md" }).run().done()
Quickstart
npm create @loop.js@latest my-loop
cd my-loop && npm install
export ANTHROPIC_API_KEY=sk-ant-… # agents run on the Claude Agent SDK
npx loop run # drives Rounds until the Loop settles or a guard fires
npx loop status # check on it any time, from any shell — human or --json
The scaffold is goal-only: goal is the field you edit; the limits block spells out
the tight engine defaults — 3 Rounds, $1, 5 minutes per Round — and every other knob is a
commented line carrying its default. First runs stop cheap by design; raising the guards
is the deliberate act.
Full quickstart →
Schedule it — local, or deployed to Modal
loop cron installs an Entry into a real scheduler and never runs one itself. A fired
Entry simply runs loop run — the Lock makes any cadence overlap-safe — and every Entry
declares its own lifetime at add:
npx loop cron add "*/30 * * * *" --until settled # a watchdog: gone at the first settle (capped)
npx loop cron add "0 8 * * *" --until forever # standing: each tick re-judges through the Verify gate
npx loop cron add "0 8 * * *" --until forever --backend modal # the same Entry, deployed to Modal's cloud
npx loop cron list
npx loop cron remove <id>
| Backend | Where it runs | Where State lives |
|---|---|---|
local (default) | crontab (Linux), launchd (macOS), Task Scheduler (Windows) | the project directory |
modal | a modal.Cron fires an ephemeral Runner per tick — no machine of yours stays on | a Modal Volume — remove deletes the Entry, never its Volume |
With --backend modal, add deploys with your own Modal token and stores your
ANTHROPIC_API_KEY once as a shared modal.Secret named loop-js — created only when
absent, rotated with one modal secret create --force, no redeploy.
Scheduling →
One Round
Goal — what "done" means; required, judged every Round
│
┌─────────────┼─────────────────── one Round ───────────────────────┐
│ ▼ │
│ Execute the worker agent builds in the work tree │
│ │ (fresh context; memory read back from disk) │
│ ▼ │
│ Handoff the worker writes a note to its successor │
│ ▼ │
│ Verify a separate, skeptical agent judges against the bar │
│ │ (read-only; escalates to the tree, the build, the │
│ ▼ transcript when a claim needs ground truth) │
│ Persist record + journal — resumable after any crash │
└─────────────┬─────────────────────────────────────────────────────┘
▼
ok → settled: the Loop succeeds
not yet → the reason lands on disk and feeds the next Round
impossible → settled: explicit give-up, budget preserved
Only a verdict settles a Loop — rounds, usd, and timeout are runaway guards, never a
definition of done.
Goal · Execute · Verify
Everything you author is a prompt — one shape, three homes. A prompt is a string, a
{ file: "./verify.md" } (re-read fresh every Round, so you can move the bar mid-loop), or
a per-round function.
goal what "done" means — the one required thing
/ \
execute verify the mirrored pair — optional, each falls back to the goal
how to work it how to judge it
Loop.define({
goal: "…",
execute: { file: "./execute.md" }, // what to work on each Round
verify: "the checks to run and the end-state that must hold",
})
// the object form binds a phase's model or permissions:
Loop.define({
goal: "…",
verify: { prompt: { file: "./verify.md" }, model: "claude-haiku-4-5" },
})
Goal-only is first-class: omit both and the engine works toward — and judges against — the goal itself. Prompts →
Embed it
The same engine is a typed library. loop.run() self-drives and returns an async-iterable
handle — iterating observes, it doesn't control; breaking out unsubscribes without
cancelling:
const run = loop.run()
for await (const e of run) {
if (e.type === "text-delta") ui.type(e.text) // live typewriter
if (e.type === "verdict") ui.badge(e.round, e.ok, e.reason)
if (e.type === "exit") ui.done(e.exit) // terminal — stream completes
}
Every event is typed, and all but the stream-only text-delta are journaled; startup failures throw, everything after resolves to a
final exit — iterating never throws. Events →
Neighbors
Shell loops (the ralph tradition) proved the core insight: re-prompt with fresh context, keep state on disk. loop.js keeps that and adds what a bare loop can't give you — an independent verdict with mandatory reasons, typed guards and exits, crash-safe resume, real schedulers. Where loop.js stands →
Claude Code's /goal, /loop, /schedule are the right tool for driving your own
session. Reach for loop.js when the loop outlives your terminal: someone else (or cron)
triggers it, money is on the line, or "done" must be judged by an agent the worker can't
influence. Full comparison →
CLI
| Command | What it does |
|---|---|
loop run | drives Rounds until the Loop settles or a guard fires; the exit code says which |
loop status | one snapshot of what has happened, from disk — human-readable or --json |
loop cron | install / list / remove schedule entries — local OS scheduler or Modal |
Status
v0.2 beta. Shipping today: the engine (Rounds, verdicts, budgets, Lock, journal), the CLI
(run / status / cron with local and Modal backends), and the Claude Agent SDK
executor — tested at every boundary (Lock CAS, event ordering, crash-partial folding; 800+
tests). Ahead of 1.0: sandbox-contained runs, a public executor interface, remote
observation. Pre-1.0 the API may still move.
Packages
| Package | What it is |
|---|---|
@loop.js/core | engine + loop CLI |
@loop.js/create | the scaffolder behind npm create @loop.js |
License
Loop Js alternatives
View more →Similar ai agents in Developer Tools, ranked by GitHub stars. Compare licence, recent activity and scan status before you choose.
| Name | Stars | Licence | Last updated | Scan |
|---|---|---|---|---|
| Loop Js (this page) loop-js | 136 | Apache-2.0 | Aug 30, 2026 | Scanned, no flagged issues |
| Deepseek Harness deepseek-ai | 268k | MIT | Oct 30, 2026 | Scanned, no flagged issues |
| Ponytail DietrichGebert | 182k | MIT | Oct 29, 2026 | Scanned, no flagged issues |
| Orca stablyai | 112k | MIT | Nov 1, 2026 | Scanned, no flagged issues |
| Deer Flow bytedance | 85k | MIT | Nov 1, 2026 | Scanned, no flagged issues |
| Ruflo ruvnet | 80k | MIT | Nov 1, 2026 | Scanned, no flagged issues |