@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 +27 -0
- package/README.md +69 -8
- package/dist/cli.js +684 -296
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +44 -3
- package/dist/index.js +549 -246
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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.
|
|
84
|
-
|
|
85
|
-
|
|
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 (
|
|
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.
|
|
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`,
|
|
962
|
-
`ANTHROPIC_API_KEY`
|
|
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
|