Handoff: custom provider configuration for Bullswarm (design discussion, 2026-09-12/13)
You are picking up a design discussion about how Bullswarm (repo /home/dev/Repo/bullswarm, the installed bullswarm CLI is a symlink into this checkout, so edits are live) should let a package user add their own provider without touching the repo. Nothing from this discussion has been implemented. Read this file, then continue the discussion with the user or, if asked, turn it into a plan or a Bullswarm workflow.
Where the repo stands
- Branch
main, 2 unpushed commits (231c5f5, 866b6b7 = unreleased 0.29.0). Seven files are modified and uncommitted:src/lib/opencode-relay.js,src/meters/registry.js,src/lib/config.js,tests/opencode-relay.test.js,tests/meters.test.js,CHANGELOG.md,.gitignore. Suite: 838 pass, 0 fail.bullswarm updaterefuses to pull while the checkout is dirty. - Uncommitted work: (1) OpenCode reasoning variants are now injected for every model a Relay provider lists in opencode.json, not just gpt-5.6-luna, so a
--variant mediumon gpt-5.6-sol actually reaches the API (verified: low 1,552 vs xhigh 5,027 reasoning tokens on relay-2); (2) the Relay meter reads used% against the pool's declaredincludedValueUsd(set-subscription --included-usd) before the host-wideRELAY_PLAN_USDand the $50 default. - Live routing state (no pins, pace decides): Claude accounts Opus on high only; grok grok-4.6 on medium only; command-code DeepSeek v4.1 flash on medium+low; Relay pools 1/2/4 gpt-5.6-sol on high only (medium reasoning); codex gpt-5.6-sol high (medium) and gpt-5.6-luna medium+low (max); Relay 3 disabled (account banned). No real job has run on codex yet.
- Relay facts: gpt-5.6-luna has no upstream channel on any key (503); sol, terra, gpt-5.5 answer on keys 1/2/4. relay-4 is a new $20 newcomer wallet, provider added to
~/.config/opencode/opencode.json(backupopencode.json.bak-20260912-relay4), key file.relay4-keyin the repo root is git-ignored. Memory note:~/.claude-acme/projects/-home-dev-Repo-bullswarm/memory/relay-declared-reset-dates.md.
The user's goal
Remove every Relay footprint from the repo while Relay keeps working in the pool system, the strategy pages and setup. Let the package user, or an agent acting for them, add a custom provider locally. Prefer declarative files over executable plugin code.
Relay footprint today (what has to move)
src/lib/opencode-relay.js: scans opencode.json for relay.example keys, clones theopencode2connector per key (poolsopencode2,opencode2:relay-2, ...), pins--model <id>/gpt-5.6-luna, injectsOPENCODE_CONFIG_CONTENTreasoning variants, setsupstreamGroup: relay:<host>. Called fromloadConnectorsinsrc/lib/config.jsright afterexpandClaudeAccountConnectors(the same hard-coded pattern).src/meters/relay.js+ the pool-to-reader mapping insrc/meters/registry.js(relayReaderFor,relayIncludedUsd, envRELAY_PLAN_USD).- One New-API phrase in
src/lib/auth-signatures.jsdefaults ("no available channel for model"); connectors can already declareauthSignaturesin JSON. - Help examples in
src/help.js, connector comments inconnectors/opencode2.json, docs, changelog, and about 20 test files that useopencode2:relay-2as a pool name. - Already generic and user-local:
~/.bullswarm/connectors/*.jsonis read byloadConnectorsfor any JSON;upstreamGroupandauthSignaturesare plain connector fields.
Design conclusions reached so far (in order; later ones supersede earlier)
- Plugin hooks (
~/.bullswarm/plugins/<name>/plugin.mjsexportingexpandConnectors,meterReaderFor,authSignatures) would work and cost about a day. Kept only as an escape hatch; the user does not want executable plugins as the primary path. - Agent-authored providers: add
bullswarm provider scaffold | probe | enable | list | validate(JSON-first) and aprovider-authoringskill.proberuns one real one-word task through the connector alone, checks the model flag, reasoning flag, event parsing and meter, and is the step not to skip. Onlyenable --yestouches routing. Keys never go in connector JSON. - Manifest pattern: a first draft with
clonefrom opencode-config,recipe: opencode-variants,http-jsonmeter engine andsharedCredentialwas rejected by the user as too Relay-shaped. - Current position, generic short form. A pool is a CLI + an environment that selects one account/backend + a quota. Essential fields:
nameagent: a known CLI name (opencode,codex,claude,grok) which inherits spawn, output parsing, model flag, reasoning flag, failure phrases; or{ run: "<cmd> {task}", output: "stdout" | "jsonl", modelFlag }for a new CLImodels: ids as the CLI expects them (also serves as discovery)env: variables that make this pool this account/backend (universal switch:OPENCODE_CONFIG_CONTENT,CLAUDE_CONFIG_DIR, base URL/key vars)quota:noneor{ window: weekly|monthly, includedUsd?, resetsAt?, reader? };readernames a built-in reader (grok, claude, codex) or a generichttp-jsonengine- optional generic:
instances: [{ suffix, env, models? }](one pool per credential, replaces every "scan another tool's config and clone" engine) andcredentialGroup(string; siblings benched together on auth failure; the existing upstreamGroup under an honest name) - Deliberately excluded from runtime: reading another tool's config to discover keys (a
provider addconvenience may writeinstancesonce), vendor-named meters, model-prefix rewriting. - Implementation shape: a normaliser expanding the short form into today's full connector so routing, verdicts and strategy pages do not change; per-CLI base profiles addressable via
agent; a shared model catalog (tier/quality/pricing by model name, replacing per-connectormodelProfiles); per-tier reasoning defaults move to strategy state whereset-rungalready writes them;instances+credentialGroupin the normaliser retire both hard-coded expanders (Claude accounts becomeagent: claude+CLAUDE_CONFIG_DIR).
- Limits check against grok (the real test): the short form covers ~80% of grok and 100% of a reseller. Two irreducible gaps for any new CLI: its event-stream rules (grok needs two rules with id/kind/summary/status paths; keep the existing rules language, it already serves four CLIs) and its meter (grok refreshes an OAuth token; stays a built-in named reader). Verdict: not too limited provided the short form is additive, every full-connector field stays legal and explicit values win over defaults. Grok would be ~25 lines instead of ~130; a reseller ~8.
Open questions to settle with the user
- Confirm the short-form field list above, especially
instancesvs. a one-timeprovider addthat discovers keys from opencode.json. - Whether Claude accounts also migrate to the short form or stay as the built-in reference expander.
- Whether changelog history mentioning Relay is scrubbed (I advised leaving history).
- Order of work proposed: normaliser + defaults, shared model catalog,
provider add/probe, then Relay extraction into~/.bullswarm/providers/with a live probe of relay-4 as acceptance. Suggested delivery: one Bullswarm workflow with parallel writers, an integration action, and an adversarial-acceptance action.
Standing rules that shaped the discussion
Real data only; evidence before "done" (exercise the real artifact); plain words, verdict first; every set-model clears all tier pins; set-rung needs --force for a model not in cached discovery; do not poll Relay usage endpoints repeatedly (429).
