rigorloop 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. {rigorloop-0.1.0 → rigorloop-0.2.0}/.gitignore +2 -0
  2. rigorloop-0.2.0/AGENTS.md +121 -0
  3. rigorloop-0.2.0/CHANGELOG.md +87 -0
  4. {rigorloop-0.1.0 → rigorloop-0.2.0}/CONTRIBUTING.md +30 -4
  5. {rigorloop-0.1.0 → rigorloop-0.2.0}/PKG-INFO +114 -13
  6. {rigorloop-0.1.0 → rigorloop-0.2.0}/README.md +112 -11
  7. {rigorloop-0.1.0 → rigorloop-0.2.0}/examples/contact-cards/rigorloop.toml +4 -3
  8. rigorloop-0.2.0/logo.webp +0 -0
  9. {rigorloop-0.1.0 → rigorloop-0.2.0}/pyproject.toml +1 -1
  10. {rigorloop-0.1.0 → rigorloop-0.2.0}/src/rigorloop/_version.py +2 -2
  11. {rigorloop-0.1.0 → rigorloop-0.2.0}/src/rigorloop/core/config_calcs.py +11 -1
  12. {rigorloop-0.1.0 → rigorloop-0.2.0}/src/rigorloop/core/prompt_calcs.py +24 -11
  13. {rigorloop-0.1.0 → rigorloop-0.2.0}/src/rigorloop/core/report_calcs.py +8 -4
  14. {rigorloop-0.1.0 → rigorloop-0.2.0}/src/rigorloop/core/strategy_calcs.py +136 -36
  15. {rigorloop-0.1.0 → rigorloop-0.2.0}/src/rigorloop/core/types.py +7 -1
  16. {rigorloop-0.1.0 → rigorloop-0.2.0}/src/rigorloop/shell/cli.py +81 -61
  17. {rigorloop-0.1.0 → rigorloop-0.2.0}/src/rigorloop/shell/io_actions.py +45 -0
  18. {rigorloop-0.1.0 → rigorloop-0.2.0/stale_markdowns}/CLAUDE.md +1 -1
  19. {rigorloop-0.1.0 → rigorloop-0.2.0/stale_markdowns}/PACKAGING_PLAN.md +1 -1
  20. rigorloop-0.2.0/stale_markdowns/POTENTIAL_PROBLEM.md +379 -0
  21. {rigorloop-0.1.0 → rigorloop-0.2.0}/tests/conftest.py +12 -1
  22. {rigorloop-0.1.0 → rigorloop-0.2.0}/tests/test_cli.py +1 -0
  23. {rigorloop-0.1.0 → rigorloop-0.2.0}/tests/test_config_calcs.py +4 -0
  24. {rigorloop-0.1.0 → rigorloop-0.2.0}/tests/test_e2e.py +132 -0
  25. {rigorloop-0.1.0 → rigorloop-0.2.0}/tests/test_io_actions.py +45 -1
  26. {rigorloop-0.1.0 → rigorloop-0.2.0}/tests/test_leakage.py +12 -1
  27. {rigorloop-0.1.0 → rigorloop-0.2.0}/tests/test_prompt_calcs.py +26 -10
  28. {rigorloop-0.1.0 → rigorloop-0.2.0}/tests/test_strategy_calcs.py +219 -17
  29. rigorloop-0.1.0/CHANGELOG.md +0 -30
  30. {rigorloop-0.1.0 → rigorloop-0.2.0}/.claude/settings.json +0 -0
  31. {rigorloop-0.1.0 → rigorloop-0.2.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  32. {rigorloop-0.1.0 → rigorloop-0.2.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  33. {rigorloop-0.1.0 → rigorloop-0.2.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  34. {rigorloop-0.1.0 → rigorloop-0.2.0}/.github/dependabot.yml +0 -0
  35. {rigorloop-0.1.0 → rigorloop-0.2.0}/.github/workflows/ci.yml +0 -0
  36. {rigorloop-0.1.0 → rigorloop-0.2.0}/.github/workflows/release.yml +0 -0
  37. {rigorloop-0.1.0 → rigorloop-0.2.0}/.pre-commit-config.yaml +0 -0
  38. {rigorloop-0.1.0 → rigorloop-0.2.0}/CODING_STYLE.md +0 -0
  39. {rigorloop-0.1.0 → rigorloop-0.2.0}/LICENSE +0 -0
  40. {rigorloop-0.1.0 → rigorloop-0.2.0}/SECURITY.md +0 -0
  41. {rigorloop-0.1.0 → rigorloop-0.2.0}/examples/contact-cards/examples.jsonl +0 -0
  42. {rigorloop-0.1.0 → rigorloop-0.2.0}/examples/contact-cards/task.md +0 -0
  43. {rigorloop-0.1.0 → rigorloop-0.2.0}/justfile +0 -0
  44. {rigorloop-0.1.0 → rigorloop-0.2.0}/scripts/live-smoke.sh +0 -0
  45. {rigorloop-0.1.0 → rigorloop-0.2.0}/src/rigorloop/__init__.py +0 -0
  46. {rigorloop-0.1.0 → rigorloop-0.2.0}/src/rigorloop/core/__init__.py +0 -0
  47. {rigorloop-0.1.0 → rigorloop-0.2.0}/src/rigorloop/core/dataset_calcs.py +0 -0
  48. {rigorloop-0.1.0 → rigorloop-0.2.0}/src/rigorloop/core/scoring_calcs.py +0 -0
  49. {rigorloop-0.1.0 → rigorloop-0.2.0}/src/rigorloop/py.typed +0 -0
  50. {rigorloop-0.1.0 → rigorloop-0.2.0}/src/rigorloop/shell/__init__.py +0 -0
  51. {rigorloop-0.1.0 → rigorloop-0.2.0}/src/rigorloop/shell/agent_calls.py +0 -0
  52. {rigorloop-0.1.0 → rigorloop-0.2.0/stale_markdowns}/PLAN.md +0 -0
  53. {rigorloop-0.1.0 → rigorloop-0.2.0}/tests/__init__.py +0 -0
  54. {rigorloop-0.1.0 → rigorloop-0.2.0}/tests/test_agent_calls.py +0 -0
  55. {rigorloop-0.1.0 → rigorloop-0.2.0}/tests/test_dataset_calcs.py +0 -0
  56. {rigorloop-0.1.0 → rigorloop-0.2.0}/tests/test_report_calcs.py +0 -0
  57. {rigorloop-0.1.0 → rigorloop-0.2.0}/tests/test_scoring_calcs.py +0 -0
  58. {rigorloop-0.1.0 → rigorloop-0.2.0}/uv.lock +0 -0
@@ -221,3 +221,5 @@ __marimo__/
221
221
  src/rigorloop/_version.py
222
222
  runs/
223
223
  .uv-cache/
224
+ stage_markdowns/
225
+ stage_markdowns
@@ -0,0 +1,121 @@
1
+ # AGENTS.md
2
+
3
+ Orientation for coding agents working in this repository. Read this first, then
4
+ read **[`CODING_STYLE.md`](CODING_STYLE.md)** before writing or modifying any
5
+ code — its rules are hard constraints, not preferences.
6
+
7
+ ## What this package is
8
+
9
+ **RigorLoop** is a statistically-sound agentic loop-engineering framework. You give it a
10
+ task description, a pile of gold-standard input/output examples, and a set of
11
+ checks; it runs agentic loops (a strategy agent directing concurrent executor
12
+ agents) that iteratively build a solution and evaluate it on a strict
13
+ **dev / validation / test** split, so the final score is trustworthy. The
14
+ produced artifact is portable: an executable Python script, an agent skill
15
+ (`SKILL.md`), or a guidance file (`AGENTS.md`/`CLAUDE.md`).
16
+
17
+ - Pure Python, **stdlib-only** by design (no runtime dependencies), Python ≥ 3.12.
18
+ - Ships a single CLI: `rigorloop` (`init` / `check` / `run` / `report`).
19
+ - Invokes agents headless and tool-less via the `claude` CLI (`claude -p`).
20
+ - User-facing docs: [`README.md`](README.md). Contributing: [`CONTRIBUTING.md`](CONTRIBUTING.md).
21
+
22
+ ## Architecture: functional core / imperative shell
23
+
24
+ This is the single most important thing to understand, and it is enforced by
25
+ [`CODING_STYLE.md`](CODING_STYLE.md):
26
+
27
+ - **`src/rigorloop/core/`** — the functional core. 100% pure: no I/O, no
28
+ mutation, no time, no randomness, no network, no environment access. It
29
+ *decides*; it returns values and plans of effects. Testable with plain inputs
30
+ and zero mocks. Core coverage is held to a higher bar (≥95%).
31
+ - **`src/rigorloop/shell/`** — the thin imperative shell. Performs all effects
32
+ (filesystem, subprocess, the `claude` CLI) and hands plain data to the core.
33
+ Keep it small.
34
+
35
+ The dev/val/test split is encoded in the type system (`DevExample`,
36
+ `ValExample`, `TestExample`) so leaking holdout data into an agent-context
37
+ prompt is a *type error*, not a runtime bug. Don't defeat this.
38
+
39
+ ## Working in this repo
40
+
41
+ - Dev commands live in the [`justfile`](justfile), each mirroring a CI job:
42
+ `just lint`, `just typecheck`, `just test`, `just check` (all three), `just fmt`.
43
+ - Tooling: `uv` for env/build, `ruff` (lint + format), `mypy --strict`, `pytest`.
44
+ `T20` (print) is banned in the core and allowed in the shell.
45
+ - Every source module has a sibling test in `tests/` (e.g. `scoring_calcs.py` →
46
+ `test_scoring_calcs.py`); `test_leakage.py` guards the split-type invariant and
47
+ `test_e2e.py` runs full loops against fake agents.
48
+
49
+ ## Folder tree
50
+
51
+ ```
52
+ RigorLoop/
53
+ ├── AGENTS.md # this file
54
+ ├── CODING_STYLE.md # MANDATORY coding rules (read before editing)
55
+ ├── CONTRIBUTING.md # contributor workflow
56
+ ├── README.md # user-facing overview & docs
57
+ ├── CHANGELOG.md
58
+ ├── SECURITY.md # runs generated code locally — read this
59
+ ├── LICENSE # MIT
60
+ ├── justfile # dev entry points (mirror CI jobs)
61
+ ├── pyproject.toml # package metadata + tool config
62
+ ├── uv.lock
63
+ ├── .pre-commit-config.yaml
64
+ ├── .gitignore
65
+
66
+ ├── src/
67
+ │ └── rigorloop/
68
+ │ ├── __init__.py # package version resolution
69
+ │ ├── py.typed # PEP 561 typing marker
70
+ │ ├── core/ # FUNCTIONAL CORE — pure, effect-free
71
+ │ │ ├── __init__.py
72
+ │ │ ├── types.py # domain model: algebraic data types (products + sums)
73
+ │ │ ├── config_calcs.py # parse rigorloop.toml → typed RunConfig
74
+ │ │ ├── dataset_calcs.py # parse examples, dedup, split, manifests, power warnings
75
+ │ │ ├── prompt_calcs.py # prompt builders (agent-context vs. evaluation channels)
76
+ │ │ ├── scoring_calcs.py # checks, aggregation, Wilson/bootstrap CIs, McNemar
77
+ │ │ ├── strategy_calcs.py # validation cohorts/cadence, stopping rules, champion selection
78
+ │ │ └── report_calcs.py # render report, check summary, budget estimate
79
+ │ └── shell/ # IMPERATIVE SHELL — effects at the edges
80
+ │ ├── __init__.py
81
+ │ ├── cli.py # argparse entry point + orchestration driver
82
+ │ ├── agent_calls.py # claude CLI subprocess wrapper, retries, concurrency
83
+ │ └── io_actions.py # run dir, artifact persist/reload, sandboxed exec
84
+
85
+ ├── tests/ # one test module per source module + leakage/e2e
86
+ │ ├── __init__.py
87
+ │ ├── conftest.py
88
+ │ ├── test_config_calcs.py
89
+ │ ├── test_dataset_calcs.py
90
+ │ ├── test_prompt_calcs.py
91
+ │ ├── test_scoring_calcs.py
92
+ │ ├── test_strategy_calcs.py
93
+ │ ├── test_report_calcs.py
94
+ │ ├── test_agent_calls.py
95
+ │ ├── test_io_actions.py
96
+ │ ├── test_cli.py
97
+ │ ├── test_leakage.py # asserts split types prevent holdout leakage
98
+ │ └── test_e2e.py # full runs against fake agents
99
+
100
+ ├── examples/
101
+ │ └── contact-cards/ # toy project; exactly what `rigorloop init` scaffolds
102
+ │ ├── rigorloop.toml
103
+ │ ├── task.md
104
+ │ └── examples.jsonl
105
+
106
+ ├── scripts/
107
+ │ └── live-smoke.sh # live smoke test against the real claude CLI
108
+
109
+ ├── .github/ # CI/release workflows, issue/PR templates, dependabot
110
+ │ ├── workflows/
111
+ │ │ ├── ci.yml
112
+ │ │ └── release.yml
113
+ │ ├── ISSUE_TEMPLATE/
114
+ │ │ ├── bug_report.md
115
+ │ │ └── feature_request.md
116
+ │ ├── PULL_REQUEST_TEMPLATE.md
117
+ │ └── dependabot.yml
118
+
119
+ └── .claude/
120
+ └── settings.json # Claude Code project settings & safety hooks
121
+ ```
@@ -0,0 +1,87 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file, following
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
5
+ [SemVer](https://semver.org/) (`0.x`: minor = features/breaking, patch = fixes).
6
+
7
+ ## [v0.2.0] - 2026-07-10
8
+
9
+ ### Changed
10
+
11
+ - **Validation now steers the search, not just final selection.** Previously
12
+ the raw dev leaderboard chose both the artifact each loop refined and the
13
+ only candidate ever validated, so an overfit dev leader could monopolize the
14
+ run while a better generalizer was never measured on validation:
15
+ - The base artifact for refinement (`base_on_champion`) and the strategy
16
+ prompt's primary "champion" are now the **validation champion** once any
17
+ candidate has been validated (the dev leader before that). A diverging dev
18
+ leader is still shown, as a clearly labeled diagnostic line with an
19
+ overfit warning — aggregate scores only, never its content.
20
+ - Each validation checkpoint evaluates a **precommitted cohort** (new
21
+ `validation.cohort_size` knob, default 2): the top unvalidated candidates
22
+ by dev score, with the last slot reserved for the best unvalidated
23
+ candidate not built on the champion (approach diversity). `max_peeks` now
24
+ budgets individual candidate evaluations, and an already-validated dev
25
+ leader no longer stalls validation of newer candidates.
26
+ - Within the McNemar noise band, champion selection tie-breaks on the
27
+ validation pass rate instead of the dev pass rate (dev is the one metric
28
+ under direct selection pressure). Significant wins still gate the
29
+ plateau/patience rule, which now counts checkpoint loops rather than
30
+ individual cohort evaluations.
31
+ - `target_pass_rate` early stopping requires the validation score's Wilson
32
+ lower confidence bound to clear the target, not the raw point estimate.
33
+ - Per-candidate dev failure samples are persisted
34
+ (`failure_samples.json` in each candidate directory) and the champion's
35
+ are reloaded every loop, so the strategy agent keeps concrete
36
+ counterexamples across non-improving loops and `--resume`.
37
+ - The report's selection-bias caveat now states that validation both steered
38
+ the search and selected the winner (the untouched test set remains the
39
+ honest number).
40
+
41
+ ## [v0.1.1] - 2026-07-9
42
+
43
+ Documentation and repository-hygiene only; no changes to the `rigorloop`
44
+ package or its behavior.
45
+
46
+ ### Added
47
+
48
+ - `AGENTS.md`: an orientation guide for coding agents, describing the package,
49
+ the functional-core / imperative-shell architecture (pointing at
50
+ `CODING_STYLE.md` as the binding rules), and a full annotated folder tree.
51
+
52
+ ### Changed
53
+
54
+ - `CONTRIBUTING.md`: expanded the maintainer *Releases* section to document the
55
+ tag-driven flow — `hatch-vcs` derives the version from the `vX.Y.Z` tag (no
56
+ file to bump), the `[Unreleased]` → `[X.Y.Z]` changelog promotion, OIDC
57
+ Trusted Publishing, prerelease/TestPyPI rehearsal, and the "publish a tag
58
+ exactly once, fix forward" rule.
59
+
60
+ ### Removed
61
+
62
+ - Internal planning docs `PLAN.md`, `PACKAGING_PLAN.md`, and `CLAUDE.md` removed
63
+ from the repository (superseded by the shipped implementation and the
64
+ README/CONTRIBUTING docs).
65
+
66
+ ## [0.1.0] - 2026-07-08
67
+
68
+ ### Added
69
+
70
+ - Initial implementation of the full RigorLoop protocol:
71
+ - `rigorloop init | check | run | report` CLI.
72
+ - Deterministic dev/validation/test splitting with content-hash manifests,
73
+ exact-duplicate collapsing, and statistical power warnings.
74
+ - Strategy agent / executor agent loop over the `claude` CLI (headless,
75
+ tool-less), with strict output contracts, reformat retries, and a
76
+ fallback path for malformed strategy replies.
77
+ - Checks: exact/normalized/JSON/regex/numeric matching, custom Python
78
+ checkers, and n-sample majority-vote LLM judges.
79
+ - Statistics: Wilson intervals, seeded bootstrap CIs, exact McNemar paired
80
+ tests; CI-band-gated champion selection, budgeted validation peeks, and
81
+ a one-shot final test evaluation with a selection-bias caveat in the
82
+ report.
83
+ - Solution kinds: executable Python scripts, agent skills (SKILL.md), and
84
+ guidance markdown (AGENTS.md/CLAUDE.md style).
85
+ - Resumable run directories (`runs/<run_id>/`) with split-drift protection.
86
+ - Type-level and test-level leakage guards keeping holdout data out of all
87
+ agent-context prompts.
@@ -72,7 +72,33 @@ Two invariants are non-negotiable and covered by dedicated tests in
72
72
 
73
73
  ## Releases (maintainer)
74
74
 
75
- 1. Ensure `main` is green and `CHANGELOG.md` has the new section.
76
- 2. `git tag vX.Y.Z && git push origin vX.Y.Z`.
77
- 3. Approve the `pypi` environment deployment.
78
- 4. Verify `pip install rigorloop==X.Y.Z` in a scratch venv.
75
+ The git tag **is** the version: `hatch-vcs` derives it from the latest
76
+ `vX.Y.Z` tag, so there is no version number to bump in any file. The only file
77
+ you edit for a release is `CHANGELOG.md`; pushing the tag does the rest.
78
+
79
+ 1. Land the change the normal way — branch, PR, `just check` green, add an
80
+ entry under `## [Unreleased]` in `CHANGELOG.md`, merge to `main`.
81
+ 2. On `main` (`git checkout main && git pull`), promote the changelog: rename
82
+ `[Unreleased]` to `[X.Y.Z] - YYYY-MM-DD`, add a fresh empty `[Unreleased]`
83
+ above it, commit, and push. The tag will point at this commit, so `main`
84
+ must be green first (branch protection requires `ci-ok`).
85
+ 3. Tag and push — this triggers `release.yml`:
86
+ ```bash
87
+ git tag vX.Y.Z && git push origin vX.Y.Z
88
+ ```
89
+ 4. Approve the `pypi` environment deployment when prompted (the publish job is
90
+ gated on manual approval). The workflow then rebuilds from the tag,
91
+ publishes to PyPI via OIDC Trusted Publishing (no token), and cuts a GitHub
92
+ Release with generated notes and the built artifacts.
93
+ 5. Verify `pip install rigorloop==X.Y.Z` in a scratch venv, then
94
+ `rigorloop --version`.
95
+
96
+ Version choice while `0.x`: **patch** (`v0.1.1`) = fixes only; **minor**
97
+ (`v0.2.0`) = features or breaking changes (call breakage out in the changelog);
98
+ `v1.0.0` when `rigorloop.toml`, the CLI, and the run-directory format are
99
+ declared stable.
100
+
101
+ A version publishes to PyPI exactly once — never re-push or edit a tag. If a
102
+ release fails partway, fix forward with a new patch tag rather than reusing the
103
+ old one. To rehearse the full pipeline first, push a prerelease tag
104
+ (e.g. `v0.2.0rc1`) against TestPyPI.
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rigorloop
3
- Version: 0.1.0
4
- Summary: Statistically-sound agentic build framework: dev/val/test-split loops that produce code artifacts without overfitting.
3
+ Version: 0.2.0
4
+ Summary: Statistically-sound agentic loop-engineering framework: dev/val/test-split loops that produce code artifacts without overfitting.
5
5
  Project-URL: Homepage, https://github.com/ronikobrosly/RigorLoop
6
6
  Project-URL: Issues, https://github.com/ronikobrosly/RigorLoop/issues
7
7
  Project-URL: Changelog, https://github.com/ronikobrosly/RigorLoop/blob/main/CHANGELOG.md
@@ -22,9 +22,13 @@ Classifier: Typing :: Typed
22
22
  Requires-Python: >=3.12
23
23
  Description-Content-Type: text/markdown
24
24
 
25
+ <p align="center">
26
+ <img src="logo.webp" alt="RigorLoop logo" width="800">
27
+ </p>
28
+
25
29
  # RigorLoop
26
30
 
27
- A statistically-sound agentic build framework. You give it a task description,
31
+ A statistically-sound agentic loop-engineering framework. You give it a task description,
28
32
  a pile of gold-standard input/output examples, and a set of checks; it runs
29
33
  agentic loops (a strategy agent directing concurrent executor agents) that
30
34
  iteratively build and refine a solution — and it evaluates that solution the
@@ -150,9 +154,12 @@ dev_examples_in_prompt = 30 # examples each builder sees (resampled per l
150
154
 
151
155
  [validation]
152
156
  val_every = 3 # scheduled validation checkpoint cadence
153
- max_peeks = 10 # total validation evaluations allowed per run
154
- patience = 2 # checkpoints without real improvement => stop
155
- target_pass_rate = 0.95 # optional: stop early on hitting this on validation
157
+ max_peeks = 10 # total candidate validation evaluations per run
158
+ cohort_size = 2 # candidates validated per checkpoint: top dev
159
+ # scorers + one diverse (non-champion-based) slot
160
+ patience = 2 # checkpoint loops without real improvement => stop
161
+ target_pass_rate = 0.95 # optional: stop early once the validation score's
162
+ # lower confidence bound clears this
156
163
 
157
164
  [agents]
158
165
  model = "claude-sonnet-5" # model for all agent roles
@@ -168,9 +175,12 @@ The knobs that matter most in practice:
168
175
  executed locally (cheap); skills/guidance are evaluated by running a model
169
176
  per example (expensive — check the budget estimate).
170
177
  - **`max_loops` × `executors_per_loop`** — your primary cost lever.
171
- - **`max_peeks` / `patience`** — how often the loop is allowed to look at the
172
- validation set, and how long it tolerates no genuine (beyond-noise)
173
- improvement before stopping.
178
+ - **`max_peeks` / `cohort_size` / `patience`** — how many candidate evaluations
179
+ the loop may spend on the validation set, how many candidates each checkpoint
180
+ compares, and how long the loop tolerates no genuine (beyond-noise)
181
+ improvement before stopping. For expensive artifact kinds (skill/guidance),
182
+ every validation evaluation is a model call per example — keep `cohort_size`
183
+ small and watch the budget estimate.
174
184
  - **`seed`** — makes the split reproducible; changing it reshuffles which
175
185
  examples land in the holdout.
176
186
 
@@ -180,11 +190,21 @@ The knobs that matter most in practice:
180
190
  resumed run can never quietly reshuffle it.
181
191
  - The building agents only ever see **dev** examples. Validation scores reach
182
192
  the strategy agent only as aggregates; test examples reach no agent, ever.
193
+ - The artifact each loop refines is the **validation champion** — the
194
+ candidate with the best evidence of generalizing — not the raw dev leader.
195
+ Each checkpoint validates a precommitted cohort (the top unvalidated dev
196
+ candidates plus one diverse alternative not built on the champion), so a
197
+ candidate that is slightly worse on dev but generalizes better still gets
198
+ discovered. The dev leaderboard is a diagnostic, not a selection rule.
183
199
  - "Improved" always means *beyond the statistical noise band* (paired tests),
184
- not just a higher number — so the loop doesn't chase luck.
185
- - The validation set (capped, counted peeks) picks the winner; the **test set
186
- is evaluated exactly once**, at the very end, and that is the number to
187
- report.
200
+ not just a higher number — so the loop doesn't chase luck. Early stopping on
201
+ `target_pass_rate` likewise requires the validation score's *lower
202
+ confidence bound* to clear the target, not a lucky point estimate.
203
+ - The validation set (capped, counted peeks) steers the search and picks the
204
+ winner — which is ordinary model selection, but it means the winner's
205
+ validation score is optimistically biased. That is exactly what the **test
206
+ set** is for: it is evaluated **exactly once**, at the very end, and that is
207
+ the number to report.
188
208
 
189
209
  ## ⚠️ The final test set is only honest once
190
210
 
@@ -213,6 +233,87 @@ A complete toy project (contact-card text → JSON) lives in
213
233
  [`examples/contact-cards/`](examples/contact-cards/) — it is exactly what
214
234
  `rigorloop init` scaffolds.
215
235
 
236
+
237
+ ## FAQ
238
+
239
+ ### When should I use this?
240
+
241
+ When all three of these are true:
242
+
243
+ 1. **The task is a transformation with a checkable right answer** — messy
244
+ text in, structured output out (extraction, normalization, tagging,
245
+ classification-with-a-format) — and "correct" can be expressed as checks.
246
+ 2. **You have, or can collect, a real pile of gold examples** — dozens at
247
+ minimum, ideally 100+ — that are representative of the inputs you'll see
248
+ in production.
249
+ 3. **What you care about is performance on *new* inputs**, and you need a
250
+ score you can quote without an asterisk.
251
+
252
+ If any of these is false, don't reach for RigorLoop. No examples means there
253
+ is nothing to split and nothing to measure honestly. And if the goal is a
254
+ deterministic script that passes a fixed handful of unit tests, a single
255
+ coding-agent session is cheaper and better — see
256
+ [When to use it (and when not to)](#when-to-use-it-and-when-not-to).
257
+
258
+ ### Can't standalone frontier agents do this? Why should I use this?
259
+
260
+ A frontier agent can absolutely write the solution — RigorLoop's executors
261
+ *are* frontier agents doing exactly that. What a standalone session can't
262
+ give you is a score you can trust. Hand an agent your examples and ask it to
263
+ iterate until things pass, and it grades its own homework: "98% accurate"
264
+ means 98% on the very examples it tuned against, which says little about the
265
+ next thousand inputs. There is no held-out data, no accounting of how many
266
+ times it peeked, and no way to tell a real improvement from a lucky one.
267
+
268
+ RigorLoop is the harness around those agents that supplies the missing
269
+ discipline:
270
+
271
+ - **a dev / validation / test split**, enforced up front — building agents
272
+ only ever see dev examples, and the test set is scored exactly once, at
273
+ the end;
274
+ - **statistics instead of vibes** — confidence intervals on every score,
275
+ paired tests so "improved" means beyond the noise band, and a counted,
276
+ capped budget of validation peeks;
277
+ - **search instead of one shot** — a strategy agent directing concurrent
278
+ executors across many loops, keeping the candidate with the best evidence
279
+ of *generalizing* rather than the one that got luckiest on dev.
280
+
281
+ You could rebuild all of that by hand around a chat session. RigorLoop is
282
+ that machinery, prebuilt and honest by construction.
283
+
284
+ ### What do I do if the solution the loop converges to still isn't accurate enough?
285
+
286
+ First, believe the number — that's the point of the tool. An honest 74% is
287
+ worth more than the inflated score a self-graded loop would have reported,
288
+ because it tells you the problem isn't solved yet. Then work through this
289
+ list, roughly cheapest first:
290
+
291
+ 1. **Read `report.md`.** The per-check breakdown and loop history usually
292
+ show *how* it fails: one check dominating the failures, scores plateauing
293
+ after a couple of loops, or confidence intervals so wide the run couldn't
294
+ tell candidates apart.
295
+ 2. **Check your checks.** An `exact_match` where you meant
296
+ `normalized_match` or `json_equality`, or a vague `llm_judge` rubric, can
297
+ make a good solution look bad.
298
+ 3. **Sharpen `task.md`.** Failing examples often reveal rules and edge cases
299
+ the description never stated. Spell them out.
300
+ 4. **Add more — and more representative — examples.** Small sets cap what
301
+ the loop can even detect (heed the power warnings from
302
+ `rigorloop check`), and noisy labels cap the ceiling no solution can
303
+ exceed.
304
+ 5. **Raise the budget.** More `max_loops`, `executors_per_loop`, and
305
+ `max_peeks` buy a wider search; a stronger model in `[agents]` buys
306
+ better builders.
307
+ 6. **Reconsider `solution_kind`.** A deterministic script may be too rigid
308
+ for a fuzzy task; a `skill` or `guidance` artifact puts a model in the
309
+ loop at inference time (at a much higher evaluation cost).
310
+ 7. **Decompose the task.** Two simple transformations chained often beat one
311
+ complicated one.
312
+
313
+ One caution: the moment you iterate *after* seeing a test score, that test
314
+ set is spent — bring fresh, never-before-used examples for the next run's
315
+ holdout (see "The final test set is only honest once" above).
316
+
216
317
  ## Contributing & development
217
318
 
218
319
  See [`CONTRIBUTING.md`](CONTRIBUTING.md). The design plan is in `PLAN.md`, the
@@ -1,6 +1,10 @@
1
+ <p align="center">
2
+ <img src="logo.webp" alt="RigorLoop logo" width="800">
3
+ </p>
4
+
1
5
  # RigorLoop
2
6
 
3
- A statistically-sound agentic build framework. You give it a task description,
7
+ A statistically-sound agentic loop-engineering framework. You give it a task description,
4
8
  a pile of gold-standard input/output examples, and a set of checks; it runs
5
9
  agentic loops (a strategy agent directing concurrent executor agents) that
6
10
  iteratively build and refine a solution — and it evaluates that solution the
@@ -126,9 +130,12 @@ dev_examples_in_prompt = 30 # examples each builder sees (resampled per l
126
130
 
127
131
  [validation]
128
132
  val_every = 3 # scheduled validation checkpoint cadence
129
- max_peeks = 10 # total validation evaluations allowed per run
130
- patience = 2 # checkpoints without real improvement => stop
131
- target_pass_rate = 0.95 # optional: stop early on hitting this on validation
133
+ max_peeks = 10 # total candidate validation evaluations per run
134
+ cohort_size = 2 # candidates validated per checkpoint: top dev
135
+ # scorers + one diverse (non-champion-based) slot
136
+ patience = 2 # checkpoint loops without real improvement => stop
137
+ target_pass_rate = 0.95 # optional: stop early once the validation score's
138
+ # lower confidence bound clears this
132
139
 
133
140
  [agents]
134
141
  model = "claude-sonnet-5" # model for all agent roles
@@ -144,9 +151,12 @@ The knobs that matter most in practice:
144
151
  executed locally (cheap); skills/guidance are evaluated by running a model
145
152
  per example (expensive — check the budget estimate).
146
153
  - **`max_loops` × `executors_per_loop`** — your primary cost lever.
147
- - **`max_peeks` / `patience`** — how often the loop is allowed to look at the
148
- validation set, and how long it tolerates no genuine (beyond-noise)
149
- improvement before stopping.
154
+ - **`max_peeks` / `cohort_size` / `patience`** — how many candidate evaluations
155
+ the loop may spend on the validation set, how many candidates each checkpoint
156
+ compares, and how long the loop tolerates no genuine (beyond-noise)
157
+ improvement before stopping. For expensive artifact kinds (skill/guidance),
158
+ every validation evaluation is a model call per example — keep `cohort_size`
159
+ small and watch the budget estimate.
150
160
  - **`seed`** — makes the split reproducible; changing it reshuffles which
151
161
  examples land in the holdout.
152
162
 
@@ -156,11 +166,21 @@ The knobs that matter most in practice:
156
166
  resumed run can never quietly reshuffle it.
157
167
  - The building agents only ever see **dev** examples. Validation scores reach
158
168
  the strategy agent only as aggregates; test examples reach no agent, ever.
169
+ - The artifact each loop refines is the **validation champion** — the
170
+ candidate with the best evidence of generalizing — not the raw dev leader.
171
+ Each checkpoint validates a precommitted cohort (the top unvalidated dev
172
+ candidates plus one diverse alternative not built on the champion), so a
173
+ candidate that is slightly worse on dev but generalizes better still gets
174
+ discovered. The dev leaderboard is a diagnostic, not a selection rule.
159
175
  - "Improved" always means *beyond the statistical noise band* (paired tests),
160
- not just a higher number — so the loop doesn't chase luck.
161
- - The validation set (capped, counted peeks) picks the winner; the **test set
162
- is evaluated exactly once**, at the very end, and that is the number to
163
- report.
176
+ not just a higher number — so the loop doesn't chase luck. Early stopping on
177
+ `target_pass_rate` likewise requires the validation score's *lower
178
+ confidence bound* to clear the target, not a lucky point estimate.
179
+ - The validation set (capped, counted peeks) steers the search and picks the
180
+ winner — which is ordinary model selection, but it means the winner's
181
+ validation score is optimistically biased. That is exactly what the **test
182
+ set** is for: it is evaluated **exactly once**, at the very end, and that is
183
+ the number to report.
164
184
 
165
185
  ## ⚠️ The final test set is only honest once
166
186
 
@@ -189,6 +209,87 @@ A complete toy project (contact-card text → JSON) lives in
189
209
  [`examples/contact-cards/`](examples/contact-cards/) — it is exactly what
190
210
  `rigorloop init` scaffolds.
191
211
 
212
+
213
+ ## FAQ
214
+
215
+ ### When should I use this?
216
+
217
+ When all three of these are true:
218
+
219
+ 1. **The task is a transformation with a checkable right answer** — messy
220
+ text in, structured output out (extraction, normalization, tagging,
221
+ classification-with-a-format) — and "correct" can be expressed as checks.
222
+ 2. **You have, or can collect, a real pile of gold examples** — dozens at
223
+ minimum, ideally 100+ — that are representative of the inputs you'll see
224
+ in production.
225
+ 3. **What you care about is performance on *new* inputs**, and you need a
226
+ score you can quote without an asterisk.
227
+
228
+ If any of these is false, don't reach for RigorLoop. No examples means there
229
+ is nothing to split and nothing to measure honestly. And if the goal is a
230
+ deterministic script that passes a fixed handful of unit tests, a single
231
+ coding-agent session is cheaper and better — see
232
+ [When to use it (and when not to)](#when-to-use-it-and-when-not-to).
233
+
234
+ ### Can't standalone frontier agents do this? Why should I use this?
235
+
236
+ A frontier agent can absolutely write the solution — RigorLoop's executors
237
+ *are* frontier agents doing exactly that. What a standalone session can't
238
+ give you is a score you can trust. Hand an agent your examples and ask it to
239
+ iterate until things pass, and it grades its own homework: "98% accurate"
240
+ means 98% on the very examples it tuned against, which says little about the
241
+ next thousand inputs. There is no held-out data, no accounting of how many
242
+ times it peeked, and no way to tell a real improvement from a lucky one.
243
+
244
+ RigorLoop is the harness around those agents that supplies the missing
245
+ discipline:
246
+
247
+ - **a dev / validation / test split**, enforced up front — building agents
248
+ only ever see dev examples, and the test set is scored exactly once, at
249
+ the end;
250
+ - **statistics instead of vibes** — confidence intervals on every score,
251
+ paired tests so "improved" means beyond the noise band, and a counted,
252
+ capped budget of validation peeks;
253
+ - **search instead of one shot** — a strategy agent directing concurrent
254
+ executors across many loops, keeping the candidate with the best evidence
255
+ of *generalizing* rather than the one that got luckiest on dev.
256
+
257
+ You could rebuild all of that by hand around a chat session. RigorLoop is
258
+ that machinery, prebuilt and honest by construction.
259
+
260
+ ### What do I do if the solution the loop converges to still isn't accurate enough?
261
+
262
+ First, believe the number — that's the point of the tool. An honest 74% is
263
+ worth more than the inflated score a self-graded loop would have reported,
264
+ because it tells you the problem isn't solved yet. Then work through this
265
+ list, roughly cheapest first:
266
+
267
+ 1. **Read `report.md`.** The per-check breakdown and loop history usually
268
+ show *how* it fails: one check dominating the failures, scores plateauing
269
+ after a couple of loops, or confidence intervals so wide the run couldn't
270
+ tell candidates apart.
271
+ 2. **Check your checks.** An `exact_match` where you meant
272
+ `normalized_match` or `json_equality`, or a vague `llm_judge` rubric, can
273
+ make a good solution look bad.
274
+ 3. **Sharpen `task.md`.** Failing examples often reveal rules and edge cases
275
+ the description never stated. Spell them out.
276
+ 4. **Add more — and more representative — examples.** Small sets cap what
277
+ the loop can even detect (heed the power warnings from
278
+ `rigorloop check`), and noisy labels cap the ceiling no solution can
279
+ exceed.
280
+ 5. **Raise the budget.** More `max_loops`, `executors_per_loop`, and
281
+ `max_peeks` buy a wider search; a stronger model in `[agents]` buys
282
+ better builders.
283
+ 6. **Reconsider `solution_kind`.** A deterministic script may be too rigid
284
+ for a fuzzy task; a `skill` or `guidance` artifact puts a model in the
285
+ loop at inference time (at a much higher evaluation cost).
286
+ 7. **Decompose the task.** Two simple transformations chained often beat one
287
+ complicated one.
288
+
289
+ One caution: the moment you iterate *after* seeing a test score, that test
290
+ set is spent — bring fresh, never-before-used examples for the next run's
291
+ holdout (see "The final test set is only honest once" above).
292
+
192
293
  ## Contributing & development
193
294
 
194
295
  See [`CONTRIBUTING.md`](CONTRIBUTING.md). The design plan is in `PLAN.md`, the
@@ -15,9 +15,10 @@ executors_per_loop = 4
15
15
  dev_examples_in_prompt = 30
16
16
 
17
17
  [validation]
18
- val_every = 3
19
- max_peeks = 10
20
- patience = 2
18
+ val_every = 3
19
+ max_peeks = 10
20
+ cohort_size = 2
21
+ patience = 2
21
22
  target_pass_rate = 0.95
22
23
 
23
24
  [agents]
Binary file
@@ -5,7 +5,7 @@ build-backend = "hatchling.build"
5
5
  [project]
6
6
  name = "rigorloop"
7
7
  dynamic = ["version"]
8
- description = "Statistically-sound agentic build framework: dev/val/test-split loops that produce code artifacts without overfitting."
8
+ description = "Statistically-sound agentic loop-engineering framework: dev/val/test-split loops that produce code artifacts without overfitting."
9
9
  readme = "README.md"
10
10
  license = "MIT"
11
11
  license-files = ["LICENSE"]
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.1.0'
22
- __version_tuple__ = version_tuple = (0, 1, 0)
21
+ __version__ = version = '0.2.0'
22
+ __version_tuple__ = version_tuple = (0, 2, 0)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -176,6 +176,9 @@ def _parse_validation(data: dict[str, object]) -> Result[ValidationConfig, Confi
176
176
  patience = _get_int(table, "patience", "validation.patience", 2)
177
177
  if isinstance(patience, Err):
178
178
  return patience
179
+ cohort_size = _get_int(table, "cohort_size", "validation.cohort_size", 2)
180
+ if isinstance(cohort_size, Err):
181
+ return cohort_size
179
182
  target: Option[float] = NOTHING
180
183
  if "target_pass_rate" in table:
181
184
  parsed = _get_float(table, "target_pass_rate", "validation.target_pass_rate", 0.0)
@@ -185,7 +188,14 @@ def _parse_validation(data: dict[str, object]) -> Result[ValidationConfig, Confi
185
188
  return Err(InvalidValue("validation.target_pass_rate", "must be in (0, 1]"))
186
189
  target = Some(parsed.value)
187
190
  return Ok(
188
- ValidationConfig(val_every.value, max_peeks.value, min_gap.value, patience.value, target)
191
+ ValidationConfig(
192
+ val_every.value,
193
+ max_peeks.value,
194
+ min_gap.value,
195
+ patience.value,
196
+ target,
197
+ cohort_size.value,
198
+ )
189
199
  )
190
200
 
191
201