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.
- {rigorloop-0.1.0 → rigorloop-0.2.0}/.gitignore +2 -0
- rigorloop-0.2.0/AGENTS.md +121 -0
- rigorloop-0.2.0/CHANGELOG.md +87 -0
- {rigorloop-0.1.0 → rigorloop-0.2.0}/CONTRIBUTING.md +30 -4
- {rigorloop-0.1.0 → rigorloop-0.2.0}/PKG-INFO +114 -13
- {rigorloop-0.1.0 → rigorloop-0.2.0}/README.md +112 -11
- {rigorloop-0.1.0 → rigorloop-0.2.0}/examples/contact-cards/rigorloop.toml +4 -3
- rigorloop-0.2.0/logo.webp +0 -0
- {rigorloop-0.1.0 → rigorloop-0.2.0}/pyproject.toml +1 -1
- {rigorloop-0.1.0 → rigorloop-0.2.0}/src/rigorloop/_version.py +2 -2
- {rigorloop-0.1.0 → rigorloop-0.2.0}/src/rigorloop/core/config_calcs.py +11 -1
- {rigorloop-0.1.0 → rigorloop-0.2.0}/src/rigorloop/core/prompt_calcs.py +24 -11
- {rigorloop-0.1.0 → rigorloop-0.2.0}/src/rigorloop/core/report_calcs.py +8 -4
- {rigorloop-0.1.0 → rigorloop-0.2.0}/src/rigorloop/core/strategy_calcs.py +136 -36
- {rigorloop-0.1.0 → rigorloop-0.2.0}/src/rigorloop/core/types.py +7 -1
- {rigorloop-0.1.0 → rigorloop-0.2.0}/src/rigorloop/shell/cli.py +81 -61
- {rigorloop-0.1.0 → rigorloop-0.2.0}/src/rigorloop/shell/io_actions.py +45 -0
- {rigorloop-0.1.0 → rigorloop-0.2.0/stale_markdowns}/CLAUDE.md +1 -1
- {rigorloop-0.1.0 → rigorloop-0.2.0/stale_markdowns}/PACKAGING_PLAN.md +1 -1
- rigorloop-0.2.0/stale_markdowns/POTENTIAL_PROBLEM.md +379 -0
- {rigorloop-0.1.0 → rigorloop-0.2.0}/tests/conftest.py +12 -1
- {rigorloop-0.1.0 → rigorloop-0.2.0}/tests/test_cli.py +1 -0
- {rigorloop-0.1.0 → rigorloop-0.2.0}/tests/test_config_calcs.py +4 -0
- {rigorloop-0.1.0 → rigorloop-0.2.0}/tests/test_e2e.py +132 -0
- {rigorloop-0.1.0 → rigorloop-0.2.0}/tests/test_io_actions.py +45 -1
- {rigorloop-0.1.0 → rigorloop-0.2.0}/tests/test_leakage.py +12 -1
- {rigorloop-0.1.0 → rigorloop-0.2.0}/tests/test_prompt_calcs.py +26 -10
- {rigorloop-0.1.0 → rigorloop-0.2.0}/tests/test_strategy_calcs.py +219 -17
- rigorloop-0.1.0/CHANGELOG.md +0 -30
- {rigorloop-0.1.0 → rigorloop-0.2.0}/.claude/settings.json +0 -0
- {rigorloop-0.1.0 → rigorloop-0.2.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {rigorloop-0.1.0 → rigorloop-0.2.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {rigorloop-0.1.0 → rigorloop-0.2.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {rigorloop-0.1.0 → rigorloop-0.2.0}/.github/dependabot.yml +0 -0
- {rigorloop-0.1.0 → rigorloop-0.2.0}/.github/workflows/ci.yml +0 -0
- {rigorloop-0.1.0 → rigorloop-0.2.0}/.github/workflows/release.yml +0 -0
- {rigorloop-0.1.0 → rigorloop-0.2.0}/.pre-commit-config.yaml +0 -0
- {rigorloop-0.1.0 → rigorloop-0.2.0}/CODING_STYLE.md +0 -0
- {rigorloop-0.1.0 → rigorloop-0.2.0}/LICENSE +0 -0
- {rigorloop-0.1.0 → rigorloop-0.2.0}/SECURITY.md +0 -0
- {rigorloop-0.1.0 → rigorloop-0.2.0}/examples/contact-cards/examples.jsonl +0 -0
- {rigorloop-0.1.0 → rigorloop-0.2.0}/examples/contact-cards/task.md +0 -0
- {rigorloop-0.1.0 → rigorloop-0.2.0}/justfile +0 -0
- {rigorloop-0.1.0 → rigorloop-0.2.0}/scripts/live-smoke.sh +0 -0
- {rigorloop-0.1.0 → rigorloop-0.2.0}/src/rigorloop/__init__.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.2.0}/src/rigorloop/core/__init__.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.2.0}/src/rigorloop/core/dataset_calcs.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.2.0}/src/rigorloop/core/scoring_calcs.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.2.0}/src/rigorloop/py.typed +0 -0
- {rigorloop-0.1.0 → rigorloop-0.2.0}/src/rigorloop/shell/__init__.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.2.0}/src/rigorloop/shell/agent_calls.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.2.0/stale_markdowns}/PLAN.md +0 -0
- {rigorloop-0.1.0 → rigorloop-0.2.0}/tests/__init__.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.2.0}/tests/test_agent_calls.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.2.0}/tests/test_dataset_calcs.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.2.0}/tests/test_report_calcs.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.2.0}/tests/test_scoring_calcs.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.2.0}/uv.lock +0 -0
|
@@ -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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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.
|
|
4
|
-
Summary: Statistically-sound agentic
|
|
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
|
|
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
|
|
154
|
-
|
|
155
|
-
|
|
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
|
|
172
|
-
validation set,
|
|
173
|
-
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
|
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
|
|
130
|
-
|
|
131
|
-
|
|
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
|
|
148
|
-
validation set,
|
|
149
|
-
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
|
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
|
|
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.
|
|
22
|
-
__version_tuple__ = version_tuple = (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(
|
|
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
|
|