Skip to main content
Loop.define(config) is a pure function: it does no I/O and claims nothing. It returns a LoopDefinition — the handle whose methods do the work:
import { Loop } from "@loop.js/core"

const loop = Loop.define({
  goal: { file: "GOAL.md" },
  limits: { rounds: 5, usd: 2, timeout: "10m" },
})

const run = loop.run()             // claims the Lock and drives Rounds
const status = await loop.status() // one snapshot read from disk

LoopConfig

Only goal is required.
FieldTypeDefaultMeaning
goalPromptrequiredThe set-once objective — injected into every Execute Round, and the bar Verify judges against when no verify prompt is given.
executePrompt | { prompt?: Prompt; model?: string; permissions?: Permissions }What to work on. A bare Prompt is shorthand for { prompt }; the object form adds an optional model and a per-phase Permissions override. Prompt omitted → the Goal stands in.
verifyPrompt | { prompt?: Prompt; model?: string; permissions?: Permissions }The success criteria. A bare Prompt is shorthand for { prompt }; the object form adds an optional model (the judge can run cheaper) and a Permissions override. Prompt omitted → the Goal stands in.
limitsLimitssee belowRunaway guards plus the per-Round timeout.
permissionsPermissionsThe loop-level Permissions — raises both phases at once (e.g. "bypass" for a contained Run). Per-phase overrides win.
workspacestring"./workspace"Points the work tree elsewhere.
{ file } next to a binding key ({ file: "./verify.md", model: "…" }) is refused with a teaching error at startup — write { prompt: { file }, model }.

Prompt

The judgment-bearing text you own — one shape for its three homes (goal, execute, verify):
type Prompt = string | { file: string } | ((ctx: PromptCtx) => string | Promise<string>)
  • Literal string — never touches disk.
  • { file } — re-read fresh at each Round start, so a mid-loop edit retargets from the next Round. Missing or unreadable is a loud error naming the path, never a silent literal.
  • Function — called per Round with the deliberately starved PromptCtx: { round, previous? }. round is 1-based across the whole Loop; previous (absent only when nothing precedes) always carries feedback: string, plus verdict iff the last Round produced one. Every function-form Prompt receives the same ctx, whichever home it lives in.

Limits

Every field optional; omitted, each falls back to an engine default.
FieldTypeDefaultMeaning
roundsnumber3Rounds across the whole Loop (runaway guard).
usdnumber1Total $ across the whole Loop (step-granular cutoff).
timeoutnumber | Duration"5m"Per-Round wall-clock timeout.
Time values use the Duration grammar — a whole number with a unit: "45s", "90m", "36h", "7d". Fields typed number | Duration also read a bare number of seconds.

Permissions

How tool calls are gated while no Sandbox contains the Run, resolved per phase: phase override > loop-level > phase default (execute "auto", verify "read").
ValueMeaning
"read"Read tools plus sandboxed commands; the write tools are denied at the adapter.
"auto"Work-tree edits auto-approved, shell in the provider’s command sandbox, the rest denied.
"bypass"Full autonomy, no gating — safe only inside a Sandbox.