hills 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hills-0.1.0/.github/workflows/publish.yml +44 -0
- hills-0.1.0/.gitignore +8 -0
- hills-0.1.0/LICENSE +21 -0
- hills-0.1.0/PKG-INFO +388 -0
- hills-0.1.0/README.md +363 -0
- hills-0.1.0/docs/SPEC.md +215 -0
- hills-0.1.0/docs/banner.png +0 -0
- hills-0.1.0/examples/circle-packing/README.md +84 -0
- hills-0.1.0/examples/circle-packing/eval.py +114 -0
- hills-0.1.0/examples/circle-packing/examples/grid/solution.json +134 -0
- hills-0.1.0/examples/circle-packing/hill.yaml +15 -0
- hills-0.1.0/examples/circle-packing/pyproject.toml +9 -0
- hills-0.1.0/examples/circle-packing/tests/test_hill.py +71 -0
- hills-0.1.0/examples/nanogpt-10min/README.md +122 -0
- hills-0.1.0/examples/nanogpt-10min/eval.py +215 -0
- hills-0.1.0/examples/nanogpt-10min/examples/baseline/train.py +143 -0
- hills-0.1.0/examples/nanogpt-10min/hill.yaml +23 -0
- hills-0.1.0/examples/nanogpt-10min/prepare_data.py +83 -0
- hills-0.1.0/examples/nanogpt-10min/pyproject.toml +12 -0
- hills-0.1.0/examples/nanogpt-10min/tests/test_hill.py +79 -0
- hills-0.1.0/pyproject.toml +49 -0
- hills-0.1.0/skills/hills/SKILL.md +308 -0
- hills-0.1.0/skills/hills/references/authoring.md +114 -0
- hills-0.1.0/skills/hills/references/cli.md +98 -0
- hills-0.1.0/skills/hills/references/climbing.md +108 -0
- hills-0.1.0/src/hills/__init__.py +7 -0
- hills-0.1.0/src/hills/__main__.py +4 -0
- hills-0.1.0/src/hills/_shim.py +65 -0
- hills-0.1.0/src/hills/_templates/default/README.md +48 -0
- hills-0.1.0/src/hills/_templates/default/eval.py +47 -0
- hills-0.1.0/src/hills/_templates/default/examples/baseline/solution.json +1 -0
- hills-0.1.0/src/hills/_templates/default/hill.yaml +19 -0
- hills-0.1.0/src/hills/_templates/default/pyproject.toml +9 -0
- hills-0.1.0/src/hills/_templates/default/tests/test_hill.py +24 -0
- hills-0.1.0/src/hills/canonical.py +14 -0
- hills-0.1.0/src/hills/check.py +158 -0
- hills-0.1.0/src/hills/cli.py +474 -0
- hills-0.1.0/src/hills/core_schema.py +91 -0
- hills-0.1.0/src/hills/devlock.py +59 -0
- hills-0.1.0/src/hills/errors.py +33 -0
- hills-0.1.0/src/hills/hashing.py +93 -0
- hills-0.1.0/src/hills/hill.py +124 -0
- hills-0.1.0/src/hills/locks.py +150 -0
- hills-0.1.0/src/hills/manifest.py +250 -0
- hills-0.1.0/src/hills/paths.py +53 -0
- hills-0.1.0/src/hills/registry.py +87 -0
- hills-0.1.0/src/hills/report.py +154 -0
- hills-0.1.0/src/hills/runner.py +249 -0
- hills-0.1.0/src/hills/scaffold.py +110 -0
- hills-0.1.0/src/hills/sdk.py +42 -0
- hills-0.1.0/src/hills/setup_cmd.py +57 -0
- hills-0.1.0/src/hills/state.py +89 -0
- hills-0.1.0/src/hills/uvenv.py +143 -0
- hills-0.1.0/src/hills/vc.py +156 -0
- hills-0.1.0/tests/conftest.py +45 -0
- hills-0.1.0/tests/test_cli_flow.py +237 -0
- hills-0.1.0/tests/test_core_schema.py +64 -0
- hills-0.1.0/tests/test_evaluator_failures.py +148 -0
- hills-0.1.0/tests/test_locks.py +98 -0
- hills-0.1.0/tests/test_manifest.py +78 -0
- hills-0.1.0/tests/test_report.py +133 -0
- hills-0.1.0/tests/test_state.py +57 -0
- hills-0.1.0/uv.lock +138 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: publish
|
|
2
|
+
|
|
3
|
+
# Publishes to PyPI when a GitHub release is published. Authentication is PyPI
|
|
4
|
+
# trusted publishing over OIDC, so there is no API token stored in this repo.
|
|
5
|
+
# Configure the publisher once at https://pypi.org/manage/project/hills/settings/publishing/
|
|
6
|
+
# owner: autolab-hq repository: hills workflow: publish.yml environment: pypi
|
|
7
|
+
|
|
8
|
+
on:
|
|
9
|
+
release:
|
|
10
|
+
types: [published]
|
|
11
|
+
workflow_dispatch:
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
publish:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
environment: pypi
|
|
17
|
+
permissions:
|
|
18
|
+
id-token: write
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
|
|
22
|
+
- uses: astral-sh/setup-uv@v5
|
|
23
|
+
with:
|
|
24
|
+
enable-cache: true
|
|
25
|
+
|
|
26
|
+
- name: Test
|
|
27
|
+
run: uv run pytest -q
|
|
28
|
+
|
|
29
|
+
- name: Build
|
|
30
|
+
run: uv build
|
|
31
|
+
|
|
32
|
+
- name: Check that the version matches the release tag
|
|
33
|
+
run: |
|
|
34
|
+
tag="${GITHUB_REF_NAME#v}"
|
|
35
|
+
version=$(uv run python -c "import hills; print(hills.__version__)")
|
|
36
|
+
if [ "$GITHUB_EVENT_NAME" = "release" ] && [ "$tag" != "$version" ]; then
|
|
37
|
+
echo "release tag $GITHUB_REF_NAME does not match version $version" >&2
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
- name: Publish
|
|
42
|
+
# `always` rather than the default `automatic`: if the OIDC token is
|
|
43
|
+
# missing, fail here instead of falling through to another auth path.
|
|
44
|
+
run: uv publish --trusted-publishing always
|
hills-0.1.0/.gitignore
ADDED
hills-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Autolab
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
hills-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hills
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Local verification environments for AI research agents
|
|
5
|
+
Project-URL: Homepage, https://github.com/autolab-hq/hills
|
|
6
|
+
Project-URL: Repository, https://github.com/autolab-hq/hills
|
|
7
|
+
Project-URL: Specification, https://github.com/autolab-hq/hills/blob/main/docs/SPEC.md
|
|
8
|
+
Project-URL: Issues, https://github.com/autolab-hq/hills/issues
|
|
9
|
+
Author: Autolab
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: agents,benchmark,evaluation,machine-learning,verification
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Intended Audience :: Science/Research
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Classifier: Topic :: Software Development :: Testing
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Requires-Dist: pyyaml>=6.0
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
<h1 align="center">hills</h1>
|
|
27
|
+
|
|
28
|
+
<p align="center"><strong>Local verification environments for AI research agents</strong></p>
|
|
29
|
+
|
|
30
|
+
<p align="center">
|
|
31
|
+
<a href="#quickstart">Quickstart</a> ·
|
|
32
|
+
<a href="#what-a-hill-is">What a hill is</a> ·
|
|
33
|
+
<a href="#how-it-stays-honest">How it stays honest</a> ·
|
|
34
|
+
<a href="#reference">Reference</a> ·
|
|
35
|
+
<a href="https://github.com/autolab-hq/hills/blob/main/docs/SPEC.md">Spec</a> ·
|
|
36
|
+
<a href="https://github.com/autolab-hq/hills/blob/main/skills/hills/SKILL.md">Agent skill</a>
|
|
37
|
+
</p>
|
|
38
|
+
|
|
39
|
+
<p align="center">
|
|
40
|
+
<img alt="MIT license" src="https://img.shields.io/badge/license-MIT-7fa846?style=flat-square&labelColor=1c1c1c">
|
|
41
|
+
<img alt="Python 3.11+" src="https://img.shields.io/badge/python-3.11+-7fa846?style=flat-square&labelColor=1c1c1c">
|
|
42
|
+
</p>
|
|
43
|
+
|
|
44
|
+
> When an agent runs research experiments autonomously, the same model writes the
|
|
45
|
+
> solution, evaluates it, and reports the result. It grades its own homework, so
|
|
46
|
+
> its numbers cannot be trusted. **Hills separates the two roles.**
|
|
47
|
+
|
|
48
|
+
<p align="center">
|
|
49
|
+
<img src="https://raw.githubusercontent.com/autolab-hq/hills/main/docs/banner.png"
|
|
50
|
+
alt="hills: agents can climb, evaluators can judge"
|
|
51
|
+
width="900">
|
|
52
|
+
</p>
|
|
53
|
+
|
|
54
|
+
A **hill** is a packaged evaluation task: a task description, an evaluator, and
|
|
55
|
+
data. The agent (the **climber**) develops however it likes, but an official
|
|
56
|
+
score comes only from `hills eval`, which runs the hill's evaluator in a separate
|
|
57
|
+
process against a frozen, committed version of the hill and returns a signed
|
|
58
|
+
report. The agent can query the verifier; it cannot modify it or grade itself.
|
|
59
|
+
|
|
60
|
+
The mental model is git. A hill is a versioned directory, `hills commit` freezes
|
|
61
|
+
it, and every score is tied to the exact version that produced it.
|
|
62
|
+
|
|
63
|
+
## Quickstart
|
|
64
|
+
|
|
65
|
+
Two minutes, no GPU, no network after install. We will create the hello-world
|
|
66
|
+
hill, freeze it, score a submission against it, and then show what happens when
|
|
67
|
+
someone edits the score.
|
|
68
|
+
|
|
69
|
+
**1. Install.**
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
uv tool install hills
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
If you want your coding agent to drive this instead, install the skill and it
|
|
76
|
+
bootstraps the CLI itself:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npx skills add autolab-hq/hills
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**2. Create a hill.** `circle-packing` ships with the tool: place 26 circles in
|
|
83
|
+
the unit square without overlaps, maximize the sum of the radii.
|
|
84
|
+
|
|
85
|
+
```console
|
|
86
|
+
$ mkdir demo && cd demo && git init -q .
|
|
87
|
+
$ hills new circle-packing -t circle-packing
|
|
88
|
+
hills: created machine state at ~/.autolab/hills
|
|
89
|
+
created demo/.hills/circle-packing
|
|
90
|
+
template circle-packing
|
|
91
|
+
version control demo/.hills/circle-packing/.vc (empty; nothing committed yet)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Nothing was asked of you and no file of yours was edited. `.hills/` carries a
|
|
95
|
+
`.gitignore` containing `*`, so it excludes itself from your project's git the
|
|
96
|
+
way `uv` excludes `.venv`.
|
|
97
|
+
|
|
98
|
+
**3. Check it, then freeze it.**
|
|
99
|
+
|
|
100
|
+
```console
|
|
101
|
+
$ hills check circle-packing
|
|
102
|
+
ok manifest circle-packing 0.1.0
|
|
103
|
+
ok layout eval.py, README.md, pyproject.toml
|
|
104
|
+
ok dependencies uv.lock is up to date
|
|
105
|
+
ok evaluator contract eval(submission, final, n, tolerance) imports and binds
|
|
106
|
+
ok tests 7 passed in 1.27s
|
|
107
|
+
|
|
108
|
+
circle-packing: all checks passed
|
|
109
|
+
|
|
110
|
+
$ hills commit circle-packing -m "initial"
|
|
111
|
+
private.lock 0 file(s), 0 bytes
|
|
112
|
+
blobs.lock 0 file(s), 0 bytes
|
|
113
|
+
|
|
114
|
+
committed circle-packing 0.1.0
|
|
115
|
+
tree hash 0a93cd12b4360ba5434b7524897c8d4ce4ba5c68
|
|
116
|
+
commit 20c61d03ca7245791b756238d8a7fa7626b08199
|
|
117
|
+
|
|
118
|
+
Scores from here on are tied to this tree hash. A new commit starts a fresh history.
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`commit` runs `check` as a gate, then regenerates the lock files from disk. The
|
|
122
|
+
**tree hash** is the hill's identity, and every score from here on is tied to it.
|
|
123
|
+
|
|
124
|
+
**4. Score a submission.** A submission is just a directory. The hill ships one:
|
|
125
|
+
|
|
126
|
+
```console
|
|
127
|
+
$ cp -r .hills/circle-packing/examples/grid ./my-packing
|
|
128
|
+
$ hills eval ./my-packing -H circle-packing -o report.json
|
|
129
|
+
|
|
130
|
+
circle-packing @ 0a93cd12b436
|
|
131
|
+
submission ./my-packing
|
|
132
|
+
hash sha256:b415d43098bf…
|
|
133
|
+
params n=26 tolerance=1e-09
|
|
134
|
+
PASSED sum_radii=2.5414 (max)
|
|
135
|
+
config n=26* mode=validation* tolerance=1e-09 (* = primary)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The full report goes to stdout as JSON; the summary above is stderr, so
|
|
139
|
+
`hills eval ... > report.json` does what you expect.
|
|
140
|
+
|
|
141
|
+
<details>
|
|
142
|
+
<summary>The report</summary>
|
|
143
|
+
|
|
144
|
+
```json
|
|
145
|
+
{
|
|
146
|
+
"hill": "circle-packing",
|
|
147
|
+
"tree_hash": "0a93cd12b4360ba5434b7524897c8d4ce4ba5c68",
|
|
148
|
+
"commit": "20c61d03ca7245791b756238d8a7fa7626b08199",
|
|
149
|
+
"submission_hash": "sha256:b415d43098bf9d11f8a0b910760d28ba13f68ca0ee54ba2288a085758ecebf08",
|
|
150
|
+
"submission_git": null,
|
|
151
|
+
"passed": true,
|
|
152
|
+
"config": [
|
|
153
|
+
{"name": "n", "value": 26, "primary": true},
|
|
154
|
+
{"name": "mode", "value": "validation", "primary": true},
|
|
155
|
+
{"name": "tolerance", "value": 1e-09, "primary": false}
|
|
156
|
+
],
|
|
157
|
+
"metrics": [
|
|
158
|
+
{"name": "sum_radii", "value": 2.5414, "direction": "max"}
|
|
159
|
+
],
|
|
160
|
+
"details": {"min_radius": 0.0414, "max_radius": 0.1},
|
|
161
|
+
"params": {"n": 26, "tolerance": 1e-09},
|
|
162
|
+
"final": false,
|
|
163
|
+
"official": true,
|
|
164
|
+
"official_reason": null,
|
|
165
|
+
"tool": {"version": "0.1.0", "sha256": "463ea637f2f79fb9…"},
|
|
166
|
+
"timestamp": "2026-08-10T02:11:05Z",
|
|
167
|
+
"report_version": 1,
|
|
168
|
+
"signature": "hmac-sha256:97553de336afdd8a3088b27392cfec0c5fee9d28c134f315294fe48599f5f3d2"
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Had `./my-packing` been a git checkout, `submission_git` would read
|
|
173
|
+
`branch@short-sha`, tying the score to the code that produced it.
|
|
174
|
+
|
|
175
|
+
</details>
|
|
176
|
+
|
|
177
|
+
**5. Now try to improve the score by editing it.**
|
|
178
|
+
|
|
179
|
+
```console
|
|
180
|
+
$ hills verify report.json
|
|
181
|
+
signature valid: circle-packing @ 0a93cd12b436
|
|
182
|
+
PASSED sum_radii=2.5414 (max)
|
|
183
|
+
signed 2026-08-10T02:11:05Z by hills 0.1.0
|
|
184
|
+
|
|
185
|
+
$ sed -i '' 's/2.5414/2.9/' report.json
|
|
186
|
+
$ hills verify report.json
|
|
187
|
+
signature INVALID: this report was edited, or it was signed on another machine.
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
That is the whole point of the tool, in one command.
|
|
191
|
+
|
|
192
|
+
**Where to go next.** `hills describe circle-packing` prints the contract your
|
|
193
|
+
agent would read. `hills new <name>` scaffolds a blank hill for your own task,
|
|
194
|
+
and `hills new <name> -t nanogpt-10min` starts from the flagship example: a
|
|
195
|
+
timed training run scored on a held-out split the climber never sees.
|
|
196
|
+
|
|
197
|
+
## What a hill is
|
|
198
|
+
|
|
199
|
+
A directory, versioned by its own embedded git repository:
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
.hills/circle-packing/
|
|
203
|
+
hill.yaml minimal manifest: watchdog, typed params, blob rules
|
|
204
|
+
README.md the contract, written for the climbing agent
|
|
205
|
+
eval.py THE entrypoint: def eval(submission: Path, **params) -> dict
|
|
206
|
+
private/ evaluator-only content; never enters git
|
|
207
|
+
examples/ minimal demonstration submissions
|
|
208
|
+
tests/ checks on the hill itself, run by `hills check`
|
|
209
|
+
.vc/ the hill's own git dir, named so it cannot collide with yours
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
`private/` is the only special directory. Everything else is climber-readable by
|
|
213
|
+
design, **including `eval.py`**: transparency about how you are judged is a
|
|
214
|
+
feature. The consequence is that anything answer-revealing has to live in
|
|
215
|
+
`private/`, not inline in the evaluator.
|
|
216
|
+
|
|
217
|
+
### The evaluator contract
|
|
218
|
+
|
|
219
|
+
One fixed function, at the hill root:
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
from pathlib import Path
|
|
223
|
+
|
|
224
|
+
def eval(submission: Path, *, final: bool = False, **params) -> dict:
|
|
225
|
+
return {
|
|
226
|
+
"passed": True,
|
|
227
|
+
"metrics": [{"name": "val_bpb", "value": 1.043, "direction": "min"}],
|
|
228
|
+
"config": [{"name": "gpu", "value": "rtx4090-24gb", "primary": True},
|
|
229
|
+
{"name": "torch", "value": "2.9.1", "primary": False}],
|
|
230
|
+
"details": {},
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
A submission is a directory. That is the whole input contract: a codebase, model
|
|
235
|
+
weights, or a single JSON file are all just files in a directory.
|
|
236
|
+
|
|
237
|
+
The tool never imports `eval.py` in-process. It runs a shim as a subprocess in
|
|
238
|
+
the hill's own uv environment, so per-hill dependencies stay isolated, a watchdog
|
|
239
|
+
can kill a hung evaluation, and an evaluator crash cannot take down the tool. For
|
|
240
|
+
timed tasks the evaluator launches the submitted code itself and enforces the
|
|
241
|
+
deadline, so the climber's code never runs the official clock.
|
|
242
|
+
|
|
243
|
+
`config` entries describe the conditions of the measurement. Primary entries
|
|
244
|
+
define comparability: two reports are comparable only if their primary config
|
|
245
|
+
tuples match. Metrics are an ordered list with per-metric direction, and ranking
|
|
246
|
+
is lexicographic in that order. Any set of reports therefore sorts into ranked
|
|
247
|
+
groups with no configuration beyond the reports themselves.
|
|
248
|
+
|
|
249
|
+
## How it stays honest
|
|
250
|
+
|
|
251
|
+
### Identity is content, not history
|
|
252
|
+
|
|
253
|
+
A hill's identity is its **git tree hash**, not its commit hash. Tree hashes are
|
|
254
|
+
a pure function of content, so the same files give the same identity on any
|
|
255
|
+
machine. All state is keyed by tree hash: a new hill version starts a fresh
|
|
256
|
+
attempts history, because a changed evaluator is a new game.
|
|
257
|
+
|
|
258
|
+
### Locks bind what git must not hold
|
|
259
|
+
|
|
260
|
+
Two lock files, regenerated from disk at every commit:
|
|
261
|
+
|
|
262
|
+
| lock | covers | why it is not in git |
|
|
263
|
+
|---|---|---|
|
|
264
|
+
| `private.lock` | every file under `private/` | git has no per-path access control and its history is permanent, so anything ever committed is distributable forever, and leaked held-out data ends up in future training corpora |
|
|
265
|
+
| `blobs.lock` | large assets outside `private/` | anything matching a `track` pattern or over the size threshold |
|
|
266
|
+
|
|
267
|
+
The tree hash *commits to* private content through the lock without *containing*
|
|
268
|
+
it. There is no blob store, no symlink farm, no content-addressed cache: the lock
|
|
269
|
+
file is the tracking, and integrity is enforced at the two moments it matters, at
|
|
270
|
+
`commit` (locks regenerated from disk) and at `eval` (disk verified against the
|
|
271
|
+
locks at HEAD; a mismatch is a hard error naming the file).
|
|
272
|
+
|
|
273
|
+
### Reports are signed, history is chained
|
|
274
|
+
|
|
275
|
+
The dict your evaluator returns is the report **core**. The tool wraps it in an
|
|
276
|
+
envelope: hill name, tree hash, commit, submission hash, submission git lineage,
|
|
277
|
+
params, tool version and package hash, timestamp, and an HMAC signature over the
|
|
278
|
+
canonicalized report. The key lives at `~/.autolab/hills/key`, mode 0600,
|
|
279
|
+
deliberately outside any project directory an agent works in.
|
|
280
|
+
|
|
281
|
+
Every eval appends to an attempts log whose entries are HMAC-chained to the one
|
|
282
|
+
before, so a deleted or edited line is detectable. `hills attempts` prints the
|
|
283
|
+
break rather than hiding it.
|
|
284
|
+
|
|
285
|
+
### Trust posture
|
|
286
|
+
|
|
287
|
+
This tool defends against **self-deception**: an agent loop accidentally or
|
|
288
|
+
opportunistically grading its own work, editing its own scores, or drifting its
|
|
289
|
+
own evaluation criteria.
|
|
290
|
+
|
|
291
|
+
It does not defend against a malicious human operator. Private files are ordinary
|
|
292
|
+
files, protected by convention and by the tool only handing their location to the
|
|
293
|
+
evaluator process. Signed reports are tamper-evident, not tamper-proof.
|
|
294
|
+
|
|
295
|
+
The honest claim: **your agent cannot fake a hills report; you could, but then
|
|
296
|
+
you're only lying to yourself.** Disputed results are re-runnable, because hills
|
|
297
|
+
are inspectable and submissions are hashed, so verification is ultimately by
|
|
298
|
+
replay, not by trust in any single machine.
|
|
299
|
+
|
|
300
|
+
## Working with a coding agent
|
|
301
|
+
|
|
302
|
+
The [agent skill](https://github.com/autolab-hq/hills/blob/main/skills/hills/SKILL.md) ships in this repo, version-locked to
|
|
303
|
+
the CLI, and installs with `npx skills add autolab-hq/hills` or `hills setup`.
|
|
304
|
+
You do not have to know what a hill is to use it: it triggers on any request to
|
|
305
|
+
improve a number by iterating, and builds the hill as part of the job.
|
|
306
|
+
|
|
307
|
+
It runs in four phases.
|
|
308
|
+
|
|
309
|
+
1. **Confirm the project.** A minute, no more. What is this, and is it what you
|
|
310
|
+
want to optimize?
|
|
311
|
+
2. **Agree on a plan.** Goal and direction, files in scope, files that are
|
|
312
|
+
read-only, what is held out, the constraint that makes runs comparable, what
|
|
313
|
+
would count as cheating, the run command, and when to stop. You confirm or
|
|
314
|
+
edit it before anything is built.
|
|
315
|
+
3. **Build and freeze the hill.** The plan becomes a hill: read-only files are
|
|
316
|
+
frozen into it, held-out data moves into `private/`, and the scoring code is
|
|
317
|
+
copied rather than imported so it cannot drift with your project. The agent
|
|
318
|
+
red-teams its own draft, then presents a brief listing the gaming vectors it
|
|
319
|
+
closed and the ones that remain open as your decision. **You** run
|
|
320
|
+
`hills commit`. The agent that wrote the evaluator does not get to freeze it.
|
|
321
|
+
4. **The experiment loop.** A fresh subagent starts from `hills describe` and
|
|
322
|
+
nothing else, then loops: edit, commit, dev-run, `hills eval`, decide. It does
|
|
323
|
+
not stop to ask permission, and it runs until your stopping criteria are met.
|
|
324
|
+
|
|
325
|
+
The third phase is the one a self-graded loop skips, and it is the reason the
|
|
326
|
+
numbers at the end mean something. Total human surface: one install, one commit,
|
|
327
|
+
one "proceed" with stopping criteria.
|
|
328
|
+
|
|
329
|
+
## Reference
|
|
330
|
+
|
|
331
|
+
### Commands
|
|
332
|
+
|
|
333
|
+
| command | what it does |
|
|
334
|
+
|---|---|
|
|
335
|
+
| `hills new <name> [-t template]` | scaffold a hill, init `.vc`, register it |
|
|
336
|
+
| `hills check <name>` | manifest, evaluator contract, `tests/` |
|
|
337
|
+
| `hills status <name>` | changes since the last commit, including lock drift git cannot see |
|
|
338
|
+
| `hills commit <name> -m "..."` | check, regenerate locks, commit, print the tree hash |
|
|
339
|
+
| `hills log <name>` | version history, with eval counts per version |
|
|
340
|
+
| `hills describe <name>` | README, params, submission contract, as JSON |
|
|
341
|
+
| `hills eval <dir> -H <name>` | score a submission directory |
|
|
342
|
+
| `hills attempts <name>` | eval history for this version; flags a broken chain |
|
|
343
|
+
| `hills verify <report.json>` | check a report's signature |
|
|
344
|
+
| `hills list` | registered hills |
|
|
345
|
+
| `hills setup` | install the agent skill into detected harnesses |
|
|
346
|
+
| `hills home` | where machine state lives |
|
|
347
|
+
|
|
348
|
+
`hills eval` flags: `-p key=value` (repeatable), `--final` for test mode,
|
|
349
|
+
`--force`, `--current`, `--queue`, `-v` to stream evaluator output, `-o` to also
|
|
350
|
+
write the report to a file.
|
|
351
|
+
|
|
352
|
+
It evaluates **HEAD, never the working tree**. A dirty hill is an error: commit
|
|
353
|
+
it, `--force` to score the last committed version anyway, or `--current` to test
|
|
354
|
+
a draft evaluator against a real submission (unofficial, `tree_hash: null`,
|
|
355
|
+
logged separately).
|
|
356
|
+
|
|
357
|
+
### Machine state
|
|
358
|
+
|
|
359
|
+
```
|
|
360
|
+
~/.autolab/hills/
|
|
361
|
+
registry.json name -> path, current tree hash
|
|
362
|
+
key the per-machine signing key (0600)
|
|
363
|
+
state/<name>@<tree_hash>/attempts.jsonl append-only, HMAC-chained
|
|
364
|
+
runs/<name>/<timestamp>-<id>/ materialized hill, submission snapshot, logs, report
|
|
365
|
+
envs/<name>/<tree_hash>/ the uv environment for that hill version
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
Hills are stateless: a hill emits signed reports and remembers nothing. Eval
|
|
369
|
+
history is tool state; the climbing agent keeps its own working notes.
|
|
370
|
+
|
|
371
|
+
### Repository layout
|
|
372
|
+
|
|
373
|
+
```
|
|
374
|
+
src/hills/ the library and CLI
|
|
375
|
+
skills/hills/ the agent skill, version-locked to the CLI
|
|
376
|
+
examples/ the example hills, also usable as `hills new -t <name>`
|
|
377
|
+
docs/SPEC.md the design specification this implements
|
|
378
|
+
tests/ tests for the tool
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
The two shipped hills: **circle-packing**, the hello-world used above, exact
|
|
382
|
+
arithmetic and no private data; and **nanogpt-10min**, a timed training run whose
|
|
383
|
+
evaluator owns the clock, keeps its splits in `private/`, and reports a
|
|
384
|
+
normalized GPU profile as primary config.
|
|
385
|
+
|
|
386
|
+
## License
|
|
387
|
+
|
|
388
|
+
MIT. See [LICENSE](https://github.com/autolab-hq/hills/blob/main/LICENSE).
|