The validation loop

Set a completion check, understand retries, and interpret the result.

With --until or /validate, Worksmith runs a check before accepting a task as complete. Failed checks return their output to the model for another attempt. Without a configured check, done means the model finished; it does not mean Worksmith independently verified the result.

Why the loop exists

A model can stop while its work still contains errors. A test command gives Worksmith an observable result to use when deciding whether to retry:

 model stops calling tools
        │
        ▼
   "I'm done"            ← a proposal, not evidence
        │
        ▼
   ┌──────────────────┐
   │ run the check    │    --until "cargo test"
   │ (does it exit 0?)│
   └────────┬─────────┘
            │
      ┌─────┴─────┐
      │           │
   passes      fails
      │           │
      ▼           ▼
   TurnDone   re-plan with the
              failure output,
              bounded retries

The model's judgment is a proposal; the check is the gate. When the check fails, the harness does not accept the claim. It feeds the failure back as a re-plan directive — "the check did not pass: … revise your approach and fix the underlying problem, then finish" — and runs the inner loop again. That is the whole product.

What "done" means

A turn's outcome describes why it stopped:

Use /stats to inspect the recorded validation result. A bare done label is not proof that a check ran.

Shell checks and the Bash tool use pipefail: a failing stage in a pipeline makes the pipeline fail even if its final command succeeds. For example, cargo test | tee test.log preserves a test failure. An explicit || true still masks failure, and consumers such as head can cause an upstream SIGPIPE by closing a pipe early.

How a failure becomes a re-plan

The turn is two loops nested inside each other. The inner loop keeps going until the model stops calling tools (or gets stuck, or hits the step cap). The outer loop is the validation gate: when the inner loop reports the model is done, the outer loop runs the check, and on a failure it re-plans and runs the inner loop again — up to a bounded number of retries.

flowchart TD
    A["inner loop: model works until it stops calling tools"] --> B{"model says done"}
    B --> C["run the --until check"]
    C -->|passes| D["TurnComplete: done"]
    C -->|fails, retries left| E["inject re-plan directive with the failure output"]
    E --> A
    C -->|fails, no retries left| F["TurnComplete: validation failed"]

The re-plans are bounded by [agent] max-retries (default 3), so a model that cannot make the check pass stops with a clear validation failed outcome instead of spinning forever. The inner loop has its own guard: if the model repeats the same tool call with no progress, it is nudged rather than left to spin ([agent] stuck-threshold, default 3).

The measurements that bound the claim

Both numbers come from evals/README.md, over the same seven tasks, each run raw (the model stops when it decides it is done) and guided (the validation-driven loop re-plans until the check passes).

Decisive on a weak model. On qwen3.5-9b, guidance took the pass rate from 52% to 86% — 11/21 to 18/21, +34 points — at flat cost per solved task (640 generated tokens before, 658 after). The detail that matters more than the headline: all ten failures among the 21 unguided attempts had outcome done. Not stuck, not out of steps. The model declared itself finished and was wrong. That is the thesis as data: the loop did not add information, it added enforcement — it caught the "I'm done" that was not.

Dead weight on a capable model. On a capable 27B (qwen3.8-27b), the same suite recorded 21/21 raw successes. The evaluation notes describe the guided result as effectively 21/21 after excluding a loss attributed to a since-fixed truncation bug; that is a qualified result, not a clean 21/21 rerun. Guidance used about 18% more tokens. On this suite the 27B self-loops and self-checks, so the loop only forces what it already does, and the extra tokens are pure overhead.

Taken together, the two runs give the shape of the differentiator: dead weight on a capable model, decisive on a weak one, at flat cost per unit of delivered work.

The limit, stated plainly

Retries can correct some failures, but success is not guaranteed. In the 9B run, one task — docx-styling — passed 0/3 in both modes, with the guided run burning 6× the tokens iterating. The loop made a wrong answer correct where the model could get there; it could not resolve that task within the tested setup and budget.

That is why the pitch is narrowed on purpose, and why the docs say so out loud. This earns its keep when the model is weak enough to need it — which is exactly the small, cheap, and local models this tool is for.