@robmclarty/volley 0.8.0 → 0.9.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,27 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.9.0 — 2026-10-20
4
+
5
+ ### Added
6
+ - **`volley critique` checks and critiques a change already made, once, with no builder (#3).** The change is everything that differs from `--baseline` (default HEAD), committed or not, untracked files included, and the critic sees what a run's critic sees, so critics can be compared on fixed, known-good and known-bad changes without paying for builder turns. It exits 0 on approval over a green check and 2 when the critic asks for changes or the check is red (status `rejected`). Every builder or loop flag, and `--config`, is refused by name (exit 5), and a `--baseline` that isn't a commit fails before any state is written. `run_critique` is exported for harnesses.
7
+ - **`--builder-max-tool-calls <n>` lets a tool-loop builder make several tool calls a turn (#4).** Or `VOLLEY_BUILDER_MAX_TOOL_CALLS`; the default stays 1, since some model and runtime pairs return parallel calls badly. Above 1, the builder's prompt asks it to batch independent reads but make one change or command per turn. The cap is recorded with the run and kept on resume.
8
+ - **Each run names its state directory.** `RunResult`, and so `--json` and `summary.json`, carry `run_state_dir`, and a run prints `state: <dir>` when it starts and in its final summary. `--dry-run` names the state root.
9
+ - **`sandbox_run_args` mounts a contained run's own state directory.** Its `run_state` option binds the host's `<root>/<run id>/` at that run's directory under `SANDBOX_STATE_ROOT`, never the whole root, so the builder inside can't read or rewrite another run's state. `default_state_root` and `SANDBOX_STATE_ROOT` are exported.
10
+
11
+ ### Changed
12
+ - **Run state leaves the workspace by default (#5).** Without `--state-dir`, a run's config, summary, trajectory, feedback, verdict, and iterations go to `<root>/<run id>/`, where the root is `$XDG_STATE_HOME/volley`, else `~/.local/state/volley`, as under `volley acp`. A check that reads every file, dot-directories included, no longer fails from the second iteration on over volley's own feedback. A harness that read `<workspace>/.volley/` reads `run_state_dir` instead. A run directory volley can't write is a configuration error (exit 5), and inside the sandbox the message names the mount. The critic's "prior iteration state" line now drops by default, since the iterations sit outside the tree it reads.
13
+ - **`volley resume <id>` finds a run from any directory**, under `--state-dir` or the default root, and falls back to the workspace's `.volley/` for a run an older volley started. It refuses a run directory with a symbolic link in it, an id that isn't a run id, and a critique, which has nothing to resume.
14
+ - **A resume on the host runs only a config this machine signed.** volley signs each run's `config.json` (as `config.sig`) under a key kept at `$XDG_CONFIG_HOME/volley/run-key`, else `~/.config/volley/run-key`, and signs nothing inside the sandbox, so a contained run's builder can't plant a check or seat for a host-side resume to run. A config without a valid signature (a contained run's, another machine's, an older volley's, or one edited by hand) resumes only with `--trust-config`, which first shows the check and seats it's about to run, escaped so a planted control sequence can't disguise them. Resume a contained run inside its container.
15
+ - **`ResolvedConfig` has `mode` and `builder_max_tool_calls`, and `RunResult` has `mode` and `run_state_dir`.** All are required, so SDK code that builds either by hand needs them. `resolve_config` always sets `state_dir`, where null still means the workspace's `.volley/`. `RunStatus` gains `rejected`, and a critique's `comparison.builder_transport` is null.
16
+
17
+ ### Fixed
18
+ - **`search_files` and `list_files` on a file path search or list that file.** They walked it as a directory and answered "no matches", which led a local critic to reject a sound change. A path that doesn't exist now says so.
19
+ - **A critic verdict with null `feedback` or `unmet_criteria` reads as empty.** A critic that holds tools isn't constrain-decoded, and such an answer failed schema validation and exited 6.
20
+ - **A `--state-dir` inside the workspace stays out of the builder's work.** Its files showed up in the change list the critic reads and were committed by `--git` checkpoints. A `--state-dir` that is the workspace itself is refused (exit 5).
21
+
22
+ ### Internal
23
+ - **The tests keep run state and the signing key out of your home directory.** A setup file gives each test file its own `XDG_STATE_HOME` and `XDG_CONFIG_HOME`, and fails a file that leaves a test run in the real state root or makes the real key.
24
+
3
25
  ## v0.8.0 — 2026-10-19
4
26
 
5
27
  ### Added
package/README.md CHANGED
@@ -54,8 +54,8 @@ volley \
54
54
  The cap is enforced in the loop guard after each phase. Sessions are never
55
55
  killed mid-flight for cost (a builder killed mid-edit leaves the workspace
56
56
  undefined), so set the cap below your true ceiling by roughly one iteration's
57
- expected cost. Per-run totals land in `.volley/summary.json` and the final
58
- terminal summary. An [OpenRouter](#openrouter) seat's cost is the charge
57
+ expected cost. Per-run totals land in the run's `summary.json` (see
58
+ [Run artifacts](#run-artifacts)) and the final terminal summary. An [OpenRouter](#openrouter) seat's cost is the charge
59
59
  OpenRouter reports for each call, so the cap trips on one as on any other.
60
60
 
61
61
  ## How it works
@@ -81,7 +81,7 @@ prompt ──> builder (full agentic session in your workspace)
81
81
  - **Critic** is read-only (`Read`, `Grep`, `Glob` — no write path at all) and
82
82
  returns a schema-validated `{ verdict, feedback, unmet_criteria }`. It is told
83
83
  which files the builder changed, so it reviews a change rather than a tree.
84
- The harness writes `.volley/feedback.md` and `.volley/verdict`; the critic
84
+ The harness writes the run's `feedback.md` and `verdict`; the critic
85
85
  cannot get them wrong.
86
86
 
87
87
  Each iteration is a fresh session — no context carries over except through the
@@ -150,6 +150,7 @@ volley [options]
150
150
  volley --config <path>
151
151
  volley resume <run-id>
152
152
  volley matrix --builders <models> --critics <models> --config <path>
153
+ volley critique --criteria <criteria> [--baseline <rev>] [options]
153
154
  volley acp --builder-provider <name> --builder-model <model> \
154
155
  --critic-provider <name> --critic-model <model> [options]
155
156
  ```
@@ -163,7 +164,8 @@ volley acp --builder-provider <name> --builder-model <model> \
163
164
  | `--builder-model` | `opus` | `opus`/`sonnet`/`haiku` or a full model id. |
164
165
  | `--builder-provider` | `claude_cli` | `claude_cli`, `ollama`, `lmstudio`, or `openrouter` (see [Local builder](#local-builder) and [OpenRouter](#openrouter)). |
165
166
  | `--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)). |
166
- | `--builder-max-steps` | `50` | Tool-loop builder step cap per iteration. Ignored for `claude_cli`. |
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`. |
167
169
  | `--allow-unsandboxed-builder` | off | Permit a builder other than `claude_cli` to run without a sandbox (see [Local builder](#local-builder)). |
168
170
  | `--critic-model` | `opus` | Model for the critic. |
169
171
  | `--critic-provider` | `claude_cli` | `claude_cli`, `ollama`, `lmstudio`, or `openrouter` (see [Local critic](#local-critic) and [OpenRouter](#openrouter)). |
@@ -178,7 +180,7 @@ volley acp --builder-provider <name> --builder-model <model> \
178
180
  | `--gate-paths` | (built-in list) | Comma-separated globs naming the gate — tests, fixtures, check config. Replaces the defaults; see [What the builder changed](#what-the-builder-changed). |
179
181
  | `--fail-on-gate-edit` | off | Halt the run (exit 8) if the builder edits a gate path, instead of reporting it. |
180
182
  | `--sandbox-image` | `volley-sandbox:latest` | Container image for the local-builder sandbox. Or `VOLLEY_SANDBOX_IMAGE`. |
181
- | `--state-dir` | the workspace's `.volley/` | Keep each run's state under `<path>/<run id>/` instead, and none in the workspace (see [Run artifacts](#run-artifacts)). Or `state_dir` in a config file. |
183
+ | `--state-dir` | `$XDG_STATE_HOME/volley`, else `~/.local/state/volley` | The root each run keeps its state under, at `<path>/<run id>/`, never in the workspace (see [Run artifacts](#run-artifacts)). Or `state_dir` in a config file. |
182
184
  | `--run-id` | a fresh UUID | The run's id, naming its run state and its `volley/<id>` branch, so a harness that owns the id finds it in both. Letters, digits, `-`, and `_`, starting with a letter or digit. |
183
185
  | `--dry-run` | off | Validate config, run `checkride doctor`, probe each tool-loop seat's model endpoint (local or OpenRouter), run the critic-seat canary (for a local critic), then exit. |
184
186
  | `--config` | — | TypeScript config file (`VolleyConfig` default export). CLI flags override. |
@@ -196,7 +198,7 @@ checkride.
196
198
  | Code | Meaning |
197
199
  |---|---|
198
200
  | 0 | Check passed and verdict approved. |
199
- | 2 | Max iterations reached without success. |
201
+ | 2 | Max iterations reached without success; for `volley critique`, the critic asked for changes or the check failed. |
200
202
  | 3 | Builder/provider error. |
201
203
  | 4 | Check pipeline error (broken pipeline, not failing checks). |
202
204
  | 5 | Configuration error. |
@@ -229,17 +231,35 @@ export default config;
229
231
  ### Resume
230
232
 
231
233
  ```sh
232
- volley resume <run-id> # in the workspace directory
233
- volley resume <run-id> --workspace ./my-project
234
+ volley resume <run-id> # from any directory
234
235
  volley resume <run-id> --state-dir ~/volley-runs # a run started with --state-dir
236
+ volley resume <run-id> --workspace ./my-project # a run an older volley kept in .volley/
235
237
  ```
236
238
 
237
- Given the root a run was started under, `resume` finds it at
238
- `<root>/<run-id>/` from any directory, since the run's recorded config names its
239
- workspace.
239
+ `resume` finds the run at `<root>/<run-id>/` under `--state-dir`, else the
240
+ default root, from any directory, since the run's recorded config names its
241
+ workspace. A run an older volley started keeps its state in the workspace's
242
+ `.volley/`, and resumes there: point `--workspace` at it, or run `resume` from
243
+ inside it. A run directory with a symbolic link in it is refused, since volley
244
+ never writes one.
245
+
246
+ **A resume on the host runs only a config this machine signed.** A resume runs
247
+ what the run's `config.json` says, its check command and its seats included.
248
+ So volley on the host signs each config it writes, as `config.sig`, under a
249
+ key that stays on the machine (`$XDG_CONFIG_HOME/volley/run-key`, else
250
+ `~/.config/volley/run-key`), and a resume on the host refuses a config whose
251
+ signature doesn't hold (exit 5). That covers:
252
+
253
+ - **A contained run's config.** Nothing is signed inside the sandbox, and the
254
+ builder there can rewrite its own run's config, so resume it inside its
255
+ container, with the same mount. Inside the container, nothing is checked.
256
+ - **Another machine's config, an older volley's, or one you edited.** Read it,
257
+ then pass `--trust-config`, and the resume says which check and seats it's
258
+ about to run before it runs them.
240
259
 
241
260
  Continues from the last completed iteration with the saved feedback and cost
242
- totals. An iteration interrupted mid-phase is discarded and re-run.
261
+ totals. An iteration interrupted mid-phase is discarded and re-run. A
262
+ [critique](#critique-a-fixed-change) has one round and nothing to resume.
243
263
 
244
264
  ### Matrix sweep
245
265
 
@@ -272,9 +292,10 @@ local providers share one GPU, so parallel combos would thrash the model loader.
272
292
  Each run forces `--worktree --discard-worktree` for a clean per-run reset — a
273
293
  sweep wants verdicts, so each seat's effects are isolated and then thrown away,
274
294
  leaving neither a branch nor a commit behind — so the **workspace must be a git
275
- repository**. Every attempt's run state is written to
276
- `.volley-matrix/<builder>__<critic>/run-NN/summary.json`, the whole sweep to
277
- `.volley-matrix/matrix.json`, and one aggregate table goes to stderr:
295
+ repository**. Each attempt keeps its run state under the state root like any
296
+ run, and its summary, which names that state as `run_state_dir`, is copied to
297
+ `.volley-matrix/<builder>__<critic>/run-NN/summary.json`. The whole sweep goes
298
+ to `.volley-matrix/matrix.json`, and one aggregate table goes to stderr:
278
299
 
279
300
  ```
280
301
  builder critic pass iters wall cost salvage flags why
@@ -309,6 +330,48 @@ A run that does not converge is a *result*, shown in the table; the sweep exits
309
330
  0 as long as every attempt produced a summary, and nonzero only when one
310
331
  produced none at all (`status: broke`).
311
332
 
333
+ ### Critique a fixed change
334
+
335
+ `volley critique` runs the check and the critic once over a change already
336
+ made, with no builder. It's how you choose a critic: replay the same
337
+ known-good and known-bad changes through the critic seat, across models and
338
+ prompts, and count how many of the bad ones each critic rejects. A matrix sweep
339
+ can't tell a critic that reads from one that approves everything, since both
340
+ pass whenever the builder happens to be right.
341
+
342
+ ```sh
343
+ volley critique --workspace ./repo --baseline <rev> \
344
+ --criteria "@criteria.md" \
345
+ --critic-provider ollama --critic-model qwen3:8b --critic reviewer
346
+ ```
347
+
348
+ - **The change** is everything that differs from `--baseline`, committed or
349
+ not, untracked files included: the set a run reports for its builder. Without
350
+ `--baseline` it's HEAD, so the uncommitted and untracked edits are the
351
+ change. A revision that isn't a commit is a configuration error (exit 5).
352
+ - **The critic** sees what it sees in a run: the changed paths and gate edits,
353
+ the check's section, and the same preset or prompt file. `--prompt` is
354
+ optional and only recorded, since a critic judges against `--criteria`.
355
+ - **The state** is a one-round run's, under the state root
356
+ ([Run artifacts](#run-artifacts)). The summary records `mode: "critique"`
357
+ and no builder: `builder` is null in the iteration's archive, and so is the
358
+ comparison block's `builder_transport`. The archived `critic.tool_calls` says
359
+ how much the critic read before it judged.
360
+ - **Exit codes**: 0 when the critic approves over a green check, 2 when it asks
361
+ for changes or the check is red (`status: "rejected"`), and the usual codes
362
+ for provider, check-pipeline, configuration, and critic errors.
363
+ - **Flags**: the critic's seat flags, `--check`, `--critic`, `--gate-paths`,
364
+ `--state-dir`, `--run-id`, `--dry-run` (with the critic-seat canary),
365
+ `--json`, and the output flags work as they do for a run. Every flag only a
366
+ builder or its loop uses, from `--builder-model` and `--max-iterations` to
367
+ `--worktree`, `--git`, and `--fail-on-gate-edit`, is refused by name (exit
368
+ 5), and so is `--config`, since a run's config file sets a builder.
369
+
370
+ With no builder, a local critic needs no sandbox opt-in. The check still runs
371
+ whatever the repository's check runs, though, so review an untrusted change
372
+ with `volley critique` inside the container, its run directory mounted as a
373
+ run's would be ([Local builder](#local-builder)).
374
+
312
375
  ## Local critic
313
376
 
314
377
  The critic is read-only and schema-constrained, which makes it a good fit for
@@ -391,6 +454,14 @@ tool loop per iteration (capped by `--builder-max-steps`, default 50; hitting
391
454
  the cap is treated as partial work, not an error). The produced workspace goes
392
455
  to check + critic exactly like a `claude_cli` build.
393
456
 
457
+ 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
462
+ then asks it to batch independent reads but make one change or command per
463
+ turn.
464
+
394
465
  ```sh
395
466
  volley \
396
467
  --prompt "@task.md" --workspace ./my-project --criteria "@criteria.md" \
@@ -417,6 +488,26 @@ mangled crossing the boundary — so a *contained* Claude role must drive by API
417
488
  key (`VOLLEY_AUTH_MODE=api_key` with `ANTHROPIC_API_KEY`); the all-Claude
418
489
  subscription path stays on the host, Docker-free.
419
490
 
491
+ **A contained run's state needs a mount of its own.** The container's root
492
+ filesystem is read-only, so volley inside has nowhere to keep the run's state
493
+ until you mount the run's own host directory where it looks:
494
+
495
+ 1. Create `<root>/<run id>/` on the host first, empty. On Linux, Docker creates
496
+ a missing one owned by root, and the container's user can't write it.
497
+ 2. Mount it at `/home/node/.local/state/volley/<run id>`. That's
498
+ `SANDBOX_STATE_ROOT`, and `run_state` on `sandbox_run_args` renders the
499
+ flag.
500
+ 3. Pass volley the same `--run-id`.
501
+
502
+ Without the mount, the run stops before any spend (exit 5) and says what to
503
+ mount. Mount that one directory, never the whole root: the builder runs
504
+ arbitrary commands, and with the root mounted it could read every other run's
505
+ trajectory or plant a check in another run's config. Its own run's config is
506
+ still the builder's to rewrite, so a resume on the host refuses it, since
507
+ volley signs only the configs it writes outside the sandbox
508
+ ([Resume](#resume)). Resume a contained run inside the container, with the same
509
+ mount.
510
+
420
511
  To run a local builder uncontained on the host anyway, opt out with
421
512
  `--allow-unsandboxed-builder` (or `VOLLEY_ALLOW_UNSANDBOXED_BUILDER=1`), which
422
513
  prints a one-time warning — only point it at a workspace you are willing to let a
@@ -606,7 +697,7 @@ for you ([below](#zed-setup)).
606
697
  `--builder-provider`, `--builder-model`, `--critic-provider`, and
607
698
  `--critic-model`. A missing one is refused with every missing flag named. The
608
699
  rest are optional: `--builder-url`, `--critic-url`, `--builder-max-steps`,
609
- `--allow-unsandboxed-builder`, `--critic`, `--check`, `--max-iterations`,
700
+ `--builder-max-tool-calls`, `--allow-unsandboxed-builder`, `--critic`, `--check`, `--max-iterations`,
610
701
  `--max-cost-usd`, and `--state-dir`.
611
702
 
612
703
  It refuses these before serving, each with exit 5:
@@ -665,12 +756,13 @@ another way.
665
756
 
666
757
  ### Where its state goes
667
758
 
668
- Nothing of volley's own lands in the repo. Each prompt's run state goes to
759
+ Nothing of volley's own lands in the repo. Each prompt's run state goes under
760
+ the same state root as any run's ([Run artifacts](#run-artifacts)):
669
761
  `<root>/<run id>/`, where the root is `--state-dir`, else
670
- `$XDG_STATE_HOME/volley`, else `~/.local/state/volley`. Nothing prunes old runs.
671
- The whole root is safe to delete whenever no prompt is running. A run's
672
- `trajectory.jsonl` there replays in `fascicle-viewer` like a CLI run's
673
- ([Run artifacts](#run-artifacts)).
762
+ `$XDG_STATE_HOME/volley`, else `~/.local/state/volley`. An unwritable root
763
+ stops `volley acp` at startup, so it shows once in the agent log. The whole
764
+ root is safe to delete whenever no prompt is running. A run's
765
+ `trajectory.jsonl` there replays in `fascicle-viewer` like a CLI run's.
674
766
 
675
767
  ### Zed setup
676
768
 
@@ -748,7 +840,7 @@ iteration and reports what moved:
748
840
  which ones moved.
749
841
  - Changed paths matching the **gate** — the tests, fixtures, and check
750
842
  configuration that decide whether the work passes — are called out separately
751
- in the prompt, listed in `.volley/summary.json` as `comparison.gate_edits`,
843
+ in the prompt, listed in the run's `summary.json` as `comparison.gate_edits`,
752
844
  and warned about on stderr as they happen.
753
845
 
754
846
  A gate edit is not misconduct: plenty of tasks are *about* the tests. It is the
@@ -777,11 +869,15 @@ that `--fail-on-gate-edit` cannot fire there.
777
869
 
778
870
  ## Run artifacts
779
871
 
780
- By default, everything volley knows lives in the workspace:
872
+ Everything volley knows about a run lives in a directory of its own, outside
873
+ the workspace: `<root>/<run id>/`, where the root is `--state-dir` (or
874
+ `state_dir` in a config file), else `$XDG_STATE_HOME/volley`, else
875
+ `~/.local/state/volley`.
781
876
 
782
877
  ```
783
- .volley/
878
+ <root>/<run id>/
784
879
  config.json # resolved run config (immutable per run)
880
+ config.sig # this machine's signature over config.json (none from inside the sandbox)
785
881
  summary.json # run-level usage, cost, status
786
882
  trajectory.jsonl # every fascicle trajectory event (replayable, below)
787
883
  feedback.md # current critic feedback (harness-written)
@@ -789,22 +885,33 @@ By default, everything volley knows lives in the workspace:
789
885
  iterations/NNN/ # per-iteration archive: feedback, verdict, check artifacts, summary
790
886
  ```
791
887
 
792
- A stale `.volley/` from a previous run is rotated to `.volley.bak.<timestamp>/`.
793
-
794
- With `--state-dir <root>` (or `state_dir` in a config file), each run writes the
795
- same files to `<root>/<run id>/` instead, and nothing to the workspace. A
796
- directory per run never needs rotating, and `config.json` records the root.
797
- `volley acp` always works this way, under a root of its own
798
- ([Where its state goes](#where-its-state-goes)). `volley matrix` still writes
799
- its sweep to `.volley-matrix/` in the workspace, and a base config's
800
- `state_dir` moves only each seat's run state.
888
+ - **Why outside.** Some checks read every file in the tree, dot-directories
889
+ included, as markdownlint-cli2 and checkride's links walk do. Kept in the
890
+ workspace, the run's own `feedback.md` would fail them from the second
891
+ iteration on, a failure the builder can't fix. Kept out, the workspace holds
892
+ only the builder's work, and `--git` checkpoints never commit volley's files.
893
+ - **Finding a run.** A run prints `state: <dir>` when it starts and again in its
894
+ final summary, and `--json` and `summary.json` carry it as `run_state_dir`.
895
+ `--dry-run` names the root, and `--run-id` names the directory.
896
+ - **Unwritable.** A run directory volley can't write is a configuration error
897
+ (exit 5) before any spend. In the sandbox container, mount the run's own
898
+ directory ([Local builder](#local-builder)).
899
+ - **Pruning.** Nothing prunes old runs. A root volley creates is yours alone
900
+ (0700), since trajectories hold prompts and tool output. A reused `--run-id`
901
+ rotates the earlier run aside to `<run id>.bak.<timestamp>/`.
902
+ - **Older runs.** An older volley kept the same files in the workspace's
903
+ `.volley/`. `volley resume` still finds such a run there, and resumes it in
904
+ place once you pass `--trust-config`, since an older volley signed nothing
905
+ ([Resume](#resume)).
906
+
907
+ `volley matrix` writes its sweep to `.volley-matrix/` in the workspace, and a
908
+ base config's `state_dir` moves each seat's run state as it would any run's.
801
909
 
802
910
  The trajectory replays in fascicle's bundled viewer — the `fascicle-viewer` bin
803
911
  ships inside the `fascicle` package; there is no separate viewer package:
804
912
 
805
913
  ```sh
806
- pnpm dlx --package=fascicle fascicle-viewer .volley/trajectory.jsonl
807
- pnpm dlx --package=fascicle fascicle-viewer <root>/<run id>/trajectory.jsonl # under --state-dir
914
+ pnpm dlx --package=fascicle fascicle-viewer ~/.local/state/volley/<run id>/trajectory.jsonl
808
915
  ```
809
916
 
810
917
  ## Environment variables
@@ -813,8 +920,9 @@ See [.env.example](./.env.example): `VOLLEY_CLAUDE_BIN`, `VOLLEY_AUTH_MODE`,
813
920
  `VOLLEY_LOG_LEVEL`, `VOLLEY_NO_COLOR`, `VOLLEY_PRICING_PATH`, the
814
921
  `ANTHROPIC_API_KEY` billing-meter warning, each seat's base URL
815
922
  (`VOLLEY_OLLAMA_URL`, `VOLLEY_LMSTUDIO_URL`, `VOLLEY_OPENROUTER_URL`), and
816
- `OPENROUTER_API_KEY` for [OpenRouter](#openrouter) seats. `volley acp` also
817
- reads `XDG_STATE_HOME` for its state root.
923
+ `OPENROUTER_API_KEY` for [OpenRouter](#openrouter) seats. Every run reads
924
+ `XDG_STATE_HOME` for its default state root, and `XDG_CONFIG_HOME` for where
925
+ it keeps the key it signs run configs with.
818
926
 
819
927
  ## Development
820
928