[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-93283":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":13,"subscribersCount":13,"size":13,"stars1d":13,"stars7d":16,"stars30d":16,"stars90d":13,"forks30d":13,"starsTrendScore":13,"compositeScore":17,"rankGlobal":10,"rankLanguage":10,"license":18,"archived":19,"fork":19,"defaultBranch":20,"hasWiki":21,"hasPages":19,"topics":22,"createdAt":10,"pushedAt":10,"updatedAt":36,"readmeContent":37,"aiSummary":38,"trendingCount":13,"starSnapshotCount":13,"syncStatus":39,"lastSyncTime":40,"discoverSource":41},93283,"loop.js","loop-js\u002Floop.js","loop-js","A loop engineering framework — state a Goal; Rounds run until a skeptical, read-only Verify agent settles it.","https:\u002F\u002Floop-js.mintlify.site\u002F",null,"TypeScript",115,0,104,20,10,46,"Apache License 2.0",false,"master",true,[23,24,25,26,27,28,29,30,31,32,33,34,35],"agent-framework","agent-loop","agent-verification","ai-agents","anthropic","autonomous-agents","claude","claude-agent-sdk","cron","llm","loop-engineering","nodejs","typescript","2026-07-22 04:02:08","# loop.js\n\n**Loop.js is the framework for loop engineering.**\n\nYou state a goal and what \"done\" means. The engine runs an agent at it, Round after Round —\nfresh context every Round, memory read back from disk — until a separate, skeptical judge\nrules the bar met. Run it from a terminal, schedule it — on your machine or deployed to\nModal's cloud — or embed it in a product: same engine, same guarantees.\n\n[![npm](https:\u002F\u002Fimg.shields.io\u002Fnpm\u002Fv\u002F@loop.js\u002Fcore.svg)](https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002F@loop.js\u002Fcore)\n[![license](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Flicense-Apache--2.0-blue.svg)](LICENSE)\n[![docs](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Fdocs-loop--js.mintlify.site-0f172a.svg)](https:\u002F\u002Floop-js.mintlify.site\u002F)\n\n```ts\nimport { Loop } from \"@loop.js\u002Fcore\"\n\nconst loop = Loop.define({\n  goal: \"Build a playable 2D platformer\",\n  verify: \"It builds clean, `bun test` passes, and the game boots to a controllable character\",\n  limits: { rounds: 20, usd: 10 },\n})\n\nconst exit = await loop.run().done()\n\u002F\u002F { settled: true, verdict: { ok: true, reason: \"…\" } } — the judge said done, not the worker\n```\n\n**Loop engineering** is designing the system that prompts an agent instead of prompting it\nturn by turn: a goal, a way to work, a way to verify, a stop discipline — the loop does the\niterating. loop.js is that system as a framework:\n[what is loop engineering →](https:\u002F\u002Floop-js.mintlify.site\u002Floop-engineering)\n\n## Why loops need a framework\n\nRunning an agent in `while (true)` is one line. Whether that loop ever *converges* is\neverything around it — and that is the part loop.js owns:\n\n| The pain | The machinery |\n| --- | --- |\n| 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. |\n| 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. |\n| 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. |\n| 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. |\n| 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. |\n| Babysitting | [`loop cron`](https:\u002F\u002Floop-js.mintlify.site\u002Fcli\u002Fcron) installs into a real scheduler — crontab, launchd, Task Scheduler, or **Modal** in the cloud. No daemon, ever. |\n\n## What do you loop?\n\n**Build something until it's actually done.** The verdict — not vibes — decides when to stop:\n\n```ts\n\u002F\u002F loop.config.ts\nexport default Loop.define({\n  goal: \"Build a playable 2D platformer — arrow keys, jump physics, win and lose states\",\n  verify: \"`bun test` passes and `bun run build` emits a bundle that boots without console errors\",\n  limits: { rounds: 30, usd: 15, timeout: \"20m\" },\n})\n```\n\n```sh\nnpx loop run   # Rounds stream by until the judge says ok — or a guard fires, with its own exit code\n```\n\n**Keep something true, on a schedule.** A time-dependent goal goes stale by itself — each\nmorning the settled Loop is *re-judged*: yesterday's brief no longer satisfies \"today's brief\nexists\", so the Loop re-opens and writes a new one. On days the bar still holds, the trigger\ncosts one judge turn and re-settles.\n\n```ts\nexport default Loop.define({\n  goal: \"Today's brief on my watchlist (NVDA, TSLA, BTC) exists in .\u002Fbriefs, named by date — overnight moves, headlines, one paragraph of context each\",\n  verify: { model: \"claude-haiku-4-5\" }, \u002F\u002F bar = the goal itself; judged by a cheaper model\n  permissions: \"bypass\",                 \u002F\u002F gating off: headlines need the network — run this loop inside your own container\n})\n```\n\n```sh\nnpx loop cron add \"0 8 * * 1-5\" --until forever   # weekday mornings, until you remove it\n```\n\n**Trade a strategy, audited every day.** The worker trades through your broker's API;\nthe judge audits every order against the strategy — and a breach settles as a give-up, so\nthe loop never retries its way into more orders:\n\n```ts\nexport default Loop.define({\n  goal: \"Today's trades are executed and logged in .\u002Ftrades, dated — follow .\u002Fstrategy.md: check its signals, size positions within its caps, attach a stop-loss to every order\",\n  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\",\n  permissions: \"bypass\", \u002F\u002F broker API + market data need the network — run contained\n  limits: { rounds: 3, usd: 2 },\n})\n```\n\n```sh\nnpx loop cron add \"30 9 * * 1-5\" --until forever --backend modal   # weekday mornings — deployed to Modal, no machine of yours stays on\n```\n\nThe `usd` guard caps what the loop spends on the model, never what the strategy trades —\nposition caps live in `strategy.md` and in your broker account's own limits.\n\n**Chores that should stay done.** Same shape, pointed at upkeep — schedule with\n`--until settled` (the entry removes itself at the first settle) or `--until forever`:\n\n- \"Dependencies are current, `bun test` is green, and the changelog has an entry\" — weekly\n- \"Every new issue has a triage label and a first response\" — nightly\n- \"Every TODO in .\u002Fsrc links an issue or is deleted\"\n- \"Test coverage ≥ 80%, no skipped tests\"\n- \"Yesterday's ETL output exists in .\u002Fdata and passes its sanity checks\"\n- \"Every post in .\u002Fposts has an up-to-date Chinese translation\"\n\n**One pass, no judging.** `Agent.define` is the same Execute phase run bare — one ungraded\npass, no verdict, no convergence machinery:\n\n```ts\nimport { Agent } from \"@loop.js\u002Fcore\"\n\nawait Agent.define({ goal: \"Summarize yesterday's git log into .\u002Fstandup.md\" }).run().done()\n```\n\n## Quickstart\n\n```sh\nnpm create @loop.js@latest my-loop\ncd my-loop && npm install\nexport ANTHROPIC_API_KEY=sk-ant-…   # agents run on the Claude Agent SDK\n\nnpx loop run      # drives Rounds until the Loop settles or a guard fires\nnpx loop status   # check on it any time, from any shell — human or --json\n```\n\nThe scaffold is goal-only: `goal` is the field you edit; the `limits` block spells out\nthe tight engine defaults — 3 Rounds, $1, 5 minutes per Round — and every other knob is a\ncommented line carrying its default. First runs stop cheap by design; raising the guards\nis the deliberate act.\n[Full quickstart →](https:\u002F\u002Floop-js.mintlify.site\u002Fquickstart)\n\n## Schedule it — local, or deployed to Modal\n\n`loop cron` installs an Entry into a **real scheduler** and never runs one itself. A fired\nEntry simply runs `loop run` — the Lock makes any cadence overlap-safe — and every Entry\ndeclares its own lifetime at `add`:\n\n```sh\nnpx loop cron add \"*\u002F30 * * * *\" --until settled                # a watchdog: gone at the first settle (capped)\nnpx loop cron add \"0 8 * * *\"    --until forever                # standing: each tick re-judges through the Verify gate\nnpx loop cron add \"0 8 * * *\"    --until forever --backend modal   # the same Entry, deployed to Modal's cloud\nnpx loop cron list\nnpx loop cron remove \u003Cid>\n```\n\n| Backend | Where it runs | Where State lives |\n| --- | --- | --- |\n| `local` (default) | crontab (Linux), launchd (macOS), Task Scheduler (Windows) | the project directory |\n| `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 |\n\nWith `--backend modal`, `add` deploys with your own Modal token and stores your\n`ANTHROPIC_API_KEY` once as a shared `modal.Secret` named `loop-js` — created only when\nabsent, rotated with one `modal secret create --force`, no redeploy.\n[Scheduling →](https:\u002F\u002Floop-js.mintlify.site\u002Fcli\u002Fcron)\n\n## One Round\n\n```\n               Goal — what \"done\" means; required, judged every Round\n                 │\n   ┌─────────────┼─────────────────── one Round ───────────────────────┐\n   │             ▼                                                     │\n   │  Execute   the worker agent builds in the work tree               │\n   │     │      (fresh context; memory read back from disk)            │\n   │     ▼                                                             │\n   │  Handoff   the worker writes a note to its successor              │\n   │     ▼                                                             │\n   │  Verify    a separate, skeptical agent judges against the bar     │\n   │     │      (read-only; escalates to the tree, the build, the      │\n   │     ▼       transcript when a claim needs ground truth)           │\n   │  Persist   record + journal — resumable after any crash           │\n   └─────────────┬─────────────────────────────────────────────────────┘\n                 ▼\n        ok          → settled: the Loop succeeds\n        not yet     → the reason lands on disk and feeds the next Round\n        impossible  → settled: explicit give-up, budget preserved\n```\n\nOnly a verdict settles a Loop — `rounds`, `usd`, and `timeout` are runaway guards, never a\ndefinition of done.\n\n## Goal · Execute · Verify\n\nEverything you author is a **prompt** — one shape, three homes. A prompt is a string, a\n`{ file: \".\u002Fverify.md\" }` (re-read fresh every Round, so you can move the bar mid-loop), or\na per-round function.\n\n```\n         goal          what \"done\" means — the one required thing\n        \u002F    \\\n  execute    verify    the mirrored pair — optional, each falls back to the goal\n  how to work it       how to judge it\n```\n\n```ts\nLoop.define({\n  goal: \"…\",\n  execute: { file: \".\u002Fexecute.md\" },   \u002F\u002F what to work on each Round\n  verify: \"the checks to run and the end-state that must hold\",\n})\n\n\u002F\u002F the object form binds a phase's model or permissions:\nLoop.define({\n  goal: \"…\",\n  verify: { prompt: { file: \".\u002Fverify.md\" }, model: \"claude-haiku-4-5\" },\n})\n```\n\nGoal-only is first-class: omit both and the engine works toward — and judges against — the\ngoal itself. [Prompts →](https:\u002F\u002Floop-js.mintlify.site\u002Fconcepts\u002Fprompts)\n\n## Embed it\n\nThe same engine is a typed library. `loop.run()` self-drives and returns an async-iterable\nhandle — iterating observes, it doesn't control; breaking out unsubscribes without\ncancelling:\n\n```ts\nconst run = loop.run()\nfor await (const e of run) {\n  if (e.type === \"text-delta\") ui.type(e.text)                \u002F\u002F live typewriter\n  if (e.type === \"verdict\")    ui.badge(e.round, e.ok, e.reason)\n  if (e.type === \"exit\")       ui.done(e.exit)                \u002F\u002F terminal — stream completes\n}\n```\n\nEvery event is typed, and all but the stream-only `text-delta` are journaled; startup failures throw, everything after resolves to a\nfinal `exit` — iterating never throws. [Events →](https:\u002F\u002Floop-js.mintlify.site\u002Fapi\u002Fevents)\n\n## Neighbors\n\n**Shell loops (the ralph tradition)** proved the core insight: re-prompt with fresh context,\nkeep state on disk. loop.js keeps that and adds what a bare loop can't give you — an\nindependent verdict with mandatory reasons, typed guards and exits, crash-safe resume, real\nschedulers. [Where loop.js stands →](https:\u002F\u002Floop-js.mintlify.site\u002Floop-engineering)\n\n**Claude Code's `\u002Fgoal`, `\u002Floop`, `\u002Fschedule`** are the right tool for driving your own\nsession. Reach for loop.js when the loop outlives your terminal: someone else (or cron)\ntriggers it, money is on the line, or \"done\" must be judged by an agent the worker can't\ninfluence. [Full comparison →](https:\u002F\u002Floop-js.mintlify.site\u002Fwhy-loop-js)\n\n## CLI\n\n| Command       | What it does                                                                     |\n| ------------- | -------------------------------------------------------------------------------- |\n| `loop run`    | drives Rounds until the Loop settles or a guard fires; the exit code says which  |\n| `loop status` | one snapshot of what has happened, from disk — human-readable or `--json`        |\n| `loop cron`   | install \u002F list \u002F remove schedule entries — local OS scheduler or Modal           |\n\n## Status\n\n`v0.2` beta. Shipping today: the engine (Rounds, verdicts, budgets, Lock, journal), the CLI\n(`run` \u002F `status` \u002F `cron` with local and Modal backends), and the Claude Agent SDK\nexecutor — tested at every boundary (Lock CAS, event ordering, crash-partial folding; 800+\ntests). Ahead of 1.0: sandbox-contained runs, a public executor interface, remote\nobservation. Pre-1.0 the API may still move.\n\n## Packages\n\n| Package                                              | What it is                                  |\n| ---------------------------------------------------- | ------------------------------------------- |\n| [`@loop.js\u002Fcore`](packages\u002Floop-js)                  | engine + `loop` CLI                         |\n| [`@loop.js\u002Fcreate`](packages\u002Fcreate-loop-js)         | the scaffolder behind `npm create @loop.js` |\n\n## License\n\n[Apache-2.0](LICENSE)\n","loop.js 是一个面向 AI 代理循环工程的 TypeScript 框架，用于构建目标驱动、可验证、带收敛保障的自主代理工作流。其核心采用双代理架构：Worker 代理执行任务，独立的、只读的 Verify 代理每轮进行客观判定；支持按轮次重置上下文、磁盘持久化记忆、预算约束（USD\u002F轮数\u002F超时）、故障恢复与调度集成（如 cron 或 Modal）。适用于需要严格收敛性、可审计性与成本可控性的 AI 自动化场景，例如自动化软件构建验证、AI 驱动的交付流水线、可验证内容生成等。",2,"2026-07-15 02:30:08","CREATED_QUERY"]