Skip to content

Releases

v0.12.0

Choose a tag to compare

@github-actions github-actions released this 09 Nov 20:08

Added

  • --effort-ladder low,medium,high runs each build one effort rung further up, off by default. Or VOLLEY_EFFORT_LADDER, or effort_ladder in a config file. Rungs come from low, medium, high, xhigh, and max, and the top rung repeats. A build climbs only after one that didn't end the run, and it records why: check_failed, else critic_rejected. Each build prints its rung ([iter 2] rung medium (climbed: check_failed)), and each iteration's summary.json records rung, climbed and effort_ignored. On claude_cli the effort reaches Claude Code as CLAUDE_CODE_EFFORT_LEVEL. On ollama and lmstudio, which ignore effort, the ladder is flat and config warns. A resumed run keeps its ladder and picks up at the rung it had reached.
  • --critic-effort sets the critic's effort on its own. Or VOLLEY_CRITIC_EFFORT, or critic_effort in a config file. It takes the same five levels and defaults to high once a ladder is set. With neither set, the critic is sent no effort, as before.
  • --tester on writes a failing test from the criteria before the first build, off by default. Or VOLLEY_TESTER, or tester in a config file. The tester runs on the critic's seat at the critic's effort; --tester-model (or VOLLEY_TESTER_MODEL) overrides the model. It writes only tests and fixtures under the gate paths, never package.json, a *.config.* file or .github/, and has no shell. The check must then fail. If it passes, the run ends as tester_passed_early (exit 2) with no builder spend. The tester needs a check, and a tester on any provider but claude_cli needs the same containment a builder does. Its spend (tester_cost_usd) and writes (tester_changes) are recorded apart, so its test isn't counted as a builder gate edit, and the critic reviews the tester's test, reporting any problems in an optional test_findings.
  • The tester runs under volley acp too. Its writes go through Zed's approval prompts with their diff, as the builder's do, and a path it may not write is refused before you're asked. A claude_cli tester is refused at startup. Round 1 shows the tester's test and the build as two headings.
  • volley run --protocol runciter.run/1 lets runciter's process kind run volley with no adapter. volley reads one RunInput on stdin, writes exactly one RunOutput on stdout, puts runciter.progress and runciter.usage lines on stderr, and exits 0, 1 or 2 by the protocol's own table. It maps the input as runciter's volley kind did: the agent's prompt plus context.md, the task's criteria, the check from the stage's verify, state under the run directory, and the critic's last feedback as a critic-feedback.md artifact. It refuses --worktree, --git, --json and a positional prompt. The README's new "Run volley from runciter" section has an agent-file example and the exit table. volley run is now another name for volley.

Changed

  • Each iteration of a run's trajectory has two more spans. The flow gained test and work branches, which pass straight through when the tester is off, so the builder's and the critic's calls, the output, and the exit codes are unchanged; the diagram draws the new branches.

Internal

  • keyframes.md lists src/protocol/ under M1 (cli), and its C2 constraint now covers the tester. The build's record is in .plumbbob/builds/2026-11-08-move-1-tester-seat-effort-ladder-and-runciter-run-1/.

v0.11.1

Choose a tag to compare

@github-actions github-actions released this 05 Nov 02:44

Internal

  • volley's intent is written down in keyframes.md. It holds four principles, a seven-part map of src/, eleven constraints and eight scenarios, each linked to the test or rule that holds it, twelve decisions, one open question and five ideas not yet built. AGENTS.md tells agents how to treat each kind of item, and CLAUDE.md imports it, so Claude Code reads it.
  • pnpm check checks keyframes.md against the repo. A new intent check runs keyframes check --strict, from @robmclarty/keyframes 0.3.0 as a dev dependency. It fails when a test, rule or path the plan points at is renamed or moved, or when the plan picks up a warning. It's a gate for developing volley and never runs in a volley run.
  • Agents ask before changing keyframes.md. Claude Code asks before it edits the file or runs a shell command that names it. A pre-commit hook shows a macOS dialog before any commit that changes it can land, whichever agent made the change, and refuses where no dialog can be shown. A clone turns the hook on with git config core.hooksPath .githooks.
  • The published package is unchanged. No command, flag or output changed since v0.11.0.

v0.11.0

Choose a tag to compare

@github-actions github-actions released this 03 Nov 03:10

Added

  • 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.
  • --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.

Fixed

  • 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.
  • 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.
  • 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.

Changed

  • 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.
  • 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.
  • ResolvedConfig has builder_trim_history. It's required, so SDK code that builds one by hand needs it (null for off).

Internal

  • 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.

v0.10.1

Choose a tag to compare

@github-actions github-actions released this 01 Nov 15:49

Fixed

  • 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.

Internal

  • 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.

v0.10.0

Choose a tag to compare

@github-actions github-actions released this 28 Oct 03:24

Added

  • 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.
  • 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.
  • --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.
  • 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.

Changed

  • 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.
  • 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.
  • 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.

Fixed

  • 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.
  • 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.

Internal

  • 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.
  • 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.
  • 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.

v0.9.0

Choose a tag to compare

@github-actions github-actions released this 21 Oct 16:25

Added

  • 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.
  • --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.
  • 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.
  • 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.

Changed

  • 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.
  • 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.
  • 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.
  • 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.

Fixed

  • 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.
  • 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.
  • 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).

Internal

  • 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.

v0.8.0

Choose a tag to compare

@github-actions github-actions released this 20 Oct 06:39

Added

  • volley acp runs volley from Zed. It serves volley over stdio as an Agent Client Protocol agent, so Zed's agent panel, or any ACP client, can drive it. Each prompt is a fresh volley run on the open project. The builder's work streams in as the reply under a Round N line, the check runs as a command card, the critic's verdict shows as a thought, and the turn ends with a summary. Edits and commands go through the editor's permission prompts. Outside volley's sandbox container, every builder command and every check asks first, Auto mode isn't offered, and neither the commands nor the check can read OPENROUTER_API_KEY or ANTHROPIC_API_KEY. Run state goes under $XDG_STATE_HOME/volley, never the repo. It requires the four seat flags and refuses --worktree, --git, --json, and a claude_cli builder (exit 5). The README covers the Zed setup and a check to run by hand.
  • Either seat can run on OpenRouter. --builder-provider openrouter and --critic-provider openrouter reach a hosted model with one OPENROUTER_API_KEY, from the plain CLI as well as volley acp, and VOLLEY_OPENROUTER_URL overrides the endpoint. An OpenRouter builder runs volley's own tool loop under the same containment gate as a local one, and an OpenRouter critic gets the local critic's retry and fallback. OpenRouter reports its own charge, so --max-cost-usd trips on it. An unset key is a configuration error (exit 5).
  • --state-dir <root> keeps run state out of the workspace. Each run's state goes to <root>/<run_id>/, and state_dir in a config file does the same. volley resume <id> --state-dir <root> finds a run there. Without the flag, state stays in .volley/ as before.
  • run_volley takes per-seat tools, a builder approval handler, and a trajectory sink. All three are optional, and a run without them is exactly what it was.

Changed

  • ResolvedConfig has state_dir. It's required (string | null), so SDK code that builds a ResolvedConfig by hand instead of through resolve_config needs it. Null keeps the run's state in .volley/.

Fixed

  • A local critic that runs out of steps falls back instead of failing the run. Since fascicle 0.10.1, a critic that spent its step budget reading files threw straight past the retry and the fallback, so the run exited 6, and --dry-run's canary exited 5. It now goes straight to judging without tools. A fallback verdict now says why: degrade_cause (stream_error or step_exhaustion) on the critic record, critic_degrade_causes in the summary's comparison block, and deg:stream or deg:steps in the matrix flags.
  • Stopping a run mid-check now stops the whole check. A stop killed only the check's shell, which left a test runner's workers, or a server the check started in the background, still running. The check now runs in a process group of its own, and a stop kills all of it.

Internal

  • Built on fascicle 0.16.3 and @agentclientprotocol/sdk 1.8.0.
  • The tests drive a whole volley turn through an in-process ACP client, against a stub model server. The live check in Zed is scripted in the README and was run by hand. Its one miss, the check's card losing its command look while it asks, waits on robmclarty/fascicle#16.
  • The flow diagram's critic retry and fallback rows name every provider but claude_cli.

v0.7.0

Choose a tag to compare

@github-actions github-actions released this 16 Oct 20:52

Added

  • Each seat can use its own model server. --builder-url and --critic-url (builder_url and critic_url in a config file) point the builder and the critic at different machines even when both run on the same provider, such as two Ollama servers. A seat without a URL of its own uses the provider's env var (VOLLEY_OLLAMA_URL or VOLLEY_LMSTUDIO_URL), then the default, never the other seat's. Everything a seat sends goes to its URL: its model calls, the Ollama prewarm and context check, and the --dry-run probe and critic canary. Inside the sandbox, a loopback seat URL crosses to VOLLEY_MODEL_HOST on its own port. Both URLs carry over to volley resume and to every volley matrix pairing. A URL on a claude_cli seat, or one that isn't plain http(s), is a configuration error (exit 5).

Changed

  • Ollama seats need ai-sdk-ollama 4.3.0 or later. Before 4.3.0 it streams a thinking model's reasoning only when a request sets think, which volley never does, so the reasoning vanished from the terminal and the trajectory and the phase's throughput read high. volley now declares the floor as an optional peer: pnpm warns about an older copy, npm refuses it with ERESOLVE, and an install without ai-sdk-ollama is unaffected.
  • --dry-run probes the critic's model endpoint too. Each local seat's endpoint is probed, once for a server both seats share, so a claude_cli builder with a local critic now gets a reachability check before the canary, and an unreachable endpoint is named with its seat.
  • ResolvedConfig has builder_url and critic_url. Both are required (string | null), so SDK code that builds a ResolvedConfig by hand instead of through resolve_config needs them. A run saved by an earlier version resumes with both null.
  • An empty VOLLEY_OLLAMA_URL or VOLLEY_LMSTUDIO_URL means the default. It used to fail the run with a configuration error.

Fixed

  • An IPv6 loopback model URL reaches the host from inside the sandbox. http://[::1]:… was never crossed to VOLLEY_MODEL_HOST, because URL keeps the brackets in the hostname.

Internal

  • Built on fascicle 0.14.0, TypeScript 7, and tsdown. tsdown replaces tsup, whose declaration build needed a compiler API TypeScript 7 no longer exports. The published files keep their names and exports, and the bundles now keep their source comments. The dev toolchain moves to checkride 0.14.0, fallow 3.33.1, oxlint 1.88.0, vitest 5.0.4, and @ast-grep/cli 0.50.0, alongside ai 7.0.140, undici 8.12.1, and cac 7.1.0, and pnpm audit is clean.
  • The throughput docs mark the gap that remains on Ollama. A turn whose only output is a tool call still reports a rate far too high until fascicle times those turns another way (robmclarty/fascicle#12).
  • An integration test holds each seat to its own server. It runs real engines against two stand-in Ollama servers, and the local-provider tests no longer reach a developer's own Ollama.

v0.6.1

Choose a tag to compare

@github-actions github-actions released this 14 Oct 16:21

Fixed

  • --worktree now isolates a Claude builder. The claude session started in the workspace instead of the worktree, so under --worktree a claude_cli builder edited your checkout while the check, the checkpoints, and the change report the critic reads all looked at the untouched worktree. Claude sessions now start in the worktree; .volley/ run state stays in the workspace as before, and local-model builders, whose tools were already rooted in the worktree, are unaffected.

Internal

  • An integration test runs the real engine against a stand-in claude binary and checks that the builder's write lands on the run branch, not in the workspace.

v0.6.0

Choose a tag to compare

@github-actions github-actions released this 28 Sep 05:20

Added

  • The container spec is exported from the package root. sandbox_run_args, network_run_args, SANDBOX_NETWORK_NAME, SANDBOX_NETWORK_SUBNET, and the SandboxNetwork type are now public, so a harness that launches volley's container renders volley's own hardened docker run flags instead of a copy that drifts from them. sandbox_run_args gains labels (one --label KEY=VALUE each, so a caller can find and reap its containers), and store_volume is now optional: leave it out and no pnpm store is mounted.
  • --run-id <id> names a run. The id names the run's .volley/ state and its volley/<id> branch in place of a fresh UUID, so a harness that owns the id finds the run in both. Ids are limited to letters, digits, -, and _, starting with a letter or digit; anything else is a config error before any spend, and the CLI refuses a bare-number id rather than silently dropping its leading zeros.

Fixed

  • Runs started from the SDK run their check. resolve_config now resolves the check the way the CLI always did. It used to return 'none' for every config, so an embedded run ran no check even with check: 'auto' in a checkride workspace or with a command check.

Internal

  • The version skill bumps package.json after writing the changelog, so pnpm's clean-tree check no longer blocks it.