Switching providers and API keys
bot/scripts/set_override.py swaps which already-deployed provider, model, and
credential slot are active — live, with no restart and no redeploy.
uv run python -m bot.scripts.set_override groq --index 1 # activate groq AND its index-1 slot, together
uv run python -m bot.scripts.set_override groq --index 1 --no-activate # index only, leave the active provider alone
uv run python -m bot.scripts.set_override groq --clear-index --no-activate # clear index only, same
uv run python -m bot.scripts.set_override groq # activate only, keep the existing index override
uv run python -m bot.scripts.set_override --clear # clear the provider override
uv run python -m bot.scripts.set_override groq --index 1 --force # write despite a failed live check
uv run python -m bot.scripts.set_override vertex --model gemini-2.5-flash # override this provider's model too
uv run python -m bot.scripts.set_override vertex --clear-model --no-activate # clear the model override only
uv run python -m bot.scripts.set_override --list # slot inventory, active index, active model
What this actually writes
This writes a provider override and/or a provider's key-index override to
the runtime_config table and takes effect on the next ticket the
dispatcher claims — no restart, no redeploy. It writes to whatever
DATABASE_URL currently resolves to, so running it against a local .env
sets a local override only; nothing reaches production unless your
local DATABASE_URL happens to be the production one.
Each provider's credential env var can have numbered siblings —
GROQ_API_KEY, GROQ_API_KEY_1, GROQ_API_KEY_2, ... — provisioned ahead
of time exactly like any other env var (one redeploy, via --sync-env or
the Render dashboard, to add a new slot). vertex rides the identical
mechanism with a differently-shaped (but still verbatim, base64-encoded, no
file path) credential: GCP_SERVICE_ACCOUNT_KEY, _1, _2, ... — so
uv run python -m bot.scripts.set_override vertex --index 1 swaps service
accounts with no redeploy and no CLI change.
Each provider tracks its own key-index independently, so switching providers never disturbs the slot chosen for the other two, and no secret value is ever written to, read from, or logged by the database — only the slot's integer index is.
--list prints slot inventory, the active index, and the active model as
names and booleans only — never a credential value — so it is safe to
run and paste anywhere, including a chat transcript or a bug report.
Live verification before writing
It verifies against the effective index — whichever index will
actually be active for that provider after the write, not always index 0 —
against the live Render service (when RENDER_API_KEY is set), and refuses
by default (exit 2; pass --force to override) if the target credential
is missing or, for index 0, differs from your local .env.
- Clearing the index override with
--no-activatenever verifies at all — an operator reaching for this during a key rotation must not be blockable by a Render/local mismatch. - Clearing it while also activating still verifies, against index 0, the slot about to become active.
- If your local
DATABASE_URLisn't the one Render's service actually reads (e.g. you're testing against a local database), verification against a local.envvalue is skipped automatically, since the write cannot affect production either way.
The read-only counterparts
bot/scripts/deploy.py's provider/provider-live checks are the read-only
counterparts of the provider override: they confirm the resolved
provider's credential is set locally, and genuinely present on Render,
respectively. api-key-live is the read-only counterpart of the key-index
override: it confirms the actively-resolved slot is genuinely present on
Render, catching the exact gap a redeploy-free index flip can introduce —
the DB says index 2, but nobody ever pushed GROQ_API_KEY_2 to Render. See
Deployment checks for the full check list.