Skip to content

Back to the catalog

loop-js/loop.js
github.comrepo

loop-js/loop.js

loop.js is a TypeScript framework for "loop engineering": you define a goal and a verification bar, and the engine runs an agent through repeated Rounds (Execute → Handoff → Verify → Persist) with fresh context each round, memory read back from disk, until a separate skeptical judge agent rules the goal met, gives up, or a budget guard (rounds/usd/timeout) fires. It ships a CLI (`loop run`, `loop status`, `loop cron`) for one-off runs or scheduled/recurring loops (local or deployed to Modal), a typed async-iterable event stream for embedding in products, and runs its worker/judge agents on the Claude Agent SDK. It's positioned against bare `while(true)` agent loops and Claude Code's own `/loop` command, aiming at loops that outlive a terminal session — cron-triggered chores, financial-trade auditing, or long-running builds — where "done" must be judged by an agent the worker can't influence.

TL;DR An open-source TypeScript framework that turns "prompt an agent until a skeptical judge says it's actually done" into a schedulable, resumable engine (CLI + library, deployable to Modal, built on the Claude Agent SDK).
Document

What it claims

loop.js (v0.2 beta, package @loop.js/core) makes a separate judge's verdict the only thing that can end a loop successfully. Rounds, USD and timeout limits are described as "runaway guards, never a definition of done." The project reports 800+ tests covering the Lock compare-and-swap, event ordering and crash-partial folding.

How it works

Each Round produces one of three verdicts, and they behave differently:

flowchart TD
  E[Execute: worker, fresh context] --> H[Handoff note to successor]
  H --> V{Verify: read-only judge}
  V -->|ok| S[Settled: success]
  V -->|impossible| G[Settled: give-up, budget preserved]
  V -->|not yet| R[Reason written to disk]
  R --> E
  V -.guard fires.-> X[Guard exit, own exit code]
  • The judge is read-only. When it needs ground truth for a claim, it escalates to the work tree, the build or the transcript.
  • Every Round is persisted to a record and journal, so a run can resume after a crash.
  • "Impossible" settles the loop instead of retrying. The trading example relies on this: any order that breaches the strategy ends the loop as a give-up, so the worker cannot retry its way into placing more orders.
  • Prompts share one shape. goal is required. execute and verify are optional and fall back to the goal. Each prompt can be:
  • a plain string,
  • { file: "./verify.md" }, which is re-read every Round, so you can change the bar mid-loop,
  • a per-round function.

The object form also binds a phase's model or permissions, for example verify: { model: "claude-haiku-4-5" } for cheaper judging.

  • Scheduled loops re-judge on every tick. A settled loop is re-judged each time the schedule fires. If the bar still holds, the tick costs one judge turn. If a time-dependent goal has gone stale ("today's brief exists"), the loop re-opens and does the work again.

How to use it

npm create @loop.js@latest my-loop
cd my-loop && npm install
export ANTHROPIC_API_KEY=sk-ant-…
npx loop run
npx loop status   # human or --json, from any shell

The scaffold contains only a goal. Its defaults are tight: 3 Rounds, $1, and 5 minutes per Round. Raising those limits is deliberate.

Scheduling commands:

npx loop cron add "*/30 * * * *" --until settled
npx loop cron add "0 8 * * *"    --until forever
npx loop cron add "0 8 * * *"    --until forever --backend modal
npx loop cron list
npx loop cron remove <id>
  • Lifetimes. --until settled removes the entry at the first settle. --until forever keeps it until you remove it.
  • No runner of its own. loop cron installs into a real scheduler and never runs loops itself. A Lock makes overlapping ticks safe.
  • Modal backend. It deploys with your own Modal token and stores ANTHROPIC_API_KEY once, as a modal.Secret named loop-js. You rotate it with modal secret create --force, with no redeploy.

Embedding. loop.run() returns an async-iterable of typed events: text-delta, verdict, exit and others.

  • Iterating only observes the run.
  • Breaking out of the loop unsubscribes but does not cancel the run.
  • Startup failures throw. After startup, everything resolves to a final exit, and iteration never throws.
  • Every event except text-delta is journaled.

Single pass. Agent.define({ goal }).run().done() runs one ungraded Execute pass, with no verdict.

Limits

  • Pre-1.0. It is a v0.2 beta, and the page says the API may still change.
  • No sandboxing yet. Sandbox-contained runs, a public executor interface and remote observation are listed as future work.
  • Claude-only for now. The Claude Agent SDK is currently the only executor.
  • Network examples turn gating off. The examples that need the network use permissions: "bypass", and the page says to run them inside your own container.
  • The USD guard covers model spend only. It caps what the model costs, not what a trading strategy trades. Position caps must live in your strategy file and in your broker account's own limits.

Organisations named here

Technologies named here

Techniques named here

  • Loop engineering
  • Round cycle
  • Re-reading a prompt file every Round
  • Time-dependent re-judging