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.
Files changed (55) hide show
  1. rigorloop-0.1.1/AGENTS.md +121 -0
  2. {rigorloop-0.1.0 → rigorloop-0.1.1}/CHANGELOG.md +24 -1
  3. {rigorloop-0.1.0 → rigorloop-0.1.1}/CONTRIBUTING.md +30 -4
  4. {rigorloop-0.1.0 → rigorloop-0.1.1}/PKG-INFO +1 -1
  5. {rigorloop-0.1.0 → rigorloop-0.1.1}/src/rigorloop/_version.py +2 -2
  6. rigorloop-0.1.0/CLAUDE.md +0 -39
  7. rigorloop-0.1.0/PACKAGING_PLAN.md +0 -292
  8. rigorloop-0.1.0/PLAN.md +0 -535
  9. {rigorloop-0.1.0 → rigorloop-0.1.1}/.claude/settings.json +0 -0
  10. {rigorloop-0.1.0 → rigorloop-0.1.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  11. {rigorloop-0.1.0 → rigorloop-0.1.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  12. {rigorloop-0.1.0 → rigorloop-0.1.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  13. {rigorloop-0.1.0 → rigorloop-0.1.1}/.github/dependabot.yml +0 -0
  14. {rigorloop-0.1.0 → rigorloop-0.1.1}/.github/workflows/ci.yml +0 -0
  15. {rigorloop-0.1.0 → rigorloop-0.1.1}/.github/workflows/release.yml +0 -0
  16. {rigorloop-0.1.0 → rigorloop-0.1.1}/.gitignore +0 -0
  17. {rigorloop-0.1.0 → rigorloop-0.1.1}/.pre-commit-config.yaml +0 -0
  18. {rigorloop-0.1.0 → rigorloop-0.1.1}/CODING_STYLE.md +0 -0
  19. {rigorloop-0.1.0 → rigorloop-0.1.1}/LICENSE +0 -0
  20. {rigorloop-0.1.0 → rigorloop-0.1.1}/README.md +0 -0
  21. {rigorloop-0.1.0 → rigorloop-0.1.1}/SECURITY.md +0 -0
  22. {rigorloop-0.1.0 → rigorloop-0.1.1}/examples/contact-cards/examples.jsonl +0 -0
  23. {rigorloop-0.1.0 → rigorloop-0.1.1}/examples/contact-cards/rigorloop.toml +0 -0
  24. {rigorloop-0.1.0 → rigorloop-0.1.1}/examples/contact-cards/task.md +0 -0
  25. {rigorloop-0.1.0 → rigorloop-0.1.1}/justfile +0 -0
  26. {rigorloop-0.1.0 → rigorloop-0.1.1}/pyproject.toml +0 -0
  27. {rigorloop-0.1.0 → rigorloop-0.1.1}/scripts/live-smoke.sh +0 -0
  28. {rigorloop-0.1.0 → rigorloop-0.1.1}/src/rigorloop/__init__.py +0 -0
  29. {rigorloop-0.1.0 → rigorloop-0.1.1}/src/rigorloop/core/__init__.py +0 -0
  30. {rigorloop-0.1.0 → rigorloop-0.1.1}/src/rigorloop/core/config_calcs.py +0 -0
  31. {rigorloop-0.1.0 → rigorloop-0.1.1}/src/rigorloop/core/dataset_calcs.py +0 -0
  32. {rigorloop-0.1.0 → rigorloop-0.1.1}/src/rigorloop/core/prompt_calcs.py +0 -0
  33. {rigorloop-0.1.0 → rigorloop-0.1.1}/src/rigorloop/core/report_calcs.py +0 -0
  34. {rigorloop-0.1.0 → rigorloop-0.1.1}/src/rigorloop/core/scoring_calcs.py +0 -0
  35. {rigorloop-0.1.0 → rigorloop-0.1.1}/src/rigorloop/core/strategy_calcs.py +0 -0
  36. {rigorloop-0.1.0 → rigorloop-0.1.1}/src/rigorloop/core/types.py +0 -0
  37. {rigorloop-0.1.0 → rigorloop-0.1.1}/src/rigorloop/py.typed +0 -0
  38. {rigorloop-0.1.0 → rigorloop-0.1.1}/src/rigorloop/shell/__init__.py +0 -0
  39. {rigorloop-0.1.0 → rigorloop-0.1.1}/src/rigorloop/shell/agent_calls.py +0 -0
  40. {rigorloop-0.1.0 → rigorloop-0.1.1}/src/rigorloop/shell/cli.py +0 -0
  41. {rigorloop-0.1.0 → rigorloop-0.1.1}/src/rigorloop/shell/io_actions.py +0 -0
  42. {rigorloop-0.1.0 → rigorloop-0.1.1}/tests/__init__.py +0 -0
  43. {rigorloop-0.1.0 → rigorloop-0.1.1}/tests/conftest.py +0 -0
  44. {rigorloop-0.1.0 → rigorloop-0.1.1}/tests/test_agent_calls.py +0 -0
  45. {rigorloop-0.1.0 → rigorloop-0.1.1}/tests/test_cli.py +0 -0
  46. {rigorloop-0.1.0 → rigorloop-0.1.1}/tests/test_config_calcs.py +0 -0
  47. {rigorloop-0.1.0 → rigorloop-0.1.1}/tests/test_dataset_calcs.py +0 -0
  48. {rigorloop-0.1.0 → rigorloop-0.1.1}/tests/test_e2e.py +0 -0
  49. {rigorloop-0.1.0 → rigorloop-0.1.1}/tests/test_io_actions.py +0 -0
  50. {rigorloop-0.1.0 → rigorloop-0.1.1}/tests/test_leakage.py +0 -0
  51. {rigorloop-0.1.0 → rigorloop-0.1.1}/tests/test_prompt_calcs.py +0 -0
  52. {rigorloop-0.1.0 → rigorloop-0.1.1}/tests/test_report_calcs.py +0 -0
  53. {rigorloop-0.1.0 → rigorloop-0.1.1}/tests/test_scoring_calcs.py +0 -0
  54. {rigorloop-0.1.0 → rigorloop-0.1.1}/tests/test_strategy_calcs.py +0 -0
  55. {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
- ## [Unreleased]
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
- 1. Ensure `main` is green and `CHANGELOG.md` has the new section.
76
- 2. `git tag vX.Y.Z && git push origin vX.Y.Z`.
77
- 3. Approve the `pypi` environment deployment.
78
- 4. Verify `pip install rigorloop==X.Y.Z` in a scratch venv.
75
+ The git tag **is** the version: `hatch-vcs` derives it from the latest
76
+ `vX.Y.Z` tag, so there is no version number to bump in any file. The only file
77
+ you edit for a release is `CHANGELOG.md`; pushing the tag does the rest.
78
+
79
+ 1. Land the change the normal way — branch, PR, `just check` green, add an
80
+ entry under `## [Unreleased]` in `CHANGELOG.md`, merge to `main`.
81
+ 2. On `main` (`git checkout main && git pull`), promote the changelog: rename
82
+ `[Unreleased]` to `[X.Y.Z] - YYYY-MM-DD`, add a fresh empty `[Unreleased]`
83
+ above it, commit, and push. The tag will point at this commit, so `main`
84
+ must be green first (branch protection requires `ci-ok`).
85
+ 3. Tag and push — this triggers `release.yml`:
86
+ ```bash
87
+ git tag vX.Y.Z && git push origin vX.Y.Z
88
+ ```
89
+ 4. Approve the `pypi` environment deployment when prompted (the publish job is
90
+ gated on manual approval). The workflow then rebuilds from the tag,
91
+ publishes to PyPI via OIDC Trusted Publishing (no token), and cuts a GitHub
92
+ Release with generated notes and the built artifacts.
93
+ 5. Verify `pip install rigorloop==X.Y.Z` in a scratch venv, then
94
+ `rigorloop --version`.
95
+
96
+ Version choice while `0.x`: **patch** (`v0.1.1`) = fixes only; **minor**
97
+ (`v0.2.0`) = features or breaking changes (call breakage out in the changelog);
98
+ `v1.0.0` when `rigorloop.toml`, the CLI, and the run-directory format are
99
+ declared stable.
100
+
101
+ A version publishes to PyPI exactly once — never re-push or edit a tag. If a
102
+ release fails partway, fix forward with a new patch tag rather than reusing the
103
+ old one. To rehearse the full pipeline first, push a prerelease tag
104
+ (e.g. `v0.2.0rc1`) against TestPyPI.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rigorloop
3
- Version: 0.1.0
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.0'
22
- __version_tuple__ = version_tuple = (0, 1, 0)
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