Skip to main content

bounded-loops logo

bounded-loops

Loop engineering you can actually run — every safety bound enforced in engine code, not described in a checklist.

67 runnable AI-agent loop folders across a dozen industries — 63 keyless, offline, and gate-verified out of the box.
The runnable engine layer for the loop-engineering practice that Karpathy, Steinberger, Cherny, Osmani, and Berman defined.

License: Apache-2.0 678 tests passing 67 loop folders keyless and offline nine bounds plus kill switch


Clone this, and in ~30 seconds — no API key, fully offline — watch an AI-agent loop wreck without its gate (it confidently claims done while the bug is still there), then watch the same loop stop cleanly with the gate enforcing it. Every loop is a real runnable folder: a runner (the agent) proposes a change, an independent gate (pytest, a JSON-Schema check, a command, a real security scanner) verifies it, and a small engine repeats under nine enforced bounds + a kill switch until the gate passes or a bound trips.

"Loop engineering" is a practice the community named in 2026 — "stop prompting your agent; design the loop that prompts it." This repo doesn't claim the term; it ships the part others describe but don't run: the runnable, enforced implementation67 runnable loop folders across a dozen industries, plus a wider catalog of loop recipes. Full credits below.

Inspired by the loop-engineering community — we build on it, not against it

bounded-loops didn't invent loop engineering. It's the runnable engine layer for a practice that brilliant people defined first, and it exists to complement their work, not compete with it.

  • Andrej Karpathy (@karpathy) framed the "loopy era" — the new skill is designing loops that keep useful work moving — and named the exact constraint this project is built around: you can only safely automate a loop when its result is easy to evaluate. bounded-loops turns that into code: the gate is that evaluable metric, and nothing is "done" until it passes.
  • Peter Steinberger lit the spark: "stop prompting your agents; design the loops that prompt them."
  • Boris Cherny (Claude Code, Anthropic): "My job is to write loops."
  • Addy Osmani named the practice and gave it its anatomy in his Loop Engineering essay.
  • Matthew Berman's Loop Library — the curated catalog of loops-as-prompts that we extend into runnable, gated folders.
  • proof-loop, repo-task-proof-loop, and agentops pioneered the runnable fresh-verifier loop; bounded-loops adds the full nine-bound safety envelope and the by-industry library on top.

What bounded-loops adds to all of it: every loop is a real folder that runs to ✓ [DONE] under nine enforced bounds — across finance, legal, healthcare, retail, operations, enterprise/ERP, security, testing, content, research, and business, not just software. If we've mischaracterized or missed your loop project, open an issue — this space grows by building on each other.

Three ways to use it

  1. As a CLI — clone, pip install -e ., then bl list / bl run loops/<name> --yes. The raw engine; see Quick start below.
  2. As an MCP server — the bounded-loops-mcp command exposes bl_run / bl_lint / bl_list / bl_show / bl_gates / bl_audit_loops / bl_runs as tools, plus catalog/manifest/prompt resources and run_loop / write_loop / audit_loop prompts, to any MCP client (Claude Code, Cursor, Codex, Antigravity…).
  3. As an agent pluginplugins/ ships a ready-to-install package for Claude Code, Codex, and Antigravity, each with a Skill, a /bl-run command, the MCP wiring, and a verify-on-stop hook — plus VS Code / GitHub Copilot files (.vscode/mcp.json, .github/ instructions and prompts) so the largest editor surface can use it too. Inside your agent you get gate-verified "done" instead of the agent claiming done. See MCP server and IDE plugins.

New in 0.2.0

The engine grew from a runnable reference into a harness you can lean on in CI:

  • Composite gates — a loop can now require several independent checks to pass together (gate.kind: composite, mode: all), so a real "done" can mean schema-valid and domain-checked, not one gate.
  • Typed external gates — first-class adapters for gitleaks, semgrep, trivy, promptfoo, great_expectations, and axe that parse structured tool output, not just exit codes. bl gates lists every gate kind and whether its tool is installed locally.
  • A first-class ERROR outcome — a runner or gate that crashes is now an auditable terminal state with a ledger entry, never an unstructured failure. Every run ends in a recorded status.
  • Stronger sandbox runners — opt-in docker and worktree runners alongside the keyless stub, for isolated real-agent runs.
  • Resumable runsbl run <loop> --run-id <id> persists a workspace + per-run ledger (indexed in SQLite); --resume continues it, and bl runs <loop> lists prior runs.
  • Inspect before you runbl show <loop> prints the runner, gate, bounds, approval posture, content hash, and risk tags; bl audit-loops checks copy-paste readiness across the whole catalog.
  • Seven agentic patterns — the catalog now spans all seven (prompt-chaining, routing, parallelization, orchestrator-workers, evaluator-optimizer, augmented-llm, agents), not one.
  • Editor adoption + CI — VS Code / GitHub Copilot files (.vscode/mcp.json, .github/ instructions and prompts), an AGENTS.md, and a CI matrix on Python 3.11–3.13.

See CHANGELOG.md for the full list.

Repository layout

bounded-loops/
├── bounded_loops/      the engine (Python package)
│   ├── domain/           pure rules + data types, no I/O — the ports (the seam) live here
│   ├── application/      the loop algorithm (run_loop.py) + bounds enforcer + manifest loader
│   ├── adapters/         concrete runners (stub, shell, claude-code, codex, docker,
│   │                     worktree, …) and gates (command, pytest, jsonschema,
│   │                     composite, osv, checkov, gitleaks, semgrep, trivy, …)
│   ├── composition.py    the ONLY file that wires adapters onto the engine (composition root)
│   ├── cli.py            the `bl` command (run · lint · list · show · gates · runs · new · audit-loops)
│   ├── mcp_server.py     the `bounded-loops-mcp` server (tools + resources + prompts)
│   └── hooks/            the verify-on-stop hook
├── loops/              the 67 runnable loop folders — the library (each = seed + gate + cassette)
├── catalog/            the recipe catalog — a browsable menu of loop ideas across industries
├── docs/               how the engine works — ARCHITECTURE, NINE-BOUNDS, WRITING-A-LOOP (SVG diagrams)
├── plugins/            install packages for Claude Code / Codex / Antigravity (skills, commands, hooks)
├── tests/              the 678 tests
└── README.md · CONTRIBUTING.md · LICENSE · pyproject.toml

docs/ explains how the machine works; catalog/ lists which loops you can run — different jobs, not duplicates. Every other top-level entry (.venv/, __pycache__/, the .*_cache/ dirs) is a local build artifact that .gitignore already excludes from the repo.

Install

The engine is a Python 3.11+ package. Install it whichever way fits your stack:

pip — native, recommended

pip install bounded-loops            # from PyPI
bl new --list
bl new pytest-basic my-loop
bl run my-loop --yes
# To browse and run the full source catalog:
git clone https://github.com/qualixar/bounded-loops
cd bounded-loops && pip install -e .
bl list
bl run loops/bug-fix-red-green --yes

npx — Node convenience wrapper (still needs Python 3.11+ on your PATH)

npx bounded-loops new --list
npx bounded-loops new pytest-basic my-loop
npx bounded-loops run my-loop --yes

The npm package is a thin launcher: on first run it finds Python 3.11+, installs the engine, then hands off to the real CLI. It does not reimplement the tool in Node — Python is the engine, npm is just a convenient front door. The full runnable loop catalog lives in the source repository; clone the repo when you want bl list to show every shipped loop folder.

Quick start (keyless, ~30s)

git clone https://github.com/qualixar/bounded-loops
cd bounded-loops && pip install -e .
bl run loops/bug-fix-red-green --yes    # keyless stub runner + real pytest gate
✓ [DONE] gate-passed (laps: 1)  ledger: loops/bug-fix-red-green/.ledger.jsonl

See the contrast that makes the point — the same loop, ungated, believing a lie:

cd loops/bug-fix-red-green && ./wreck.sh    # exits 1: "LIE CONFIRMED"

And browse every loop with its role, rung, and gate:

bl list

Nine bounds (+ kill switch)

Every loop's bounds.yaml maps onto nine bounds, enforced across the engine's layers — not nine flat booleans in one file:

# Bound Enforced by
1 Iteration control — hard lap cap + no-progress stall detection bounds.max_iterations / bounds.no_progress_window, in run_loop.py
2 Sandboxing — the agent operates on an isolated scratch copy of seed/, never the source dir; symlinks refused bounds.sandbox + composition._make_scratch_workspace
3 Input quarantine — secret-bearing files (.env*, .ssh, .aws, *.pem/*.key, id_rsa, credentials, .git) are excluded from the sandbox copy, so a shared loop can't smuggle or exfiltrate credentials bounds.quarantine_inputs (active in _make_scratch_workspace)
4 Output schema validation bounds.schema, consumed by JsonSchemaGate when a loop opts in
5 Tracing — one OTel span per lap, or a no-op tracer when the otel extra isn't installed bounds.trace + TracerPort
6 Regression evaluation — satisfied by the loop's gate choice (pytest / command / jsonschema / osv / checkov), not a Bounds field the GatePort adapter a loop selects
7 Token budget — real token counts parsed from claude-code's usage, accumulated and enforced by BudgetMeter. (shell/codex/antigravity report 0 tokens — an honest tool limitation, not a silent gap; stub/python_callable supply counts from the cassette/glue.) bounds.max_tokens + BudgetMeter
8 Human approval gating bounds.require_approval (derived from rung when unset: L1 → none, L2/L3 → required)
9 Wall-clock timeout — max_wallclock_s: null means "use the conservative 1-hour default", not "unbounded". It is an inter-lap ceiling (checked before each lap); the in-lap guard against a single long turn is the runner's and gate's own subprocess timeout_s bounds.max_wallclock_s + BudgetMeter; runner/gate timeout_s

On top of all nine, an env-var kill switch (BOUNDED_LOOPS_KILL) is polled once per lap, before anything else — the highest-priority stop. And a frozen invariant runs through all of it: the engine never trusts the agent's own claim of "done." Only the gate decides. The runner's agent_claimed_done flag is recorded but never substituted for a real gate pass.

Runners and gates

Runners (what proposes a change each lap):

Runner What it does Needs
stub Replays a recorded cassette of agent turns — fully deterministic, zero external calls. The keyless demos (including the osv/checkov loops' quick path) use this: the cassette simulates the agent's fix so the loop runs offline. To exercise a real agent/scanner, install the tool and use --runner or a non-stub cassette nothing (keyless)
shell Pipes the loop's prompt to any CLI command over stdin whatever CLI you point it at
python_callable Calls a run_turn(prompt, workspace) -> dict in a spawn-isolated subprocess with a scrubbed env a Python module implementing the contract
claude-code claude -p --output-format json --bare; parses real total_cost_usd + usage tokens the claude CLI and ANTHROPIC_API_KEY/apiKeyHelper--bare never reads OAuth/keychain (confirmed against the real binary)
codex codex exec --json, sandbox mode derived from rung the codex CLI (JSON schema not yet smoke-tested against a real binary — see Known limitations)
antigravity agy -p, approval policy derived from rung the agy CLI
docker Runs the agent turn inside a container for stronger isolation the docker CLI + an image
worktree Runs the agent turn in an isolated git worktree, sharing history but not the working tree a git repo

Gates (what decides if a lap is done):

Gate Checks Needs
command Any command (tokenized, run without a shell — no |/&& chaining); exit 0 = pass. Verifies by exit code only — for real output validation prefer a typed gate below. A gate needing shell features ships a wrapper script in its loop folder whatever command you configure
pytest pytest -q in the workspace pytest
jsonschema workspace/output.json against a JSON Schema (path from bounds.schema) nothing beyond the core deps (keyless)
osv osv-scanner reports zero known vulnerabilities (fails closed on an empty/garbage report) osv-scanner binary + network (fetches the OSV.dev advisory DB)
checkov checkov reports zero failed IaC checks (fails closed on uninterpretable output) checkov
composite Runs several child gates and passes only if all pass (mode: all); records a per-child verdict in the ledger whatever its child gates need
gitleaks · semgrep · trivy · promptfoo · great_expectations · axe Typed adapters that parse each tool's structured output (not just its exit code) into a gate verdict the respective tool on PATH

bl gates prints every gate kind and whether its tool is installed locally. Additional gate adapters are pluggable via the composition root; the keyless-first set (command / pytest / jsonschema / composite) needs nothing beyond the core deps — no gate in any shipped loop's default manifest requires a paid product.

Runnable loops (67 folders, across a dozen industries)

Every one is a real folder you run to DONE — a broken seed, a mechanical gate, the nine bounds, an L1/L2/L3 rung, and a forbid: guard so the agent can't rewrite the gate to cheat. 63 of the 67 reach ✓ [DONE] gate-passed with zero setup (keyless stub runner + a real gate); the 4 framework examples need their framework installed. bl list prints them all — here's the spread:

Industry / role # Examples
Software · backend · dev 12 bug-fix-red-green, data-contract, openapi-schema-valid, dead-import-clean, type-annotations-present, json-config-schema
Security · supply-chain 7 secret-scan-keyless, dependency-pinning, dockerfile-no-root, cors-not-wildcard, jwt-alg-not-none, osv-scanner-example
Finance · accounting 6 ledger-reconciliation, invoice-3way-match, journal-entries-balance, iso20022-payment-valid, fx-rate-sanity
Legal · compliance 6 citation-existence-check, contract-clause-extraction, nda-required-clauses, gdpr-dpa-terms, contract-defined-terms
Content · marketing 6 content-fact-gate, frontmatter-schema, broken-internal-links, alt-text-present, reading-level-gate, seo-meta-limits
Retail · commerce 5 product-feed-schema, price-margin-floor, inventory-nonnegative, gtin-checkdigit, catalog-required-fields
Operations · SRE 5 runbook-completeness, oncall-coverage, slo-error-budget, conventional-commits, alert-runbook-link
Enterprise · ERP/SAP 5 idoc-xml-schema, transport-request-manifest, cds-view-annotations, bapi-payload-contract, material-master-completeness
Business alignment 5 okr-measurable, prd-acceptance-criteria, rfc-decision-recorded, roadmap-field-contract, meeting-action-items
Testing · QA 4 test-naming-contract, assertion-density, no-hardcoded-sleep, test-presence-per-module
Research pipeline 4 claim-source-mapping, dataset-license-allowed, reproducibility-manifest, bibliography-completeness
Healthcare · frontend 2 clinical-note-completeness, a11y

The flagship for "AI reliability" is citation-existence-check: a runnable form of the single most-documented AI failure in law — fabricated case citations (1,600+ sanction cases catalogued by mid-2026) — where the gate fails until every cited case resolves to a real one.

Every loop's own README shows the "prove the gate genuinely fails on the unfixed seed, then passes on the fix" walkthrough with actual captured output — run, not asserted. The wider recipe catalog lists more loop recipes across these same industries (each naming a real gate) to wire up next.

On accessibility specifically: axe-core/Lighthouse need a running browser, and no keyless universal a11y tool exists — so the a11y loop ships a small, dependency-free static linter (missing alt, missing lang, unlabeled inputs). It's honest about catching the static WCAG subset, not rendered-DOM issues.

Scaffolding your own loop

bl new --list                    # pytest-basic, jest-basic, go-test-basic,
                                 # cargo-test-basic, rspec-basic, junit-basic
bl new pytest-basic my-loop      # <template> <destination> (positional)

MCP server and IDE plugins

bounded-loops-mcp exposes bl_run/bl_lint/bl_list as MCP tools — a thin shim over the same engine the CLI uses. bl_run's confirm is a real, server-side-enforced gate: it binds the full run identity (gate + runner + iteration cap), so a caller can't preview a safe shell runner and then confirm a credentialed one; and a loop needing interactive approval is refused before it's ever wired. plugins/{claude-code,codex,antigravity}/ each ship the MCP config + an AGENTS.md snippet + a verify-on-stop hook.

Known limitations (stated plainly, not hidden)

  • The codex runner's real-binary JSON schema is not yet smoke-tested (no codex install at build time); its token accounting reports 0 until verified.
  • langgraph-example's glue hard-codes changed: True — its no-progress bound can't fire for that example; a real integration should diff before/after.
  • Two gates are not offline, and require network at check time: content-fact-gate (link liveness) and the osv gate (osv-scanner fetches the OSV.dev advisory database). The "~30-second, no-API-key, fully offline" quick-start refers to the default bug-fix-red-green loop specifically (stub runner + pytest gate), which needs neither network nor a key. The keyless-first set — stub/shell runners with pytest/jsonschema/command gates — is offline; the security-scanner gates (osv, checkov) bring their own binary and, for osv, a network fetch.
  • python_callable always spawns and scrubs the child env to a small allowlist — by design, but your callable's import side effects run in a clean environment.

Contributing

See CONTRIBUTING.mdbl new a template, replace the seed, prove your gate genuinely fails then genuinely passes, and open a PR. The bar for a catalog recipe or a loop: a real gate and a testable done-condition, never "an LLM decides."

Citation

bounded-loops is research-grade software from the Qualixar AI Reliability Engineering initiative. If it's useful in your work, please cite it — GitHub's "Cite this repository" button reads CITATION.cff:

@software{bhardwaj_bounded_loops_2026,
  author    = {Bhardwaj, Varun Pratap},
  title     = {bounded-loops: runnable, bounded AI-agent loops},
  year      = {2026},
  publisher = {Qualixar},
  version   = {0.2.1},
  url       = {https://github.com/qualixar/bounded-loops}
}

Security

Found a way to trick the gate or escape a bound? That's the most valuable bug we can get — please report it privately per SECURITY.md.

License

Apache-2.0. Copyright © 2026 Varun Pratap Bhardwaj / Qualixar.


Part of the Qualixar AI Reliability Engineering initiative

bounded-loops is one product in Qualixar's open-source platform for making AI agents reliable:

Product Purpose Install
SuperLocalMemory Local-first agent memory + learning npm install superlocalmemory
Qualixar OS Universal agent runtime npx qualixar-os
SLM Mesh P2P coordination across sessions npm i slm-mesh
SLM MCP Hub Federate 430+ MCP tools pip install slm-mcp-hub
AgentAssay Token-efficient agent testing pip install agentassay
AgentAssert Behavioral contracts + drift detection pip install agentassert
SkillFortify Formal verification for agent skills pip install skillfortify
Agent Amplifier Runtime amplification hooks pip install agent-amplifier
bounded-loops (this repo) Runnable, gate-verified agent loops pip install bounded-loops

Local-first. Honest about what runs. Built to complement the ecosystem, not fence it off.

Start here → qualixar.com · Author: Varun Pratap Bhardwaj — founder, Qualixar.


If bounded-loops saves you from an agent that lies about being done,
star it on GitHub — it helps other developers find it.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

bounded_loops-0.2.1.tar.gz (404.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

bounded_loops-0.2.1-py3-none-any.whl (172.5 kB view details)

Uploaded Python 3

File details

Details for the file bounded_loops-0.2.1.tar.gz.

File metadata

  • Download URL: bounded_loops-0.2.1.tar.gz
  • Upload date:
  • Size: 404.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for bounded_loops-0.2.1.tar.gz
Algorithm Hash digest
SHA256 136760b2cec563060cb70599b6aa6c631abb242fd0f56350d00fdbe37fb197c2
MD5 da3076f7f135ed7fd92c9625e8995847
BLAKE2b-256 7a646340c947a3029b2ebf287d887a16c94cbd90d640f6a8d0cc576780ffc017

See more details on using hashes here.

File details

Details for the file bounded_loops-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: bounded_loops-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 172.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for bounded_loops-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 ff9ace63a935c14317944fd8c614fa29c5455e1e53e5c9104a048a204b0d02b5
MD5 1ddd5f747b1bad92f3c9e1d7af547c42
BLAKE2b-256 c3a903386b1befec6b55aaeea73f2301a56b55ff68180ff4cb82c51918bf6f2a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page