Skip to main content

bounded-loops logo

bounded-loops

Agent loops that stop when an independent gate passes—not when the agent says it is done.

CI status PyPI version npm version Apache-2.0 license

Ungated agent claim compared with a gate-verified bounded loop

Quick start

pip install bounded-loops
git clone https://github.com/qualixar/bounded-loops
cd bounded-loops
bl run loops/bug-fix-red-green --yes

No API key is needed. The default cassette proposes the fix; a real pytest gate checks it. The command ends with a receipt:

✓ [DONE] gate-passed (laps: 1)  ledger: loops/bug-fix-red-green/.ledger.jsonl
Gate verified: the independent acceptance gate passed after 1 lap.
Next: inspect the ledger above; use --keep-workspace when you need to debug the resulting files.

Now see why the gate matters:

./loops/bug-fix-red-green/wreck.sh   # exits 1: the agent claimed GREEN; pytest still fails
bl run loops/convergence-demo --yes  # two failed verdicts, then DONE on lap 3

The hero GIF is generated from those real commands. Rebuild it with python3 scripts/render_demo_gif.py; assets/demo.tape is the scene specification.

Use a real agent

The keyless examples make the control flow reproducible. They do not pretend a cassette is a model. Swap in a credentialed runner when you want the agent to perform each turn:

bl run loops/citation-existence-check --runner codex --yes
bl run loops/citation-existence-check --runner claude-code --yes

The Codex path uses codex exec --json in the engine's isolated scratch workspace, maps Codex failure events to an auditable ERROR, and records live input/output token usage. See the committed Codex run receipt.

What the engine does

Each loop is a folder containing a task, a broken seed, a runner, an independent gate, bounds, and optional recorded turns. On every lap:

  1. The runner works inside a quarantined scratch copy.
  2. The gate evaluates the result independently.
  3. The engine records the verdict, token use, timing, and decision.
  4. A passing gate yields DONE; a bound yields HALT; a crash yields ERROR.

The agent's agent_claimed_done field is evidence only. It never controls termination.

Ports-and-adapters architecture

The domain rules are standard-library-only. Concrete runners, gates, ledgers, memory, tracing, approval, and kill-switch implementations sit behind ports; bounded_loops/composition.py is the composition root. Read ARCHITECTURE.md for the full design.

Nine enforced bounds and a kill switch

# Bound Enforcement
1 Iteration and stall limits max_iterations, no_progress_window
2 Scratch sandbox isolated copy; symlinks refused
3 Input quarantine secrets and key material excluded by default
4 Output schema JSON Schema gate when configured
5 Tracing one span per lap, with a no-op fallback
6 Regression evaluation the selected independent gate
7 Token budget accumulated runner usage
8 Human approval explicit or rung-derived approval
9 Wall-clock limit inter-lap budget plus subprocess timeouts

BOUNDED_LOOPS_KILL is checked before every lap. Gate commands are tokenized and run without a shell. Runner environments use an allowlist, and protected gate/reporter files can be declared with forbid:. Details and threat boundaries are in NINE-BOUNDS.md and SECURITY.md.

Runners and gates

Runner Purpose
stub Replays deterministic turns; keyless and offline
shell Pipes the prompt to a configured CLI
python_callable Runs framework glue in a spawned, scrubbed process
codex Runs the logged-in Codex CLI and parses JSONL events and usage
claude-code Runs Claude Code and parses its JSON result and usage
antigravity Runs agy with rung-derived approval policy
docker / worktree Adds stronger process or repository isolation

Built-in gates include command, pytest, jsonschema, and composite, plus typed adapters for osv, checkov, gitleaks, semgrep, trivy, promptfoo, great_expectations, and axe. Typed gates parse structured output and fail closed on malformed reports. Run bl gates to see local tool availability.

68 runnable loop folders

The source catalog contains 68 loops across software, security, finance, legal, healthcare, retail, operations, enterprise/ERP, testing, content, research, and business roles. Sixty-four are keyless; four framework examples require their framework package (langgraph, crewai, agent-framework, or google-adk). Missing packages now fail with the exact install command.

These examples are deliberately dominated by deterministic acceptance checks: linters, schemas, tests, reconciliation rules, citation reporters, and security scanners. That is a strength, not a claim that every domain problem reduces to a linter. Bounded loops are appropriate when the result has a checkable contract. When evaluation is subjective, keep a human approval gate.

Start with:

  • convergence-demo to watch two gate failures, a successful third lap, and a deliberate max-iteration trip.
  • citation-existence-check to see a legal citation corrected over two laps while the reporter and checker stay protected.
  • bug-fix-red-green for the smallest pytest loop and its intentionally ungated counterexample.
  • catalog/README.md for the full role and pattern index.

The accessibility example is explicitly a static HTML checker. It does not claim rendered-DOM coverage; use axe or Lighthouse against a live page for that.

Create your own loop

bl new --list
bl new pytest-basic my-loop
bl doctor
bl lint my-loop
bl run my-loop --yes

Packaged templates work from a wheel; the full 68-loop catalog lives in this repository. Follow WRITING-A-LOOP.md and prove the unfixed seed fails before proving the fix passes.

Codex, Claude Code, MCP, and editors

Install the MCP extra, then the native Codex plugin:

pip install "bounded-loops[mcp]"
git clone https://github.com/qualixar/bounded-loops
cd bounded-loops
codex plugin marketplace add .
codex plugin add bounded-loops@bounded-loops

The Codex package uses .codex-plugin/plugin.json and ships the bounded-loops skill plus bounded-loops-mcp wiring. Claude Code and Antigravity packages, the isolated install test, and local-development commands are documented in plugins/README.md. VS Code/GitHub Copilot MCP files are also included.

The MCP server exposes run, lint, list, show, gates, audit, and run-history tools over the same composition root. Confirmation binds the gate, runner, and iteration cap; a caller cannot preview a safer run and confirm a different one.

Known limitations

  • Framework example glue uses deterministic edits and currently reports changed: true; production glue should compute a before/after diff so the no-progress bound remains meaningful.
  • content-fact-gate and an OSV scan require network access. Scanner-backed gates require their named binary; the quick start itself is offline.
  • python_callable runs in a deliberately small environment allowlist. Import side effects that depend on arbitrary parent variables will not work.
  • The npm package is a thin Python launcher, not a second engine. Python 3.11+ is required.

Credits

bounded-loops did not invent loop engineering. Addy Osmani named and described the practice in Loop Engineering. The project also builds on Andrej Karpathy's evaluability framing, Boris Cherny's agent-loop practice, Peter Steinberger's prompting-loop discussion, Matthew Berman's Loop Library, and runnable verifier-loop projects such as proof-loop, repo-task-proof-loop, and agentops. This repository's contribution is the executable harness: enforced bounds, independent gates, receipts, and a cross-domain source catalog.

Contributing, citation, and security

See CONTRIBUTING.md. A contributed loop needs a real failing seed, a passing fix, a testable done-condition, and bl lint --contrib compliance. Never use “an LLM decides” as the gate.

Research citation metadata is in CITATION.cff. Report gate bypasses or sandbox escapes privately through SECURITY.md.

Apache-2.0. Copyright © 2026 Varun Pratap Bhardwaj / Qualixar, an independent AI Reliability Engineering research initiative.

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.3.0.tar.gz (680.8 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.3.0-py3-none-any.whl (170.2 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for bounded_loops-0.3.0.tar.gz
Algorithm Hash digest
SHA256 d7991c8e036b7427a8d7a0c07c275d82ef7c223d9e10cf2c78b5fccd11094188
MD5 5a16287b2b0d42a8672ac377d11d4d5c
BLAKE2b-256 d11ed60edff43b05a60ba453e2986e3976f02556f73ca7930c48f6121519ba29

See more details on using hashes here.

File details

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

File metadata

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

File hashes

Hashes for bounded_loops-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 eca902dc7b4821e9d7fecee96e4524be04a7a22602796e5bdd51fef53c7f8e4a
MD5 c2f582e41aecb021abffdaa3b2e77ecf
BLAKE2b-256 550b9308c2da47189a0c92ae6bb7488236257b61642cb1c5df2520afbaa6b36a

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