0.6.1 — pip install now comes with the loop catalog
Changelog
All notable changes to bounded-loops are documented here. This project follows
Semantic Versioning.
[Unreleased]
[0.6.1] — 2026-08-15
Fixed
-
pip install bounded-loopsnow comes with the loops it advertises. The catalog lived
only in the git repository, so a pip-only user ranbl loops list, saw "No loops found",
and was told to "run from a bounded-loops source checkout" — by a package whose README
opens by advertising 68 of them. The wheel now carries the catalog, andbl loops install <name>copies one into your project.Bundled rather than downloaded on demand, deliberately. A catalog that needed github.com
would be unavailable in the air-gapped, corporate-proxied and egress-restricted
environments this engine is aimed at, and the whole posture is that it runs offline with no
credential. 2.5 MB in the wheel is the cheaper honesty.Installing is a separate step from bundling because
bl runwrites its ledger BESIDE the
loop.site-packagesis not writable in a managed environment and is not a sensible place
to accumulate one user's run receipts.bl loops installputs the loop in the project
workspace, which is where a run's evidence belongs.--overwriterefuses any target that is
not itself a loop package, and the loop name is validated as one path segment before it can
reach a delete. -
README images rendered broken on PyPI.
readme = "README.md"makes that file the PyPI
project page, and PyPI does not resolve relative image paths the way GitHub does. The
architecture diagram had been relative since it was added, so it was broken on the page most
people reach frompip installfor every prior release — while looking correct in the editor
and on GitHub the whole time. Two tests now cover it: one for relative paths, one for
absolute URLs pointing at files that were never committed. -
Switching runs in the monitor left a node from the other run's graph selected. The
Configure panel stayed headed with a node the displayed graph did not contain. The saved-graph
path already cleared this, with a comment explaining why; the run path never did.
Changed
hatchlingis pinned. Unpinned it emittedMetadata-Version: 2.5, which twine rejects and
whose PyPI acceptance could not be confirmed.
[0.6.0] — 2026-08-15
Two independent auditors went through this release end to end, five focused passes each. Eight
HIGH findings survived verification; all eight are fixed below, along with everything they found
at MEDIUM and LOW. Every fix carries a test that was checked by re-introducing the bug.
Fixed — the MCP server
-
bl_run(confirm=true)could never execute. The preview→confirm handshake keyed its state
on the MCP session object, and MCP 2.0 builds a newServerSessionfor every request — so the
preview landed under one key and the confirm looked under another. Every confirm came back
"no matching preview", from every host, on both protocol eras. The tool's core function was
unreachable on the transport it ships over.The handshake is now a signed token:
confirm=falsereturns aconfirm_token, and
confirm=truerequires it. It is an HMAC over the run's full executable identity — gate
command, runner, agent_cmd, cassette, iteration cap, run_id, resume, and a content hash of the
loop's files — signed with a per-process secret and valid for 15 minutes. It works identically
on stdio, HTTP, stateless or pooled, and it closes the old fallback path in which one client
could confirm another client's preview.This changes the tool contract. A caller that passes
confirm=truewithout a token is
refused, and told what to do. Nothing that worked before stops working, because nothing
worked before.
Fixed — surfaces that claimed more than the receipts support
bl graph statusandbl graph metricsrefused runs they had just watched succeed, with
"package digest is not admitted". Four reload sites passed no admitted loop packages, and that
parameter defaults to the empty set, so forgetting it produced a confident wrong answer rather
than an error. An AST check now fails the build if any caller omits it.- STATE.md ended a run with "all nodes succeeded" while the node table printed directly above
it showed a SKIPPED branch. It now says how many branches were not taken, and names them. - The Arena drew "Gate passed — an independent check confirmed the result" on any SUCCEEDED
node. An approval node succeeds because a human held it, with no gate verdict in the log at
all. The badge now reads the verdict it was already being handed. - The confirm screen listed only
irreversibleandfinancialas effects that cannot be
undone, so a graph whose publish node declaresexternal_write— every shipped publish graph
— showed an empty list under a sentence about work that cannot be undone. Both this and the
configuration interview now take the set from the domain. - An approval preview said only "approved approval node 'gate'". Approving a gate releases
everything downstream of it, which is what the recorded grant has always contained. The preview
now names those effects and flags the irreversible ones, and the monitor renders them. - A loop declaring an isolation tier this host cannot deliver was started anyway, then failed
at the node — whilebl_capabilitiespromised refusal before the run starts. The pre-run gate
had exempted loop nodes using the connector-transport predicate; a loop needs no transport
while very much running in a sandbox. - The Seatbelt probe checked whether
sandbox-execis executable, which is vacuously true
inside a nested sandbox where applying a profile fails. It now applies one and reads the exit
status.
Fixed — durability and safety
- A run killed mid-flight left receipts nothing could open.
run-meta.json,plan.jsonand
the manifest were written after the work finished, so a hard kill — including Ctrl-C on
bl monitor, whose execute route runs on a daemon thread — produced a hash-valid log that
every surface refused. Those files describe the plan, not the outcome, so they are now written
before the first node runs. graph.savewrote through a symlink. It resolved the path and then asked whether it was a
symlink, a question that can only answer False once the link has been followed. An alias inside
the workspace silently overwrote the file it pointed at while reporting the alias was saved.- The string
"false"counted as confirmation and started runs. Confirmation now requires
the boolean. - Two confirms in the same second could mint the same run directory, and
exist_ok=True
swallowed it: both callers were told the run started, and both then watched the first run's
receipts. - Symlinked directories were advertised as runs, and the refusal to open one escaped as a
closed socket plus a traceback on the operator's terminal. - Monitor pages now carry a Content-Security-Policy and
X-Frame-Options: DENY.
Fixed — the host pack
- Every shipped command named MCP tools that were never registered —
bl_graph_statusfor
graph_status, and wrong parameter names throughout. The contract test only readSKILL.md,
so the primary product path was broken and nothing said so. All corrected, and the test now
checks every command and agent against the live registry. bl graph digestdid not exist although the composer agent instructed models to run it to
obtain the one field they are forbidden to invent. It exists, andbl_catalognow carries the
digest too.
Changed
- MCP 2.0.
mcp>=2,<3, protocol revision2026-07-28, with 2025-era clients still served
from the same process. The 1.x line went maintenance-only, andmcp.server.fastmcp— which the
old pin depended on — no longer exists. - Jarvis is now the bounded-loops monitor.
bl monitoris unchanged; the package moved.
Added
bl graph digest <loop-dir>— the content digest a loop node must reference./bl-configure— walks a saved graph's interview and applies the answers.scripts/sync_host_pack.py— mirrors the canonical host pack to all three hosts. The contract
test told developers to run this for some time before it existed.- Approval receipts now record who decided, not only which tenant, with the source of that
name and an explicit note that it is not authenticated on a local run.
Added — the project home and the monitor (built for this release)
-
.bounded-loops/is now a project home.bl initcreates it;bl whereprints the
resolved workspace and, more usefully, why that one was chosen. It holdsconfig.toml,
graphs/,loops/,runs/,tickets/, and anindex.jsoncache. One resolver answers
"where does this project keep its runs" for the CLI, MCP, and the UI — the same question
answered twice is the defect class the 0.5 audits kept finding.Discovery walks up from the current directory for an existing
.bounded-loops/, bounded by
the git repository root so a checkout can never silently borrow a workspace sitting above it,
then falls back to the repository root, then to the current directory. A symlinked workspace
root is refused: it would silently relocate every receipt in the project. -
A capability contract, over MCP and on the command line.
bl_capabilities(MCP) and
bl capabilities(CLI) serve the same document from one function: node kinds with their
kind-specific fields, gate kinds and what each mechanically checks plus whether it is
available on this host, isolation tiers with the controls actually enforced here, which failure
policies are honoured versus merely declared, the repair contract and its bound,
the effect vocabulary, every budget field and where it is enforced, the terminal statuses and
which of them are not success, and all 37 refusals with the fix for each.This is the document a host model reads instead of guessing. Two rules govern it: declared is
not honoured, and here is not everywhere. -
bl_catalogandbl_search_loops(MCP). The loop catalog with role/gate/keyless filters,
and a ranked search against a described task. The ranking is lexical and the response says
so — it matches words, it does not understand meaning. -
A refusal reference. Every one of the 37 validator refusals now has a plain-language
summary and an actionable fix, checked againstvalidate_graph.py's own source in both
directions so the table cannot document a refusal that cannot happen, or miss one that can.
Readable asbl capabilities --refusals.
Fixed
-
The authoring schema advertised two failure policies the compiler refuses.
on_failure's
enum offerscontinueandawait_human, but the validator rejects both with
on_failure_unimplemented— correctly, since the runtime routes every failure tofail_graph
and accepting them would silently discard the declared policy. Anything generating an authoring
UI from the schema would have offered them as choices. The schema now carries
x-unimplemented, and a drift test pins it to the validator's own set.isolationlikewise
carriesx-never-availableforcustomer_managed_worker, which no host can enforce. -
bl graph run --executeno longer requires--out. It defaults to
.bounded-loops/runs/<stamp>-<rand>/, creating the workspace if needed, and announces the
resolved path on stderr. An explicit--outbehaves exactly as it did in 0.4.0, and no
existing run directory moves —bl run --run-idstill writes package-local, so every
0.4.0/0.5.x run stays resumable under--resumeandbl runs.
[0.5.1] — 2026-08-14
Fixed
-
import bounded_loopsfailed on Python 3.11. A dataclass field defaulted to
MappingProxyType({}), which reads as safe because it is immutable — but 3.11's dataclasses
reject any default whose class is unhashable, andmappingproxyonly became hashable in 3.12.
The class body therefore raised at import time on the oldest Python this package supports.
0.5.0 is unusable on 3.11 and should be skipped. -
Reference-graph digests did not match a fresh clone.
bl run <package>writes
.ledger.jsonlinto the package directory, and that file was not excluded from the package
content digest — so any machine that had followed the README quickstart digested
bug-fix-red-greendifferently from a clean checkout, and the committed graph pins were generated
on such a machine..ledger.jsonland.trust.jsonare now excluded, and a test asserts that no
digested entry in a shipped package is untracked. -
A live isolation-provider test failed instead of skipping on hosts without the capability,
which made a red CI build the normal state from 0.4.0 onward.
[0.5.0] — 2026-08-14
Changed — BREAKING for embedders
-
NodeWorkerPortandIndependentGatePortgained required arguments.executenow takes
repair_round;evaluatenow takesattemptandrepair_round. Both are keyword-only and have
no default, so a custom worker or gate raisesTypeErroruntil it accepts them. Add the
parameters — one line per implementation — and ignore the values if you do not need them. See
docs/EMBEDDING.md.Required rather than defaulted, because a default here is a silent wrong answer: attempts RESET at
a repair boundary, so(node, attempt=1)happens once per round andattemptalone is not an
identity. Two things this unblocks:- A
kind: loopnode may now declareon_failure: repair. It was refused at validation
before — the round could not reach a loop worker, so the receipt would have named round 0 for
every round, and a false round inside a hash-chained log is worse than a refusal. - The loop gate verifies the receipt's attempt. A receipt claiming
attempt=99used to pass,
becauseevaluatehad no attempt to compare against.
- A
Changed
-
The Wilson comparison figure is now the one the test suite reproduces. Every mention of
Wilson's measured coverage said 31–41%, attributed to "real retry data". Both halves were
wrong: it came from a reviewer's separate simulation whose parameters were never recorded, and the
shipped harness cannot reproduce it at any correlation strength (Wilson measures 0.75–0.80 across
ρ ∈ [1.0, 3.5]). It now reads 77.5%, from the seeded simulation in
tests/graph/application/test_confidence_sequence.py. -
A
plan_idmismatch on resume now says which explanation applies. The message reported two
digests, which is also what a tampered run directory looks like, so an engine upgrade sent users
hunting for an edit that never happened. Run directories recordcompiler_version, and the error
distinguishes a compiler change from a modified directory. -
Gate false-accept rate intervals now report measured coverage instead of an assumed one.
The Wilson score interval required independent Bernoulli trials, which retried
attempts violate. It is replaced by an empirical-Bernstein interval with a
predictable plug-in. Thebl graph metricslabel changes from
nominal-95% iid (UNCALIBRATED)toemp-Bernstein 95% (COVERAGE-MEASURED).Both measured numbers are coverage of the same thing, and it is not α. Under
the simulated regime — per-run latent ratep_run ~ logit-normal(mean α, ρ=1.8),
evaluated at every sample size under optional stopping — Wilson coveredp_run
77.5% of the time and the empirical-Bernstein interval covered it 96.9%. The
quantitybl graph metricsreports as the false-accept rate is the marginal
rateE[p_run]; coverage of the marginal rate is a separate estimand that
these figures do not measure. So "96.9% vs 77.5%" is a statement about
p_run, and quoting it as "α coverage" or as a 19-point improvement on α is a
misreading the numbers cannot support. Measured on the same simulation: coverage of
the marginal rate is 0.5850. For the quantitybl graph metricsprints, this is
a 58.5% interval, not a 95% one.This is NOT an anytime-valid confidence sequence. The radius is the
fixed-time empirical-Bernstein form and carries no stitching term, so
simultaneous validity over all sample sizes does not follow from it.
Fixed
-
A 0.4.0 graph with a
publishnode can resume again. The compiler began carrying
publication_policyin the plan so the publish worker could read it, which changedplan_idfor
every graph that had a publish node — and those are the graphs with an irreversible effect. The
value is authored in the manifest and therefore already covered bysource_graph_digest, so it is
excluded from the plan's canonical form. Verified against v0.4.0's own compiler: the same graph
compiles to the sameplan_idit did in 0.4.0. -
An empty directory inside a loop package now moves its content digest.
shutil.copytree
reproduces empty directories into the workspace, so a gate whoserun:branched on
test -d seed/hidden_branchcould change verdict while the pinned digest stayed fixed — a mutable
region under a content address. -
Conditional edges (
when) now actually apply. An edge'swhenwas accepted,
validated and stored — then ignored by the scheduler, so a graph with a condition on
an edge ran that edge unconditionally and nothing warned you. Conditions are now
enforced.Breaking:
whenaccepts only the source node's outcome —succeeded,failed,
skipped, orterminal(ornullfor the default,succeeded). Anything else is now
refused when the graph is validated instead of being silently dropped. If a graph of
yours stops compiling, that condition was never being applied — the error tells you
which edge and what the accepted values are.Data-dependent conditions such as
result.status == 'failed'are not supported. -
A condition that could never fire is refused too.
when: failed,skipped, and
terminalare rejected underfail_mode: fail_closed, because that mode stops the run at
the first node failure — so such an edge could never apply. The error names the mode to use
instead. Same rule that already applies toon_failure: continue|repair|await_human. -
fail_mode: continue_declarednow does something. It was accepted by the schema and
ignored by the runtime, so every run was fail-closed whatever the graph declared.
Added
-
kind: loopnodes run. A graph node can now be a whole bounded loop, executed as a child
workflow. 0.4.0 accepted these nodes at compile and lint time and then refused them at preflight;
they now execute.The package is pinned by content digest, not by name:
loop_package: sha256:<64 hex>is
computed from the package's own bytes, re-verified inside the node's subprocess before the loop
runs, and resolution is by digest only — so pulling new commits cannot silently change what a
persistedplan_idexecutes. Isolation is per node and never defaulted; the node's sandbox wraps
the loop's own runner and gate, so the loop inherits the graph's execution envelope.The outer gate verifies the loop's receipt — that the promoted outcome parses, names the
package the plan admitted, names this node, attempt and repair round, and reachedDONE. It does
not re-run the loop's own gate: the loop already contains an independent gate, so re-running it
would make one object both producer and judge. -
kind: joinandkind: publishnodes have workers and gates. A join records the live state
and guard of every predecessor it observed, and its gate replays the scheduler's own admission
predicate rather than trusting the receipt. A publish node is the one place a graph may do
something it cannot undo; its effect is recorded in a publication ledger keyed on
run_id / plan_id / node_id—attemptandrepair_roundare excluded on purpose, because
including either would fire the effect again per attempt or per round — with a payload digest
over the upstream artifacts.
Seedocs/graph-capabilities.mdsection 14, including what the local ledger does not
guarantee. -
Six reference graphs, in
graphs/. Finance payment assurance, engineering release gate, retail
listing release, marketing campaign release, customer data request, solo-builder ship. Each is
fan-out to parallel loop checks → join → human approval → one irreversible publish, uses only
keyless shipped loops, and costs nothing to run. All six execute end to end in CI, from a checkout. -
Wire data between a loop and a graph. A loop package may declare
inputs:andoutputs:port
blocks in itsloop.yaml; the engine materialises each declared input before the loop starts and
promotes each declared output as a graph artifact afterwards. A loop that declares neither runs in
fixture mode, exactly as before. See docs/EMBEDDING.md. -
A documented embedding surface.
bounded_loops.__all__is the stable API —load_loop,
wire,Bounds,Outcome,Status,LoopManifest, plusNodeWorkerPort,WorkerResult,
IndependentGatePortandGateVerdictfor plugging in your own worker or gate. Everything else is
internal and may change in any release. docs/EMBEDDING.md is the walkthrough. -
--loop-roots <dir>onbl graph run,lintandplan, repeatable, to add your own catalog
of loop packages. Resolution stays by digest, so an extra root can only make a package findable —
never redirect an admitted digest to different code. -
Real spend ceilings. A run can declare token and cost caps, from a file or per-dimension
flags, and a node that declares a spend budget refuses to run on a worker that reports no usage
rather than metering it as free. -
Route around a failed node. With
fail_mode: continue_declared,when: failedruns a
downstream node only when its upstream failed — a cleanup, notification, or fallback
branch.when: terminalruns a branch whatever the outcome.Continuation is deliberately narrow: the run keeps going only past the node's own
bounded-loop outcome (gate rejection, worker fault, unverified artifact, spent budget,
exhausted re-drives). A broken gate, a denied policy or isolation refusal, a missing
worker, a rejected or unresolved approval, an exhausted spend cap, a broken worker
contract, or an unmeasurable budget still stop the run — continuing past those would keep
spending, trust an unreliable gate, or route around a control. -
Untaken branches are recorded, not stranded. A node whose every incoming condition
excluded it is marked SKIPPED, with the reason on its receipt, and a run whose only
unfinished work was an untaken branch completes successfully instead of reporting a
failure. A node that failed still fails the run. -
Repair a node upstream (
on_failure: repair). When a node exhausts its retry budget it
can send the run back to an ancestor, which then re-runs along with everything downstream of
it. Write it ason_failure: {mode: repair, target: <node_id>}and set a
policies.repair_budget.The budget is a global cap on repair rounds for the whole run, not per node, and that is
what makes the run provably finish: total node executions are bounded by
(1 + repair_budget) × Σ(max_attempts). Per-node retry budgets alone do not bound a
graph that can repair.Every round is recorded —
run.repair.roundfor the boundary,node.repairedfor each node
reset, and the round number on every receipt in it — so a run that repaired is still fully
auditable, and a replay refuses a boundary it cannot prove legal.Refused up front: a target that is not a strict ancestor, a missing target, a budget of 0, or
a halting fail mode where a repair could never begin. -
A run's fail mode is durable. Recorded in
run-meta.json, soresumeandapprove
drive the graph the way the original run did.
[0.4.0] — 2026-08-12
The headline of this line is the bounded-loops graph engine (bl graph): a
DAG of independently-gated bounded loops built on the same keyless loop engine.
Added
- Graph engine (
bl graph) — compile and run a DAG of bounded loops where an
independent gate decides each node and a producer never grades its own work.
Subcommands:init,lint,plan,run(with--execute),approve,
console,arena,status,artifacts,demo, andstudio. - Guided setup (
bl graph init) — an interactive installer that writes your
connector mode and egress posture to~/.bounded-loops/egress.json, so nothing
has to be configured by hand. Every prompt also has a flag for scripted use.
Defaults to running your own logged-in CLI with the network open. Credentials
are never written to disk. - Approve a paused run from the CLI (
bl graph approve) — a run that reaches a
human-approval checkpoint now pauses durably and exits 3 (distinct from
success and failure) instead of being refused. Record the decision with
bl graph approve --run <dir> --node <id> --decision approved|rejectedand the
run continues past the gate. - Approve from a browser (
bl graph console) — a local click-to-approve page
for a paused run, bound to127.0.0.1and gated by a one-time token printed on
start. It records decisions through the same durable path as the CLI. Intended
for a single operator on their own machine; a shared deployment needs real
authentication in front of it. - Choose how much network your connector gets — three egress postures,
selectable per deployment viabl graph init,BOUNDED_LOOPS_EGRESS_POSTURE, or
the config file.open(the default) leaves your subscription CLI exactly as
it is today, with the network open.allowlistis an opt-in lockdown that runs
it inside a real macOS Seatbelt cage and permits outbound traffic only to hosts
you list — it refuses to start rather than quietly running unconfined on a
machine that cannot enforce it.brokerroutes API-key traffic through the
no-secret broker. - Two credential-safe connector modes — Local-CLI (runs your already-logged-in
claude/codex/grok/muse/agysubscription; credentials are never read or
logged) and BYOK/HTTPS (a frontier-model API through a no-secret egress broker
with single-use, time-bound leases and SSRF/DNS-rebind protection). - Receipt-derived read-only Arena — an append-only, hash-chained event log plus
content-addressed artifacts, rendered as a non-executing HTML projection
(bl graph arena). Local runs are markedLOCAL/UNVERIFIED. - Cross-model audit coverage —
--audit-planruns independent auditor nodes;
the Arena shows a release verdict that blocks on producer-only cells or
unresolved high-severity findings. - Durable human approvals — a decision survives a restart: it is persisted and
rehydrated on resume, whether it was recorded from the CLI, the local console, or
programmatically. - MCP graph surface — the
bl graphtools are exposed over MCP with
session-bound subject identity.
Notes
bl graphis a beta. See the honest capability matrix in the README and
docs/RELEASE-READINESS.mdfor exactly what is
enforced, and where.- Upgrading from 0.3.x needs no action: the default egress posture leaves existing
behavior unchanged, and there is no config file to create unless you want the
lockdown tier. - The base loop engine, the nine bounds, the 68-loop catalog, and all
bl run
behavior are unchanged.
[0.3.1] — 2026-07-13
Fixed
- Added the standard
bl --versionprobe so Python and npm clean-install
verification can report the exact engine release.
[0.3.0] — 2026-07-13
Minor release for the verified install, convergence, and agent-integration
experience.
Added
- A three-lap
convergence-demoplus a max-iteration trip variant, both
keyless and covered by ledger assertions. - Native Codex and Claude Code plugin manifests, a repository Codex
marketplace, tested installation instructions, and an MCP stdio smoke test. - A real Codex-backed citation run receipt with a machine-readable ledger and
redacted transcript excerpt. bl doctor,bl runs <loop> --show <run-id>, andbl lint --contrib.- Clean-room CI across macOS and Ubuntu on Python 3.11–3.13, built from the
wheel and exercising the README, scaffolding, and MCP server. - Reproducible terminal GIF and 1280×640 GitHub social-preview assets.
Changed
pytestis now a core dependency because shipped pytest gates invoke it.- Codex runner failures now become auditable engine errors, live token usage is
recorded, and non-Git scratch workspaces use Codex's explicit skip-check flag. - The citation example now takes two deterministic laps; framework examples
fail with exact dependency-install guidance. - README and release metadata now use the canonical count: 68 loop folders, 64
keyless out of the box. The README puts the verified quick start first and
uses the real CI badge. - The npm launcher pins the Python engine to the same version as the npm
package, preventing silent cross-ecosystem version drift.
Fixed
- Clean wheel installs can execute shipped pytest gates.
- Runner overrides are shown accurately in the pre-run trust preview.
- Stale CLI output examples and orphaned private-course section references were
removed.
[0.2.1] — 2026-07-08
Patch release for the public install experience.
Changed
- Clarified PyPI and npm install docs: installed users start with
bl new --list
and scaffold a local loop; source checkouts usebl listfor the full catalog. - Updated public loop-count wording to distinguish 67 loop folders from the 63
keyless, zero-setup loops.
Fixed
bl listoutside a source checkout now gives actionable scaffold/clone
guidance instead of a dead-endNo loops found.message.- Clean dev type-checking now passes for the full source and test tree.
[0.2.0] — 2026-07-07
Production-hardening release. The engine moves from a runnable reference library
to a harness you can rely on in CI, while keeping the keyless-first defaults.
Added
- Composite gates (
gate.kind: composite,mode: all) — a loop can require
several independent checks to pass together, with a per-child verdict recorded
in the ledger. - Typed external gates:
gitleaks,semgrep,trivy,promptfoo,
great_expectations, andaxe— adapters that parse structured tool output,
not just exit codes. Status.ERROR— runner/gate execution failures are now a first-class,
auditable terminal outcome with a ledger entry, instead of an unstructured exit.dockerandworktreerunners for stronger, opt-in sandbox isolation.- Resumable runs —
bl run <loop> --run-id <id>persists a workspace and
per-run ledger (indexed in SQLite);--resumecontinues it;bl runs <loop>
lists prior runs. - New CLI commands:
bl show(inspect runner/gate/bounds/risk/deps),
bl gates(gate kinds + local availability),bl audit-loops(catalog
copy-paste readiness). - Expanded MCP surface:
bl_show/bl_gates/bl_audit_loops/bl_runs
tools, catalog/manifest/prompt resources, andrun_loop/write_loop/
audit_loopprompts. - Editor adoption: VS Code / GitHub Copilot files (
.vscode/mcp.json,
.github/instructions and prompts) and anAGENTS.md. - CI matrix on Python 3.11–3.13, with optional gate/runner end-to-end jobs.
bounds.production.yamlfor L2/L3 loops, so keyless demos stay approval-free
while copy-paste production use defaults to requiring human approval.
Changed
- Loop catalog now spans all seven agentic patterns (
prompt-chaining,routing,
parallelization,orchestrator-workers,evaluator-optimizer,
augmented-llm,agents), reclassified from a single pattern. - Scratch workspaces are cleaned up after a run by default;
--keep-workspace
retains them for debugging. - Runner timeouts derive from the remaining wall-clock budget.
Fixed
- Loop integration tests no longer assume a
.venv/bin/blpath; they invoke the
package entrypoint directly. - Optional OpenTelemetry tests skip correctly when only
opentelemetry-apiis
installed. - Removed machine-specific absolute paths from example docs; added a lint that
fails on them.
[0.1.0] — 2026-07-06
Initial public release: the bounded-loops engine, the nine bounds + kill switch,
67 runnable loop folders across a dozen industries, MCP server, and agent plugins.