enabled: true touches: [crates/generation/src/model.rs, crates/generation/src/bin/absolute_real_model_run.rs, docs/model-first-cost.md, roadmap.md] depends-on: [] #
Candidate generation: a Claude Code subprocess provider #
Candidate generation has three sources today (crates/generation/src/model.rs):
DirectoryCandidateSource (files on disk), RemoteCandidateSource (the rig's
OpenAI-compatible gateway via HttpModelClient), and ClaudeCandidateSource
(AnthropicClient, the metered Anthropic Messages API, selected by
GENERA_PROVIDER=anthropic in absolute_real_model_run.rs). A batch run through
that metered path has now cost noticeably more than docs/model-first-cost.md
projected. This adds a fourth source that draws each candidate by running the
claude CLI as a subprocess in headless mode, billed under the same interactive
subscription the spec-watch tick and its writers already run on (~/.claude/bin/ spec-watch-tick.sh invokes claude ... -p '/spec-watch'; ~/.claude/commands/ spec-watch.md invokes claude --bg --model <model> --permission-mode acceptEdits '<brief>') rather than metered per-token.
Add ClaudeCliCandidateSource to model.rs, alongside the existing sources, same
shape as ClaudeCandidateSource: one candidate per prompt, count drawn
GENERA_CONCURRENCY at a time via the existing draw_concurrently. Where
ClaudeCandidateSource calls AnthropicClient::complete (an HTTP POST), this
spawns claude -p "<prompt>" (confirm the exact flags against claude --help —
headless output needs to be just the completion text with no interactive chrome,
which may mean --output-format text or json rather than the default; a model
override, if wanted, is --model). Reuse extract_rust_source on the captured
stdout the same way ClaudeCandidateSource does on a Messages API response, since
the source is still Rust in a fenced block. Telemetry.prompt_tokens /
completion_tokens have no equivalent from a CLI subprocess — leave them 0
rather than inventing a number; wall_clock_ms still times the subprocess call
itself, the same way Telemetry is populated elsewhere in this file. stop_reason
has no CLI equivalent either — leave it None.
Wire it into absolute_real_model_run.rs as a new GENERA_PROVIDER value
(e.g. GENERA_PROVIDER=claude-code), alongside the existing
GENERA_PROVIDER=anthropic branch and the rig-gateway default, following the same
match shape the file already uses for provider selection.
Record the addition in docs/model-first-cost.md (a short section next to
wherever the Anthropic-provider cost note already lives, naming this as the
subscription-billed alternative) and in roadmap.md wherever the candidate-source
options are listed.
This spec is implementation only — it adds the provider and switches nothing over
by default. No real model batch runs as part of this work: cargo test --workspace and fragment_check --random (216/227, unchanged) are the gate. A
follow-up spec decides whether absolute-batches-style work re-runs through this
provider instead of the metered one; that decision is not this spec's to make.