- Eval-gated fallbacks no longer fake progress. When
[progress] require_eval_progress = true, a fallback marker such asLOCAL-UPGRADEresets the stall window so the next iteration can test the new work, but it does not mark the current iteration as progressed unless eval emits the configured metric marker. - Progress can now require eval improvement.
[progress] require_eval_progress = truemakes LoopForge ignore act/learn progress markers unless[eval]emits a configured marker such asEVAL-UPGRADE. This prevents long-running quality loops from treating artifact refreshes or self-reported edits as real improvement when the score stayed flat. L016 now flagsrequire_eval_progresswithout both an eval command and markers. - A self-reported progress marker no longer counts when the independent verify fails.
progressednow requires the marker ANDverified is not False, so an agent that printsLOCAL-UPGRADEevery round can no longer reset the stall counter and defeat the convergence brake without actually changing anything. Loops with no verify step are unaffected. Guards the exact failure where a progress marker was logged while the underlying edit silently no-op'd. - No-progress can be observational instead of terminal.
stop_after_fallback_no_progressnow hands back only whenno-progressis explicitly listed in[handback].on; otherwise the runner recordsno-progress, runs the fallback after each configured stall window, and continues to the hard iteration cap. The fallback counter resets after a no-change fallback when handback is not enabled, so a dry source search does not run on every later iteration. This supports research loops that must keep cycling through worst-item improvement plus fresh-idea discovery without wasting a human handback on every dry well. runnow executes[learn]as a first-class pre-act stage. Loops can refresh sources, mine reading lists, or generate candidate work before each action instead of waiting until the no-progress fallback fires. Learn output is written to the JSONL trace, can emit progress markers, and repeated learn failures hand backlearn-failed-twice.- New rule
L018(learn command required). A declared[learn]block with nocommandis now a major lint finding. - Progress fallback is now real convergence control. Progress markers are explicit line-start
status tokens, so
NO-LOCAL-UPGRADEno longer accidentally counts asLOCAL-UPGRADE. A newstop_after_fallback_no_progressoption lets an improvement loop hand backno-progressafter its fallback also finds nothing new, instead of repeating the same discovery step until the hard iteration cap.NEEDS-HUMANhandback now respects[handback].on, so loops can treat it as an advisory status while continuing until their configured convergence brake fires. - New example
examples/convergent-improve/— the reference pattern for an open-ended "improve X" loop that satisfiesL015: a[progress]block (markers +no_progress_limit+ a discoveryfallback_command) plusgoal-reached/no-progresshandback. The shape that converges-or-discovers instead of burning budget on an asymptote or manufacturing progress. Guarded by a newtests/test_examples.pyso shipped examples stay lint-clean. runnow executes[eval]as a first-class stage after verify. Eval output is written to the JSONL trace, eval failures stop the loop after repeated failures, and eval output can emit progress markers such asLOCAL-UPGRADE. This closes a dogfood gap where[eval]existed in the parser/rules but the runner ignored it, so loops could "verify" without measuring whether the actual improvement metric moved.- New rule
L017(eval command required). A declared[eval]block with nocommandis now a major lint finding. - New rule
L015(convergence brake). An open-ended "improve X" loop (goal mentions improve / raise / optimize / toward 10-10 / better) must declare a stop-when-progress-stalls signal — agoal-reached/no-progress/all-gaps-resource-boundhandback condition, a[convergence]block, or an[eval]metric gate. Without it the loop spends its whole budget chasing an asymptote, and an agent with no way to declare "done" tends to manufacture progress (inflate a score, fabricate evidence) rather than stop.major, since the budget cap is still the ultimate hard stop. Maintenance loops with a concrete goal are exempt. - Require a named handback owner so autonomous output always returns to an accountable person or team; include that owner in dry-run plans and notifications.
- Add an optional JSONL execution trace with complete act/verify commands, outputs, timestamps, worktree, outcome, and accountable owner for harness-level evaluation and recovery.
- Runner trace now records command durations.
learn,act,verify,eval, and fallback trace entries includeduration_seconds, making slow improvement loops easier to profile and optimize instead of guessing which stage burns the run budget. - Runner commands now receive loop state in env vars.
LOOPFORGE_ITERATION,LOOPFORGE_NO_PROGRESS_ROUNDS,LOOPFORGE_NO_PROGRESS_LIMIT, andLOOPFORGE_FALLBACK_DUE_NEXTlet mature loops run cheap probes during stalled rounds and reserve expensive work for changed artifacts or fallback windows.
evalno longer lets one bad row silently corrupt a metric. Two scoring bugs:- A
proboutside[0,1](e.g. a confidence logged as75for0.75) added a~(75-1)²term that wrecked the mean Brier score. Out-of-range probabilities are now skipped, so calibration reflects only valid rows. - Decimal odds
<= 1.0would book a winning bet as flat/negative P&L (stake*(odds-1) <= 0). Such rows are now treated as invalid odds and skipped from P&L. Both found by routing a correctness review ofevaluate()through modelbroker (review → claude).
- A
runaccepts a directory likelintdoes —loopforge run mydir/resolves the singleloop.tomlinside instead of crashing with "Is a directory". Found by dogfooding (lint a dir, then run it). Multiple loops in one dir → a clear "name the one to run" error.
evalgate fix —--min-accuracyno longer fails when no predictions are resolved yet. A scheduled loop whose outcomes resolve later would previously fail its gate every tick (no data read as failure); now the gate passes until there is real accuracy evidence to judge. CLI prints a note so the pass is explicit.
loopforge eval— score recorded predictions against real outcomes (the deepest verify: predicted vs ground truth). Reads a CSV (id,predicted,actual,prob,stake,odds,result) and reports accuracy, calibration (Brier), and betting P&L/ROI.--min-accuracymakes it a verify gate, so a loop that keeps predicting badly fails itself. Domain-agnostic (bets, stock calls, anything). Pair it with a resolver that fetches the latest data +loopforge scheduleto auto-validate on a cadence. Seeexamples/eval-soccer/.
loopforge schedule— turn a loop's declarative[trigger].croninto a real cron entry, so a scheduled loop actually fires.schedule install <loop.toml>(with--dry-run),schedule list,schedule remove <name>. Entries are tagged# loopforge:<name>and managed idempotently. A hard no-clobber guard refuses to write if the existing crontab can't be read, so it never wipes your other jobs. (macOS: cron needs Full Disk Access to touch protected dirs.)
- Real worktree isolation in the runner. When
[isolation] mode = "worktree"and the loop is in a git repo,runnow executes act/verify in an isolatedgit worktree(left in place for you to review and merge), while the memory ledger is written back to the original repo so the record survives. Previously isolation was linted but ignored by the runner — a describe-vs-do gap, now closed. The worktree path is reported in the run output. reviewer_commandnow receives the act output. A second-model review can finally see the work it's reviewing (it was being run with no input).- Memory ledger fixed. Each iteration now records a distinct action summary (what act did) and outcome — previously both columns held the same string.
- New rule
L013— referenced files exist. Flagsskills/memory/prompt_filepaths inloop.tomlthat don't exist on disk (only when linting a real file). Catches the typo'd path the runner would otherwise load as "(MISSING)". - 63 tests (was 56).
- L002 now requires a hard stop. A goal (
until) no longer counts as a brake on its own — a goal that's never met loops forever. The runaway check is satisfied only by a positivemax_iterationsor a[budget]time/token/cost ceiling. A non-int or zeromax_iterationsno longer counts either (it now agrees with the runner). - New rule
L011— trigger is actually wired up. Catches ascheduletrigger with nocron, aneventtrigger with no source, anuntil-goaltrigger with nountil, and typo'd trigger types (e.g."scheduled") that would silently never fire. - 56 tests (was 50).
First release.
- lint — grade any loop A–F against the six blocks of Loop Engineering (trigger, isolation,
skills, act, verify, memory) plus a cost brake and a human brake. 11 deterministic rules
(
L001–L012); only the no-brake runaway iscritical. - init — scaffold a complete, lint-clean loop (
loop.toml+ skills + memory + prompt). - run — execute a loop (wake → act → verify → record → decide). Enforces iteration and wall-clock limits, appends every step to the memory ledger, and refuses to start a loop that has no brake.
- Zero runtime dependencies, Python ≥ 3.11.
humanandjsonoutput;--score,--select,--fail-at.