Why the green is earned

A run ends in a report, not a claim. This page is what that report can tell you — that the checks were real, that the agent did the work, that nothing it wasn't allowed to touch was touched — how each check is made, which are always on and which you arm in the spec, and where each has honest limits. loopgen lint catches a bad spec before any agent runs at all.

Prefer learning by doing? The interactive workshop has a red-team playground for this exact material — pick an attack, toggle the guard policy, watch the engine respond: Module 5 · The Trust Model.

What the report can tell you

"All checks passed" is only meaningful if the checks actually exercise the new requirement and the agent actually did something. The loop runner that doesn't believe the agent earns that title in layers:

Real files changed — change detection (git)

Every iteration is diffed non-destructively, via a throwaway index. The report shows a git diff --stat, and a green run that changed no files is flagged as likely vacuous. Build and runtime artifacts (logs, databases, compile caches, generated assets) are excluded so that merely running the test suite can't masquerade as "the agent did work"; add your own globs with workspace.ignore. When the workspace isn't a git repo (or is git-ignored), it falls back to an independent content-hash walk of the working tree — so a driver that omits (or fabricates) changedFiles still can't silence the vacuous-success guard; driver-reported files are only consulted as a secondary signal when the walk hits its file cap. The run then carries a persistent caveat: no unified diff, and very large trees are capped.

The checks were red before the work — baseline evaluation

The baseline runs the checks once before any agent work. If they already pass, your checks don't test the requirement — there is nothing for the agent to earn — so by default (limits.baseline: "strict", or --strict-baseline) the run fails right there with baseline-vacuous, before a single agent turn is spent. true (or --baseline) downgrades that to a warning on an otherwise-normal run. false skips the pre-agent run entirely; set it when your checks have side effects (db migrate/seed) that must not run twice. It's stack-agnostic: it just runs whatever evaluators you defined on the pre-agent workspace.

The checks didn't race each other — sequential evaluators (default)

Evaluators run one at a time (evaluation.concurrency: 1) so checks that share external state (several bin/rails checks against one SQLite database, say) can't race and deadlock into false failures. Raise evaluation.concurrency only for genuinely independent checks that are safe to run in parallel.

The success criteria weren't rewritten — spec-integrity guard

If the loop spec lives inside the workspace, the agent can edit its own success criteria. The runner watches the spec file, excludes it from the work diff (so a spec-only edit can't fake "work"), and by default (specGuard: "error") fails the run (spec-tampered) if the agent modified it, so an altered contract can't report green. "warn" lets the green stand with a caveat; "off" disables the watch. Best practice: keep specs outside the target repo.

The tests weren't edited — evaluator-integrity guard

The real success criteria for a command check are the test files it runs, and the agent could fake a green by editing them. The runner watches those files — auto-detected from the command (e.g. bin/rails test test/foo_test.rb), plus any evaluators[].guard paths — excludes them from the work diff, and by default (evaluatorGuard: "error") fails the run (evaluator-tampered) if any changed. "warn" lets the green stand with a caveat; "off" disables it. A bare runner with no file arguments (npm test) names nothing and is intentionally not watched. When both guards fire, spec-tamper takes precedence.

The agent actually finished — honest outcomes

Drivers report a stopReason (completed | max_turns | aborted | error). When the agent runs out of turns or errors but the checks pass anyway, the run still succeeds (checks are the source of truth), but the report says so instead of showing a clean green. The same honesty applies at the process level: a CLI driver whose binary fails to spawn surfaces as an error rather than a vacuous success, and a user abort takes precedence over the incidental spawn error it causes.

All caveats are collected in report.warnings and printed under ⚠ warnings: — and when the run is traced, they also become signal records in the trace and span events in the OTLP tree, so a suspicious green is visible in your tracing UI too. Drivers that report a session id can also resume after a max_turns stop (opt-in: driver.options.resume: true).

Linting before you run

A misconfigured spec can burn hours before failing for a reason that had nothing to do with the agent. loopgen lint catches those statically, in milliseconds, before any agent turn:

loopgen lint my-feature.loop.yaml
loopgen lint punch-list.batch.yaml      # lints the manifest + every item's spec
loopgen lint my.loop.yaml --strict      # exit non-zero on warnings too
loopgen lint my.loop.yaml --json        # machine-readable findings

It flags, among other things:

RuleSeverityWhat it catches
SPEC-WORKDIR-NOT-PROJECT error The resolved workspace isn't a git repo and has no markers for the declared stack, yet the spec expects an existing project. Catches a workspace.dir and a batch base that both go relative and compound (e.g. ../.. applied twice, landing in $HOME).
SPEC-EVAL-BINARY-MISSING
SPEC-EVAL-FILE-MISSING
error A check's binary or referenced script doesn't exist where it will run.
SPEC-EVAL-DESTRUCTIVE-ENV warn A check mutates a database without a test env, so it would alter your dev data every iteration.
SPEC-EVAL-SHARED-RESOURCE warn Multiple stateful checks set to run in parallel (evaluation.concurrency > 1) can race on a shared database. Evaluators run sequentially by default, so this only fires when you opt in.
SPEC-SMOKE-SELF-FULFILLING warn A smoke test that creates records but never drives a real endpoint may pass without exercising the feature.
BATCH-MAXITER-OVERRIDE
BATCH-NEEDS-AS-ORDERING
BATCH-FAILFAST-CHAIN
warn Batch-manifest pitfalls: item overrides, needs used purely for ordering, and fail-fast chains.

Exit codes: 2 if any errors, 1 if --strict and warnings, else 0. The error-severity workspace checks also run automatically as part of every run (the resolved workspace is printed up front); skip with --skip-preflight.

Budget ceilings

limits.maxCostUsd and limits.maxTokens (both optional) cap a run's cumulative driver-reported usage. The engine sums usage across iterations and, when a non-converging iteration pushes the total past a limit, stops with outcome budget-exceeded rather than funding another turn.