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.
Files changed (63) hide show
  1. hills-0.1.0/.github/workflows/publish.yml +44 -0
  2. hills-0.1.0/.gitignore +8 -0
  3. hills-0.1.0/LICENSE +21 -0
  4. hills-0.1.0/PKG-INFO +388 -0
  5. hills-0.1.0/README.md +363 -0
  6. hills-0.1.0/docs/SPEC.md +215 -0
  7. hills-0.1.0/docs/banner.png +0 -0
  8. hills-0.1.0/examples/circle-packing/README.md +84 -0
  9. hills-0.1.0/examples/circle-packing/eval.py +114 -0
  10. hills-0.1.0/examples/circle-packing/examples/grid/solution.json +134 -0
  11. hills-0.1.0/examples/circle-packing/hill.yaml +15 -0
  12. hills-0.1.0/examples/circle-packing/pyproject.toml +9 -0
  13. hills-0.1.0/examples/circle-packing/tests/test_hill.py +71 -0
  14. hills-0.1.0/examples/nanogpt-10min/README.md +122 -0
  15. hills-0.1.0/examples/nanogpt-10min/eval.py +215 -0
  16. hills-0.1.0/examples/nanogpt-10min/examples/baseline/train.py +143 -0
  17. hills-0.1.0/examples/nanogpt-10min/hill.yaml +23 -0
  18. hills-0.1.0/examples/nanogpt-10min/prepare_data.py +83 -0
  19. hills-0.1.0/examples/nanogpt-10min/pyproject.toml +12 -0
  20. hills-0.1.0/examples/nanogpt-10min/tests/test_hill.py +79 -0
  21. hills-0.1.0/pyproject.toml +49 -0
  22. hills-0.1.0/skills/hills/SKILL.md +308 -0
  23. hills-0.1.0/skills/hills/references/authoring.md +114 -0
  24. hills-0.1.0/skills/hills/references/cli.md +98 -0
  25. hills-0.1.0/skills/hills/references/climbing.md +108 -0
  26. hills-0.1.0/src/hills/__init__.py +7 -0
  27. hills-0.1.0/src/hills/__main__.py +4 -0
  28. hills-0.1.0/src/hills/_shim.py +65 -0
  29. hills-0.1.0/src/hills/_templates/default/README.md +48 -0
  30. hills-0.1.0/src/hills/_templates/default/eval.py +47 -0
  31. hills-0.1.0/src/hills/_templates/default/examples/baseline/solution.json +1 -0
  32. hills-0.1.0/src/hills/_templates/default/hill.yaml +19 -0
  33. hills-0.1.0/src/hills/_templates/default/pyproject.toml +9 -0
  34. hills-0.1.0/src/hills/_templates/default/tests/test_hill.py +24 -0
  35. hills-0.1.0/src/hills/canonical.py +14 -0
  36. hills-0.1.0/src/hills/check.py +158 -0
  37. hills-0.1.0/src/hills/cli.py +474 -0
  38. hills-0.1.0/src/hills/core_schema.py +91 -0
  39. hills-0.1.0/src/hills/devlock.py +59 -0
  40. hills-0.1.0/src/hills/errors.py +33 -0
  41. hills-0.1.0/src/hills/hashing.py +93 -0
  42. hills-0.1.0/src/hills/hill.py +124 -0
  43. hills-0.1.0/src/hills/locks.py +150 -0
  44. hills-0.1.0/src/hills/manifest.py +250 -0
  45. hills-0.1.0/src/hills/paths.py +53 -0
  46. hills-0.1.0/src/hills/registry.py +87 -0
  47. hills-0.1.0/src/hills/report.py +154 -0
  48. hills-0.1.0/src/hills/runner.py +249 -0
  49. hills-0.1.0/src/hills/scaffold.py +110 -0
  50. hills-0.1.0/src/hills/sdk.py +42 -0
  51. hills-0.1.0/src/hills/setup_cmd.py +57 -0
  52. hills-0.1.0/src/hills/state.py +89 -0
  53. hills-0.1.0/src/hills/uvenv.py +143 -0
  54. hills-0.1.0/src/hills/vc.py +156 -0
  55. hills-0.1.0/tests/conftest.py +45 -0
  56. hills-0.1.0/tests/test_cli_flow.py +237 -0
  57. hills-0.1.0/tests/test_core_schema.py +64 -0
  58. hills-0.1.0/tests/test_evaluator_failures.py +148 -0
  59. hills-0.1.0/tests/test_locks.py +98 -0
  60. hills-0.1.0/tests/test_manifest.py +78 -0
  61. hills-0.1.0/tests/test_report.py +133 -0
  62. hills-0.1.0/tests/test_state.py +57 -0
  63. 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
@@ -0,0 +1,8 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .pytest_cache/
8
+ .DS_Store
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).