PR #30 (qwen/kimi built-ins) is merged and the image is published, so the
last-resort pin points at it. Verified the tag exists in the registry before
pointing consumers at it — a pin to a tag that was never pushed breaks every
consumer at once, and the build succeeding is not by itself proof the tag
landed.
GADFLY_REVIEWER_TAG still overrides this, and an explicit reviewer_tag input
overrides both.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
majordomo now ships qwen and kimi as built-ins that ARE the openai client at
their own base URL, so "qwen/qwen3.8-max" works as a GADFLY_MODELS entry once
the key reaches the container. This wires up the parts that key has to pass
through.
Two provider switches had to learn the names, not one. resolveModel's
GADFLY_BASE_URL override was the obvious one; endpointProvider's
GADFLY_ENDPOINT_* parser is its sibling, and I fixed the first and missed the
second on the first pass — a config that resolves one way and errors the other
for no reason a user could guess. TestOpenAICompatProvidersResolveOnBothPaths
now asserts both from one table so the pair fails together; break-checked in
both directions.
QWEN_API_KEY (and KIMI_API_KEY) are declared as workflow_call secrets and
forwarded to the container, with gadfly's own stub forwarding QWEN_API_KEY so a
qwen entry can join the default swarm by editing GADFLY_DEFAULT_MODELS alone —
no workflow edit, no re-release.
The run.sh credential pre-flight is now a provider→variable table instead of an
ollama-cloud special case. Without it a forgotten key surfaces as five
identical per-lens agent failures naming no variable, and the operator reads a
stack trace to find out which secret they missed. Google stays out of the table
on purpose: it accepts either GOOGLE_API_KEY or GEMINI_API_KEY, and a one-var
entry would wrongly skip a correctly-configured run. Verified across 17
provider x key-state combinations, including that a wrong-provider key never
satisfies qwen (majordomo refuses cross-provider fallback) and that unkeyed
providers are never blocked.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Nearly every consumer re-pin of review-reusable.yml was a reviewer IMAGE bump,
not a structural change — but the tag was baked into a `uses: docker://` step,
which is parsed before any expression context exists and so can't read a
variable. Run the reviewer as the JOB container instead (container.image DOES
accept expressions) with an explicit `run: /entrypoint.sh` step: the tag now
resolves per-run via inputs.reviewer_tag → user var GADFLY_REVIEWER_TAG → the
baked fallback pin (sha-b37cd09, unchanged).
A Gadfly release becomes: build the image, update the ONE user-level variable —
every consumer pinned to this file's @<sha> picks it up on its next review with
no re-pin. Workflow re-pins remain only for structural yml changes (this is one
— intended to be the last routine one).
- Entrypoint contract unchanged: same env block, same /entrypoint.sh brains
(bash + entrypoint already live in the image; ENTRYPOINT was /entrypoint.sh).
- Keep the variable on immutable sha- tags, never :latest (act_runner caches
:latest and often does not re-pull a moved one).
- Tradeoff noted in comments: vars are unmasked and move without a commit
trail — same blast radius as editing this file, minus the audit trail.
- README (image/pinning para + Central config table) and examples/reusable.yml
updated per the maintenance rule.
Co-Authored-By: Claude Fable 5 <[email protected]>
Adopt the provider-wide lens budget build (PR #27) for gadfly's own reviews
and for consumers pinning this reusable. No image rebuild needed — the tag
already exists — so skip CI.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Concurrency was two multiplicative gates in two processes: entrypoint.sh
capped MODELS-at-once per provider (GADFLY_PROVIDER_CONCURRENCY) while each
model's binary separately capped its own lenses (GADFLY_LENS_CONCURRENCY).
A model therefore held its whole model-slot until its LAST lens finished,
stalling the next model even with idle lens capacity.
Collapse to one throttle: a provider-wide lens budget shared across all of
that provider's models. entrypoint now runs every model in a lane at once and
seeds a single cross-process permit pool per lane (a dir of N flock files,
sized by GADFLY_PROVIDER_LENS_CONCURRENCY -> GADFLY_LENS_CONCURRENCY). Each
lens pass (review+recheck) acquires a permit before it runs and releases it
after, so a model winding down immediately yields its freed permits to
another model's queued lenses. flock auto-releases on process death, so a
killed/crashed model can't leak budget.
- cmd/gadfly/lenssem.go: the flock permit pool (+ lenssem_test.go).
- main.go: runSpecialists holds a shared permit per lens; fanout sized to the
budget so a lone model can use all of it. Falls back to the in-process limit
when no pool is set (local runs, tests).
- entrypoint.sh: drop provider_cap/DEFAULT_CONC; run_lane runs all models and
seeds the per-lane pool.
- GADFLY_PROVIDER_CONCURRENCY / GADFLY_CONCURRENCY are now ignored; the
reusable workflow marks provider_concurrency deprecated and stops forwarding
it. Docs (README, CLAUDE.md, examples) updated per the maintenance rule.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
sha-bb98fae is the image built from the PR #26 merge, so it bundles the opencode
CLI and can run the new opencode/<model> engine. Consumers pinned to the gadfly
ref of this commit inherit it. Previous pin (sha-f468fe6) predated opencode.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
The self-review kept skipping every PR with "event 'workflow_call' not handled":
review-reusable.yml pinned the reviewer image at sha-3095ebf, which predates the
entrypoint.sh reclassification (added in 9d74cb9) that maps a called workflow's
github.event_name = 'workflow_call' back to pull_request/issue_comment. Under
Gitea >= 1.27 the container therefore saw an unhandled event and self-skipped in
~1s, even though PR was populated.
Bump the pin to sha-f468fe6 (current main HEAD, which contains the fix) so the
dogfood path actually reviews. The pr_number threading in the prior commit fixes
the manual-dispatch input propagation; this fixes the pull_request path.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Add a third review harness alongside the in-process majordomo loop and the
claude-code CLI shell-out: the OpenCode CLI (opencode.ai) driving an ollama-cloud
model, selected by an "opencode/<model>" spec. The goal is to benchmark gadfly's
boutique executus harness against a freely-available agentic harness on the SAME
model (e.g. "ollama-cloud/glm-5.2" vs "opencode/glm-5.2").
OpenCode has no --append-system-prompt flag, so the lens system prompt and the
read-only discipline are delivered through a generated config injected via
OPENCODE_CONFIG_CONTENT: a "gadfly" agent whose prompt is the system prompt with
edit/bash denied at both the global and agent level, plus a "gadfly" ollama-cloud
provider. That env var is the highest-precedence config source in the container,
so a reviewed repo's own opencode.json can't re-enable edits on the reviewer.
Spec forms: "opencode/<model>" (wrapped in the generated provider), the
"open-code/" alias, "opencode/<provider>/<model>" pass-through to OpenCode's own
registry, and bare "opencode". Model ids are taken verbatim so colon-bearing
ollama ids (qwen3-coder:480b-cloud) survive. Auth reuses OLLAMA_CLOUD_API_KEY
(mapped to OLLAMA_API_KEY, referenced as {env:OLLAMA_API_KEY} in config, never a
literal secret). Knobs mirror GADFLY_CLAUDE_*: GADFLY_OPENCODE_BIN/MODEL/BASE_URL/
EXTRA_ARGS. openCodeEnv() forwards OLLAMA_API_KEY (the inverse of claudeEnv) but
still withholds the Gitea/findings/Anthropic secrets.
main.go engine selection is now a switch (claude-code / opencode / majordomo), and
the auto-select path uses a type-check instead of a boolean so a shell-out engine
can never hit the *majordomoEngine assertion. auto-select and delegate_investigation
stay majordomo-only and are skipped for opencode (the CLI does its own legwork).
Dockerfile bundles opencode-ai (npm auto-selects its musl build on alpine) with a
best-effort version check + provider pre-warm that never fails the shared image
build. README/examples/CLAUDE.md/scripts updated per the maintenance rules.
Tests: new opencode_test.go mirrors engine_test.go (spec/model/args/config/env-
filter + stub-CLI runtime tests). Verified end-to-end with a fake opencode CLI:
correct argv, injected config, and consolidated markdown output.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Gitea >= 1.27 does not propagate the caller's workflow_dispatch inputs
into a called workflow's github.event (same rework that changed
event_name), so a manual 'review PR #N' dispatch arrived with an empty
PR and died at the entrypoint's 'PR required' check. Accept pr_number as
an explicit workflow_call input and fold it into the PR env fallback
chain; caller stubs pass github.event.inputs.pr_number through.
Co-Authored-By: Claude Fable 5 <[email protected]>
GADFLY_ENDPOINT_RAGNAROS is empty in Gitea vars and no ragnaros/<model>
is in the pool; netherstorm is the live local GPU endpoint. Requested by
Steve.
Co-Authored-By: Claude Fable 5 <[email protected]>
A reusable workflow can't enumerate arbitrary vars.GADFLY_ENDPOINT_*;
the netherstorm endpoint var was added after the forwarding list, so
every netherstorm/<model> reviewer failed with 'unknown provider:
netherstorm' — the correctly-formatted var never reached the container.
Co-Authored-By: Claude Fable 5 <[email protected]>
Phase 3: gadfly's own multi-model reviews now also post a COMMENT-state PR
review with inline comments anchored to changed lines. External consumers
re-pin separately.
[skip ci]
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Phase 2: gadfly's own multi-model reviews now post ONE cross-model consensus
comment instead of N per-model comments. External consumers re-pin separately.
[skip ci]
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Makes the Phase 1 gadfly-findings contract live for gadfly's own dogfood
reviews (the local-ref reusable). External consumers re-pin separately.
[skip ci]
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Self-review on PR #9 flagged two doc-drift spots left over from the
explicit-secret-forwarding switch. Cosmetic.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
The first attempt failed at entrypoint.sh:61 'GITEA_TOKEN required' — with
explicit secrets (no `inherit`), secrets.GITEA_TOKEN resolves empty in the
reusable job. github.token comes from the github context (not a forwarded
secret), so it's present regardless. The forwarded provider/findings secrets
arrived correctly; only the auto-token sourcing was wrong.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
The swarm (reviewing the mort/executus rollout PRs) correctly flagged that
`secrets: inherit` forwards EVERY caller secret to the reusable review
workflow — registry/deploy/db creds the reviewer never touches. Fix:
- review-reusable.yml: declare workflow_call.secrets (all optional) so a
caller can forward only what the reviewer needs.
- adversarial-review.yml (gadfly's own caller) + examples/reusable.yml:
replace `secrets: inherit` with an explicit forward of just
OLLAMA_CLOUD_API_KEY / CLAUDE_CODE_OAUTH_TOKEN / findings tokens.
GITEA_TOKEN stays automatic.
- Docs (README, examples) updated; also advise pinning consumers to an
immutable @<sha> instead of @main (supply-chain, the other finding).
gadfly's own review on this PR exercises the explicit-secrets path (local
reusable ref) — validating it on the act_runner before mort/executus adopt it.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Centralizes the consumer stub into a reusable Gitea workflow
(.gitea/workflows/review-reusable.yml, workflow_call + defaulted inputs +
secrets: inherit); gadfly's own dogfood is now a thin caller of it, which
proved end-to-end that github.event context propagates into the reusable
on this act_runner. Adds the slim examples/reusable.yml stub + docs.
Folded in the swarm's findings: timeout_minutes default 30->45, map
GADFLY_API_KEY, explicit permissions block, drop the dead specialist_suite
input, and harden the example's actor gate. ~70 findings graded.
Completes the gadfly-games build (Phases 1-4 + quality fixes).
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Co-authored-by: Steve Dudenhoeffer <[email protected]>
Co-committed-by: Steve Dudenhoeffer <[email protected]>