Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 57 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
<img src="https://raw.githubusercontent.com/qualixar/bounded-loops/main/assets/bounded-loops-logo.svg" alt="bounded-loops logo" width="170"/>
</p>

<h1 align="center">bounded-loops</h1>
<h1 align="center">bounded-loops: Bounded Loops &amp; Graphs</h1>

<p align="center"><strong>An AI coding agent can tell you it finished when it did not.</strong></p>
<p align="center"><a href="https://arxiv.org/abs/2609.27871"><strong>Read the paper · arXiv:2609.27871</strong></a><br/><small>Research experiments: v0.6.6 · Current software: v0.7.6</small></p>

<p align="center"><strong>AI agents can claim done. Bounded Loops &amp; Graphs verify the result.</strong></p>

<p align="center">bounded-loops runs your agent until a check it cannot edit or grade says the work<br/>
actually passes — and stops at a limit you set before the run starts.<br/>
Expand All @@ -20,18 +22,24 @@ Every run leaves a hash-chained record you can re-verify afterwards.</p>

<p align="center"><em>Works with Hermes, Claude Code, OpenAI Codex, Antigravity, or no agent at all.</em></p>

![Ungated agent claim compared with a gate-verified bounded loop](https://raw.githubusercontent.com/qualixar/bounded-loops/main/assets/demo.gif)
![Light-theme HTML replay of the shipped ungated claim and pytest-gated bounded loop](https://raw.githubusercontent.com/qualixar/bounded-loops/bc752dd21512e8c3f68e0504bc93443b2414fecd/assets/launch-loop-proof-light.gif)

*Replay of the shipped keyless reference example: the worker is a stub, and the pytest gate is real.*

```bash
pip install bounded-loops
bl loops install bug-fix-red-green
bl run .bounded-loops/loops/bug-fix-red-green --yes # a real planted bug, a real pytest gate, no API key
```

69 loop packages ship **inside the wheel**; 65 need no API key. `bl loops list` shows them all
and `bl loops install` copies one into your project — nothing is downloaded, so this works
offline. Installing is a step because `bl run` writes its ledger beside the loop, and
`site-packages` is the wrong place to keep run receipts.
The wheel includes 69 loop packages; 65 need no API key. This repository also ships seven
digest-pinned graphs. `bl loops install` copies a package offline into your project, where
`bl run` can write its ledger. Try a [failing test](loops/bug-fix-red-green/),
[retail margin rule](loops/price-margin-floor/), or [invoice match](loops/invoice-3way-match/).
Use a graph for joins, repair routes, or approval before an effect.

**Research scope.** The [paper](https://arxiv.org/abs/2609.27871) evaluates v0.6.6;
the current software is v0.7.6. Later changes are not paper results.

---

Expand Down Expand Up @@ -124,46 +132,34 @@ API key.

## See it working

Every screenshot below is a real run of a shipped reference graph on a laptop, with no
credential and no network. Nothing is mocked or staged.

```bash
bl graph run --execute graphs/solo-builder-ship/graph.yaml
bl monitor # opens on 127.0.0.1, loopback only, one-time token
```

### The monitor

`bl monitor` is a local web UI over the same run directory the CLI reads. It holds no state
of its own — close it and nothing is lost, because the receipt log was always the truth.

![The bounded-loops monitor showing a completed seven-node graph run](https://raw.githubusercontent.com/qualixar/bounded-loops/main/assets/screenshots/monitor-dag.png)
This HTML replay follows an archived local `solo-builder-ship` run from 18 August 2026:
three passing checks, a join, approval pause, and local effect receipt. Its raw log contains
machine-local metadata and is not distributed here, so the replay is explanatory. A
[real-agent loop receipt](docs/real-run-example/) is checked in separately.

### What the engine actually recorded
![Light-theme replay of the archived seven-node local graph run](https://raw.githubusercontent.com/qualixar/bounded-loops/bc752dd21512e8c3f68e0504bc93443b2414fecd/assets/launch-graph-replay-light.gif)

Click a node and you get what the receipts say, not a summary of them: the isolation tier the
OS really enforced, the independent gate's own verdict **and its reason**, and the artifact
digest.
**Actual monitor UI:** the existing screenshot shows a selected saved graph, including
checks, join, approval, and publish nodes.

![Node detail showing enforced isolation controls, the gate verdict, and the artifact digest](https://raw.githubusercontent.com/qualixar/bounded-loops/main/assets/screenshots/monitor-node-detail.png)
![Actual bounded-loops monitor UI with a completed seven-node graph selected](https://raw.githubusercontent.com/qualixar/bounded-loops/main/assets/screenshots/monitor-dag.png)

### A human gate, and what saying yes releases
To execute a reference graph yourself, use a host that can enforce the manifest's declared
isolation:

An approval node declares no effects of its own, so "approve this" reads as harmless right up
until the publish it lets through. The confirm panel names the effects downstream of the gate
and flags the ones stopping the run will not take back.

Note the gate line: **"no verdict — the gate has not evaluated this node."** A human hold is
not a gate pass, and the UI says so rather than painting a green check.

![The approval panel naming external_write as an effect that cannot be undone](https://raw.githubusercontent.com/qualixar/bounded-loops/main/assets/screenshots/monitor-approval-preview.png)

### A shareable report
```bash
bl graph run --execute graphs/solo-builder-ship/graph.yaml
bl monitor # opens on 127.0.0.1, loopback only, one-time token
```

`bl graph arena --run <dir>` writes one self-contained HTML file. No server, no network, no
build step — send it to someone who was not there.
`bl monitor` is a local view over the same run directory the CLI reads. A node detail shows
the recorded isolation tier, gate verdict and reason, and artifact digest. A human hold
shows **no gate verdict** until its gate runs; its approval panel names the downstream effects
the decision releases. `bl graph arena --run <dir>` writes a self-contained read-only report.

![The Arena report for a completed run](https://raw.githubusercontent.com/qualixar/bounded-loops/main/assets/screenshots/arena-report.png)
Existing [node detail](assets/screenshots/monitor-node-detail.png),
[approval](assets/screenshots/monitor-approval-preview.png), and
[Arena](assets/screenshots/arena-report.png) screenshots show more of the current UI.

---

Expand Down Expand Up @@ -289,7 +285,10 @@ A local web UI over the run directory — live DAG, per-node evidence, spend, an
controls. It detects which agent CLIs you already have logged in and lists your runs; it never
asks for a credential of its own.

![The monitor's workspace rail listing detected orchestrators and runs](https://raw.githubusercontent.com/qualixar/bounded-loops/main/assets/screenshots/monitor-workspace.png)
![Actual monitor UI showing a selected graph node, its gate verdict, isolation tier, and artifact digest](https://raw.githubusercontent.com/qualixar/bounded-loops/main/assets/screenshots/monitor-node-detail.png)

The [empty workspace view](assets/screenshots/monitor-workspace.png) is available for setup context;
the selected-run screenshots above show the graph and its evidence.

It is a **view**, not a service. Loopback bind, a token per invocation that never touches disk,
and a same-origin requirement on every data route — so a page in another tab cannot drive it
Expand Down Expand Up @@ -425,7 +424,7 @@ review.** The cross-model audit engine (`--audit-plan`) is the overlay for that.
|---|---|
| Gate-verified DAG (worker ≠ gate, controller-enforced identity check) | Shipped — object-identity check only: one object cannot hold both roles, but gate logic is not proven independent ([detail](#the-engine-loop)) |
| Local-CLI + BYOK/HTTPS connectors via `bl graph run --execute` | Shipped |
| `kind: loop` nodes executable via `bl graph run --execute` | Shipped — digest-pinned package, receipt-verifying gate. `isolation` is per-node and never defaulted; the six reference graphs pin `process_restricted`. `workspace_only` is NOT an OS sandbox |
| `kind: loop` nodes executable via `bl graph run --execute` | Shipped — digest-pinned package, receipt-verifying gate. `isolation` is per-node and never defaulted; the seven reference graphs pin `process_restricted`. `workspace_only` is NOT an OS sandbox |
| No-secret egress broker (single-use leases; SSRF / DNS-rebind denied) | Shipped |
| Hash-chained event log; on resume, full chain re-verified | Shipped — local runs marked `LOCAL/UNVERIFIED` |
| Cross-model audit coverage gate (`--audit-plan` → Arena verdict) | Shipped — read-side; independence is receipt-asserted |
Expand Down Expand Up @@ -580,8 +579,22 @@ See [CONTRIBUTING.md](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](CITATION.cff). Report gate
bypasses or sandbox escapes privately through [SECURITY.md](SECURITY.md).
For the research paper, copy this BibTeX entry:

```bibtex
@article{bhardwaj2026boundedloops,
title={Bounded Loops: Pre-Run Spend Bounds, Proved Termination, and Verified Completion for Agent Harnesses},
author={Bhardwaj, Varun Pratap and Singh, Garima and Bhardwaj, Arun Pratap},
year={2026},
eprint={2609.27871},
archivePrefix={arXiv},
primaryClass={cs.SE},
url={https://arxiv.org/abs/2609.27871}
}
```

[CITATION.cff](CITATION.cff) cites the software. Report gate bypasses or sandbox
escapes privately through [SECURITY.md](SECURITY.md).

[Apache-2.0](LICENSE). Copyright &copy; 2026 Varun Pratap Bhardwaj / Qualixar,
an independent AI Reliability Engineering research initiative.
Binary file added assets/launch-graph-replay-light.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/launch-loop-proof-light.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 15 additions & 3 deletions graphs/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Reference graphs

For the research model behind bounded loops composed as graphs, read the [paper on arXiv](https://arxiv.org/abs/2609.27871). Its reported experiments use v0.6.6; this guide describes the current v0.7.6 repository.

Each directory here is one portable graph that **composes loop packages from [`loops/`](../loops/)**.
Nothing here is a new example: every node's gate is a real mechanical check that shipped and was
validated before the graph engine could run it.
Expand All @@ -17,7 +19,7 @@ so the generator and the test read one definition rather than two that agree by

## What ships

Six graphs, one per domain, composing **24 distinct shipped loop packages**. Every one has the same
Seven graphs across seven workflow domains, composing **28 distinct shipped loop packages**. Every one has the same
skeleton — three parallel checks → join → approval → one irreversible effect, plus a conditional
`when: failed` branch to a remediation loop — because that skeleton is what exercises the engine:
fan-out, cross-node causality, the guard grammar, a human checkpoint, and exactly one effect.
Expand All @@ -29,6 +31,7 @@ fan-out, cross-node causality, the guard grammar, a human checkpoint, and exactl
[`marketing-campaign-release`](marketing-campaign-release/) | marketing | content-editor | publish the campaign page |
[`engineering-release-gate`](engineering-release-gate/) | IT / development | release-manager | cut the release tag |
[`customer-data-request`](customer-data-request/) | customer | privacy-officer | send the data-subject response |
[`operations-incident-gate`](operations-incident-gate/) | operations | sre-lead | send the incident-response notification |
[`solo-builder-ship`](solo-builder-ship/) | personal projects | maintainer | ship the release notes |

No shipped loop carries a `customer`, `personal-projects` or `marketing` `role:` tag. Those three
Expand All @@ -37,11 +40,20 @@ on why the domain belongs to the graph.

## Running a graph end to end

All six reference graphs run end to end, keyless, with no spend — **from a checkout**.
The seven reference graphs use keyless stub workers and mechanical gates. They need no
metered model calls, but execution requires a host that can enforce each node's declared
isolation. An explicit parameterized test executes each reference graph
through its loop gates and join to the human approval checkpoint; separate approval
tests cover the resume-and-publish control path.
Every loop node pins its package by content digest, and the `loops/` catalog ships in the
repository, not in the wheel, so a `pip install` alone leaves nothing for those digests to
resolve against. `git clone` first, or pass `--loop-roots` pointing at your own catalog.
`tests/graphs/test_reference_graphs.py` runs all six on every CI push.
The structural and digest checks run in the default test suite. The execution-to-approval checks are
marked `external_tool` and must be selected explicitly on a capable host:

```bash
uv run pytest -q -m external_tool tests/graphs/test_reference_graphs.py
```

```bash
bl graph run graphs/finance-payment-assurance/graph.yaml --execute --out /tmp/bl-run
Expand Down
2 changes: 2 additions & 0 deletions npm/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# bounded-loops

**Research paper:** [Bounded Loops · arXiv:2609.27871](https://arxiv.org/abs/2609.27871). The paper's experiments use v0.6.6; the current software is v0.7.6.

[![PyPI version](https://img.shields.io/pypi/v/bounded-loops)](https://pypi.org/project/bounded-loops/)
[![npm version](https://img.shields.io/npm/v/bounded-loops)](https://www.npmjs.com/package/bounded-loops)
[![Python versions](https://img.shields.io/pypi/pyversions/bounded-loops)](https://pypi.org/project/bounded-loops/)
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ dependencies = [
]

[project.urls]
Homepage = "https://qualixar.com"
Homepage = "https://github.com/qualixar/bounded-loops"
Repository = "https://github.com/qualixar/bounded-loops"
Documentation = "https://github.com/qualixar/bounded-loops/tree/main/docs"
Changelog = "https://github.com/qualixar/bounded-loops/blob/main/CHANGELOG.md"
Issues = "https://github.com/qualixar/bounded-loops/issues"
Paper = "https://arxiv.org/abs/2609.27871"

[project.optional-dependencies]
qualixar-gates = [
Expand Down
5 changes: 3 additions & 2 deletions tests/release/test_hermes_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from pathlib import Path
import sys
import types
from typing import Any

import yaml

Expand Down Expand Up @@ -87,7 +88,7 @@ def launch(self, request):

fake_agent = types.ModuleType("agent")
fake_lifecycle = types.ModuleType("agent.subagent_lifecycle")
fake_lifecycle.SubagentLaunchRequest = Request
setattr(fake_lifecycle, "SubagentLaunchRequest", Request)
monkeypatch.setitem(sys.modules, "agent", fake_agent)
monkeypatch.setitem(sys.modules, "agent.subagent_lifecycle", fake_lifecycle)

Expand Down Expand Up @@ -149,7 +150,7 @@ def test_active_graph_discovery_is_silent_when_workspace_has_no_run(tmp_path) ->

def test_register_activates_the_packaged_skill_and_honours_executable_setting(monkeypatch) -> None:
module = _load_plugin()
calls = {"commands": {}, "hooks": {}, "skills": []}
calls: dict[str, Any] = {"commands": {}, "hooks": {}, "skills": []}

class Context:
subagent_lifecycle = object()
Expand Down
12 changes: 6 additions & 6 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading