Swarm: second provider family (Gemini) behind the same CLI - #15
Merged
Conversation
The swarm tool talked only to the OpenAI Responses API. A Gemini key is now
available, and the reason to wire it in is not redundancy or price — the two
floor tiers are within 25% of each other — it is that docs/CYCLE.md step 4
wants skeptic re-implementations written by a *different* implementation, and
a different model family is the strongest version of that available cheaply.
scripts/swarm.py grows a Provider layer: each class translates the same four
job knobs (model, prompt, effort, token cap) into one API's dialect and reads
back text, normalized {input,output}_tokens usage, and a reason-string when
the response came back short. Nothing outside the layer knows which API is in
use; a third provider is a class plus a PROVIDERS row.
Provider is inferred from the model name (gemini-*/gemma-* -> gemini, else
openai, so an OpenAI-compatible proxy's model name keeps working); --provider
overrides. --effort maps straight onto Gemini's thinkingLevel under the same
four names, so no translation table can drift.
Two Gemini specifics that would silently corrupt records if missed, both
pinned by tests: thought-summary parts are dropped from the response text, and
thinking tokens are billed at the output rate so they are folded into the
output bucket rather than dropped from the cost estimate. Truncation
(finishReason MAX_TOKENS) fails the job exactly as OpenAI's `incomplete` does
- a truncated draft is worse than a retry. 503 "high demand" is routine on the
Gemini side and is absorbed by the existing retry ladder.
Verified live end to end: plan -> run -> resume (skips paid work) -> status,
with the provider recorded in each job's meta. 230 tests pass.
docs/SWARM.md gains the provider table and the rule that follows from it: the
family that drafted a claim stays off that claim's verification, and a record
leaning on both must say which did what.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FPhAGm1qhBoCYYiMFAjziD
Three defects the first cross-family sweep exposed, all of which fail quietly in the direction that matters most -- a verification pass that looks done but is not what it claims. DEFAULT_GEMINI_MODEL was dead code. `--provider gemini` with no `--model` fell through to the OpenAI default, so a sweep launched as a cross-family skeptic pass silently ran on the same family it was meant to be independent of, and the only trace was one line of stdout. resolve_target() now derives (provider, model) from whichever of the two the caller actually gave, and the test pins the gemini-with-no-model case specifically. The backoff ladder ignored Google's retry hint. OpenAI throttles via a `Retry-After` header; Google sends nothing in the headers and puts the wait in a google.rpc.RetryInfo in the error body. A free-tier key's quota window is 30s -- longer than the whole 2/4/8/16 ladder -- so every throttled job burned all five attempts and died. retry_hint_seconds() reads both channels, rounds fractional delays up, and is capped at 60s: a job stuck behind a multi-minute quota should fail and be resumed, not hold a worker slot. The Gemini default is now gemini-3.7-flash, the current Flash release (2026-08-13). It is the tier tuned for coding and multi-step agent work, which is what a skeptic re-implementation brief actually asks for, and $0.75/$3.75 is introductory pricing through 2026-12-31 -- docs/SWARM.md notes the date it doubles. gemini-3.1-flash-lite stays documented as the floor tier for breadth work returning prose rather than programs. docs/SWARM.md also records the free-tier symptom explicitly, because the fix is enabling billing rather than adding retries, and the 429 body says so in a place that is easy to miss. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FPhAGm1qhBoCYYiMFAjziD
001's one nonstandard lead asked for dominator trees, minimum cuts and
treewidth on the truncated Collatz digraph G_B for B = 2^12..2^24, with the
falsifiable dichotomy "interface bounded vs growing". It resolves degenerately,
and the reason is one paragraph rather than one cycle of computation: the
Collatz map is a function, so G_B has out-degree one everywhere, so it is a
forest plus the 1->4->2->1 triangle. Every invariant the lead proposed is fixed
before any arithmetic is consulted.
- Dominator tree IS the orbit tree: idom(v) = T(v), zero mismatches to B = 2^18
under a real Cooper-Harvey-Kennedy computation rather than by assertion.
- Treewidth is exactly 2 for every B, so "track growth in B" has nothing to
track, and the flow-cutter tooling never needs building.
- Minimum cut has the closed form |S_k| = (2^{k-1} - (-1)^{k-1})/3 = J_{k-1},
~ 2^k/6, decided by the single inequality 3r+1 > 2^{k+1} -- magnitude, no
dynamics -- with B dropping out of the cut entirely.
Keeping the arithmetic labels, as 001 instructed, shows nothing: the cut is an
interval of odd integers, so it is uniform mod 3 by construction (233,017 each
at k = 22).
Verified three independent ways past the census itself: unit-capacity Dinic
max-flow on the explicit graph (k <= 12), and three gemini-3.7-flash workers
given only the definitions under three different stances (k <= 14, unanimous).
This is the first use of the cross-family skeptic rule -- the census was
written by the session model, so its verification went to the other family --
and it paid: one worker returned a cleaner proof than the census was built on,
showing crossing candidates above 2^k have in-degree zero outright. That
argument is adopted in the record. The escape fraction quoted as a by-product
was separately re-derived by unmemoized brute force.
The novelty pre-step (001 gap 2) is discharged: no dominator/treewidth Collatz
work found, but the inverted-graph-is-a-tree fact is classical, which explains
the empty literature better than novelty does. Literature remains [T]-grade --
web search, no PDFs -- and the record says so.
The collapse argument generalizes to any iterated map, so it prices the whole
graph-decomposition family, not just this lead; recorded as the mechanism tag
functional-graph-invariants and as a dead-end entry. One live lead survives:
the escape fraction #{n <= 2^k : orbit > 2^{k+1}}/2^k is non-monotone -- 0.609
at k = 12, 0.379 at k = 13, then flat near 0.404 through k = 22 -- and is
unexplained.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FPhAGm1qhBoCYYiMFAjziD
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The swarm tool talked only to the OpenAI Responses API. A Gemini key is now
available, and the reason to wire it in is not redundancy or price — the two
floor tiers are within 25% of each other — it is that docs/CYCLE.md step 4
wants skeptic re-implementations written by a different implementation, and
a different model family is the strongest version of that available cheaply.
scripts/swarm.py grows a Provider layer: each class translates the same four
job knobs (model, prompt, effort, token cap) into one API's dialect and reads
back text, normalized {input,output}_tokens usage, and a reason-string when
the response came back short. Nothing outside the layer knows which API is in
use; a third provider is a class plus a PROVIDERS row.
Provider is inferred from the model name (gemini-/gemma- -> gemini, else
openai, so an OpenAI-compatible proxy's model name keeps working); --provider
overrides. --effort maps straight onto Gemini's thinkingLevel under the same
four names, so no translation table can drift.
Two Gemini specifics that would silently corrupt records if missed, both
pinned by tests: thought-summary parts are dropped from the response text, and
thinking tokens are billed at the output rate so they are folded into the
output bucket rather than dropped from the cost estimate. Truncation
(finishReason MAX_TOKENS) fails the job exactly as OpenAI's
incompletedoesGemini side and is absorbed by the existing retry ladder.
Verified live end to end: plan -> run -> resume (skips paid work) -> status,
with the provider recorded in each job's meta. 230 tests pass.
docs/SWARM.md gains the provider table and the rule that follows from it: the
family that drafted a claim stays off that claim's verification, and a record
leaning on both must say which did what.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01FPhAGm1qhBoCYYiMFAjziD