@robmclarty/volley 0.10.0 → 0.11.0

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,32 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.11.0 — 2026-11-02
4
+
5
+ ### Added
6
+ - **Each iteration's `summary.json` lists the builder's turns as `builder.steps` (#7).** Each turn's request size (`input_tokens`), round trip, prefill and decode times where they can be told apart (`timing_basis` says how: Ollama's own durations, derived from them on the ai_sdk transport, which leaves `prompt_eval_duration` out, or the time to the first streamed chunk), and how many characters each tool result added. It shows whether a long local session that slows down spends its time reading the prompt or generating. The README gives a `jq` line to read them.
7
+ - **`--builder-trim-history <tokens>` trims a long tool-loop session's older tool results, off by default (#7).** Or `VOLLEY_BUILDER_TRIM_HISTORY`. Above that estimated request size, a turn sends older tool results as one-line stubs: a read of a file a later call changed says so, and any other result names its call and size. The last six turns, and every message that isn't a tool result, go as they were. The cut moves at turn boundaries every eight turns, and only once the trimmed request has grown past the size again, so a local runtime's cached prefix holds between moves. Experimental: it stays off until a long local session is shown to run faster with it on.
8
+
9
+ ### Fixed
10
+ - **A Claude seat's file tools stay in the build root (#10).** The builder's allowlist passed `Write`, `Edit`, and `MultiEdit` bare, and a bare allow rule approves every path on the machine, so a `claude_cli` builder could write anywhere you can. The builder's file tools are now only `Read(/**)` and `Edit(/**)`, and the critic's only `Read(/**)`: rules anchored at the session's working directory, which is the build root. Both leave out the bare `Grep` and `Glob` entries, which only reached outside it. Claude Code matches no `Write(path)` or `MultiEdit(path)` rule, so an `Edit` rule is what covers the Write tool. Both seats also run with Claude Code's `blockWritesOutsideWorkingDirectories` and `blockReadsOutsideWorkingDirectories` settings, which refuse file tools outside the build root in every permission mode, `bypassPermissions` included. `--dry-run` names the Claude Code version and warns below 2.1.314, where the write block goes unenforced, or 2.1.257 for the read block; it never refuses. `Bash` is still unconfined.
11
+ - **A `claude_cli` seat under `VOLLEY_AUTH_MODE=oauth` gets neither seat's key.** v0.10.1 kept `OPENROUTER_API_KEY` and `ANTHROPIC_API_KEY` from every process volley starts, but the Claude CLI under `oauth` inherits the whole environment to reach its login, so it got `OPENROUTER_API_KEY` and handed it to every command it ran. fascicle 0.17.7's `omit_env` keeps both out, and under `api_key` the configured key still reaches the CLI.
12
+ - **The `ANTHROPIC_API_KEY` warning fires only when the CLI is handed the key.** It warned whenever the key was set, but only `VOLLEY_AUTH_MODE=api_key` passes it to the Claude CLI; under `auto` and `oauth` the CLI gets no key and stays on its login. It now warns under `api_key` when a seat runs on the Claude CLI, and `.env.example` no longer says `auto` forwards the key.
13
+
14
+ ### Changed
15
+ - **Built on fascicle 0.17.7.** Besides `omit_env`, a `volley acp` thread takes its title from its first prompt, the default retry policy makes 4 attempts over 7 to 10 s before a provider failure gives up, up from 3 over 1.5 to 2.5 s, and provider errors name what to check.
16
+ - **A `claude_cli` seat that can't authenticate on the subscription login says what to try (#9).** Claude Code says `--bare` will become `claude -p`'s default, and a bare run never reads the login, so `auto` and `oauth` seats will stop authenticating until fascicle passes the opt-out. Volley's error now names `VOLLEY_AUTH_MODE=api_key` and volley#9, and the README says what the change means.
17
+ - **`ResolvedConfig` has `builder_trim_history`.** It's required, so SDK code that builds one by hand needs it (`null` for off).
18
+
19
+ ### Internal
20
+ - **Tests drive the real engine against a stand-in `claude` that records its argv and environment**, and pin the allowlists, the settings blocks, and each auth mode's environment. The history trim runs through fascicle's real tool loop, where `assert_prefix_stable` holds between boundary moves and fails across one.
21
+
22
+ ## v0.10.1 — 2026-11-01
23
+
24
+ ### Fixed
25
+ - **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.
26
+
27
+ ### Internal
28
+ - **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.
29
+
3
30
  ## v0.10.0 — 2026-10-27
4
31
 
5
32
  ### Added
package/README.md CHANGED
@@ -80,14 +80,17 @@ prompt ──> builder (full agentic session in your workspace)
80
80
  limit, the prompt says so and asks it to run the check first. In a
81
81
  checkride workspace it's asked to run `pnpm exec checkride --digest` after
82
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.
83
+ capped file for every failing slot, before any slot's raw output. On
84
+ `claude_cli` it reads, writes, and edits files only inside the build root,
85
+ runs any command through `Bash`, and runs with `--permission-mode
86
+ acceptEdits` by default (see
87
+ [What a Claude seat can touch](#what-a-claude-seat-can-touch)).
86
88
  - **Check** is auto-detected: if the workspace uses checkride, volley runs
87
89
  `pnpm exec checkride --json --digest` and gates on the summary. Any shell
88
90
  command works too (`--check "npm test"`), or `--check none` for critic-only
89
91
  runs.
90
- - **Critic** is read-only (`Read`, `Grep`, `Glob` — no write path at all) and
92
+ - **Critic** is read-only (it reads and searches the build root, with no write
93
+ path at all) and
91
94
  returns a schema-validated `{ verdict, feedback, unmet_criteria }`. It is told
92
95
  which files the builder changed, so it reviews a change rather than a tree.
93
96
  The harness writes the run's `feedback.md` and `verdict`; the critic
@@ -152,6 +155,30 @@ unrelated project); the binary is still `volley`:
152
155
  pnpm add -g @robmclarty/volley
153
156
  ```
154
157
 
158
+ ### What a Claude seat can touch
159
+
160
+ A `claude_cli` seat runs in the build root (the worktree under `--worktree`).
161
+ Its file tools stay there: the builder's allowlist names only `Read(/**)` and
162
+ `Edit(/**)`, rules anchored at that directory (an `Edit` rule covers the Write
163
+ tool too), and both seats run with Claude Code's
164
+ `blockWritesOutsideWorkingDirectories` and
165
+ `blockReadsOutsideWorkingDirectories` settings on, which refuse file tools
166
+ outside it in every permission mode, `bypassPermissions` included. The write
167
+ block needs Claude Code 2.1.314 and the read block 2.1.257; `--dry-run` names
168
+ the version and warns when it is older. `Bash` is not confined: a builder's
169
+ commands can reach anything you can.
170
+
171
+ ### Subscription seats and bare mode
172
+
173
+ Under `VOLLEY_AUTH_MODE` `auto` (the default) or `oauth`, a `claude_cli` seat
174
+ runs on the Claude CLI's own login, your subscription. Claude Code says `--bare`
175
+ will become the default for `claude -p` in a future release, and a bare run
176
+ never reads that login. Once that release ships, these seats stop
177
+ authenticating until fascicle passes the opt-out (tracked as
178
+ [volley#9](https://github.com/robmclarty/volley/issues/9)), and volley's error
179
+ says what to do. `VOLLEY_AUTH_MODE=api_key` with `ANTHROPIC_API_KEY` works
180
+ either way, billed at API rates.
181
+
155
182
  ## Usage
156
183
 
157
184
  ```
@@ -176,6 +203,7 @@ volley acp --builder-provider <name> --builder-model <model> \
176
203
  | `--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
204
  | `--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
205
  | `--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`. |
206
+ | `--builder-trim-history` | off | Experimental. Above this estimated request size, in tokens, a tool-loop builder's turns send older tool results as one-line stubs (see [Local builder](#local-builder)). Or `VOLLEY_BUILDER_TRIM_HISTORY`. Ignored for `claude_cli`. |
179
207
  | `--allow-unsandboxed-builder` | off | Permit a builder other than `claude_cli` to run without a sandbox (see [Local builder](#local-builder)). |
180
208
  | `--critic-model` | `opus` | Model for the critic. |
181
209
  | `--critic-provider` | `claude_cli` | `claude_cli`, `ollama`, `lmstudio`, or `openrouter` (see [Local critic](#local-critic) and [OpenRouter](#openrouter)). |
@@ -413,7 +441,7 @@ Providers and their setup:
413
441
  Install the peer for your provider in the project running volley (e.g.
414
442
  `pnpm add ai-sdk-ollama@^4.3.0`); fascicle loads it lazily only when the local
415
443
  critic actually runs. The version matters. The major must satisfy fascicle's
416
- declared peer range (`^4` for fascicle 0.17.1, which tracks the AI SDK v7
444
+ declared peer range (`^4` for fascicle 0.17.7, which tracks the AI SDK v7
417
445
  line), and volley declares 4.3.0 as its floor (an optional peer, so your
418
446
  package manager flags an older copy at install time). Before 4.3.0,
419
447
  ai-sdk-ollama streams a thinking model's reasoning only when the request sets
@@ -499,6 +527,32 @@ when you set one, since a hosted model has its own output limit and can refuse
499
527
  a request for more; uncapped, a turn cut off at the provider's limit still ends
500
528
  the session.
501
529
 
530
+ Every turn resends the whole session so far, so a long session's turns grow
531
+ and slow. Each iteration's `summary.json` lists the builder's turns as
532
+ `builder.steps`: each one's request size (`input_tokens`), its round trip, its
533
+ prefill and decode times where they can be told apart (`timing_basis` says
534
+ how: Ollama's own durations, or the time to the first streamed chunk), and how
535
+ many characters each tool result added. That shows whether a slowing session
536
+ spends its time reading the prompt or generating:
537
+
538
+ ```sh
539
+ jq -r '.builder.steps[] | [.index, .input_tokens, .duration_ms, .prefill_ms, .decode_ms] | @tsv' \
540
+ ~/.local/state/volley/<run id>/iterations/001/summary.json
541
+ ```
542
+
543
+ `--builder-trim-history <tokens>` is an experiment against that growth, off by
544
+ default. Once a turn's request is estimated above that many tokens, it sends
545
+ the older tool results as one-line stubs. A read of a file a later call
546
+ changed says so, and any other result names its call and size, so the model
547
+ can run it again. The last six turns, and every message that isn't a tool
548
+ result, go as they were. A local runtime reuses its cache only for the prefix
549
+ a request shares with the last one, so the trim cuts at a turn boundary that
550
+ moves on every eight turns, and only when the trimmed request has grown past
551
+ the size again. Between moves each request starts with the bytes of the last,
552
+ and each move costs one re-read of the shorter prompt. Half the model's context
553
+ window is a place to start (`32768` for a `num_ctx` of 65,536); whether it
554
+ makes a long session faster is what `builder.steps` will show.
555
+
502
556
  ```sh
503
557
  volley \
504
558
  --prompt "@task.md" --workspace ./my-project --criteria "@criteria.md" \
@@ -663,7 +717,13 @@ OPENROUTER_API_KEY=sk-or-v1-... volley \
663
717
  - **The key.** `OPENROUTER_API_KEY` is required whenever either seat is on
664
718
  `openrouter`. Unset, volley exits 5 naming it, before any work. It goes out
665
719
  as a Bearer token, with `X-Title: volley` so OpenRouter attributes the
666
- traffic.
720
+ traffic. Every process volley starts (the check, each command a tool-loop
721
+ builder runs, and volley's own git and checkride calls) gets volley's
722
+ environment without it, or `ANTHROPIC_API_KEY`, so nothing the builder
723
+ writes, a git filter or hook included, can read a key a seat spends. A
724
+ `claude_cli` seat under `VOLLEY_AUTH_MODE=oauth` inherits the rest of the
725
+ environment, as the Claude CLI's login needs, but not those two keys, so
726
+ neither reaches a command Claude runs.
667
727
  - **The URL.** `VOLLEY_OPENROUTER_URL` (default `https://openrouter.ai/api/v1`)
668
728
  points both seats at a proxy or another OpenAI-compatible gateway, and
669
729
  `--builder-url` / `--critic-url` give one seat a URL of its own, under the
@@ -958,8 +1018,9 @@ pnpm dlx --package=fascicle fascicle-viewer ~/.local/state/volley/<run id>/traje
958
1018
  ## Environment variables
959
1019
 
960
1020
  See [.env.example](./.env.example): `VOLLEY_CLAUDE_BIN`, `VOLLEY_AUTH_MODE`,
961
- `VOLLEY_LOG_LEVEL`, `VOLLEY_NO_COLOR`, `VOLLEY_PRICING_PATH`, the
962
- `ANTHROPIC_API_KEY` billing-meter warning, each seat's base URL
1021
+ `VOLLEY_LOG_LEVEL`, `VOLLEY_NO_COLOR`, `VOLLEY_PRICING_PATH`,
1022
+ `ANTHROPIC_API_KEY` (which only `VOLLEY_AUTH_MODE=api_key` hands the Claude CLI,
1023
+ with a billing-meter warning), each seat's base URL
963
1024
  (`VOLLEY_OLLAMA_URL`, `VOLLEY_LMSTUDIO_URL`, `VOLLEY_OPENROUTER_URL`), and
964
1025
  `OPENROUTER_API_KEY` for [OpenRouter](#openrouter) seats. Every run reads
965
1026
  `XDG_STATE_HOME` for its default state root, and `XDG_CONFIG_HOME` for where