Runtime and Model Switching Guide
April 6, 2026 · View on GitHub
Canonical operator guide for switching an existing DiscoClaw instance between supported runtime adapters and model configurations.
Use this page when you need to answer:
- Which
.envis authoritative for this instance? - Which runtime names are canonical, and where is each one actually supported?
- What does
!modelschange live, and what survives restart? - How do I move between
claude-cli,gemini-api,codex-cli,openai, andopenrouter? - How do
DISCOCLAW_TIER_OPENROUTER_FAST,DISCOCLAW_TIER_OPENROUTER_CAPABLE, andDISCOCLAW_TIER_OPENROUTER_DEEPwork? - Why did
!models resetor a restart not do what I expected?
Linux systemd --user is the primary path below. macOS launchd differences are called out separately.
Quick decision table
| Goal | Change it in | Restart required | Persists across restart |
|---|---|---|---|
| Live-switch the main runtime to another adapter right now | !models set chat <runtime> | No | No |
| Change the default adapter for the whole instance | .env (PRIMARY_RUNTIME) | Yes | Yes |
| Change a role's model override | !models set <role> <tier-or-model> | No | Yes |
| Make voice stay on a different adapter | !models set voice <runtime> | No | Yes |
| Move fast-tier work onto another adapter | !models set fast <exact-model> | No | Yes, if the model uniquely auto-switches fastRuntime |
| Change OpenRouter tier routing | .env DISCOCLAW_TIER_OPENROUTER_<TIER> | Yes | Yes |
| Revert roles to this instance's startup defaults | !models reset | No | Yes |
Important: !models reset means "reset to this instance's startup defaults as resolved at boot from .env plus built-in fallbacks". It does not mean "reset to repo defaults".
Important: if chat is currently live-swapped to another runtime, !models reset or !models reset chat resets the chat model/default but does not switch the active chat runtime back immediately. There is no persisted chatRuntime overlay to clear; restart or switch chat again.
Canonical runtime names
| Adapter name | Startup via PRIMARY_RUNTIME | Requirement |
|---|---|---|
claude-cli | yes | Claude CLI available (legacy alias claude still accepted) |
gemini-api | yes | GEMINI_API_KEY |
codex-cli | yes | Install-mode-specific Codex proof gate, not binary presence alone (legacy alias codex still accepted) |
openai | yes | Install-mode-specific OpenAI proof gate, not OPENAI_API_KEY presence alone |
openrouter | yes | Shipped env-key path plus live openrouter-key: ok proof; not key presence alone |
claude-api | no | Voice-only direct API runtime; not a valid PRIMARY_RUNTIME (legacy alias anthropic still accepted) |
For the authoritative 1.0 provider/auth verdicts, including the blessed Claude CLI default path, the supported Codex CLI secondary path, the PARTIAL status of OpenAI/OpenRouter/Gemini, and the OUT OF SCOPE status of direct Anthropic for chat/startup, see docs/audit/provider-auth-1.0-matrix.md.
Use these canonical names in operator-facing docs, prompts, and commands. Legacy aliases (claude, codex, anthropic) are still accepted as input and normalize to the canonical names.
For codex, use the proof gate that matches the install mode you are actually operating:
- Source checkout: docs/audit/codex-blank-machine-readiness.md, including the manual
codex exec ...session-auth step. - npm-managed install: docs/audit/codex-npm-managed-path.md. That path is currently not fully support-claimable; the documented shell gate does not by itself prove daemon parity.
For openai, prove the active path the same way:
- Source checkout: when any configured route uses OpenAI, use the repo smoke harness described in docs/audit/codex-blank-machine-readiness.md, for example
OPENAI_SMOKE_TEST_TIERS=fast pnpm test. - npm-managed Codex/OpenAI alternate path: follow docs/audit/codex-npm-managed-path.md and confirm live runtime-visible evidence such as
openai-key: okafter startup.OPENAI_API_KEYin.envis config only. - Treat both as
PARTIALin the 1.0 matrix unless the exact route and model under test have the separate proof they require.
For openrouter, the current shipped boundary is narrower:
- Set
OPENROUTER_API_KEYin the authoritative.env, but treat that as config presence only. - For source checkouts,
pnpm preflight*remains setup/bootstrap evidence only, and repo smoke-path validation is the only shipped workload-proof surface for OpenRouter-backed routes. - Start or restart DiscoClaw and confirm
!statusor the startup credential report showsopenrouter-key: ok. - Treat
!models set chat openrouter,PRIMARY_RUNTIME=openrouter,.envkey presence, and npm-manageddiscoclaw doctoras routing/config evidence only until that proof appears. - Treat the path as
PARTIALin the 1.0 matrix unless you are making only the narrower env-key or exact-workload claim that the audits permit.
For openai and openrouter, set OPENAI_COMPAT_TOOLS_ENABLED=1 if you expect full tool use. In logs, the Claude CLI adapter runtime ID is claude_code even though the user-facing adapter name is claude-cli.
Runtime-path contract
The operator-facing contract is placement-specific. Supported runtime names, persistence, and reset behavior intentionally differ by placement:
| Placement | Operator path | Supported canonical runtime names | Persistence | Reset behavior |
|---|---|---|---|---|
| Startup default | .env PRIMARY_RUNTIME | claude-cli, codex-cli, gemini-api, openai, openrouter | .env | Edit .env, then restart |
| Live chat runtime swap | !models set chat <runtime> | claude-cli, codex-cli, gemini-api, openai, openrouter | Memory only | Restart or another explicit chat runtime switch |
| Persisted fast runtime overlay | !models set fast <model> when an exact model string uniquely reverse-maps to another runtime | claude-cli, codex-cli, gemini-api, openai, openrouter | runtime-overrides.json (fastRuntime) | !models reset fast or !models reset |
| Persisted voice runtime overlay | !models set voice <runtime> or voice auto-switch from an exact cross-provider model string | claude-api, claude-cli, codex-cli, gemini-api, openai, openrouter | runtime-overrides.json (voiceRuntime) | !models reset voice or !models reset |
Contract notes:
- Legacy aliases (
claude,codex,anthropic) are accepted as input and normalize to their canonical names. claude-apiis valid only for voice placement. It is intentionally voice-only and is never a validPRIMARY_RUNTIMEor!models set chat <runtime>target.- There is no persisted
chatRuntimeoverlay inruntime-overrides.json. Chat runtime swaps are intentionally live-only. voiceRuntimeandfastRuntimeare the only persisted runtime-path overlays today.
Find the authoritative instance first
Do not edit anything until you know which install you are operating on.
1. Resolve the unit name
- If
.envcontainsDISCOCLAW_SERVICE_NAME=<name>, the Linux unit is<name>.service. - Otherwise the default unit is
discoclaw.service.
Examples below use discoclaw.service. Replace it with the real unit name for named multi-instance installs.
2. Resolve the service working directory
systemctl --user show -p WorkingDirectory discoclaw.service
systemctl --user cat discoclaw.service
The working directory is the first place to inspect. That is the .env that matters on restart.
3. Determine install mode
- Source checkout: the working directory is a repo clone and contains
.git. - npm-managed install: the working directory contains
.env,workspace/, anddata/, but no.git.
Do not assume the global npm package directory is authoritative. For npm-managed installs, the working directory created during discoclaw init is the instance.
4. Identify the state files
Authoritative files for runtime/model state:
.envin the service working directorymodels.jsonat$DISCOCLAW_DATA_DIR/models.json, or./data/models.jsonifDISCOCLAW_DATA_DIRis unsetruntime-overrides.jsonat$DISCOCLAW_DATA_DIR/runtime-overrides.json, or./data/runtime-overrides.json
Minimum inspection set:
grep -nE '^(DISCOCLAW_SERVICE_NAME|DISCOCLAW_DATA_DIR|PRIMARY_RUNTIME|RUNTIME_MODEL|DISCOCLAW_FAST_MODEL|DISCOCLAW_FAST_RUNTIME|DISCOCLAW_SUMMARY_MODEL|DISCOCLAW_CRON_MODEL|DISCOCLAW_CRON_AUTO_TAG_MODEL|DISCOCLAW_CRON_EXEC_MODEL|DISCOCLAW_TASKS_AUTO_TAG_MODEL|DISCOCLAW_VOICE_MODEL|FORGE_DRAFTER_MODEL|FORGE_AUDITOR_MODEL|OPENAI_MODEL|OPENROUTER_MODEL|GEMINI_MODEL|CODEX_MODEL|DISCOCLAW_TIER_[A-Z0-9_]+_(FAST|CAPABLE|DEEP))=' .env
grep -nE '^(OPENAI_API_KEY|OPENROUTER_API_KEY|GEMINI_API_KEY|ANTHROPIC_API_KEY)=' .env | sed -E 's/=.*/=<set>/'
sed -n '1,200p' "${DISCOCLAW_DATA_DIR:-./data}/models.json"
sed -n '1,200p' "${DISCOCLAW_DATA_DIR:-./data}/runtime-overrides.json"
Prefer !restart logs for service logs. On Linux, journalctl --user -u discoclaw.service -n 100 --no-pager is a shell fallback. On macOS, prefer !restart logs over an ad hoc log show query.
Do not print the full .env into Discord, terminal transcripts, or audit logs unless you already redacted secrets. If DISCOCLAW_DATA_DIR is defined only inside .env, read it from the filtered output first and substitute the real path manually. A missing runtime-overrides.json file is normal.
The four layers of runtime/model state
| Layer | What it controls | Where it lives | How it changes |
|---|---|---|---|
| Built-in defaults | Repo-shipped role defaults and tier maps | Code (src/model-config.ts, src/runtime/model-tiers.ts) | Only when the software version changes |
| Startup defaults for this instance | What DiscoClaw boots with before live overrides | .env plus built-in fallbacks | Edit .env, then restart |
| Persistent live overrides | Role model overrides plus persisted fast/voice runtime overlays | models.json and runtime-overrides.json | !models ... and fast/voice runtime auto-switches |
| Live-only chat runtime swap | Temporary main-runtime adapter changes after startup | Memory only | !models set chat <runtime>, then restart or switch chat again |
Keep the files separate:
models.jsonstores model strings per role.runtime-overrides.jsonstores runtime-only overlays such asvoiceRuntimeandfastRuntime.- There is no
chatRuntimekey because chat runtime swaps do not persist.
On first run, models.json is scaffolded from the startup defaults that instance booted with.
What ships by default
Role defaults shipped in code:
chat:capablefast:fastplan-run:capablesummary:fastforge-drafter:capableforge-auditor:deepcron:fastcron-exec:capablevoice:capable
Built-in tier maps shipped in code:
| Runtime | fast | capable | deep |
|---|---|---|---|
claude-cli (claude_code) | haiku | claude-opus-4-6 | claude-opus-4-6 |
gemini-api | gemini-2.5-flash | gemini-2.5-pro | gemini-2.5-pro |
openai | gpt-5-mini | gpt-5.4 | gpt-5.4-pro |
openrouter | openai/gpt-5-mini | anthropic/claude-sonnet-4.6 | anthropic/claude-opus-4.6 |
codex-cli | gpt-5.1-codex-mini | gpt-5.4 | gpt-5.4 |
OpenRouter tier defaults and overrides
DiscoClaw already ships an OpenRouter tier map. With no env overrides set, OpenRouter resolves:
fasttoopenai/gpt-5-minicapabletoanthropic/claude-sonnet-4.6deeptoanthropic/claude-opus-4.6
OPENROUTER_MODEL separately defaults the OpenRouter adapter itself to anthropic/claude-sonnet-4.6, so a role that follows the adapter default starts from the same concrete string as the shipped capable tier.
If you set DISCOCLAW_TIER_OPENROUTER_FAST, DISCOCLAW_TIER_OPENROUTER_CAPABLE, or DISCOCLAW_TIER_OPENROUTER_DEEP, that exact string replaces only that tier entry in the effective OpenRouter tier map. Override precedence is simple: tier lookup uses the env override when present and otherwise falls back to the shipped built-in value. OPENROUTER_MODEL does not rewrite the tier map; only DISCOCLAW_TIER_OPENROUTER_<TIER> does.
Set only the tiers you intentionally want to change. Even a single unique effective tier entry is enough for exact-string reverse-mapping in fast/voice runtime auto-switching.
Exact-match rules:
openai/gpt-5-minimatchesopenai/gpt-5-minigpt-5-minidoes not matchopenai/gpt-5-minifastdoes not match anything because it is a tier name, not a concrete model string
Even without overrides, PRIMARY_RUNTIME=openrouter and the default OPENROUTER_MODEL work against the shipped OpenRouter tier map. Keep the support claim narrow: source checkouts still need repo smoke-path validation for the exact OpenRouter-backed workload under test, while npm-managed installs should stop at post-start evidence such as openrouter-key: ok.
Where each kind of change persists
| Change | Persists in | Notes |
|---|---|---|
| Default chat adapter at startup | .env PRIMARY_RUNTIME | Restart required |
| Adapter default model | .env OPENAI_MODEL, OPENROUTER_MODEL, GEMINI_MODEL, CODEX_MODEL | GEMINI_MODEL applies to gemini-api; used when a role follows the adapter default |
| Tier map for a runtime | .env DISCOCLAW_TIER_<RUNTIME>_<TIER> | Restart required |
| Per-role model override | models.json | Written by !models set <role> <tier-or-model> |
| Persistent fast runtime override | runtime-overrides.json (fastRuntime) | Written when fast auto-switches to another runtime from an exact uniquely owned model string |
| Persistent voice runtime override | runtime-overrides.json (voiceRuntime) | Written by !models set voice <runtime> or by voice auto-switch |
| Temporary main-runtime swap | Memory only | Written by !models set chat <runtime>; lost on restart because there is no persisted chatRuntime overlay |
Legacy note: DISCOCLAW_FAST_RUNTIME is deprecated. Prefer !models set fast <model>, which updates models.json and can persist fastRuntime in runtime-overrides.json when the model string uniquely identifies one runtime. Shared IDs such as gpt-5.4 fail closed and do not auto-switch. Keep DISCOCLAW_FAST_RUNTIME only for startup compatibility; !models reset does not clear it because reset does not edit .env.
What each !models command really does
| Role | !models set persistence | Runtime behavior |
|---|---|---|
chat | Tier/model values persist to models.json; runtime-name swaps do not | !models set chat <runtime> swaps the main runtime live, resets chat to that adapter default, keeps plan/deferred-run/cron-exec on the main runtime adapter, and does not write any chatRuntime key to disk |
fast | Persists the model value to models.json | Concrete exact-match model strings can auto-switch the fast runtime and write canonical runtime names to fastRuntime |
plan-run | Persists the model value to models.json | Used by !plan run, !plan run-one, !plan run-phase, and planRun. It has its own startup default (DISCOCLAW_PLAN_RUN_MODEL, default capable) and does not inherit the chat role's current model string |
summary | Persists to models.json | Uses the fast runtime; model strings do not auto-switch runtimes |
cron | Persists to models.json | Uses the fast runtime; model strings do not auto-switch runtimes |
cron-exec | Persists to models.json unless you use default | Follows chat runtime unless overridden by cron-specific config; model strings do not auto-switch runtimes |
forge-drafter | Persists to models.json | Follows chat runtime unless env says otherwise; model strings do not auto-switch runtimes |
forge-auditor | Persists to models.json | Follows chat runtime unless env says otherwise; model strings do not auto-switch runtimes |
voice | Tier/model values persist to models.json; runtime-name swaps persist only in runtime-overrides.json | !models set voice <runtime> or a concrete cross-provider model can write canonical runtime names to voiceRuntime; anthropic is supported here only |
Only fast and voice auto-switch runtimes from concrete model ownership. For chat, only an explicit runtime name such as openrouter changes the runtime; plain model strings stay on the current runtime. plan-run, summary, cron, cron-exec, forge-drafter, and forge-auditor also keep their current runtime and can therefore be left pointing at a model string that the active runtime cannot serve.
Important reset semantics:
!models resetwrites startup-default model strings back intomodels.json.!models resetclearsfastRuntimeandvoiceRuntimeoverlays fromruntime-overrides.json.!models resetdoes not remove legacy env vars such asDISCOCLAW_FAST_RUNTIME.!models reset chatresets the chat model string, but it does not undo a live chat adapter swap until you restart or switch chat again because there is no persistedchatRuntimeoverlay to clear.
If you ran !models set chat openrouter, the active runtime row can stay on OpenRouter even after !models reset chat. Restart if you want the startup adapter back immediately.
Verify switch outcomes and registry failures
!models can confirm what is active after a switch attempt. It cannot prove ahead of time that an inactive adapter is registered.
Before switching, verify prerequisites instead:
- The filtered
.envoutput shows the expected credential or model keys for the target adapter. - Recent logs do not already show missing-binary or missing-credential warnings for that adapter.
After switching, run !models and inspect:
- the
runtimerow - any
[runtime: ...]annotations onvoice,summary,cron-auto-tag, ortasks-auto-tag - whether a role now shows a real model, or a literal adapter name where a model should be
If the switch fails or the target never appears active, inspect !restart logs first. On Linux, journalctl --user -u discoclaw.service -n 100 --no-pager is a useful shell fallback.
Useful warnings:
runtime-overrides: voiceRuntime is not a registered runtime; ignoringruntime-overrides: fastRuntime is not a registered runtime; ignoringDISCOCLAW_FAST_RUNTIME is not registered; falling back to PRIMARY_RUNTIME
Do not use !health as the only registration check. It does not show the runtime registry state.
Safe switch recipes
1. Persistently switch the whole instance to another adapter
Use this when the default adapter should remain changed after restart.
- Confirm the authoritative working directory and
.env. - Record the current
!modelsoutput and the.envkeys you are about to change. - Edit
.envand setPRIMARY_RUNTIMEto one ofclaude-cli,gemini-api,codex-cli,openai, oropenrouter. Legacy aliases (claude,codex) are still accepted. - Set or verify the adapter-specific default model env var if you care about adapter-default behavior:
GEMINI_MODEL,CODEX_MODEL,OPENAI_MODEL, orOPENROUTER_MODEL. - If the target is OpenRouter tier switching, inspect or set the specific
DISCOCLAW_TIER_OPENROUTER_<TIER>vars you want to override from the shipped defaults. - Clear role overrides that should stop fighting the new startup defaults, usually with
!models reset chat,!models reset fast,!models reset plan-run,!models reset summary,!models reset cron,!models reset cron-exec,!models reset voice,!models reset forge-drafter, and!models reset forge-auditor. - Restart the service.
- Verify with
!modelsand logs.
If you changed credentials through Discord DMs, !secret set KEY=value updates the authoritative .env but does not restart DiscoClaw. You still need a restart.
2. Live-switch the main runtime to another adapter without changing startup defaults
Use this for a temporary experiment:
!models set chat codex-cli
!models set chat openrouter
!models set chat gemini-api
Expected result in !models: the runtime row changes to the new adapter, the chat row usually becomes that adapter's default model, and plan/deferred-run/cron-exec follow that runtime adapter immediately. Plan execution no longer inherits the chat model string, though: it uses the dedicated plan-run role and stays on its own startup default or override. Fast and voice can still remain separate if they already have their own runtime overrides. This change is live-only and is lost on restart. To end the experiment, restart or switch chat to another runtime explicitly. Do not assume !models reset chat will switch the runtime row back immediately.
3. Change chat to another model on the current adapter
!models set chat capable
!models set chat gpt-5.4
!models set chat anthropic/claude-sonnet-4.6
Tier names resolve against the current chat runtime's tier map. Concrete model strings are stored as-is in models.json. They do not auto-switch chat to another provider: !models set chat gpt-5-mini keeps the current chat runtime and only changes the stored model string.
4. Move fast-tier work onto another provider/model
Preferred path: use !models set fast <model>, not DISCOCLAW_FAST_RUNTIME.
!models set fast gemini-2.5-flash
!models set fast openai/gpt-5-mini
!models set fast fast
Fast runtime auto-switching only happens when the concrete model string exactly matches another runtime's tier map entry and that ownership is unique. That means:
!models set fast gemini-2.5-flashcan auto-switch to Gemini!models set fast openai/gpt-5-minican auto-switch to OpenRouter because that exact string is already the shipped OpenRouterfasttier, or because you overrodeDISCOCLAW_TIER_OPENROUTER_FASTto that same exact string!models set fast gpt-5.4does not auto-switch because that model ID is shared byopenaiandcodex-cli!models set fast fastchanges the model tier but does not identify another provider
5. Move voice independently
!models set voice gemini-api
!models set voice codex-cli
!models set voice capable
!models set voice google/gemini-2.5-pro
Verification pattern in !models: the voice row shows [runtime: <adapter>] when voice differs from chat. Voice runtime changes persist because they write voiceRuntime to runtime-overrides.json. As with fast runtime, a tier name such as capable changes the voice model value but does not, by itself, identify another provider; cross-provider auto-switching needs an exact model string from a tier map.
6. Change plan, forge, cron, or summary roles
!models set plan-run capable
!models set forge-drafter capable
!models set forge-auditor deep
!models set cron fast
!models set cron-exec default
!models set summary fast
cron-exec default is the one special reset-like value for that role. It clears the explicit cron-exec override and returns that role to its startup default.
Important: these roles do not auto-switch runtimes from model ownership. If you point plan-run, summary, cron, cron-exec, forge-drafter, or forge-auditor at a cross-provider model string, DiscoClaw still runs that role on its current fast/chat-derived runtime unless you move that runtime separately. That can leave an invalid runtime/model pairing.
Rollback checklist
Use this if a switch fails, the wrong adapter stays active, or the new runtime cannot start cleanly.
- Start from the values you recorded before the change: the previous
!modelsoutput plus the exact.envkeys you edited. - If the failed change was live-only, restore the prior live state first:
!models set chat <previous-runtime>or restart for chat,!models reset fastor!models set fast <previous-model>for fast, and!models reset voiceor!models set voice <previous-runtime-or-model>for voice. - If the failed change was persistent, restore the previous
.envvalues forPRIMARY_RUNTIME, any adapter default model vars you changed, and anyDISCOCLAW_TIER_*vars you changed. - Clear bad persistent overlays if they are still fighting the restored defaults: run
!models resetor targeted!models reset <role>commands, then inspectruntime-overrides.jsonfor stalefastRuntimeorvoiceRuntime. - Restart the service. On macOS, if you changed
.env, rerundiscoclaw install-daemonor reload the launchd plist before restarting. - Re-verify with
!modelsand!restart logsbefore attempting another switch.
Verification checklist
After any switch:
- Run
!models. - Confirm the runtime row and the affected role match the intended outcome.
- Confirm any separate fast/voice runtime shows as
[runtime: ...]on the affected rows. - If you edited
.env, restart and run!modelsagain. - If startup failed or the adapter did not switch, inspect
!restart logsbefore changing anything else.
Good signs:
runtimechanged after a live chat runtime switchvoiceshows[runtime: gemini-api],[runtime: claude-api], or similar when intentionally separatedsummary,cron-auto-tag, ortasks-auto-tagshow[runtime: ...]when fast runtime moved- the displayed model is a concrete model or a tier resolution like
capable → anthropic/claude-sonnet-4.6
Bad signs:
runtimestayed on the old adapter when you expected a chat runtime swap- a role literally shows
openrouterorcodexas the model string - startup logs warn that the runtime is unregistered or missing credentials
Restart and rebuild rules
No restart, no rebuild
Use !models set ... only for live main-runtime experiments via !models set chat <runtime>, role model overrides written to models.json, and voice runtime changes via !models set voice <runtime>.
Restart required, rebuild not required
Edit .env, then restart. Examples: changing PRIMARY_RUNTIME, OPENROUTER_MODEL, OPENAI_MODEL, GEMINI_MODEL, CODEX_MODEL, DISCOCLAW_TIER_OPENROUTER_FAST/CAPABLE/DEEP, or credentials such as OPENROUTER_API_KEY.
Rebuild first, then restart
Only needed when code changed or the installed package was updated.
cd ~/code/discoclaw
git pull
pnpm install
pnpm build
Restart only after the build succeeds.
macOS launchd caveat
On macOS, discoclaw install-daemon writes a launchd plist with the current .env values baked into it. launchd does not re-read .env on !restart or launchctl kickstart, so editing .env alone is not enough.
After changing credentials, PRIMARY_RUNTIME, OPENROUTER_MODEL, or any DISCOCLAW_TIER_* env var on macOS, either rerun discoclaw install-daemon from the authoritative working directory or manually rewrite and reload the plist with launchctl bootout ... and launchctl bootstrap .... A plain restart reuses the old env snapshot.
Troubleshooting
!models reset did not return the model I expected
Check the startup-default env vars first: RUNTIME_MODEL, DISCOCLAW_PLAN_RUN_MODEL, DISCOCLAW_FAST_MODEL, DISCOCLAW_SUMMARY_MODEL, DISCOCLAW_TASKS_AUTO_TAG_MODEL, DISCOCLAW_CRON_MODEL, DISCOCLAW_CRON_AUTO_TAG_MODEL, DISCOCLAW_CRON_EXEC_MODEL, DISCOCLAW_VOICE_MODEL, FORGE_DRAFTER_MODEL, FORGE_AUDITOR_MODEL, and any DISCOCLAW_TIER_* overrides.
DISCOCLAW_FAST_MODEL is the legacy startup fallback for the fast role. If DISCOCLAW_SUMMARY_MODEL, DISCOCLAW_CRON_MODEL, DISCOCLAW_CRON_AUTO_TAG_MODEL, or DISCOCLAW_TASKS_AUTO_TAG_MODEL are unset, they inherit from that same fast-model fallback. Also inspect DISCOCLAW_FAST_RUNTIME in .env and voiceRuntime or fastRuntime in runtime-overrides.json.
The adapter did not actually switch
Symptoms:
!models set chat openrouterleaves theruntimerow unchanged!modelsshows a literal runtime name as the model string- logs contain missing credential or missing binary warnings
Fix the missing prerequisite first, restart, then retry.
I edited .env, restarted, and nothing changed
You almost certainly edited the wrong .env. Re-check systemctl --user show -p WorkingDirectory discoclaw.service, the path of the .env you edited, and DISCOCLAW_DATA_DIR from that same working directory.
Old fast defaults keep returning after restart
You still have a startup or persistent fast-runtime override somewhere. Inspect .env for DISCOCLAW_FAST_RUNTIME, models.json, and runtime-overrides.json. Also check whether DISCOCLAW_FAST_MODEL is still setting the startup fallback that fast, summary, cron, or task auto-tagging inherit from.
Preferred cleanup path: remove the legacy DISCOCLAW_FAST_RUNTIME entry once the replacement !models set fast <model> workflow is verified, because the env var survives !models reset.
Voice keeps coming back on the wrong adapter
Inspect runtime-overrides.json for voiceRuntime and confirm whether ANTHROPIC_API_KEY is auto-registering the voice-only anthropic runtime.
An AI agent keeps quoting stale workspace docs
If workspace/AGENTS.md or workspace/TOOLS.md contains old bootstrap-copied runtime-switching instructions, an agent may follow those instead of this guide.
Fix:
- keep
docs/runtime-switching.mdas the canonical operator runbook - trim
workspace/AGENTS.mdandworkspace/TOOLS.mddown to local overrides only - remove stale copied boilerplate, especially old runtime-switching or install instructions
AI operator handoff prompt
Use this when another AI agent needs to guide a user through a switch:
Help me switch this DiscoClaw instance to a different runtime adapter or model safely.
First determine whether this is a source checkout or an npm-managed install, and identify the service/unit name plus the authoritative `.env`, `models.json`, and `runtime-overrides.json`.
Then:
1. Show the current effective state with `!models`.
2. Inspect the filtered `.env`, `models.json`, and `runtime-overrides.json`, and call out stale fast/voice runtime overrides before changing anything.
3. Verify prerequisites for the target adapter from those filtered config keys and from recent service logs; do not claim an inactive adapter is registered based on `!models` alone.
4. Explain which parts are startup defaults from `.env` plus built-in fallbacks versus persistent overrides.
5. Record the current runtime/model state before making changes so rollback is possible.
6. If the goal is a persistent adapter switch, update `.env` and clear conflicting overrides.
7. If the goal is OpenRouter tier switching, inspect or set the needed `DISCOCLAW_TIER_OPENROUTER_<TIER>` vars, and explain that reverse-mapping is exact-string only.
8. If the goal is a live experiment, use `!models set ...` instead of editing `.env`.
9. After changes, verify with `!models` and `!restart logs`.
Do not assume `!models reset` means repo defaults. Treat it as reset-to-this-instance-startup-defaults, remember that `!models reset chat` does not undo a live chat runtime swap until restart, and remember that only `fast` and `voice` auto-switch runtimes from concrete model strings.