rigorloop 0.1.0__tar.gz → 0.1.1__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.1/AGENTS.md +121 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/CHANGELOG.md +24 -1
- {rigorloop-0.1.0 → rigorloop-0.1.1}/CONTRIBUTING.md +30 -4
- {rigorloop-0.1.0 → rigorloop-0.1.1}/PKG-INFO +1 -1
- {rigorloop-0.1.0 → rigorloop-0.1.1}/src/rigorloop/_version.py +2 -2
- rigorloop-0.1.0/CLAUDE.md +0 -39
- rigorloop-0.1.0/PACKAGING_PLAN.md +0 -292
- rigorloop-0.1.0/PLAN.md +0 -535
- {rigorloop-0.1.0 → rigorloop-0.1.1}/.claude/settings.json +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/.github/dependabot.yml +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/.github/workflows/ci.yml +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/.github/workflows/release.yml +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/.gitignore +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/.pre-commit-config.yaml +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/CODING_STYLE.md +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/LICENSE +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/README.md +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/SECURITY.md +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/examples/contact-cards/examples.jsonl +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/examples/contact-cards/rigorloop.toml +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/examples/contact-cards/task.md +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/justfile +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/pyproject.toml +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/scripts/live-smoke.sh +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/src/rigorloop/__init__.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/src/rigorloop/core/__init__.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/src/rigorloop/core/config_calcs.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/src/rigorloop/core/dataset_calcs.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/src/rigorloop/core/prompt_calcs.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/src/rigorloop/core/report_calcs.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/src/rigorloop/core/scoring_calcs.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/src/rigorloop/core/strategy_calcs.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/src/rigorloop/core/types.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/src/rigorloop/py.typed +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/src/rigorloop/shell/__init__.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/src/rigorloop/shell/agent_calls.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/src/rigorloop/shell/cli.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/src/rigorloop/shell/io_actions.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/tests/__init__.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/tests/conftest.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/tests/test_agent_calls.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/tests/test_cli.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/tests/test_config_calcs.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/tests/test_dataset_calcs.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/tests/test_e2e.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/tests/test_io_actions.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/tests/test_leakage.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/tests/test_prompt_calcs.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/tests/test_report_calcs.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/tests/test_scoring_calcs.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/tests/test_strategy_calcs.py +0 -0
- {rigorloop-0.1.0 → rigorloop-0.1.1}/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 build 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 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
|
+
```
|
|
@@ -4,7 +4,30 @@ All notable changes to this project are documented in this file, following
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
|
|
5
5
|
[SemVer](https://semver.org/) (`0.x`: minor = features/breaking, patch = fixes).
|
|
6
6
|
|
|
7
|
-
## [
|
|
7
|
+
## [v0.1.1]
|
|
8
|
+
|
|
9
|
+
Documentation and repository-hygiene only; no changes to the `rigorloop`
|
|
10
|
+
package or its behavior.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `AGENTS.md`: an orientation guide for coding agents, describing the package,
|
|
15
|
+
the functional-core / imperative-shell architecture (pointing at
|
|
16
|
+
`CODING_STYLE.md` as the binding rules), and a full annotated folder tree.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- `CONTRIBUTING.md`: expanded the maintainer *Releases* section to document the
|
|
21
|
+
tag-driven flow — `hatch-vcs` derives the version from the `vX.Y.Z` tag (no
|
|
22
|
+
file to bump), the `[Unreleased]` → `[X.Y.Z]` changelog promotion, OIDC
|
|
23
|
+
Trusted Publishing, prerelease/TestPyPI rehearsal, and the "publish a tag
|
|
24
|
+
exactly once, fix forward" rule.
|
|
25
|
+
|
|
26
|
+
### Removed
|
|
27
|
+
|
|
28
|
+
- Internal planning docs `PLAN.md`, `PACKAGING_PLAN.md`, and `CLAUDE.md` removed
|
|
29
|
+
from the repository (superseded by the shipped implementation and the
|
|
30
|
+
README/CONTRIBUTING docs).
|
|
8
31
|
|
|
9
32
|
## [0.1.0] - 2026-07-08
|
|
10
33
|
|
|
@@ -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,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rigorloop
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: Statistically-sound agentic build 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
|
|
@@ -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.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 1,
|
|
21
|
+
__version__ = version = '0.1.1'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 1)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
rigorloop-0.1.0/CLAUDE.md
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# CLAUDE.md for RigorLoop
|
|
2
|
-
|
|
3
|
-
## Project overview
|
|
4
|
-
|
|
5
|
-
This a is statistically-sound agentic build framework that employs agentic loops to create code artifacts (whether a script, a skill markdown file, etc). Crucially, it splits verification data into the classic data science-like dev, validation, and final test sets to avoid overfitting.
|
|
6
|
-
|
|
7
|
-
This framework IS NOT meant to be used when someone wants to create an agentic engineering loop to produce a simple, deterministic script that will pass a series of simple unit tests. Instead, it is meant to be used for data science-like efforts like when the user needs to create a coding solution to take a set of inputs (structured or unstructured text data) and convert it into a set of structured outputs. The user would provide this frame with a (hopefully) large set of gold-standard, correct example inputs and outputs, and this agentic framework attempts to create a coding solution through statistically-rigorous loops and verifications.
|
|
8
|
-
|
|
9
|
-
The "coding solution" can take a number of forms:
|
|
10
|
-
|
|
11
|
-
- a set of executable scripts
|
|
12
|
-
- a "skill" for an agentic coding harness (e.g. Claude Skills)
|
|
13
|
-
- a markdown file to guide agents (e.g. AGENTS.md, CLAUDE.md, etc.)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
## Requirements
|
|
17
|
-
|
|
18
|
-
* A strict splitting of a "development", "validation", and "final test" set must be made out of the input data the user provides.
|
|
19
|
-
* The user must provide structured input text with the expected output too. The user must provide a set of either deterministic or probablistic verifications/checks of the proposed solutions. This framework must flexibly allow for whatever the user provides (within realistic bounds and with guidelines). The examples the user provides should be highly-representative of the universe of inputs and outputs they might encounter.
|
|
20
|
-
* The framework will generate a series of solutions that iteratively improve through a series of loops.
|
|
21
|
-
* The framework will employ a strategy agent (with its own prompt) and a series of execution agents (with their own prompts). The strategy agent will review each iterations results on the "development" set, and periodically spin up an agent to test the best approach on the "validation" set. Each execution agent will only be able to see the current loop's strategy and not know about prior loops. The one sanctioned exception: the strategy agent may embed the current champion solution's *content* in a directive as a refinement starting point — solution content only, never scores, mistakes, or per-example failures from prior loops.
|
|
22
|
-
* Protection must be taken to prevent data leakage among the agents: e.g. an execution agent sees all mistakes from prior loops. Again, the idea is that the strategy agent understands what works and doesn't work, loop-to-loop, keeps a log for itself, and farms out the pure execution to a set of concurrent executor agents. This leakage guarantee applies to *agent-context* prompts (strategy and executor). *Evaluation* prompts — running a solution-under-test or an LLM judge on a single example — necessarily embed that example (whatever its split) and are a separate, sanctioned channel whose outputs return only to the harness as scores.
|
|
23
|
-
* The final loop will produce the best performing coding solution, which should be able to be copied by the user, used outside of this framework, and produce good results for them on new input data.
|
|
24
|
-
* Claude will be called in headless model and without tools (`claude -p --tools ""`) to spin up the above agents.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
## Comprehensive implementation plan
|
|
28
|
-
|
|
29
|
-
See the plan in `PLAN.md`
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
## Coding style
|
|
33
|
-
|
|
34
|
-
See the rules and guidelines described in `CODING_STYLE.md`
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
## Packaging as an open source release
|
|
38
|
-
|
|
39
|
-
See the `PACKAGING_PLAN.md` file for instructions on how to setup the packaging around this framework. Includes details on dependencies, the `pyproject.toml` file, linting, testing, and deploying to PyPi.
|
|
@@ -1,292 +0,0 @@
|
|
|
1
|
-
# RigorLoop Packaging & Release Plan
|
|
2
|
-
|
|
3
|
-
How RigorLoop will be packaged as an open-source Python package, tested and
|
|
4
|
-
gated in CI via GitHub Actions, and published directly to PyPI. This document
|
|
5
|
-
complements `PLAN.md` (which covers the software itself) and inherits its
|
|
6
|
-
decisions: Python ≥ 3.12, `src/` layout, stdlib-only runtime dependencies,
|
|
7
|
-
console script `rigorloop`, and a `pytest` + `ruff` + `mypy --strict` quality
|
|
8
|
-
gate.
|
|
9
|
-
|
|
10
|
-
Verified starting facts: the repo is MIT-licensed (LICENSE, 2026, Roni
|
|
11
|
-
Kobrosly) and the name `rigorloop` is currently **unclaimed on PyPI**
|
|
12
|
-
(`pypi.org/pypi/rigorloop/json` returns 404). Claiming the name early — via a
|
|
13
|
-
first `0.0.1` release — is step one of the rollout (§9).
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## 1. Distribution model at a glance
|
|
18
|
-
|
|
19
|
-
| Concern | Choice | Rationale |
|
|
20
|
-
|---|---|---|
|
|
21
|
-
| Package name | `rigorloop` | Matches repo and CLI; available on PyPI. |
|
|
22
|
-
| Build backend | `hatchling` | Modern, zero-config for `src/` layouts, wide adoption, plays well with dynamic versioning. |
|
|
23
|
-
| Versioning | Dynamic from git tags via `hatch-vcs` | One source of truth (the `vX.Y.Z` tag), no version-bump commits; the tag that triggers a release *is* the version. |
|
|
24
|
-
| Version scheme | SemVer, starting `0.x` | `0.x` signals pre-stable API; `1.0.0` when the config format and CLI stabilize. |
|
|
25
|
-
| Runtime deps | **None** (stdlib only) | Per `PLAN.md`; makes the wheel trivially installable and keeps supply-chain surface minimal. |
|
|
26
|
-
| Dev workflow tool | `uv` with a committed `uv.lock` | Fast, reproducible dev/CI environments; plain `pip` remains supported (§3). |
|
|
27
|
-
| Dev deps | PEP 735 `[dependency-groups]` in `pyproject.toml` | The standard successor to `requirements-dev.txt`; understood by `uv` and `pip ≥ 25.1`. No separate requirements files needed. |
|
|
28
|
-
| Publishing | PyPI **Trusted Publishing** (OIDC) via `pypa/gh-action-pypi-publish` | No long-lived API tokens in repo secrets; publish rights are bound to a specific repo + workflow + environment. |
|
|
29
|
-
| Artifacts | sdist + pure-Python wheel (`py3-none-any`) | No compiled code; a single universal wheel. |
|
|
30
|
-
| Typing | `py.typed` marker shipped in the wheel | The package is strictly typed; downstream users get full type information. |
|
|
31
|
-
| License metadata | SPDX `license = "MIT"` + `license-files` | Current PEP 639 style. |
|
|
32
|
-
|
|
33
|
-
## 2. `pyproject.toml` (the single packaging file)
|
|
34
|
-
|
|
35
|
-
Everything — metadata, build config, dev dependencies, and tool configuration —
|
|
36
|
-
lives in `pyproject.toml`. No `setup.py`, `setup.cfg`, `requirements*.txt`,
|
|
37
|
-
`mypy.ini`, `pytest.ini`, or `.ruff.toml`.
|
|
38
|
-
|
|
39
|
-
```toml
|
|
40
|
-
[build-system]
|
|
41
|
-
requires = ["hatchling", "hatch-vcs"]
|
|
42
|
-
build-backend = "hatchling.build"
|
|
43
|
-
|
|
44
|
-
[project]
|
|
45
|
-
name = "rigorloop"
|
|
46
|
-
dynamic = ["version"]
|
|
47
|
-
description = "Statistically-sound agentic build framework: dev/val/test-split loops that produce code artifacts without overfitting."
|
|
48
|
-
readme = "README.md"
|
|
49
|
-
license = "MIT"
|
|
50
|
-
license-files = ["LICENSE"]
|
|
51
|
-
authors = [{ name = "Roni Kobrosly", email = "roni.kobrosly@gmail.com" }]
|
|
52
|
-
requires-python = ">=3.12"
|
|
53
|
-
dependencies = [] # stdlib-only, by design
|
|
54
|
-
keywords = ["agents", "llm", "evaluation", "claude", "data-science"]
|
|
55
|
-
classifiers = [
|
|
56
|
-
"Development Status :: 3 - Alpha",
|
|
57
|
-
"Intended Audience :: Developers",
|
|
58
|
-
"Intended Audience :: Science/Research",
|
|
59
|
-
"Operating System :: OS Independent",
|
|
60
|
-
"Programming Language :: Python :: 3",
|
|
61
|
-
"Programming Language :: Python :: 3.12",
|
|
62
|
-
"Programming Language :: Python :: 3.13",
|
|
63
|
-
"Programming Language :: Python :: 3.14",
|
|
64
|
-
"Topic :: Software Development :: Code Generators",
|
|
65
|
-
"Typing :: Typed",
|
|
66
|
-
]
|
|
67
|
-
|
|
68
|
-
[project.urls]
|
|
69
|
-
Homepage = "https://github.com/ronikobrosly/RigorLoop"
|
|
70
|
-
Issues = "https://github.com/ronikobrosly/RigorLoop/issues"
|
|
71
|
-
Changelog = "https://github.com/ronikobrosly/RigorLoop/blob/main/CHANGELOG.md"
|
|
72
|
-
|
|
73
|
-
[project.scripts]
|
|
74
|
-
rigorloop = "rigorloop.shell.cli:main"
|
|
75
|
-
|
|
76
|
-
[tool.hatch.version]
|
|
77
|
-
source = "vcs" # version = latest vX.Y.Z git tag
|
|
78
|
-
|
|
79
|
-
[tool.hatch.build.hooks.vcs]
|
|
80
|
-
version-file = "src/rigorloop/_version.py" # generated; git-ignored
|
|
81
|
-
|
|
82
|
-
# ---- development dependencies (PEP 735) ----
|
|
83
|
-
[dependency-groups]
|
|
84
|
-
dev = [
|
|
85
|
-
"pytest>=8",
|
|
86
|
-
"pytest-cov>=5",
|
|
87
|
-
"hypothesis>=6", # property-style tests for splitting/scoring math
|
|
88
|
-
"mypy>=1.16", # the exhaustive-match error code (§4) needs ≥1.16
|
|
89
|
-
"ruff>=0.8",
|
|
90
|
-
"pre-commit>=4",
|
|
91
|
-
]
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
Runtime version lookup uses `importlib.metadata.version("rigorloop")` (for
|
|
95
|
-
`rigorloop --version`), with the generated `_version.py` as the in-repo
|
|
96
|
-
fallback for editable installs.
|
|
97
|
-
|
|
98
|
-
## 3. Environments and "requirements files"
|
|
99
|
-
|
|
100
|
-
There are deliberately **no `requirements*.txt` files**. The complete story:
|
|
101
|
-
|
|
102
|
-
- **Runtime:** `dependencies = []`. Nothing to pin.
|
|
103
|
-
- **Development:** the `dev` dependency group above, resolved and locked in a
|
|
104
|
-
committed **`uv.lock`**. Setup is one command: `uv sync` (creates `.venv`,
|
|
105
|
-
installs the package editable plus the dev group, honoring the lock).
|
|
106
|
-
- **Without uv:** `pip install -e . --group dev` (pip ≥ 25.1) — same group,
|
|
107
|
-
unlocked resolution. Documented in `CONTRIBUTING.md` as the fallback path.
|
|
108
|
-
- **CI:** uses `uv sync --locked`, which fails if `uv.lock` is stale — so CI
|
|
109
|
-
always runs the exact locked toolchain and the lock can't silently drift.
|
|
110
|
-
|
|
111
|
-
Tool-version pinning for reproducibility lives in exactly two places:
|
|
112
|
-
`uv.lock` (Python tools) and `.pre-commit-config.yaml` (hook revisions).
|
|
113
|
-
Dependabot keeps both fresh (§6).
|
|
114
|
-
|
|
115
|
-
## 4. Development tools and their configuration
|
|
116
|
-
|
|
117
|
-
All tool config in `pyproject.toml`:
|
|
118
|
-
|
|
119
|
-
| Tool | Role | Key configuration |
|
|
120
|
-
|---|---|---|
|
|
121
|
-
| **ruff** (lint) | Correctness + style lints, import sorting | `target-version = "py312"`; rule sets: `E,W,F` (pycodestyle/pyflakes), `I` (isort), `UP` (pyupgrade), `B` (bugbear), `C4` (comprehensions), `SIM` (simplify), `RUF`. Notably `T20` (no stray `print`) with a per-file ignore for `src/rigorloop/shell/**` — printing is a shell effect, forbidden only in the core. |
|
|
122
|
-
| **ruff format** | Formatting (Black-compatible) | Defaults; line length 100. |
|
|
123
|
-
| **mypy** | Strict static typing — load-bearing per `PLAN.md` (exhaustive `match`, split-type leakage guards) | `strict = true`, `warn_unreachable = true`, `enable_error_code = ["exhaustive-match", "ignore-without-code"]`; zero untyped code, including tests. |
|
|
124
|
-
| **pytest** | Test runner | `testpaths = ["tests"]`; markers `unit`, `integration`, `e2e`; `addopts = "--strict-markers"`. |
|
|
125
|
-
| **pytest-cov / coverage** | Coverage measurement | `source = ["rigorloop"]`, branch coverage on; **fail-under gates: 95% for `src/rigorloop/core/`** (pure functions have no excuse), 80% overall. Reported in CI job summary; no external coverage service initially. |
|
|
126
|
-
| **hypothesis** | Property-based tests | For `dataset_calcs` invariants (disjoint/exhaustive/deterministic splits) and `scoring_calcs` math (CI bounds, aggregation identities). |
|
|
127
|
-
| **pre-commit** | Local fast gate mirroring CI | Hooks: `ruff check --fix`, `ruff format`, `mypy` (via local hook running `uv run mypy`), plus hygiene hooks (end-of-file, trailing whitespace, TOML/YAML syntax). |
|
|
128
|
-
|
|
129
|
-
A `justfile` (or `Makefile` — pick one, `justfile` preferred) gives memorable
|
|
130
|
-
entry points that match CI exactly: `just lint`, `just typecheck`, `just test`,
|
|
131
|
-
`just check` (all three), `just build`.
|
|
132
|
-
|
|
133
|
-
## 5. Repository additions for open source
|
|
134
|
-
|
|
135
|
-
```
|
|
136
|
-
.
|
|
137
|
-
├── pyproject.toml
|
|
138
|
-
├── uv.lock
|
|
139
|
-
├── justfile
|
|
140
|
-
├── .pre-commit-config.yaml
|
|
141
|
-
├── CHANGELOG.md # Keep a Changelog format; updated per release
|
|
142
|
-
├── CONTRIBUTING.md # setup (uv + pip paths), quality gates,
|
|
143
|
-
│ # pointer to CODING_STYLE.md as hard rules
|
|
144
|
-
├── SECURITY.md # supported versions + private reporting via
|
|
145
|
-
│ # GitHub security advisories; note that
|
|
146
|
-
│ # RigorLoop executes generated code (PLAN §7)
|
|
147
|
-
├── .github/
|
|
148
|
-
│ ├── workflows/
|
|
149
|
-
│ │ ├── ci.yml
|
|
150
|
-
│ │ └── release.yml
|
|
151
|
-
│ ├── dependabot.yml
|
|
152
|
-
│ ├── ISSUE_TEMPLATE/{bug_report.md, feature_request.md}
|
|
153
|
-
│ └── PULL_REQUEST_TEMPLATE.md
|
|
154
|
-
└── src/rigorloop/
|
|
155
|
-
├── py.typed # ships in the wheel
|
|
156
|
-
└── _version.py # generated by hatch-vcs; in .gitignore
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
README gets the standard badge row (CI status, PyPI version, Python versions,
|
|
160
|
-
license) once the first release is out.
|
|
161
|
-
|
|
162
|
-
## 6. CI workflow — `.github/workflows/ci.yml`
|
|
163
|
-
|
|
164
|
-
Triggers: every PR, and pushes to `main`. Concurrency group cancels superseded
|
|
165
|
-
runs on the same ref. All jobs pin actions to major versions and set
|
|
166
|
-
`permissions: contents: read` at the workflow level.
|
|
167
|
-
|
|
168
|
-
Jobs:
|
|
169
|
-
|
|
170
|
-
1. **lint** (ubuntu, Python 3.12): `uv sync --locked` →
|
|
171
|
-
`ruff check .` → `ruff format --check .`.
|
|
172
|
-
2. **typecheck** (ubuntu, 3.12): `mypy src tests` (strict).
|
|
173
|
-
3. **test** — matrix: `python: [3.12, 3.13, 3.14]` × `os: [ubuntu-latest,
|
|
174
|
-
macos-latest]` (the shell drives subprocesses, so POSIX coverage on both;
|
|
175
|
-
Windows is explicitly unsupported in v1 — `claude` CLI and sandboxing
|
|
176
|
-
assumptions are POSIX). Runs `pytest --cov` with coverage gates; uploads
|
|
177
|
-
the coverage summary to the job summary. Uses `astral-sh/setup-uv` with
|
|
178
|
-
built-in cache.
|
|
179
|
-
4. **build** (ubuntu, 3.12): `uv build` (sdist + wheel) → `twine check
|
|
180
|
-
--strict dist/*` → install the built wheel into a fresh venv and run
|
|
181
|
-
`rigorloop --version` and `python -c "import rigorloop"` — catches
|
|
182
|
-
packaging mistakes (missing `py.typed`, broken entry point, bad metadata)
|
|
183
|
-
on every PR, not at release time. Uploads `dist/` as a workflow artifact.
|
|
184
|
-
5. **ci-ok** — a single no-op job that `needs:` all of the above; the one
|
|
185
|
-
required status check for branch protection (matrix-proof).
|
|
186
|
-
|
|
187
|
-
Notes:
|
|
188
|
-
|
|
189
|
-
- `actions/checkout` with `fetch-depth: 0` everywhere — `hatch-vcs` needs tag
|
|
190
|
-
history to compute the version.
|
|
191
|
-
- No `claude` CLI in CI: unit/integration/E2E tests use the stub CLI and fake
|
|
192
|
-
agent function from `PLAN.md` §11. Live smoke tests against real `claude`
|
|
193
|
-
stay local/manual.
|
|
194
|
-
|
|
195
|
-
## 7. Release workflow — `.github/workflows/release.yml`
|
|
196
|
-
|
|
197
|
-
Trigger: push of a tag matching `v[0-9]+.[0-9]+.[0-9]+*` (e.g. `v0.3.0`).
|
|
198
|
-
|
|
199
|
-
```
|
|
200
|
-
jobs:
|
|
201
|
-
build: # rebuild from the tag; never reuse CI artifacts
|
|
202
|
-
- uv build
|
|
203
|
-
- twine check --strict dist/*
|
|
204
|
-
- upload dist/ as artifact
|
|
205
|
-
|
|
206
|
-
publish-pypi:
|
|
207
|
-
needs: build
|
|
208
|
-
environment: pypi # GitHub environment, protection rules on
|
|
209
|
-
permissions:
|
|
210
|
-
id-token: write # OIDC for Trusted Publishing — no token
|
|
211
|
-
steps:
|
|
212
|
-
- download dist/ artifact
|
|
213
|
-
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
214
|
-
|
|
215
|
-
github-release:
|
|
216
|
-
needs: publish-pypi
|
|
217
|
-
permissions:
|
|
218
|
-
contents: write
|
|
219
|
-
steps:
|
|
220
|
-
- create GitHub Release from the tag with auto-generated notes
|
|
221
|
-
plus the matching CHANGELOG.md section; attach dist/*
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
Security posture:
|
|
225
|
-
|
|
226
|
-
- **Trusted Publishing** (OIDC): on PyPI, register the publisher as repo
|
|
227
|
-
`ronikobrosly/RigorLoop`, workflow `release.yml`, environment `pypi`. No
|
|
228
|
-
`PYPI_API_TOKEN` secret ever exists.
|
|
229
|
-
- The `pypi` GitHub environment carries protection rules (required reviewer:
|
|
230
|
-
the maintainer) so a pushed tag alone can't publish without a human click —
|
|
231
|
-
cheap insurance for a solo-maintainer project.
|
|
232
|
-
- Publish job contains *only* download-and-publish; it never runs the test
|
|
233
|
-
suite or arbitrary project code with the OIDC token available.
|
|
234
|
-
- Tags are pushed by the maintainer; branch protection on `main` requires the
|
|
235
|
-
`ci-ok` check, so anything tagged has passed CI.
|
|
236
|
-
|
|
237
|
-
Optional (recommended) pre-flight: a `publish-testpypi` job that runs on
|
|
238
|
-
prerelease tags (`v*rc*`) against `test.pypi.org` with its own trusted
|
|
239
|
-
publisher + `testpypi` environment, verifying the full pipeline before the
|
|
240
|
-
first real release.
|
|
241
|
-
|
|
242
|
-
## 8. Release process (maintainer runbook)
|
|
243
|
-
|
|
244
|
-
1. Ensure `main` is green and `CHANGELOG.md` has an entry for the new version
|
|
245
|
-
(a small `ci.yml` step warns on PRs that touch `src/` without touching
|
|
246
|
-
`CHANGELOG.md` — advisory, not blocking).
|
|
247
|
-
2. `git tag v0.3.0 && git push origin v0.3.0`.
|
|
248
|
-
3. Approve the `pypi` environment deployment when prompted.
|
|
249
|
-
4. Workflow publishes to PyPI and cuts the GitHub Release.
|
|
250
|
-
5. Verify: `pip install rigorloop==0.3.0` in a scratch venv; `rigorloop
|
|
251
|
-
--version`.
|
|
252
|
-
|
|
253
|
-
Version semantics while `0.x`: minor bump = features or breaking changes
|
|
254
|
-
(config format, CLI, artifact layout — call breakage out in the changelog);
|
|
255
|
-
patch bump = fixes only. `1.0.0` when `rigorloop.toml`, the CLI surface, and
|
|
256
|
-
the run-directory format are declared stable.
|
|
257
|
-
|
|
258
|
-
## 9. Rollout milestones
|
|
259
|
-
|
|
260
|
-
Ordered; the first three can happen immediately after `PLAN.md` Phase 1
|
|
261
|
-
(scaffolding) lands, and are prerequisites for everything after.
|
|
262
|
-
|
|
263
|
-
1. **Packaging skeleton** — `pyproject.toml` as specified, `py.typed`,
|
|
264
|
-
`uv.lock`, `justfile`, `.pre-commit-config.yaml`, tool configs.
|
|
265
|
-
*Accept:* `uv sync && just check && uv build && twine check dist/*` all
|
|
266
|
-
pass locally; wheel installs and `rigorloop --version` works.
|
|
267
|
-
2. **CI live** — `ci.yml` + Dependabot config; branch protection on `main`
|
|
268
|
-
requiring `ci-ok`.
|
|
269
|
-
*Accept:* a deliberately-failing PR is blocked; a clean PR is green across
|
|
270
|
-
the full matrix.
|
|
271
|
-
3. **Name claim** — configure the PyPI trusted publisher, then tag `v0.0.1`
|
|
272
|
-
(packaging skeleton, honest "pre-alpha" description) to claim `rigorloop`
|
|
273
|
-
and prove the release pipeline end-to-end.
|
|
274
|
-
*Accept:* `pip install rigorloop` works from PyPI.
|
|
275
|
-
4. **OSS hygiene** — `CONTRIBUTING.md`, `SECURITY.md`, `CHANGELOG.md`, issue/PR
|
|
276
|
-
templates, README badges.
|
|
277
|
-
5. **First real release** — tag `v0.1.0` once `PLAN.md` Phase 7 (full loop
|
|
278
|
-
with validation/finalization) is done.
|
|
279
|
-
6. **Post-1.0 niceties** (deferred): docs site (mkdocs-material) on GitHub
|
|
280
|
-
Pages, Sigstore attestations for releases, Windows support decision,
|
|
281
|
-
conda-forge feedstock if demand appears.
|
|
282
|
-
|
|
283
|
-
## 10. Risks and mitigations
|
|
284
|
-
|
|
285
|
-
| Risk | Mitigation |
|
|
286
|
-
|---|---|
|
|
287
|
-
| Name squatting before first release | Milestone 3 claims `rigorloop` with a `v0.0.1` release as soon as CI is green. |
|
|
288
|
-
| Tag-based publishing fires accidentally | `pypi` environment requires manual approval; strict tag pattern; publishing rights bound to one workflow via OIDC. |
|
|
289
|
-
| `hatch-vcs` version wrong in CI/builds | `fetch-depth: 0` on all checkouts; the build job's wheel-install smoke test asserts `rigorloop --version` matches the expected pattern. |
|
|
290
|
-
| Lock/tool drift breaking new-contributor setup | `uv sync --locked` in CI fails on stale lock; Dependabot PRs keep `uv.lock`, GitHub Actions, and pre-commit hooks current (weekly, grouped). |
|
|
291
|
-
| Matrix cost creep | 3 Python × 2 OS test matrix only; lint/typecheck/build run once on 3.12; concurrency cancellation on superseded pushes. |
|
|
292
|
-
| Supply-chain exposure | Zero runtime deps; dev deps locked; actions pinned; `permissions` minimal per job; no secrets anywhere in the pipeline (OIDC only). |
|
rigorloop-0.1.0/PLAN.md
DELETED
|
@@ -1,535 +0,0 @@
|
|
|
1
|
-
# RigorLoop Implementation Plan
|
|
2
|
-
|
|
3
|
-
This plan describes how to build RigorLoop: a statistically-sound agentic build
|
|
4
|
-
framework that iteratively produces a coding solution (script, agent skill, or
|
|
5
|
-
guidance markdown) from user-provided gold-standard input/output examples, using
|
|
6
|
-
a strict dev / validation / final-test split to avoid overfitting.
|
|
7
|
-
|
|
8
|
-
It is written against the requirements in `CLAUDE.md` and the architectural
|
|
9
|
-
constraints in `CODING_STYLE.md` (functional core / imperative shell).
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## 1. Goals and non-goals
|
|
14
|
-
|
|
15
|
-
**Goals**
|
|
16
|
-
|
|
17
|
-
- Accept a user-provided task description, a set of gold-standard
|
|
18
|
-
`(input, expected_output)` examples, and a set of verification checks.
|
|
19
|
-
- Split examples into disjoint **dev**, **validation**, and **test** sets, and
|
|
20
|
-
enforce that split throughout the run with type-level and process-level
|
|
21
|
-
leakage guards.
|
|
22
|
-
- Run an iterative loop: a **strategy agent** (with persistent private memory)
|
|
23
|
-
reviews dev-set results and directs a pool of **executor agents** (stateless,
|
|
24
|
-
concurrent, seeing only the current directive) that each produce a candidate
|
|
25
|
-
solution.
|
|
26
|
-
- Score candidates against the dev set with the user's checks; periodically
|
|
27
|
-
evaluate the current best candidate on the validation set; evaluate the final
|
|
28
|
-
chosen solution **exactly once** on the test set.
|
|
29
|
-
- Emit a portable final artifact: the winning solution plus a statistical
|
|
30
|
-
report (scores with confidence intervals on all three sets).
|
|
31
|
-
|
|
32
|
-
**Non-goals**
|
|
33
|
-
|
|
34
|
-
- Building deterministic scripts to pass simple unit tests (explicitly out of
|
|
35
|
-
scope per `CLAUDE.md`).
|
|
36
|
-
- Training or fine-tuning models. All "learning" happens in the strategy
|
|
37
|
-
agent's log and directives.
|
|
38
|
-
- Giving agents tools. Every agent call is `claude -p --tools ""` — headless,
|
|
39
|
-
no tools; the RigorLoop shell performs all execution and I/O itself.
|
|
40
|
-
|
|
41
|
-
## 2. Key decisions (assumptions to confirm)
|
|
42
|
-
|
|
43
|
-
| Decision | Choice | Rationale |
|
|
44
|
-
|---|---|---|
|
|
45
|
-
| Language | Python ≥ 3.12 | `CODING_STYLE.md` examples are Python; `.gitignore` is Python. 3.12 gives `match` statements, `typing.assert_never`, and stdlib `tomllib`. |
|
|
46
|
-
| Dependencies | Stdlib-only core; minimal shell deps | Purity rules make heavy frameworks unhelpful. No FP library — a small handwritten `Result`/`Option` in `core/types.py`. |
|
|
47
|
-
| Example format | JSONL: one `{"input": ..., "expected_output": ...}` per line | Simple, streamable, natural for text-in/structure-out tasks. |
|
|
48
|
-
| Config format | `rigorloop.toml` | Stdlib `tomllib`; human-editable. |
|
|
49
|
-
| Agent invocation | `claude -p --tools "" --output-format json --model <m>` via subprocess | Mandated by `CLAUDE.md`; `--output-format json` gives parseable envelopes (verified against the installed CLI). |
|
|
50
|
-
| Concurrency | `concurrent.futures.ThreadPoolExecutor` around subprocess calls | Executor agents are subprocess-bound; threads are sufficient and simple. |
|
|
51
|
-
| Packaging | `pyproject.toml`, `src/` layout, console script `rigorloop` | Standard modern Python packaging. |
|
|
52
|
-
|
|
53
|
-
## 3. Architecture: functional core / imperative shell
|
|
54
|
-
|
|
55
|
-
Everything that *decides* is a pure function; everything that *acts* is a thin
|
|
56
|
-
shell that executes the decisions. The core never touches I/O, time,
|
|
57
|
-
randomness, the network, or subprocesses — those arrive as plain-data inputs.
|
|
58
|
-
|
|
59
|
-
```
|
|
60
|
-
src/rigorloop/
|
|
61
|
-
├── core/ # 100% pure; testable with zero mocks
|
|
62
|
-
│ ├── types.py # Result/Option, domain ADTs, effect descriptions
|
|
63
|
-
│ ├── dataset_calcs.py # parsing examples, splitting, split manifests
|
|
64
|
-
│ ├── scoring_calcs.py # check evaluation, aggregation, statistics
|
|
65
|
-
│ ├── strategy_calcs.py # context assembly, decision & response parsing,
|
|
66
|
-
│ │ # cadence rules, stopping rules, leaderboard
|
|
67
|
-
│ └── prompt_calcs.py # pure prompt-string builders in two channels:
|
|
68
|
-
│ # agent-context (strategy, executor) and
|
|
69
|
-
│ # evaluation (solution-under-test, judge)
|
|
70
|
-
└── shell/
|
|
71
|
-
├── agent_calls.py # claude subprocess wrapper, concurrency, retries
|
|
72
|
-
├── io_actions.py # run directory, file read/write, sandboxed
|
|
73
|
-
│ # execution of candidate scripts & custom checks
|
|
74
|
-
└── cli.py # argparse entry point; the orchestration loop
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
Notes:
|
|
78
|
-
|
|
79
|
-
- `prompt_calcs.py` and `types.py` extend the layout sketched in
|
|
80
|
-
`CODING_STYLE.md`; the three prescribed core modules and two shell modules
|
|
81
|
-
are kept as named (snake_case per Python convention: `io_actions`).
|
|
82
|
-
- The orchestration loop lives in the shell (`cli.py`), but it is a *dumb
|
|
83
|
-
driver*: at each step it hands current state to a core function and receives
|
|
84
|
-
back a value describing what to do next (an effect description), then
|
|
85
|
-
performs it. The core sequences nothing; it returns plans.
|
|
86
|
-
- Dependencies are injected: the RNG seed, the clock, the run-id generator,
|
|
87
|
-
and the "run an agent" function are parameters, never imported globals.
|
|
88
|
-
Integration tests swap in a fake agent function.
|
|
89
|
-
|
|
90
|
-
## 4. Domain model (core/types.py)
|
|
91
|
-
|
|
92
|
-
All data is frozen dataclasses (products) and tagged unions typed as
|
|
93
|
-
`Union[...]` matched exhaustively with `match`/`assert_never` (sums). No
|
|
94
|
-
`None`-as-maybe: `Option[T]`. No exceptions for expected failure: `Result[T, E]`
|
|
95
|
-
with meaningful error sum types.
|
|
96
|
-
|
|
97
|
-
Key types (illustrative, not exhaustive):
|
|
98
|
-
|
|
99
|
-
```python
|
|
100
|
-
# Foundations
|
|
101
|
-
Option[T] = Some(value) | Nothing
|
|
102
|
-
Result[T, E] = Ok(value) | Err(error)
|
|
103
|
-
|
|
104
|
-
# Examples — the split is encoded in the TYPE, not a field.
|
|
105
|
-
Example = (example_id: str, input_text: str, expected_output: str)
|
|
106
|
-
DevExample = wraps Example # only type agent-context builders accept
|
|
107
|
-
ValExample = wraps Example # never enters any agent-context prompt
|
|
108
|
-
TestExample = wraps Example # never enters any agent-context prompt
|
|
109
|
-
SplitManifest = (seed, ratios, per-split example-id hashes)
|
|
110
|
-
|
|
111
|
-
# What we are building
|
|
112
|
-
SolutionKind = ScriptSolution | SkillSolution | GuidanceSolution
|
|
113
|
-
Candidate = (candidate_id, loop_index, kind, content, directive_id)
|
|
114
|
-
|
|
115
|
-
# Verification checks (user-configured)
|
|
116
|
-
Check = ExactMatch | NormalizedMatch(rules) | JsonEquality
|
|
117
|
-
| RegexMatch(pattern) | NumericTolerance(atol, rtol)
|
|
118
|
-
| CustomPython(script_path) # run by the shell
|
|
119
|
-
| LlmJudge(rubric, n_samples, pass_threshold)
|
|
120
|
-
CheckOutcome = Passed | Failed(reason) | Errored(detail)
|
|
121
|
-
|
|
122
|
-
# Scores
|
|
123
|
-
ExampleResult = (example_id, raw_output, outcomes per check)
|
|
124
|
-
CandidateScore = (pass_rate, ci_low, ci_high, per_check_breakdown, n)
|
|
125
|
-
|
|
126
|
-
# Strategy
|
|
127
|
-
StrategyLogEntry = (loop_index, observations, hypotheses, directives_issued,
|
|
128
|
-
dev_summary, Option[val_summary])
|
|
129
|
-
ChampionArtifact = (candidate_id, kind, content) # solution content ONLY —
|
|
130
|
-
# no scores, no failures
|
|
131
|
-
Directive = (directive_id, approach_summary, instructions,
|
|
132
|
-
base: Option[ChampionArtifact]) # refine champion / explore
|
|
133
|
-
StrategyDecision = Continue(directives, validate: Option[candidate_id])
|
|
134
|
-
| Stop(StopReason) # validation is a field of
|
|
135
|
-
# Continue, not a variant:
|
|
136
|
-
# a peek never stalls a loop
|
|
137
|
-
StopReason = BudgetExhausted | ValidationPlateau | TargetReached
|
|
138
|
-
| StrategyRequestedStop(reason) | StrategyUnresponsive
|
|
139
|
-
|
|
140
|
-
# Prompt channels — distinct types; the §6 leakage guards apply to the first
|
|
141
|
-
AgentContextPrompt = prompt for the strategy or executor agents; builders
|
|
142
|
-
accept Dev-typed examples and aggregate scores only
|
|
143
|
-
EvalPrompt = prompt running a solution-under-test or an LLM judge on
|
|
144
|
-
ONE example (any split); its output returns to the
|
|
145
|
-
harness as data, never into an AgentContextPrompt
|
|
146
|
-
|
|
147
|
-
# Effects — descriptions the core returns and the shell executes
|
|
148
|
-
AgentRequest = (role, prompt, model, timeout_s)
|
|
149
|
-
RunScript = (script_path, stdin_text, timeout_s)
|
|
150
|
-
EffectPlan = list of the above + persistence descriptions
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
**"Parse, don't validate":** the shell reads raw JSONL/TOML and immediately
|
|
154
|
-
calls core parsers that return `Result[TypedThing, ParseError]`. Past that
|
|
155
|
-
boundary the core never re-checks validity.
|
|
156
|
-
|
|
157
|
-
## 5. The run protocol
|
|
158
|
-
|
|
159
|
-
### Phase A — Intake and split (once per run)
|
|
160
|
-
|
|
161
|
-
1. Shell reads `rigorloop.toml` + examples JSONL; core parses both into typed
|
|
162
|
-
config and `list[Example]` (`Result`-returning).
|
|
163
|
-
2. Core detects **exact duplicates** (identical input text) before splitting:
|
|
164
|
-
duplicates are collapsed to one example and a warning with counts is
|
|
165
|
-
surfaced. A duplicate straddling dev and test would silently corrupt the
|
|
166
|
-
holdout. Near-duplicate detection is out of scope for v1 and called out as
|
|
167
|
-
a caveat in the README.
|
|
168
|
-
3. `dataset_calcs.split(examples, ratios, seed)` deterministically partitions
|
|
169
|
-
into dev/val/test (default **60/20/20**, configurable). Pure: the seed is a
|
|
170
|
-
parameter. Guarantees: disjoint, exhaustive, stable for a given seed.
|
|
171
|
-
4. Core emits a `SplitManifest` with content hashes; shell persists it. On
|
|
172
|
-
resume, the manifest is re-verified so a re-run can never reshuffle examples
|
|
173
|
-
across splits (a silent-leakage guard). The manifest also pins the
|
|
174
|
-
configured agent model and `claude` CLI version, because skill/guidance
|
|
175
|
-
scores are conditional on the evaluating model (§8).
|
|
176
|
-
5. Core computes power warnings (see §8) — e.g. "validation set of 12 examples
|
|
177
|
-
can only distinguish pass-rate differences of ~±25 points" — and the shell
|
|
178
|
-
surfaces them before spending tokens.
|
|
179
|
-
|
|
180
|
-
### Phase B — The loop (repeated up to `max_loops`)
|
|
181
|
-
|
|
182
|
-
Each iteration:
|
|
183
|
-
|
|
184
|
-
1. **Strategy turn.** Core assembles the strategy context — full detail for
|
|
185
|
-
the most recent `strategy_full_detail_loops` loops (default 4) with compact
|
|
186
|
-
per-loop summaries beyond that (so the context can't grow without bound),
|
|
187
|
-
the dev leaderboard with confidence intervals, aggregated dev failure
|
|
188
|
-
patterns for recent candidates, the current champion's full solution
|
|
189
|
-
content, and (if a validation was run) the aggregate validation score.
|
|
190
|
-
`prompt_calcs.build_strategy_prompt` renders it. Shell runs the agent; core
|
|
191
|
-
parses the JSON reply into a `StrategyDecision` (`Result`; one
|
|
192
|
-
reformat-retry on parse failure). If the retry also fails, the harness
|
|
193
|
-
substitutes a fallback decision — `Continue` with a single
|
|
194
|
-
refine-the-champion directive — and logs the substitution; two consecutive
|
|
195
|
-
fallbacks end the run with `Stop(StrategyUnresponsive)`.
|
|
196
|
-
2. **Executor fan-out.** For `Continue(directives, _)`, core builds one
|
|
197
|
-
executor prompt per directive: task description, the directive (which may
|
|
198
|
-
embed the champion artifact as a refinement starting point — see §6), the
|
|
199
|
-
output contract, the check descriptions, and a sampled subset of dev
|
|
200
|
-
examples (default `min(30, all)`). The subset is **resampled every loop**,
|
|
201
|
-
deterministically from the injected seed and loop index; the strategy
|
|
202
|
-
prompt states this explicitly so loop-to-loop score movement isn't
|
|
203
|
-
over-attributed to directives when it is partly sample luck. Shell runs the
|
|
204
|
-
K agents concurrently (default 4).
|
|
205
|
-
3. **Materialize & execute.** Core parses each reply's single fenced
|
|
206
|
-
`solution` block into a `Candidate` (malformed → one retry, then recorded
|
|
207
|
-
as a failed candidate, never crashing the loop). Shell materializes and
|
|
208
|
-
evaluates it against every dev example, per solution kind (§7), collecting
|
|
209
|
-
raw outputs as plain data. Evaluation of a candidate **short-circuits**
|
|
210
|
-
after `max_consecutive_eval_failures` consecutive errors/timeouts (default
|
|
211
|
-
5) — a hanging script must not burn dev-set-size × timeout — and the
|
|
212
|
-
candidate is recorded with an aborted evaluation.
|
|
213
|
-
4. **Score.** `scoring_calcs` evaluates deterministic checks purely; for
|
|
214
|
-
`LlmJudge` checks the shell first collects judge verdicts
|
|
215
|
-
(n-sample majority vote via `claude -p --tools ""`), then hands the verdict
|
|
216
|
-
data to the core for aggregation. Output: `CandidateScore` with a Wilson
|
|
217
|
-
interval, plus per-example results.
|
|
218
|
-
5. **Bookkeeping.** Core folds the new scores into the leaderboard and
|
|
219
|
-
produces the next `StrategyLogEntry`; shell persists loop artifacts.
|
|
220
|
-
|
|
221
|
-
### Phase C — Validation checkpoints
|
|
222
|
-
|
|
223
|
-
- Cadence decided by a pure rule in `strategy_calcs`: validate the current
|
|
224
|
-
dev-best candidate every `val_every` loops (default 3), or when a new
|
|
225
|
-
candidate beats the previous dev-best **beyond the paired-test noise band**
|
|
226
|
-
(§8 — raw margins chase noise), or when the strategy agent sets the
|
|
227
|
-
`validate` field of `Continue`. Triggered peeks respect a minimum gap of
|
|
228
|
-
`min_loops_between_peeks` loops (default 2), so the easy improvements of
|
|
229
|
-
early loops can't front-load the peek budget.
|
|
230
|
-
- Hard cap on total validation evaluations (default 10). Every peek at
|
|
231
|
-
validation weakens it as an unbiased signal, so peeks are budgeted, counted,
|
|
232
|
-
and reported.
|
|
233
|
-
- Only the **aggregate** validation score (and dev–val gap) is ever fed back
|
|
234
|
-
to the strategy agent — never raw validation examples or per-example
|
|
235
|
-
failures.
|
|
236
|
-
|
|
237
|
-
### Phase D — Finalization (once)
|
|
238
|
-
|
|
239
|
-
1. Stopping rule fires (budget exhausted, validation plateau over `patience`
|
|
240
|
-
checkpoints — where "improvement" means exceeding the CI band, not any raw
|
|
241
|
-
uptick — target score reached, or strategy stop).
|
|
242
|
-
2. Winner = the reigning validation champion under **noise-aware selection**:
|
|
243
|
-
a challenger displaces the champion only when its validation score exceeds
|
|
244
|
-
the champion's beyond the paired-test noise band (McNemar / paired
|
|
245
|
-
bootstrap, §8); dev score breaks within-band ties. Selecting on validation
|
|
246
|
-
rather than dev is the core anti-overfitting mechanism; requiring the
|
|
247
|
-
noise band blunts the winner's curse of taking a max over noisy peeks.
|
|
248
|
-
3. Shell evaluates the winner on the test set — the only time test examples
|
|
249
|
-
are ever read after splitting. No agent sees test data or test results;
|
|
250
|
-
this is purely a harness computation, run exactly once.
|
|
251
|
-
4. Shell writes `final/`: the solution artifact (directly usable outside the
|
|
252
|
-
framework), plus `report.md` with dev/val/test scores + CIs, the dev–val–test
|
|
253
|
-
gaps, loop history, validation-peek count, per-check breakdowns, an
|
|
254
|
-
explicit note that the winner's validation score is selection-biased (the
|
|
255
|
-
test score is the honest number), and — for skill/guidance kinds — the
|
|
256
|
-
pinned eval model version the scores are conditional on.
|
|
257
|
-
|
|
258
|
-
## 6. Agent roles and leakage controls
|
|
259
|
-
|
|
260
|
-
| | Strategy agent | Executor agents | LLM judge |
|
|
261
|
-
|---|---|---|---|
|
|
262
|
-
| Cardinality | 1, logically persistent across loops (via its log; each call is still stateless `claude -p`) | K per loop, concurrent | n samples per (example, judge check) |
|
|
263
|
-
| Sees | Own prior log (recent loops in full, older compacted), dev leaderboard + CIs, dev failure patterns, the champion's solution content, **aggregate** val scores | Task, current directive (optionally embedding the champion artifact), check descriptions, dev-example sample. **Nothing else about prior loops.** | Rubric, one candidate output, one expected output |
|
|
264
|
-
| Never sees | Raw val/test examples, per-example val results | Other executors' work, strategy log, any val/test data, prior mistakes or per-example failures | Anything else |
|
|
265
|
-
| Produces | JSON `StrategyDecision` | One fenced `solution` block | JSON verdict |
|
|
266
|
-
|
|
267
|
-
Prompts are split into **two typed channels**, and the leakage guarantee is
|
|
268
|
-
scoped to the first:
|
|
269
|
-
|
|
270
|
-
- **Agent-context prompts** (`AgentContextPrompt`: strategy and executor).
|
|
271
|
-
The guarantee applies absolutely: no val/test example content, ever.
|
|
272
|
-
- **Evaluation prompts** (`EvalPrompt`: running a skill/guidance candidate on
|
|
273
|
-
an example, or an LLM judge over one output). These *necessarily* embed the
|
|
274
|
-
example under evaluation — including val/test examples at checkpoint and
|
|
275
|
-
finalization time. That is sanctioned: an `EvalPrompt` is built by a
|
|
276
|
-
separate builder, executed in isolation, and its result returns to the
|
|
277
|
-
harness as plain data. Nothing from the evaluation channel ever reaches an
|
|
278
|
-
agent-context prompt except as aggregate scores.
|
|
279
|
-
|
|
280
|
-
Leakage is then enforced twice:
|
|
281
|
-
|
|
282
|
-
- **By type:** agent-context prompt builders accept only `DevExample` values
|
|
283
|
-
(and aggregate score types). Passing a `ValExample` or `TestExample` is a
|
|
284
|
-
type error; `AgentContextPrompt` and `EvalPrompt` are distinct types, so an
|
|
285
|
-
evaluation prompt cannot flow into an agent call site.
|
|
286
|
-
- **By test:** dedicated tests assert that no *agent-context* prompt produced
|
|
287
|
-
during a simulated full run contains any val/test example content
|
|
288
|
-
(substring scan over every strategy/executor prompt built with a fake agent
|
|
289
|
-
runner).
|
|
290
|
-
|
|
291
|
-
The requirement that "each execution agent only sees the current loop's
|
|
292
|
-
strategy" holds by construction: executor prompts are built from the
|
|
293
|
-
directive alone, and the strategy log is a distinct type that the executor
|
|
294
|
-
prompt builder cannot accept. The **one sanctioned carry-forward channel** is
|
|
295
|
-
the champion artifact: the strategy agent may embed the current best
|
|
296
|
-
solution's *content* in a directive as a refinement starting point, which is
|
|
297
|
-
what makes incremental convergence possible at all. Solution content only —
|
|
298
|
-
never scores, never prior mistakes, never per-example failures. The
|
|
299
|
-
`ChampionArtifact` type carries nothing else, and a dedicated test asserts
|
|
300
|
-
that rendered directives contain no score or failure text.
|
|
301
|
-
|
|
302
|
-
## 7. Solution kinds and how each is evaluated
|
|
303
|
-
|
|
304
|
-
| Kind | Artifact | Evaluation of one example |
|
|
305
|
-
|---|---|---|
|
|
306
|
-
| `ScriptSolution` | Executable Python script | Shell runs it in a subprocess (`RunScript` effect): example input on stdin, structured output expected on stdout, timeout + output-size cap. Non-zero exit / timeout → `Errored`. |
|
|
307
|
-
| `SkillSolution` | Skill markdown (e.g. Claude Skill `SKILL.md`) | Shell runs `claude -p --tools ""` with the skill content injected via `--append-system-prompt` and the example input as the prompt; the reply is the raw output to score. |
|
|
308
|
-
| `GuidanceSolution` | Guidance markdown (AGENTS.md / CLAUDE.md style) | Same harness as skills: guidance prepended as system prompt, input as prompt. |
|
|
309
|
-
|
|
310
|
-
Skill/guidance evaluation and judge calls run in the **evaluation prompt
|
|
311
|
-
channel** (§6) — sanctioned to embed the example under evaluation, whatever
|
|
312
|
-
its split. Two documented caveats for these kinds: (a) evaluation is
|
|
313
|
-
tool-less by design while guidance files in the wild steer tool-using agents,
|
|
314
|
-
so measured transfer to real usage is weaker — noted in the report; (b) a
|
|
315
|
-
candidate's score is a draw from a *stochastic* evaluator, handled
|
|
316
|
-
statistically in §8 and by pinning the eval model version in the manifest.
|
|
317
|
-
|
|
318
|
-
The executor **output contract** is strict and stated in the prompt: exactly
|
|
319
|
-
one fenced block tagged `solution`, nothing executable outside it. The core
|
|
320
|
-
parser returns `Result[Candidate, MalformedReply]`; the shell grants one
|
|
321
|
-
reformat retry.
|
|
322
|
-
|
|
323
|
-
Sandboxing: generated scripts and `CustomPython` checks are untrusted code.
|
|
324
|
-
V1 mitigations: subprocess with hard timeout, no stdin inheritance, output
|
|
325
|
-
caps, and a scratch working directory. Documented loudly as *not* a security
|
|
326
|
-
boundary; OS-level sandboxing is a listed future hardening item (§12).
|
|
327
|
-
|
|
328
|
-
## 8. Statistical methodology
|
|
329
|
-
|
|
330
|
-
This is the "rigor" in RigorLoop; all of it lives in `scoring_calcs.py` as
|
|
331
|
-
pure, individually testable functions.
|
|
332
|
-
|
|
333
|
-
- **Headline pass rate:** an example passes iff **all** configured checks
|
|
334
|
-
pass on it (conjunctive); per-check pass rates are always reported
|
|
335
|
-
alongside so a single strict check can't hide behind the aggregate.
|
|
336
|
-
- **Pass-rate uncertainty:** Wilson score intervals (95%) on every reported
|
|
337
|
-
pass rate — honest at the small n this framework will often see.
|
|
338
|
-
- **Continuous scores** (e.g. judge scores averaged): bootstrap percentile
|
|
339
|
-
CIs; the resample indices derive from an injected seed so results are
|
|
340
|
-
reproducible.
|
|
341
|
-
- **Comparing candidates on the same set:** paired analysis — McNemar's test
|
|
342
|
-
for pass/fail checks, paired bootstrap for continuous scores. The
|
|
343
|
-
leaderboard marks differences that are within noise, and the strategy prompt
|
|
344
|
-
states them as "not statistically distinguishable" so the strategy agent
|
|
345
|
-
doesn't chase noise. With dozens of candidates per run, pairwise 95% flags
|
|
346
|
-
will include some false positives; there is no formal multiple-comparison
|
|
347
|
-
correction in v1, and the strategy-prompt language hedges accordingly.
|
|
348
|
-
- **CI-band-gated improvement:** everywhere the protocol asks "did it
|
|
349
|
-
improve?" — champion switching (§5 D), triggered validation peeks (§5 C),
|
|
350
|
-
and the plateau stopping rule — improvement means exceeding the paired-test
|
|
351
|
-
noise band, never a raw score uptick.
|
|
352
|
-
- **Stochastic evaluators:** for skill/guidance kinds (and judge checks),
|
|
353
|
-
each per-example outcome carries model-sampling variance on top of
|
|
354
|
-
example-sampling variance. v1 evaluates one sample per example, so CIs for
|
|
355
|
-
these kinds are flagged in the leaderboard and report as conditional on the
|
|
356
|
-
pinned eval model and as understating total uncertainty; the manifest pins
|
|
357
|
-
the model and CLI version so every number is attributable.
|
|
358
|
-
- **Judge self-preference:** the same model family builds and judges
|
|
359
|
-
solutions — a documented upward bias that deterministic checks don't share.
|
|
360
|
-
Per-role models (a different judge model) are the post-v1 answer.
|
|
361
|
-
- **Overfitting signal:** dev–val gap tracked per checkpoint and plotted in
|
|
362
|
-
the final report; a widening gap triggers a warning in the strategy context.
|
|
363
|
-
- **Selection & reporting discipline:** noise-aware selection on validation,
|
|
364
|
-
never dev; test evaluated once; the winner's validation score carries an
|
|
365
|
-
explicit selection-bias caveat in the report (test is the honest number);
|
|
366
|
-
validation-peek count reported; power warnings at intake when splits are
|
|
367
|
-
too small to support the configured target margins.
|
|
368
|
-
|
|
369
|
-
## 9. Configuration and CLI
|
|
370
|
-
|
|
371
|
-
```toml
|
|
372
|
-
# rigorloop.toml
|
|
373
|
-
[task]
|
|
374
|
-
description_file = "task.md"
|
|
375
|
-
solution_kind = "script" # script | skill | guidance
|
|
376
|
-
examples_file = "examples.jsonl"
|
|
377
|
-
|
|
378
|
-
[split]
|
|
379
|
-
ratios = [0.6, 0.2, 0.2]
|
|
380
|
-
seed = 17
|
|
381
|
-
|
|
382
|
-
[loop]
|
|
383
|
-
max_loops = 12
|
|
384
|
-
executors_per_loop = 4
|
|
385
|
-
dev_examples_in_prompt = 30 # resampled every loop (seed + loop index)
|
|
386
|
-
max_consecutive_eval_failures = 5 # short-circuit a hanging candidate's eval
|
|
387
|
-
strategy_full_detail_loops = 4 # older loops appear as compact summaries
|
|
388
|
-
|
|
389
|
-
[validation]
|
|
390
|
-
val_every = 3
|
|
391
|
-
max_peeks = 10
|
|
392
|
-
min_loops_between_peeks = 2 # damps triggered-peek front-loading
|
|
393
|
-
patience = 2 # checkpoints without CI-band improvement → stop
|
|
394
|
-
target_pass_rate = 0.95 # optional early-success stop
|
|
395
|
-
|
|
396
|
-
[agents]
|
|
397
|
-
model = "claude-sonnet-5"
|
|
398
|
-
timeout_s = 300
|
|
399
|
-
|
|
400
|
-
[[checks]]
|
|
401
|
-
type = "json_equality"
|
|
402
|
-
|
|
403
|
-
[[checks]]
|
|
404
|
-
type = "llm_judge"
|
|
405
|
-
rubric = "Output captures every entity mentioned in the input..."
|
|
406
|
-
n_samples = 3
|
|
407
|
-
pass_threshold = 0.67
|
|
408
|
-
```
|
|
409
|
-
|
|
410
|
-
CLI (argparse, in `shell/cli.py`):
|
|
411
|
-
|
|
412
|
-
- `rigorloop init` — scaffold `rigorloop.toml`, `task.md`, an example JSONL.
|
|
413
|
-
- `rigorloop check` — parse config/examples, print split sizes + power
|
|
414
|
-
warnings, estimate the agent-call budget **kind-aware**: for skill/guidance
|
|
415
|
-
kinds, candidate evaluation itself costs one `claude` call per dev example
|
|
416
|
-
per candidate — the dominant cost, ahead of judge calls. No tokens spent.
|
|
417
|
-
- `rigorloop run [--resume RUN_ID]` — execute the protocol.
|
|
418
|
-
- `rigorloop report RUN_ID` — re-render the report from persisted artifacts.
|
|
419
|
-
|
|
420
|
-
## 10. Run directory (persistence & resumability)
|
|
421
|
-
|
|
422
|
-
```
|
|
423
|
-
runs/<run_id>/
|
|
424
|
-
├── manifest.json # config snapshot + SplitManifest (hashes)
|
|
425
|
-
│ # + pinned eval model & claude CLI version
|
|
426
|
-
├── splits/{dev,val,test}.jsonl
|
|
427
|
-
├── strategy_log.jsonl # append-only; the strategy agent's memory
|
|
428
|
-
├── leaderboard.json
|
|
429
|
-
├── loops/<n>/
|
|
430
|
-
│ ├── strategy_{prompt,reply}.md
|
|
431
|
-
│ └── candidates/<id>/{solution.*, outputs.jsonl, scores.json}
|
|
432
|
-
└── final/
|
|
433
|
-
├── solution.* # the portable deliverable
|
|
434
|
-
├── report.md
|
|
435
|
-
└── test_results.json
|
|
436
|
-
```
|
|
437
|
-
|
|
438
|
-
Every artifact is plain JSON/JSONL/markdown written by `io_actions.py`
|
|
439
|
-
(append-only where possible). Resume = shell reloads state files, core
|
|
440
|
-
re-derives the in-memory state, loop continues. The split manifest hash check
|
|
441
|
-
on resume prevents dataset drift mid-run.
|
|
442
|
-
|
|
443
|
-
## 11. Testing strategy
|
|
444
|
-
|
|
445
|
-
- **Core, zero mocks (the bulk):** splitting determinism/disjointness/ratios;
|
|
446
|
-
every check evaluator; Wilson/bootstrap/McNemar math against known values;
|
|
447
|
-
strategy & executor reply parsers (valid, malformed, adversarial);
|
|
448
|
-
cadence/stopping rules; prompt builders (golden-file tests).
|
|
449
|
-
- **Leakage tests (first-class):** the §6 substring-scan test over all
|
|
450
|
-
*agent-context* prompts from a simulated run (evaluation-channel prompts
|
|
451
|
-
are exempt by design); a test that rendered directives carry champion
|
|
452
|
-
solution content only — no score or failure text; a test asserting the test
|
|
453
|
-
set is read at most once; type-level guards for both prompt channels
|
|
454
|
-
exercised.
|
|
455
|
-
- **Shell integration (thin):** `agent_calls` against a stub executable that
|
|
456
|
-
mimics `claude -p --output-format json` (success, timeout, garbage output);
|
|
457
|
-
script sandbox timeout/output-cap behavior; run-dir round-trip + resume.
|
|
458
|
-
- **End-to-end with a fake agent function:** a scripted fake plays strategy +
|
|
459
|
-
executors, letting a full multi-loop run (with validation checkpoints and
|
|
460
|
-
finalization) execute in milliseconds with deterministic assertions on the
|
|
461
|
-
final report.
|
|
462
|
-
- Tooling: `pytest`, `ruff`, `mypy --strict` (strict typing is load-bearing —
|
|
463
|
-
it enforces the exhaustive-match and split-type guarantees).
|
|
464
|
-
|
|
465
|
-
## 12. Implementation milestones
|
|
466
|
-
|
|
467
|
-
Each phase ends green: tests pass, `mypy --strict` clean.
|
|
468
|
-
|
|
469
|
-
1. **Scaffolding** — `pyproject.toml`, package layout, CI-ready test harness;
|
|
470
|
-
`core/types.py` (`Result`, `Option`, first domain ADTs).
|
|
471
|
-
*Accept:* `pytest` and `mypy --strict` run clean on the skeleton.
|
|
472
|
-
2. **Dataset core** — example parsing, exact-duplicate detection, splitting,
|
|
473
|
-
manifests, power warnings.
|
|
474
|
-
*Accept:* property-style tests for determinism/disjointness pass.
|
|
475
|
-
3. **Scoring core** — deterministic check evaluators, aggregation, Wilson +
|
|
476
|
-
bootstrap + McNemar.
|
|
477
|
-
*Accept:* statistics validated against hand-computed known values.
|
|
478
|
-
4. **Shell foundations** — `agent_calls.py` (subprocess wrapper, JSON envelope
|
|
479
|
-
parsing → `Result`, retries, concurrency), `io_actions.py` (run dir,
|
|
480
|
-
script sandbox), `cli.py` with `init`/`check`.
|
|
481
|
-
*Accept:* stub-CLI integration tests pass; `rigorloop check` works on a
|
|
482
|
-
sample project.
|
|
483
|
-
5. **Single-loop end-to-end (script kind)** — executor prompt building, reply
|
|
484
|
-
parsing, candidate materialization/execution, dev scoring; one loop with a
|
|
485
|
-
hard-coded strategy directive.
|
|
486
|
-
*Accept:* fake-agent E2E produces a scored leaderboard.
|
|
487
|
-
6. **Strategy loop** — strategy prompts/parsing, strategy log with windowed
|
|
488
|
-
compaction (full detail last N loops), champion carry-forward in
|
|
489
|
-
directives, the strategy fallback path, multi-loop orchestration, leakage
|
|
490
|
-
tests for both prompt channels.
|
|
491
|
-
*Accept:* multi-loop fake-agent E2E; leakage scan test green; a refine
|
|
492
|
-
directive demonstrably embeds the champion artifact.
|
|
493
|
-
7. **Validation & stopping** — checkpoints, peek budget + gap damping,
|
|
494
|
-
CI-band plateau/target stopping, noise-aware winner selection,
|
|
495
|
-
finalization with one-shot test evaluation and `report.md` (including the
|
|
496
|
-
selection-bias caveat).
|
|
497
|
-
*Accept:* E2E run yields full report; test set touched exactly once.
|
|
498
|
-
8. **Remaining kinds & judge checks** — `LlmJudge` (n-sample majority),
|
|
499
|
-
`SkillSolution`/`GuidanceSolution` evaluation harness, `CustomPython`
|
|
500
|
-
checks.
|
|
501
|
-
*Accept:* per-kind E2E with fake agents.
|
|
502
|
-
9. **Hardening & docs** — resume, cost/budget accounting surfaced in `check`
|
|
503
|
-
and the report, README + worked example project (the README must carry the
|
|
504
|
-
cross-run test-reuse warning: re-running after seeing a test score burns
|
|
505
|
-
the holdout), live smoke test against the real `claude` CLI on a toy task.
|
|
506
|
-
|
|
507
|
-
Future (post-v1) items: OS-level sandboxing for generated code, stratified
|
|
508
|
-
splitting, adaptive validation cadence, HTML report.
|
|
509
|
-
|
|
510
|
-
## 13. Risks and mitigations
|
|
511
|
-
|
|
512
|
-
| Risk | Mitigation |
|
|
513
|
-
|---|---|
|
|
514
|
-
| Malformed agent replies break loops | Strict output contracts; `Result`-returning parsers; one reformat retry; failed candidates recorded, never fatal. |
|
|
515
|
-
| Overfitting to dev despite the design | Noise-aware selection on validation; capped + gap-damped peeks; dev–val gap surfaced to strategy agent and report; selection-bias caveat in report; test untouched until the end. |
|
|
516
|
-
| Test set burned by repeated runs | Per-run guarantee only; README warns loudly that iterating after seeing a test score requires fresh holdout examples. |
|
|
517
|
-
| Loops plateau because executors regenerate from scratch | Champion artifact carried forward: strategy agent may embed the best solution's content in a refine directive (content only — never mistakes or per-example failures). |
|
|
518
|
-
| Small datasets → meaningless stats | Power warnings at `check` time; CIs on every number; leaderboard marks statistically indistinguishable differences. |
|
|
519
|
-
| Token/cost blowout | Kind-aware `rigorloop check` pre-run budget estimate; per-candidate eval short-circuiting; per-run call ceiling; loop and executor caps in config. |
|
|
520
|
-
| Untrusted generated code | Timeouts, output caps, scratch dirs; documented as not a security boundary; OS sandboxing on the roadmap. |
|
|
521
|
-
| LLM nondeterminism muddies comparisons | n-sample judge voting; paired statistical tests; reproducible seeds for everything the harness controls. |
|
|
522
|
-
| `claude` CLI flag drift | All flags isolated in one shell function in `agent_calls.py`; stub-CLI tests define the expected envelope. |
|
|
523
|
-
|
|
524
|
-
## 14. Open questions (defaults chosen; flag if wrong)
|
|
525
|
-
|
|
526
|
-
1. **Split ratios** default to 60/20/20 — acceptable default?
|
|
527
|
-
2. **Structured inputs**: v1 treats `input`/`expected_output` as strings
|
|
528
|
-
(JSON-encoded when structured). Native multi-field examples later?
|
|
529
|
-
3. **Model choice**: single configured model for all roles in v1; per-role
|
|
530
|
-
models (cheaper executors, stronger strategist) is an easy follow-on.
|
|
531
|
-
4. **Judge budget**: `LlmJudge` multiplies per-example evaluation cost by
|
|
532
|
-
`n_samples` (and for skill/guidance kinds it stacks on top of the already
|
|
533
|
-
call-per-example evaluation) — is an n-sample majority of 3 per example
|
|
534
|
-
acceptable, or should judges score only failures of deterministic checks
|
|
535
|
-
first (tiered checking)?
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|