Bounded Loops: Pre-Run Spend Bounds, Proved Termination, and Verified Completion for Agent Harnesses
Organizations: Qualixar / Independent Researcher, India · Independent Researcher, India
Abstract
In mainstream agent frameworks, a step ends when the agent's own output says it has finished. Durable-execution platforms bound retries and time, but their checker conventionally lives in the same codebase as the work: a discipline the deployment is trusted to keep, not a property the harness enforces. We state what an agent harness must guarantee, prove it, and build the instrument that measures whether a harness delivers it. A bounded loop is a worker, an independent gate the worker cannot write to, and a declared budget; a bounded-loop graph composes them with a repair relation that lets a downstream failure re-run a finished upstream node. Three guarantees follow. It finishes: termination holds under repair, with the worst-case attempt total in closed form, if the repair budget is global not per node. It does not drift: no node reaches DONE without a gate verdict in an append-only hash-chained ledger, proved from control flow, since repair leaves no topological order to induct along. It does not overspend: the ceiling is enforced inside an attempt, not between attempts. Gates are measured against a two-tier held-out mutant corpus. We characterise two classes that let a sound-looking check pass anything: vacuity, satisfied by the absence of the thing checked, and self-attestation, where the subject supplies the value the check is applied to. On a 69-loop catalogue the instrument found 47 vacuous gates in shipped, reviewed code. Against the repaired gates it reports no false accepts over 209 destroying mutants (, Wilson 95%); that figure is saturation, not quality: freezing the gates and applying a fresh operator family recovers a 23.3% false-accept rate where the exhausted corpus reported none. A rate belongs to a specific gate; the apparatus, not our number, is the contribution. Engine, catalogue and corpus are Apache-2.0.
Figures & tables
| Workload | Predicted | Observed | Terminal | |
|---|---|---|---|---|
| 1 | 1 | 1 | done | 0.1 |
| 2 | 2 | 2 | done | 0.2 |
| 3 | 3 | 3 | done | 0.3 |
| 4 | 4 | 4 | done | 0.4 |
| 6 | 6 | 6 | done | 0.6 |
| 8 | 8 | 8 | done | 0.8 |
| Productive attempts | Observed | Attempts saved | |
|---|---|---|---|
| 0 | 3 | 3 | 7 |
| 1 | 4 | 4 | 6 |
| 2 | 5 | 5 | 5 |
| 4 | 7 | 7 | 3 |
| Reads as a judgement call | What the gate actually computes |
|---|---|
| objective is measurable | a numeric target , a non-empty unit , and a deadline matching ˆ\d{4}-\d{2}-\d{2}$ |
| prose is readable | mean words per sentence |
| runbook is complete | seven required heading strings are present, and each has content beneath it |
| privacy policy is complete | six required disclosure phrases occur in the normalised body |
| Tier 1 | Tier 2 | |
| mutants | 233 | 38 |
| destroying (label incorrect ) | 171 | 38 |
| preserving (label correct ) | 62 | 0 |
| judged | 233 / 233 | 38 / 38 |
| gate errors ( incapacity ) | 0 | 0 |
| false accepts over destroying | 0 / 171 | 0 / 38 |
| Direction | Defect | Disclosed? |
| false accept | data-processing terms: substring match inside a denial | yes |
| false accept | runbook: required headings present, no content under any | yes |
| false accept | alt-text: attribute absent, image passes | yes |
| false accept | citations: pattern derived from the reference set ( Example 8.2 ) | no |
| false reject | conventional commits: ! breaking-change marker refused | no |
| false reject | dependency pinning: extras and spacing legal under the spec refused | no |
| loop | how the gate was evaded |
|---|---|
| test-presence-per-module | a test with no import and no assertion; a test importing a different module; a test asserting on a standalone calculation; renaming the source so the import orphans |
| nda-required-clauses , gdpr-dpa-terms | the required heading kept, the obligation underneath it negated |
| okr-measurable , dependency-pinning , conventional-commits | the offending item deleted rather than repaired |
| dependency-pinning | pinned to a version that was never released; pinned below the file’s own stated floor |
| conventional-commits | subject substituted wholesale; a docs change typed build ; an already-conforming line rewritten |
| secret-scan-keyless | credential split across a concatenation; password renamed to passwd ; the config deleted rather than moved to the environment |
| estimand | anytime-valid | fixed-time |
|---|---|---|
| — mean propensity of the attempts in the log | 1.0000 | 0.9590 |
| population marginal, 1 node pooled | 0.8333 | 0.6067 |
| population marginal, 6 nodes pooled | 0.9717 | 0.8450 |
| population marginal, 30 nodes pooled | 0.9983 | 0.9783 |
| population marginal, heterogeneous ( ) | 0.8267 | 0.6000 |
| loop | gate kind | provider | terminal | attempts to done | wall (s) |
|---|---|---|---|---|---|
| citation-existence-check | command | A | done | 1 | 26.6 |
| B | done | 1 | 27.5 | ||
| C | done | 1 | 54.9 | ||
| D | done | 1 | 86.6 | ||
| bug-fix-red-green | pytest | A | done | 1 | 24.3 |
| B | done | 1 | 36.0 |
| Tier 1 (saturated) | E7 (post-freeze) | |
| loops covered | 57 | 54 |
| destroying mutants | 171 | 60 |
| preserving mutants | 62 | 35 |
| gate errors ( incapacity ) | 0 | 0 |
| false accepts | 0 / 171 | 14 / 60 = 23.3% |
| Wilson 95% |
Appendix figures & tables5 assets
Supplementary material from the paper’s appendix.
Appendix
| Pattern | n | Verdict is | Choose it when |
|---|---|---|---|
| Schema | 10 | a document validates against a declared schema | the requirement is about shape , and the schema is the contract a consumer already relies on |
| Predicate | 44 | a purpose-built program exits zero | the requirement is about content and reduces to a computation over one artifact |
| Executable spec | 9 | a test suite passes | the requirement is behavioural, and the artifact is code that can be exercised |
| Conjunction | 3 | every child gate passes | two independent obligations must both hold, or a model judge is being admitted ( Theorem 10.3 ) |
| External tool | 2 | a third-party analyser reports clean | a mature checker already exists and re-implementing it would be worse |
| Failure mode | The gate passes because | Evidence |
|---|---|---|
| Absence | the artifact it quantifies over is gone, so every universal claim holds | 47 instances; Example 8.1 |
| Hollowing | the artifact keeps its structure and loses its content, and the gate checked structure | 14 instances; Section 12.12 |
| Self-derived scope | the checker builds its search pattern from its own reference data, so anything outside it is never examined | Example 8.2 |
| Misfiled incapacity | a diagnosis the gate reached was returned as an inability to diagnose, so the loop stopped instead of retrying | 84 of 233 mutants; Section 4 |
| Scope blindness | the measurement resolved one gate kind and silently returned nothing for the others | 24 loops; Figure 11 |
| Prompt-only obligation | the requirement was stated to the worker and never to the gate | Example 8.1 |
| domain | gate kind | n | representative requirement |
|---|---|---|---|
| legal / contract | command | 9 | required clauses present and not negated in the body ; citations resolve against shipped reference data |
| finance | pytest, command | 8 | three-way invoice match; a ledger reconciles and no seeded transaction disappeared |
| clinical | jsonschema | 5 | a note carries every required field with the stated types |
| security | command | 7 | no credential-shaped literal, including across string concatenation and renamed keys |
| infrastructure | command, external | 8 | resources declare required tags; scanned policies pass |
| software eng. | pytest, command | 14 | a failing suite goes green; every module has a test that imports it and asserts on what it imported |
| reason | n | why, and how it is verified |
|---|---|---|
| unshipped SDK | 4 | the runner needs an agent framework the package does not vendor. Probed against the live environment in both directions — a loop claimed excludable that in fact runs fails the check, as does the converse |
| external tool | 3 | the gate invokes a security scanner not assumed present |
| negative requirement | 1 | the requirement is that an artifact must not acquire a property; a mutation that introduces the property is a conforming input, not a defect, so the operator set cannot construct a labelled defective case |
| Tier-1 inexpressible | 3 | the requirement is a relation between artifacts. A single-artifact operator has no notion of consistency across two files. Covered by Tier 2, which found four defects in one of them |
| result | command, and what it pins |
|---|---|
| E1, E2 — Table 4 | uv run pytest tests/evaluation The corpus is a test: a regression in any gate fails the build rather than moving a number in a paper. |
| Blindness — Theorem 10.1 | ... test_generator_is_blind.py AST assertion over every generator module. |
| Tier-2 prompt integrity | ... test_tier2_authoring_was_blind.py Recomputes each mutant’s authoring digest from the repository. |
| E3 — Table 7 | ... test_reported_interval_estimand.py Includes the three shrink/shift controls that must fail if the interval were vacuous. |
| E4 | ... tests/graph/test_gate_gaming.py Adversarial workers. |
| E6 | ... test_no_test_asserts_only_inside_a_loop.py Carries a deliberately vacuous case it must flag. |