The loopexec Contract
This page summarizes the canonical contract for readers. It mirrors the structure of SPEC.md, the normative source in the repository.
What loopexec is
Section titled “What loopexec is”loopexec is a deterministic runtime for loop engineering: it runs bounded, stateless, auditable work loops that progress through an external check, durable state, isolated execution, and explicit stop conditions.
A loop runs until an external check passes, a guard trips, a budget is spent, or a human is required - never because an agent reports it is done. Every halt emits a computed halt reason (never a flag) and a replayable receipt.
loopexec is not an agent, a model, or a task runner. It composes with those by governing how work is executed, recorded, and resumed.
Deeper context: loop engineering.
The two machines
Section titled “The two machines”loopexec hosts two control topologies. The mode is explicit and recorded in every receipt.
loop.mode | Stop condition | Gate |
|---|---|---|
check_fixpoint | external check returns its success_exit_code | the application oracle (test / build / lint / score) |
task_list | all plan tasks completed | state-hygiene validation plus a per-task acceptance oracle |
loop: mode: check_fixpoint # or: task_listInvariant T1. check_fixpoint is the degenerate one-task plan whose acceptance criterion is the external check. A task_list loop MUST attach a real per-task acceptance oracle; state-hygiene validation alone is NOT an acceptance oracle - it cannot decide application correctness. A task_list loop with no per-task oracle MUST refuse to mark tasks completed and MUST halt check_inadequate.
Invariant T2. The receipt MUST record, per iteration, which oracle was evaluated at which commit. A receipt that cannot answer “which oracle, at which commit, under which pinned model, having spent how much, with which guards intact” is not a halt record.
Deep dive: the two machines.
The oracle contract (O1-O6)
Section titled “The oracle contract (O1-O6)”The check is the loop’s stop oracle. It carries six obligations.
- O1 - External. The check MUST be an external process returning an exit code, independent of the agent. An agent’s self-assessment is not a check.
- O2 - Determinism as a maintained confidence bound.
probe-checkreports an achieved confidence bound on the flake rate, not a pass/fail of N runs. In-loop, each check invocation is a free Bernoulli sample; the runtime maintains a sequential lower bound on stability and haltscheck_not_deterministicwhen it drops below target. - O3 - Hermeticity. A check declared deterministic MUST be hermetic: frozen clock/seed/TZ/locale, OS-assigned (
:0) ports, ephemeral fixtures, pinned tool versions. A non-hermetic check MUST be rejected, not silently looped on. - O4 - Adequacy != determinism. A check that returns
0regardless of the diff is deterministic and useless. Every changed line MUST be exercised, and a mutation canary in changed lines MUST turn the check red. Failure =>check_inadequate. - O5 - Determinism != idempotency. A deterministic but side-effecting check (passes once, fails on rerun in the same workspace) is
check_has_side_effects, cured by reset - not by editing the check. - O6 - Single capture. The runtime runs the check once per iteration and derives both the verdict and the failing-test set from that one captured artifact
{exit_code, stdout, stderr}. It MUST NOT re-invoke the suite to scrape failure text.
Deep dive: determinism and the check.
Iteration lifecycle - check_fixpoint
Section titled “Iteration lifecycle - check_fixpoint”Each iteration MUST execute as one ordered transaction:
- Build context - a narrow, budgeted slice. The token budget is a true ceiling; a slice that cannot fit even after widening MUST emit
context_budget_unsatisfiable, never silently evict relevant files. - Execute the agent in the agent zone.
- Run the check once (O6); capture the artifact; parse the failing set
F_i. - Guards dominate success (G-before-S). Evaluate guards BEFORE any green branch can fire. A check that went green because a guard was violated MUST classify as the guard reason, NOT
success_condition_met. - Acceptance (anti-regression ratchet). Accept the diff only if the passing-test set is a superset of the last accepted set. On violation, revert to the last accepted commit and re-prompt.
- Progress / feasibility. Halt on no strict decrease in
|F_i|over K iterations, on a previously-passing test re-enteringF(oscillation), or on a contradictory pair across cycles. Distinguish “still improving at cutoff” from “stalled / infeasible.” - Checkpoint the receipt. Append a typed JSONL event and update durable state.
- Continue or halt. Halt only on a computed condition; otherwise iterate. The full check phase MUST pass before declaring
success_condition_met.
Deep dive: the iteration lifecycle.
Halt reasons and exit codes
Section titled “Halt reasons and exit codes”The halt_reason string is the stable integration contract; the exit code is a coarse class CI can branch on without parsing JSON. Every reason MUST be computed from observed state - --halt-reason is a hidden test fixture only.
| exit | class | example halt_reason strings |
|---|---|---|
| 10 | converged | success_condition_met |
| 12 | iteration-cap | max_iterations_reached |
| 13 | integrity | blocked_path_modified, metric_integrity_violation |
| 14 | oracle-untrusted | check_flaky, check_not_hermetic |
| 15 | check-inadequate | check_inadequate |
| 17 | no-convergence | no_progress_detected, oscillation_detected, infeasible_suspected |
| 18 | budget | budget_exceeded, cost_anomaly |
| 19 | liveness/drift | heartbeat_stale, model_drift_detected, context_budget_unsatisfiable |
Exit codes 0/10/11/12/20/30/40/50 ship today, and classes 13, 14, 15, 16, 17, 18, and 19 now emit with their computed halt reasons (class 18, budget, via inspect-cost; class 15, check-inadequate, via the doctor --mutate-cmd adequacy canary); only class 11’s task-list reasons stay reserved. See capability status.
LoopExec owns everything a number can decide. Judgment (a cross-lab judge) owns only what a number cannot express, layered on top of the deterministic gate. Judgment is never the stop oracle: it can veto a green loop or escalate, but it cannot promote a red one to green.
Deep dive: halt reasons for the full canonical map.
Guards and the metric-integrity gate
Section titled “Guards and the metric-integrity gate”Guards are evaluated against an immutable baseline t0 captured at loop start, before any green declaration, covering working-tree, staged, committed, and untracked changes.
blocked_paths- globs the loop MUST NOT modify (default includestest/,infra/,.env,.git/). Violation =>blocked_path_modified.- The metric-integrity gate supersedes
git diff --quiet -- test/. The collected-test set MUST NOT lose a member; test-count and AST-level assertion-count MUST NOT decrease; a protected-manifest hash over the full test-determining surface MUST be stable; coverage MUST NOT drop below thet0floor. Tests SHOULD run from a read-only mount frozen att0. Violation =>metric_integrity_violation. - Judge (advisory). A cross-model, cross-lab judge MAY veto a green iteration. It receives a behavioral oracle, treats the diff as untrusted data, and never replaces the deterministic gate.
Deep dive: guards and metric integrity.
Two-zone isolation and secrets
Section titled “Two-zone isolation and secrets”A single --network none container cannot both run a cloud agent and isolate untrusted code. The contract requires two-zone isolation, the two zones sharing only a work volume that is a detached clone - a git worktree is ergonomic, NOT a security boundary, because it shares the object DB, refs, remotes, hooks, and credentials.
- Agent zone - the reasoning surface. Network: default-deny plus an egress allowlist to the model endpoint only, via an auditing proxy. Credentials: exactly one per-run minted, short-TTL, spend-capped key (never a
~/.claudebind-mount). Filesystem: RW/work, RO elsewhere, no$HOME. - Exec zone - untrusted code execution. Network:
none. Hermetic (O3). Ephemeral: reset each run. doctorpreconditions (fail-closed): rejectnetwork: nonefor a cloud or local-host model (isolation_unsatisfiable); reject any~/.claudemount (credential_scope_invalid); rejectexec_zone.network != none; require an egress allowlist.
Deep dive: two-zone isolation.
Receipts, replay, and state
Section titled “Receipts, replay, and state”Receipt (per run). The receipt MUST pin everything that determines output, or fail closed (workspace_invalid): the model-identity tuple {provider, model_id, version, quantization, runtime, endpoint}, recorded sampling params, a context manifest [{path, content_sha256}], parsed cost actuals, the per-iteration check command + exit code + commit, the computed halt reason, and the achieved determinism confidence bound. Receipts SHOULD be signed.
Replay and re-execute MUST NOT be conflated:
replay= VERIFY. Re-run the deterministic check against the recorded end-state and confirm the fingerprint matches the receipt. Agent-free, budget-free, deterministic.reexecute= RE-RUN. Run the live agent loop again. Non-deterministic; reports a statistical match, never byte identity. Budget-burning,--confirm-gated.
A live-LLM trajectory is not reproducible; only the verdict is. This is why the contract says replayable verdicts, never “replayable runs.”
State. .loop_state.json is durable and resumable. On resume, the runtime MUST revalidate the determinism probe and metric-integrity snapshot before the first agent call and halt on drift.
Deep dive: receipts and replay.
Cost and liveness
Section titled “Cost and liveness”- Budget MUST be a run-total hard cap (not only per-turn), accumulated from parsed provider usage, with a rolling-sigma anomaly detector (
cost_anomaly) distinct from the absolute cap (budget_exceeded). The cost model includes agent tokens plus judge cost plus per-iteration check-execution cost; the ceiling is a quantile, not a mean mislabeled “upper bound.” - Liveness. The heartbeat MUST be read by an external watchdog that times out a wedged agent call and emits
heartbeat_stale. Every exit - including a non-zero agent exit - MUST pass through the typed logger; aset -e-style silent death is non-conformant. - Comprehension.
diffs_merged_unreadMUST be tracked; the loop SHOULD haltcomprehension_debt_exceededpast a threshold, cleared by a signedack. It is a forcing and visibility gate, not proof of comprehension.
Deep dive: cost and liveness.
Command surface
Section titled “Command surface”By contract, the command surface separates the three verbs that must never blur: probe-check measures the oracle’s stability, replay verifies a recorded verdict, and reexecute re-runs the live loop. doctor is the precondition gate the runtime trails, not leads. Every command in --json mode emits exactly one JSON object to stdout; human logs and errors go to stderr.
See the CLI Reference for the full command list and exit semantics.
Capability status
Section titled “Capability status”Each capability is Shipped (in cmd/loopexec with tests) or Shipped (core) (the core ships in cmd/loopexec; a named sub-part is still Planned, marked inline). This matrix is the contract between the binary and this site; it is normative in SPEC.md section 11.
| Capability | Status |
|---|---|
CLI contract: --json, stable exit codes, deterministic output | Shipped |
init / status / check / step commands | Shipped |
run as a real iterating loop | Shipped |
Computed halt reasons replacing --halt-reason | Shipped |
| Typed JSONL receipt and durable state | Shipped |
probe-check confidence bound (O2) | Shipped (core) |
doctor precondition gate (O3-O5, isolation) | Shipped (core) |
build-context, ratchet, replay / reexecute, escalate / watch, attest / ack | Shipped |
| Two-zone isolation and per-run minted key | Shipped (core) |
| Metric-integrity gate | Shipped (core) |