Skip to content

The loopexec Contract

This page summarizes the canonical contract for readers. It mirrors the structure of SPEC.md, the normative source in the repository.

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.

loopexec hosts two control topologies. The mode is explicit and recorded in every receipt.

loop.modeStop conditionGate
check_fixpointexternal check returns its success_exit_codethe application oracle (test / build / lint / score)
task_listall plan tasks completedstate-hygiene validation plus a per-task acceptance oracle
loop:
mode: check_fixpoint # or: task_list

Invariant 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 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-check reports 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 halts check_not_deterministic when 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 0 regardless 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.

Each iteration MUST execute as one ordered transaction:

  1. 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.
  2. Execute the agent in the agent zone.
  3. Run the check once (O6); capture the artifact; parse the failing set F_i.
  4. 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.
  5. 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.
  6. Progress / feasibility. Halt on no strict decrease in |F_i| over K iterations, on a previously-passing test re-entering F (oscillation), or on a contradictory pair across cycles. Distinguish “still improving at cutoff” from “stalled / infeasible.”
  7. Checkpoint the receipt. Append a typed JSONL event and update durable state.
  8. 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.

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.

exitclassexample halt_reason strings
10convergedsuccess_condition_met
12iteration-capmax_iterations_reached
13integrityblocked_path_modified, metric_integrity_violation
14oracle-untrustedcheck_flaky, check_not_hermetic
15check-inadequatecheck_inadequate
17no-convergenceno_progress_detected, oscillation_detected, infeasible_suspected
18budgetbudget_exceeded, cost_anomaly
19liveness/driftheartbeat_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 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 includes test/, 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 the t0 floor. Tests SHOULD run from a read-only mount frozen at t0. 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.

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 ~/.claude bind-mount). Filesystem: RW /work, RO elsewhere, no $HOME.
  • Exec zone - untrusted code execution. Network: none. Hermetic (O3). Ephemeral: reset each run.
  • doctor preconditions (fail-closed): reject network: none for a cloud or local-host model (isolation_unsatisfiable); reject any ~/.claude mount (credential_scope_invalid); reject exec_zone.network != none; require an egress allowlist.

Deep dive: two-zone isolation.

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.

  • 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; a set -e-style silent death is non-conformant.
  • Comprehension. diffs_merged_unread MUST be tracked; the loop SHOULD halt comprehension_debt_exceeded past a threshold, cleared by a signed ack. It is a forcing and visibility gate, not proof of comprehension.

Deep dive: cost and liveness.

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.

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.

CapabilityStatus
CLI contract: --json, stable exit codes, deterministic outputShipped
init / status / check / step commandsShipped
run as a real iterating loopShipped
Computed halt reasons replacing --halt-reasonShipped
Typed JSONL receipt and durable stateShipped
probe-check confidence bound (O2)Shipped (core)
doctor precondition gate (O3-O5, isolation)Shipped (core)
build-context, ratchet, replay / reexecute, escalate / watch, attest / ackShipped
Two-zone isolation and per-run minted keyShipped (core)
Metric-integrity gateShipped (core)