@robmclarty/volley 0.9.0 → 0.10.1

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.10.1 — 2026-11-01
4
+
5
+ ### Fixed
6
+ - **No process volley starts can read a seat's key.** `volley acp` already kept `OPENROUTER_API_KEY` and `ANTHROPIC_API_KEY` from the check and the builder's commands, but a plain run handed both to the check (which runs the tests the builder wrote), to a tool-loop builder's `bash`, and to volley's own `git` and `checkride doctor` calls, where a git filter, textconv, or fsmonitor named in a repo config the builder wrote could read them. Every child process now gets volley's environment less those two keys. A `claude_cli` seat under `VOLLEY_AUTH_MODE=oauth` still inherits the whole environment, as the Claude CLI's login needs.
7
+
8
+ ### Internal
9
+ - **An ast-grep rule, `spawn-sets-env`, fails any child process in `src/` that leaves `env` out**, and a test runs volley's git calls through a planted filter that dumps its environment, which must show neither key.
10
+
11
+ ## v0.10.0 — 2026-10-27
12
+
13
+ ### Added
14
+ - **A builder sees what the last check said (#6).** When the previous iteration's check failed, the builder's prompt carries a `PREVIOUS CHECK RESULT` section: the exit code, the failing slots, and an excerpt capped at 6,000 characters, taken from checkride's digest (volley now runs `pnpm exec checkride --json --digest` and archives `check/digest.md`), else the start of each failing slot's raw output, else the end of a command's output. A resume rebuilds it from the archive, and a check the user rejected under `volley acp` reads as never having run, not as failed.
15
+ - **A tool-loop builder's last steps count down (#6).** The last five steps of a session, or a tenth of a longer budget, end with a note of how many are left and the command that runs the check, and the last says that only `finish` runs on it. When a session hits its step limit, the next prompt says so and asks for the check first.
16
+ - **`--builder-max-output-tokens <n>` caps a tool-loop builder's output per turn (#6).** Or `VOLLEY_BUILDER_MAX_OUTPUT_TOKENS`. Reasoning counts, and Ollama gets it as `num_predict`. An ollama or lmstudio builder is capped at 16,384 by default, an openrouter one only when you set a cap. A turn cut off before it calls a tool costs that step instead of ending the session, and each iteration's summary counts those turns as `builder.truncated_turns`.
17
+ - **`builder_max_steps_hits` counts the builder sessions that ran out of steps (#6).** It's on `RunResult`, so `--json` and `summary.json` carry it, since the warning each one prints goes to stderr, which `--json` silences.
18
+
19
+ ### Changed
20
+ - **Reads don't count against `--builder-max-tool-calls` (#6).** `read_file`, `search_files`, and `list_files` are read-only, so a builder that batches them gets them all, and past the cap the next request names the calls that were dropped. Above a cap of one, step 2 of the builder's prompt no longer names the cap.
21
+ - **The builder is asked to check as it goes (#6).** In a checkride workspace it's told to run `pnpm exec checkride --digest` after each batch of edits and to read `.check/digest.md` before any slot's raw output, and both harness prompts say to verify after each batch of edits, not only before finishing.
22
+ - **`RunResult` has `builder_max_steps_hits` and `ResolvedConfig` has `builder_max_output_tokens`.** Both are required, so SDK code that builds either by hand needs them.
23
+
24
+ ### Fixed
25
+ - **A contained `--worktree` run keeps its work.** The sandbox image has no git identity, so volley's own commits failed inside it: the salvage commit that keeps a converged run's work on `volley/<run id>`, the `--git` checkpoints, and the squash merge. They now fall back to `volley <volley@volley.invalid>` for whatever git lacks, and an identity you configured still wins.
26
+ - **A failing slot's raw output is read only from inside the check directory.** Slot names and output files come from a summary the builder could have written, and a `../` in one could put a file from outside it in front of the critic or the next builder.
27
+
28
+ ### Internal
29
+ - **Built on fascicle 0.17.1**, which names a turn's dropped tool calls to the model and can go on past a turn cut off at its output cap.
30
+ - **The publish workflow keeps its OIDC identity out of the job that runs dependencies.** A `build` job verifies the tag, installs, runs the gate, builds, and packs with read-only permissions, and a separate `publish` job holds `id-token: write` behind the `npm-publish` approval and publishes that tarball with `--ignore-scripts`.
31
+ - **The tests drive the step countdown, the per-turn cap's read-only reads, and the output cap through fascicle's real tool loop**, against a stand-in OpenAI server that now takes several calls a turn and can end one on `length`.
32
+
3
33
  ## v0.9.0 — 2026-10-20
4
34
 
5
35
  ### Added
package/README.md CHANGED
@@ -73,11 +73,20 @@ prompt ──> builder (full agentic session in your workspace)
73
73
  ```
74
74
 
75
75
  - **Builder** gets the task, the acceptance criteria, and the previous
76
- iteration's critic feedback. It has full tools (Read/Write/Edit/Bash) and
77
- runs with `--permission-mode acceptEdits` by default.
76
+ iteration's critic feedback. When the previous iteration's check failed, it
77
+ also gets what the check said: checkride's digest of the failing slots, else
78
+ the start of each failing slot's raw output, else the end of a command's
79
+ output, capped at 6,000 characters. If the previous session hit its step
80
+ limit, the prompt says so and asks it to run the check first. In a
81
+ checkride workspace it's asked to run `pnpm exec checkride --digest` after
82
+ each batch of edits and to read `.check/digest.md` when that fails, one
83
+ capped file for every failing slot, before any slot's raw output. It has full tools
84
+ (Read/Write/Edit/Bash) and runs with `--permission-mode acceptEdits` by
85
+ default.
78
86
  - **Check** is auto-detected: if the workspace uses checkride, volley runs
79
- `pnpm exec checkride --json` and gates on the summary. Any shell command
80
- works too (`--check "npm test"`), or `--check none` for critic-only runs.
87
+ `pnpm exec checkride --json --digest` and gates on the summary. Any shell
88
+ command works too (`--check "npm test"`), or `--check none` for critic-only
89
+ runs.
81
90
  - **Critic** is read-only (`Read`, `Grep`, `Glob` — no write path at all) and
82
91
  returns a schema-validated `{ verdict, feedback, unmet_criteria }`. It is told
83
92
  which files the builder changed, so it reviews a change rather than a tree.
@@ -85,7 +94,7 @@ prompt ──> builder (full agentic session in your workspace)
85
94
  cannot get them wrong.
86
95
 
87
96
  Each iteration is a fresh session — no context carries over except through the
88
- workspace filesystem and the critic's feedback. Swap the critic to change what
97
+ workspace filesystem, the critic's feedback, and a failed check's output. Swap the critic to change what
89
98
  the loop optimizes for: `--critic reviewer` (default), `optimizer`,
90
99
  `researcher`, or a path to your own system prompt.
91
100
 
@@ -164,8 +173,9 @@ volley acp --builder-provider <name> --builder-model <model> \
164
173
  | `--builder-model` | `opus` | `opus`/`sonnet`/`haiku` or a full model id. |
165
174
  | `--builder-provider` | `claude_cli` | `claude_cli`, `ollama`, `lmstudio`, or `openrouter` (see [Local builder](#local-builder) and [OpenRouter](#openrouter)). |
166
175
  | `--builder-url` | the provider's env var | The builder's model server base URL, for `ollama`, `lmstudio`, or `openrouter` only. Lets the builder and critic run on different machines with the same provider (see [Seats on different machines](#seats-on-different-machines)). |
167
- | `--builder-max-steps` | `50` | Tool-loop builder step cap per iteration. Or `VOLLEY_BUILDER_MAX_STEPS`. Ignored for `claude_cli`. |
168
- | `--builder-max-tool-calls` | `1` | Tool calls a tool-loop builder may make per turn; past it, fascicle runs the first ones and drops the rest. Raise it for a model and runtime that return parallel calls well. Or `VOLLEY_BUILDER_MAX_TOOL_CALLS`. Ignored for `claude_cli`. |
176
+ | `--builder-max-steps` | `50` | Tool-loop builder step cap per iteration. Or `VOLLEY_BUILDER_MAX_STEPS`. Ignored for `claude_cli`. `--json` and `summary.json` count the sessions that hit it as `builder_max_steps_hits`. |
177
+ | `--builder-max-tool-calls` | `1` | Tool calls a tool-loop builder may make per turn, not counting reads (`read_file`, `search_files`, `list_files`); past it, fascicle runs the calls before the first one over, drops the rest, and tells the model which it dropped. Raise it for a model and runtime that return parallel calls well. Or `VOLLEY_BUILDER_MAX_TOOL_CALLS`. Ignored for `claude_cli`. |
178
+ | `--builder-max-output-tokens` | `16384` (ollama, lmstudio), the provider's (openrouter) | Output tokens a tool-loop builder may generate per turn, reasoning included (`num_predict` on Ollama). A turn cut off at it before it calls a tool costs that step, not the session. Or `VOLLEY_BUILDER_MAX_OUTPUT_TOKENS`. Ignored for `claude_cli`. |
169
179
  | `--allow-unsandboxed-builder` | off | Permit a builder other than `claude_cli` to run without a sandbox (see [Local builder](#local-builder)). |
170
180
  | `--critic-model` | `opus` | Model for the critic. |
171
181
  | `--critic-provider` | `claude_cli` | `claude_cli`, `ollama`, `lmstudio`, or `openrouter` (see [Local critic](#local-critic) and [OpenRouter](#openrouter)). |
@@ -241,7 +251,9 @@ default root, from any directory, since the run's recorded config names its
241
251
  workspace. A run an older volley started keeps its state in the workspace's
242
252
  `.volley/`, and resumes there: point `--workspace` at it, or run `resume` from
243
253
  inside it. A run directory with a symbolic link in it is refused, since volley
244
- never writes one.
254
+ never writes one. The resumed run's first builder gets what the last completed
255
+ iteration left it, as it would have without the stop: the critic's feedback,
256
+ the check's output if it failed, and the step-limit note.
245
257
 
246
258
  **A resume on the host runs only a config this machine signed.** A resume runs
247
259
  what the run's `config.json` says, its check command and its seats included.
@@ -401,7 +413,7 @@ Providers and their setup:
401
413
  Install the peer for your provider in the project running volley (e.g.
402
414
  `pnpm add ai-sdk-ollama@^4.3.0`); fascicle loads it lazily only when the local
403
415
  critic actually runs. The version matters. The major must satisfy fascicle's
404
- declared peer range (`^4` for fascicle 0.16.3, which tracks the AI SDK v7
416
+ declared peer range (`^4` for fascicle 0.17.1, which tracks the AI SDK v7
405
417
  line), and volley declares 4.3.0 as its floor (an optional peer, so your
406
418
  package manager flags an older copy at install time). Before 4.3.0,
407
419
  ai-sdk-ollama streams a thinking model's reasoning only when the request sets
@@ -452,16 +464,41 @@ surface itself — `read_file`, `search_files`, `list_files`, `write_file`,
452
464
  `edit_file`, `bash`, `fetch`, and an explicit `finish` — and runs one bounded
453
465
  tool loop per iteration (capped by `--builder-max-steps`, default 50; hitting
454
466
  the cap is treated as partial work, not an error). The produced workspace goes
455
- to check + critic exactly like a `claude_cli` build.
467
+ to check + critic exactly like a `claude_cli` build. Each hit prints a warning,
468
+ and `--json`, which silences warnings, and `summary.json` count them as
469
+ `builder_max_steps_hits`.
470
+
471
+ So that a model that explores first still verifies, the last few steps of each
472
+ session count down: five, or a tenth of a longer budget. Each of those turns
473
+ ends with a note of how many steps are left and the command that runs the
474
+ check, and the last says that only `finish` runs on it, as fascicle refuses
475
+ any other call there. The note is added to what that one turn sends, never to
476
+ the transcript, so it never piles up and a local runtime's cached prefix holds.
456
477
 
457
478
  A local builder makes one tool call per turn by default, since some model and
458
- runtime pairs return parallel calls badly. For a pair that handles them, such
459
- as qwen3.8 on Ollama's MLX runner, which returns several reads at once,
460
- `--builder-max-tool-calls <n>` lets each turn carry up to `n`. fascicle runs a
461
- turn's calls in order, so a read can't race an edit, and the builder's prompt
479
+ runtime pairs return parallel calls badly. Reads don't count: `read_file`,
480
+ `search_files`, and `list_files` are read-only, so a model that batches them,
481
+ as qwen3.8 on Ollama's MLX runner does, gets them all. A turn's calls run in
482
+ order, so a read can't race an edit. Past the cap, fascicle drops the
483
+ remaining calls and the next request names them, so the model can make them
484
+ again. For a pair that handles parallel calls, `--builder-max-tool-calls <n>`
485
+ lets each turn make up to `n` changes or commands, and the builder's prompt
462
486
  then asks it to batch independent reads but make one change or command per
463
487
  turn.
464
488
 
489
+ A thinking model can spend one turn reasoning for a long time: on Ollama's MLX
490
+ runner, with nothing capping a turn, one qwen3.8 turn ran to 30,847 tokens and
491
+ 19 minutes before a single `read_file`, since the runner lets an unset
492
+ `num_predict` run to the model's trained context less the prompt. So a local
493
+ builder's turns are capped at 16,384 output tokens, reasoning included, and
494
+ `--builder-max-output-tokens <n>` sets another cap. A turn cut off there
495
+ before it makes a tool call costs that one step: fascicle tells the model it
496
+ hit the limit and the session goes on. Each iteration's `summary.json` counts
497
+ those turns as `builder.truncated_turns`. An OpenRouter builder is capped only
498
+ when you set one, since a hosted model has its own output limit and can refuse
499
+ a request for more; uncapped, a turn cut off at the provider's limit still ends
500
+ the session.
501
+
465
502
  ```sh
466
503
  volley \
467
504
  --prompt "@task.md" --workspace ./my-project --criteria "@criteria.md" \
@@ -526,6 +563,10 @@ surprise at teardown:
526
563
  | `--worktree` | Committed onto `volley/<run id>` and left there. Recover it with `git switch volley/<run id>` or `git cherry-pick`; the branch is named in the final summary and in `--json` as `salvaged_branch`. |
527
564
  | `--worktree --discard-worktree` | Thrown away with the branch — verdicts only, nothing kept. |
528
565
 
566
+ volley's own commits (`--git` checkpoints, the squash merge, and the salvage
567
+ commit) use your git identity. Where git has none, as in the sandbox image,
568
+ they fall back to `volley <volley@volley.invalid>` rather than fail.
569
+
529
570
  Any run that does *not* converge (cost cap, budget exhausted, interrupt, error)
530
571
  is discarded wholesale in all three modes: an abandoned phase leaves nothing
531
572
  behind.
@@ -622,7 +663,12 @@ OPENROUTER_API_KEY=sk-or-v1-... volley \
622
663
  - **The key.** `OPENROUTER_API_KEY` is required whenever either seat is on
623
664
  `openrouter`. Unset, volley exits 5 naming it, before any work. It goes out
624
665
  as a Bearer token, with `X-Title: volley` so OpenRouter attributes the
625
- traffic.
666
+ traffic. Every process volley starts (the check, each command a tool-loop
667
+ builder runs, and volley's own git and checkride calls) gets volley's
668
+ environment without it, or `ANTHROPIC_API_KEY`, so nothing the builder
669
+ writes, a git filter or hook included, can read a key a seat spends. A
670
+ `claude_cli` seat under `VOLLEY_AUTH_MODE=oauth` still inherits the whole
671
+ environment, as the Claude CLI's login needs.
626
672
  - **The URL.** `VOLLEY_OPENROUTER_URL` (default `https://openrouter.ai/api/v1`)
627
673
  points both seats at a proxy or another OpenAI-compatible gateway, and
628
674
  `--builder-url` / `--critic-url` give one seat a URL of its own, under the
@@ -697,7 +743,7 @@ for you ([below](#zed-setup)).
697
743
  `--builder-provider`, `--builder-model`, `--critic-provider`, and
698
744
  `--critic-model`. A missing one is refused with every missing flag named. The
699
745
  rest are optional: `--builder-url`, `--critic-url`, `--builder-max-steps`,
700
- `--builder-max-tool-calls`, `--allow-unsandboxed-builder`, `--critic`, `--check`, `--max-iterations`,
746
+ `--builder-max-tool-calls`, `--builder-max-output-tokens`, `--allow-unsandboxed-builder`, `--critic`, `--check`, `--max-iterations`,
701
747
  `--max-cost-usd`, and `--state-dir`.
702
748
 
703
749
  It refuses these before serving, each with exit 5:
@@ -882,7 +928,7 @@ the workspace: `<root>/<run id>/`, where the root is `--state-dir` (or
882
928
  trajectory.jsonl # every fascicle trajectory event (replayable, below)
883
929
  feedback.md # current critic feedback (harness-written)
884
930
  verdict # approved | changes_requested (harness-written)
885
- iterations/NNN/ # per-iteration archive: feedback, verdict, check artifacts, summary
931
+ iterations/NNN/ # per-iteration archive: feedback, verdict, check artifacts and digest, summary
886
932
  ```
887
933
 
888
934
  - **Why outside.** Some checks read every file in the tree, dot-directories