Skip to content

Specification

Documentation source v0.5.0-rc.1. Published releases

Status: normative. This is the single source of truth for the loopexec contract. The binary (cmd/loopexec) and the docs site (loopexec.dev) MUST conform to this document. Where docs/loop-contract.md and this file disagree, this file wins; loop-contract.md is the task_list mode profile of this spec.

Spec version: 2.0.0 | Conformance keywords: MUST / MUST NOT / SHOULD / MAY per RFC 2119.


1. 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 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.


2. Topology - the two machines, made explicit

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

loop.modeStop conditionGateProfile
check_fixpointexternal check returns success_exit_codethe application oracle (test/build/lint/score)this document, section 4
task_listall SMALL plan tasks completedsmall check --strict (state hygiene) + a per-task acceptance oracledocs/loop-contract.md

Invariant T1. check_fixpoint is the degenerate one-task plan whose acceptance criterion is the external check. task_list 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 without a 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.


3. The oracle contract (the check)

O1 - External. The check MUST be an external process returning an exit code, independent of the agent. The agent’s self-assessment is not a check.

O2 - Determinism as a maintained confidence bound (not a one-time probe).

  • loopexec probe-check MUST report an achieved confidence bound on the check’s flake rate, not a pass/fail of N runs. The run count is derived from a target max_flake_rate, not a magic constant.
  • During a loop, each in-loop check invocation is treated as a free Bernoulli sample; the runtime maintains a sequential (Wilson) lower bound on stability and MUST halt check_not_deterministic when it drops below confidence_target.
  • Probing MUST be adversarial where configured (test order, seed, clock, concurrency, load) and MUST report which dimension broke.

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 by doctor (check_not_hermetic), not silently looped on.

O4 - Adequacy != determinism. A check that returns 0 regardless of the diff is deterministic and useless. doctor MUST verify adequacy: every changed/added line is exercised by the check (coverage delta), and a mutation canary injected into 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 distinct from a flaky one; it MUST be reported check_has_side_effects and cured by reset, not by editing the check.

O6 - Single capture. The runtime MUST run the check once per iteration and derive both the green/red 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.


4. Iteration lifecycle - check_fixpoint

Each iteration MUST execute as one ordered transaction:

  1. Build context - a narrow, budgeted slice (build-context). Token budget is a true ceiling, measured with a code-calibrated estimator; a slice that cannot fit even after widening tiers MUST emit context_budget_unsatisfiable, never silently evict relevant files.
  2. Execute the agent in the agent zone (section 7).
  3. Run the check once (O6); capture the artifact; parse the failing-test set F_i.
  4. Guards dominate success (G-before-S). Evaluate guards (section 6) BEFORE any green branch can fire. A check that went green because a guard was violated (e.g., a weakened test) MUST classify as the guard reason, NOT success_condition_met.
  5. Acceptance (anti-regression ratchet). Accept the iteration’s diff only if the passing-test set is a superset of the last accepted set (primary guarantee). Optionally enforce a numeric score >= best_so_far ratchet with an acceptance band. On violation, revert to the last accepted commit and re-prompt.
  6. Progress / feasibility. Update F_i. 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 >=2 cycles. Distinguish “still improving at cutoff” (raise the limit) from “stalled/infeasible” (do not retry).
  7. Checkpoint the receipt (section 8). Every iteration MUST append a typed JSONL event and update durable state.
  8. Continue or halt. Halt only on a computed condition from section 5; otherwise iterate. Before declaring success_condition_met, the full check phase MUST pass (a targeted subset MAY drive intermediate iterations).

5. Halt reasons -> exit codes (canonical map)

The halt_reason string is the stable integration contract; the exit code is a coarse class CI can branch on without parsing JSON. Existing codes 0/10/11/12/20/30/40/50 predate this spec; 13-19 are new classes introduced here. The table below is the full normative taxonomy; which halt_reason strings the binary emits today versus holds reserved is tracked in section 11 - as of v0.2.0, every class 13-19 emits (their core reasons; 18 via inspect-cost, 15 via doctor --mutate-cmd), while only class 11’s task-list reasons (the task_list topology) stay reserved until their detectors ship. Every reason MUST be computed from observed state - --halt-reason is a hidden test fixture only.

exitclasshalt_reason stringsowner
0nominal(loop ran, no halt)LoopExec
10convergedsuccess_condition_metLoopExec
11terminal-blockedno_actionable_tasks, human_requiredhuman
12iteration-capmax_iterations_reachedLoopExec
13integrityblocked_path_modified, reward_hacking_detected, metric_integrity_violation, credential_scope_invalid, objective_unverifiedLoopExec (det.)
14oracle-untrustedcheck_flaky, check_has_side_effects, check_not_hermetic, hermeticity_violationLoopExec (det.)
15check-inadequatecheck_inadequateLoopExec (det.)
16resumable-judgmentescalation_pending, reviewer_rejectedMusketeer / human
17no-convergenceno_progress_detected, same_failure_repeated, oscillation_detected, same_test_regressed, unsatisfiable_constraints, infeasible_suspectedLoopExec (det.)
18budgetbudget_exceeded, cost_anomalyLoopExec (det.)
19liveness/driftheartbeat_stale, model_drift_detected, comprehension_debt_exceeded, context_budget_unsatisfiableLoopExec (det.)
20invariantinvariant_failedLoopExec
30workspaceworkspace_invalid, isolation_unsatisfiableLoopExec
40executionexecution_failureLoopExec
50internalinternal_errorLoopExec

Ownership stance. LoopExec owns everything a number can decide (exit codes, the F_i trajectory, a fingerprint hash, a coverage/mutation delta, a manifest hash, a spend ledger, a model-identity tuple, a sequential flake bound). Musketeer owns only what a number cannot express - reward_hacking_detected, reviewer_rejected - layered on top of the deterministic metric_integrity_violation, never replacing it. Humans own the irreducible decision and the ack. Judgment is never the stop oracle: it can veto a green loop or escalate, but it cannot promote a red one to green.

Migration. The legacy bare "blocked" string (one word, three meanings) is split: no_actionable_tasks (11), blocked_path_modified (13), human_required (11).


6. Guards & metric integrity

Guards are evaluated against an immutable baseline t0 captured at loop start, BEFORE any green declaration (G-before-S, section 4.4), covering working-tree + staged + committed + untracked changes.

  • blocked_paths - globs the loop MUST NOT modify (default includes test/, infra/, .env, .git/). Violation => blocked_path_modified.
  • Metric integrity (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 (runner/coverage config, CI yaml, test-dep lockfile) 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 (reviewer_rejected / reward_hacking_detected). It receives a behavioral oracle (check log, result delta, collected-set delta, coverage/mutation signal), treats the diff as untrusted data, and never replaces the deterministic gate.

7. Isolation & secrets (two zones)

A single --network none container cannot both run a cloud agent and isolate untrusted code. Isolation MUST be two zones sharing only a work volume that is a detached clone (a git worktree is ergonomic, NOT a security boundary - it shares the object DB/refs/remotes/hooks/credentials).

  • Agent zone - reasoning surface. Network: default-deny + egress allowlist to the model endpoint only, via an auditing proxy. Credentials: exactly one per-run minted, short-TTL, spend-capped key (never a ~/.model-home bind-mount). Filesystem: RW /work, RO elsewhere, no $HOME.
  • Exec zone - untrusted code execution (check/build/test). Network: none. Hermetic (O3). Ephemeral: reset each run.
  • doctor preconditions (fail-closed): reject network: none for a cloud/local-host model (isolation_unsatisfiable); reject any ~/.model-home mount (credential_scope_invalid); reject exec_zone.network != none; require an egress allowlist; warn on worktree + credentialed origin.

8. Receipts, replay, and state

Receipt (per run). MUST pin everything that determines output, or fail closed (workspace_invalid): the model-identity tuple {provider, model_id, version/build, quantization, runtime, endpoint}, recorded sampling params {temperature, top_p, seed, max_tokens}, a context manifest [{path, content_sha256}], parsed cost actuals {prompt_tokens, completion_tokens, usd}, the per-iteration check command + exit code + commit, the computed halt_reason, and the achieved determinism confidence. Receipts SHOULD be signed (attest).

Replay vs 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. Answers “does this receipt’s verdict hold?”
  • reexecute = RE-RUN. Run the live agent loop again. Non-deterministic (the LLM samples); reports a statistical match, never byte identity. Budget-burning; --confirm-gated.

A live-LLM trajectory is not reproducible; only the verdict is. Marketing copy MUST say “replayable verdicts,” not “replayable runs.”

State (durable, resumable). The implemented snapshot is .loopexec/state.json with per-run snapshots. The target full-state profile MUST carry at least: phase, iteration, last_green_commit, open_failures, cumulative_usd, baseline (Step-1 measurement), determinism_probe result, metric_integrity snapshot, model_pin, and escalation state. In the full-state profile, the runtime MUST revalidate determinism_probe and metric_integrity before the first agent call and halt on drift.


9. Cost & liveness

  • Budget: strict monetary caps require run-total accounting plus credible per-call upper bounds enforced by the invoking adapter/provider (section 12). Observed metering is explicitly post-call and MUST NOT claim a preventive cap. Unmetered usage reports unknown dollars. Parsed actuals include builder, critic, and explicitly priced tool/test calls. cost_anomaly remains distinct from budget_exceeded. A historical quantile alone is not a hard reservation bound.
  • Liveness. The heartbeat MUST be read by an external watchdog (loopexec watch) 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 after a configured threshold, cleared by a signed ack. (A forcing/visibility gate, not proof of comprehension.)

10. Command surface

CommandPurposeExit semantics
initcreate .loopexec/ runtime directory0 / workspace_invalid
runthe real iterating loop (section 4); computed haltper section 5
run --oncesingle iteration (debug); absorbs legacy stepper section 5
probe-checkdeterminism as a confidence bound (O2); agent-free0 / class 14
build-contextbounded relevant slice; budget-unsatisfiable handling0 / context_budget_unsatisfiable
doctorprecondition gate: determinism + hermeticity + adequacy + isolation0 / class 14
checkSMALL/state-hygiene validation only (NOT the application oracle)0 / invariant_failed
ratchetinspect/advance the monotonic acceptance frontier0
replayVERIFY a recorded receipt (agent-free, budget-free)0 / class 13
reexecutelive re-run; statistical match; --confirm0
report / statusrender receipt / live tail0
inspect-costcomposite cost model + caps + anomaly0
explain-halthuman rationale; separates “raise the limit” from “never retry”0
escalate / watchstructured human handoff + external watchdogescalation_pending / heartbeat_stale
attest / acksign a receipt / signed comprehension ack0

JSON contract. Every command in --json mode emits exactly one JSON object to stdout; human logs and errors go to stderr. The object includes at least {tool, version, status, errors[]} and, where applicable, {run_id, iteration, halt_reason}. The schema is additive: new fields MAY be added; existing field meanings MUST NOT change within a major spec version.


11. Capability status (normative source for the docs matrix)

This matrix describes current source. Publication status comes from GitHub Releases; new v0.4.1 report behavior requires a matching public binary.

Each capability is Shipped (in cmd/loopexec with tests), In progress (on a feature branch), or Planned (specified here, not yet built). The docs site MUST render this distinction and MUST NOT present Planned capability as Shipped.

CapabilityStatus
CLI contract: --json, stable exit codes, deterministic outputShipped
init / status / check / step stubsShipped
run as a real iterating loop (section 4)Shipped
Computed halt reasons (section 5) replacing --halt-reasonShipped
Typed JSONL receipt + durable state (section 8)Shipped
probe-check confidence bound (O2)Shipped (core; adversarial perturbation + in-loop sequential monitor Planned)
doctor precondition gate (O3-O5, section 7)Shipped (determinism + isolation preflight + adequacy via mutation canary --mutate-cmd; hermeticity and the coverage-delta tier reported as planned)
Set-based progress + no-regression ratchet: oscillation / no-progress / regression halts (section 3.2)Shipped (legacy --failures-cmd; opt-in manifest snapshots and best-candidate restoration via --workflow)
explain-halt: raise-the-limit vs do-not-retry (feasibility)Shipped
Metric-integrity gate: guards dominate success (section 6)Shipped (fail-closed collected-set monotonicity and configured protected-file hashes; assertion-count / coverage-floor Planned)
doctor isolation preflight: credential-mount + exec-network fail-closed (section 7)Shipped
Receipt pinning: model-identity tuple + sampling + context manifest (sha256) + cost + check fingerprint (section 8)Shipped (legacy flags record metadata; workflow usage hooks support exact in-loop strict/observed/unmetered accounting)
replay: verify a receipt offline by re-running the check and matching the fingerprint (section 8)Shipped
attest: HMAC-sign a receipt and --verify it (section 8)Shipped
report: read-only event/failure/missing-evidence reportingShipped (v0.4.1 source; signature presence is unverified, candidate retention is not acceptance)
reexecute: live re-run of the recorded config N times, halt-reason distribution (section 8)Shipped
escalate / watch / ack + comprehension gate (section 9)Shipped (file/stdout channels, heartbeat + staleness detection, comprehension --comprehension-every; github/slack channels + kill-the-PID actuator Planned)
build-context: budgeted relevant-file slice with workdir-confined, symlink-safe file resolution (section 4)Shipped (stacktrace + last-diff + untracked relevance; import_closure / dep_graph tiers Planned)
Two-zone isolation orchestration (isolate): detached-clone sandbox + per-run minted/revoked credential (0600 env-file, never on the argv) + rendered/launched exec-zone (network:none) and agent-zone (egress-allowlist) (section 7)Shipped (orchestration; the container engine, the auditing egress proxy, and the provider key API are operator-provided hooks: --runtime, --egress-proxy, --mint-cmd/--revoke-cmd)
Shared command lifecycle: total/per-command deadlines, process-group cancellation, bounded outputShipped (macOS/Linux groups; other platforms direct child only)
Numeric score/patience and candidate-bound acceptance guardsShipped (opt-in workflow policy)
Shared scoped model-call budgetShipped (opt-in call-count gate across run IDs; independent of monetary metering)
Offline one-attempt Go creative adapter with signed synthetic review evidenceShipped (fixtures only; live engines/models are operator integrations)

This table is the contract between the binary and the site. When a capability moves status, update it here first; the binary tests and the docs matrix both reference this section.

Every row above has a Shipped core. The remaining work is named, inline sub-parts (the operator-provided infra hooks, provider-specific metering wrappers, the deeper metric-integrity layers, the import_closure/dep_graph context tiers, github/slack escalation channels, the kill-the-PID watchdog actuator) — not whole capabilities.

Additional integration and topology status:

CapabilityStatus
SMALL CLI validation and v1/v2 compatibility proofsShipped (repository/adapter integration; SMALL remains an optional external CLI)
Full SMALL-driven task_list topologyPlanned
Provider-specific live usage wrappersPlanned (adapters must supply provider enforcement for strict monetary caps)
Container/Nix/remote substrates and multi-worker orchestrationPlanned (existing isolation hooks compose with operator infrastructure)

12. Unattended workflow extension

Implementation status: shipped, with offline unit, subprocess, and CLI tests. The following extension is opt-in; legacy set-based progression remains the default. No model-provider, Blender, Godot, or Python orchestration dependency belongs in the governor.

All run hooks share —timeout (total), —command-timeout, and —terminate-grace. Zero deadlines preserve legacy unlimited time. Linux/macOS own a process group, TERM on deadline/cancellation, KILL after grace, wait, and close bounded pipes. Other platforms bound only the direct child; process-tree containment is not claimed there. SIGINT/SIGTERM yield a final atomic state; SIGKILL recovery is limited to journaled state, never an assertion that interrupted work completed. Command events identify phase, duration, exit, and cause, never raw tool output. Output capture is bounded to 1 MiB; separate 16 KiB tails are retained in private run-owned diagnostic files, never printed or stored in receipts. There are no default full logs. Collector stdout is separate from stderr. Collector stderr, nonzero, truncated, or malformed structured output fails closed. Identity collectors accept legacy lines or {“ids”:[]}; valid empty stdout with no stderr remains an empty set. All configured guards run before the success branch (external check 0, CLI 10).

The —workflow JSON contract pins metering, numeric score policy, and constrained candidate management before the first attempt. Monetary strict mode requires per-call upper bounds AND adapter/provider enforcement. Observed mode reports post-call totals without claiming a preventive cap. Unmetered usage has unknown monetary cost, with iteration/time/call/token bounds where supplied. Money uses integer micro-USD (six decimal places), never float accumulation. A nonzero —budget-usd without a metering contract is invalid. Reservations and actuals use stable run/iteration/phase/call IDs; unresolved reservations block restart work. A separate opt-in scoped call budget lets adapters reserve individual child model calls across run IDs before launch. Its absolute shared store pins the scope policy, counts crash/failed-launch reservations conservatively, and refuses changed limits or damaged records. It caps call count by scope and phase, not tokens or dollars; it never substitutes for provider-enforced monetary limits.

Numeric progression compares domain-checked scores with distinct acceptance tolerance and meaningful-improvement thresholds. Best accepted score and last patience-resetting score are separate. Only reviewed technical successes consume visual patience. Technical failures consume attempts but are unreviewed. Stable failure IDs with improving scores do not cause oscillation in numeric mode.

Candidates are confined to a configured manifest under a dedicated candidate root, with runtime journals/evidence outside it. The governor snapshots binary and untracked files as well as text, pins protected policy/config hashes, and restores the best technically valid candidate after rejection. There is no reset/clean of the user’s checkout. No valid baseline means best is absent until a technically valid candidate is accepted. Promotion is journaled before an atomic best-pointer update; interrupted promotion is not accepted. Failed restore is terminal and invalidates exposure of the best candidate. Filesystem naming and hashes are not an adversarial security boundary: production builders must run with OS-enforced write access only to candidate content, with verifier, policy, controller state and critic evidence inaccessible for modification.

The exact workflow schemas, comparison boundaries, restart behavior, trust boundary, and offline commands are normative in docs/workflows.md.

13. Documentation and acceptance continuity

Implementation changes MUST ship with matching canonical documentation and release notes. CLI command/flag/default/help and JSON field contracts MUST pass the generated reference comparison. Release archives MUST include their source-bound public docs snapshot. Automated publication MUST fail closed until the official docs site serves the matching manifest, source files and homepage build marker. Semantic review and account-level release protections remain required; automated provenance is not a claim that every sentence or rendered page is correct. See the normative continuity policy for the gates and rollout limits.

LoopExec is a general-purpose governor. Domain-specific targets and oracle policies belong to integrations. Candidate retention, convergence under the recorded run contract, explicit human acceptance and publication MUST remain distinct. Owner adjustments MUST preserve original receipts and desired objectives, with separate decision evidence; they MUST NOT silently reset budgets or authorize new work. See acceptance policy. Native cross-run decision history is not implemented; these records currently belong to the adapter/operator.

14. Explicit model assignments

The optional schema-1 workflow model_policy defines named profiles, a selected task and per-role assignments with declared capability requirements and rationale. models plan MUST perform no work or spending. Advisory mode MUST leave adapter invocation unchanged. Explicit mode MUST bind every assigned model and nonempty reasoning value through whole argv arguments, reject conflicting exec overrides, and require matching pre-call declarations and adapter-reported usage identity. Valid incurred usage MUST remain recorded even when identity drift halts the run. Model policy is hashed for resume and included in attestation; legacy records without it remain compatible. Operator declarations and trusted adapter reports MUST NOT be represented as independent provider identity/capability attestation. Assignments MUST NOT lower acceptance targets, renew allowances or create a second retry controller. Automatic routing and escalation are not implemented. See model assignments for the normative adapter contract, validation boundaries and offline proof.