Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 23 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
# shell-swap

Provider/model-**agnostic** mass model switch for OpenClaw. Resolves the target
against the live config alias map and stamps a consistent `{model, provider}`
pair across config + every agent session store in one command.
Provider/model-**agnostic** mass model switch for OpenClaw. It can safely
unpin sessions to the configured default through Gateway (no restart), or
hard-pin an explicit non-default model for maintenance workflows.

## Included

• `SKILL.md` — OpenClaw skill definition
• `scripts/switch.sh` — the switch script
• `scripts/test.sh` — hermetic regression tests

## Usage

```bash
exec scripts/switch.sh <target> [--agent NAME] [--all-agents] [--crons] [--dry-run]
exec scripts/switch.sh <target> [--set-primary|--pin-exact] [--agent NAME] [--all-agents] [--crons] [--dry-run]
exec scripts/switch.sh --think LEVEL [--fast MODE] [--agent NAME] [--dry-run]
exec scripts/switch.sh --fast MODE [--agent NAME] [--dry-run]
```

`<target>` is resolved against `agents.defaults.models` in the live config:

• **default/reset/unpin** — clear model overrides so config primary + fallbacks win
• **alias** — any configured alias (`opus`, `gpt`, `minimax`, `grok-4.3`, `kimi`, …)
• **provider/model** — full key (`anthropic/claude-opus-4-8`, `venice/grok-4-20`)
• **raw id** — any `provider/model` not yet in the allowlist (passthrough)
Expand All @@ -28,6 +30,10 @@ map to multiple providers, so the provider can't be guessed.

```bash
exec scripts/switch.sh opus --dry-run
exec scripts/switch.sh default
exec scripts/switch.sh sol # unpins when sol is already the configured primary
exec scripts/switch.sh sol --pin-exact # intentionally disables fallbacks
exec scripts/switch.sh opus --set-primary # explicit config change, then unpin
exec scripts/switch.sh anthropic/claude-opus-4-8
exec scripts/switch.sh minimax --agent <your-agent>
exec scripts/switch.sh --think high
Expand All @@ -37,14 +43,20 @@ exec scripts/switch.sh --think default --fast default

## What it updates

1. `~/.openclaw/openclaw.json` — `agents.defaults.model.primary` when a model
target is provided and the run is not scoped to one agent (allowlist +
fallbacks left untouched)
2. `~/.openclaw/agents/*/sessions/sessions.json` — `model`, `modelOverride`,
1. `~/.openclaw/openclaw.json` — only with explicit `--set-primary`; the
default command never mutates config
2. Default/reset uses Gateway `sessions.patch {model:null}` to remove model
overrides and stale `liveModelSwitchPending` state. Active sessions are
deferred and must be retried once idle. No Gateway restart is needed.
3. Exact non-default targets—and a configured-primary target paired with
`--pin-exact`—update `model`, `modelOverride`,
`modelProvider`, `providerOverride`, `modelOverrideSource`; strips stale
fallback fields. Model + provider are set together so they never diverge.
3. Session overrides — `--think` / `--fast` patch warm sessions through Gateway
fallback fields. These are `source=user` hard pins and disable configured
model fallback for those sessions.
4. Session overrides — `--think` / `--fast` patch warm sessions through Gateway
by default, or edit `sessions.json` directly with `--session-mode offline`
4. (opt-in `--crons`) legacy `~/.openclaw/cron/jobs.json` — `payload.model`
5. (opt-in `--crons`) legacy `~/.openclaw/cron/jobs.json` — `payload.model`

Backups are written before modification (`*.bak`).

Run tests with `/opt/homebrew/bin/bash scripts/test.sh` on macOS.
69 changes: 49 additions & 20 deletions SKILL.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
---
name: shell-swap
description: "Admin tool to mass-switch every OpenClaw session and the default model to ANY provider/model. Provider- and model-agnostic. Use when asked to change model, switch lanes, set the default model, do a fleet-wide model change, or \"shell swap\"."
description: "Admin tool to safely unpin or mass-switch OpenClaw session models. Config mutation is explicit-only; default/reset uses Gateway model:null so configured fallbacks keep working without a restart."
---

# Shell Swap

Provider/model-**agnostic** mass model switch. Resolves the target against the
**live config alias map** (`agents.defaults.models`) — the single source of
truth — then stamps a consistent `{model, provider}` pair across config + every
agent session store. No hardcoded alias table; works for any model the config
knows about (Anthropic, OpenAI, Venice, xAI, OpenRouter, NVIDIA, Ollama, …).
Provider/model-**agnostic** model administration. The safe default operation
unpins sessions through Gateway so they inherit the configured primary and its
fallback chain. Exact non-default hard pins remain available, but are explicit
`source=user` selections and therefore disable configured model fallback.

## Usage

```bash
exec scripts/switch.sh <target> [--agent NAME] [--all-agents] [--crons] [--dry-run]
exec scripts/switch.sh <target> [--set-primary|--pin-exact] [--agent NAME] [--all-agents] [--crons] [--dry-run]
exec scripts/switch.sh --think LEVEL [--fast MODE] [--agent NAME] [--dry-run]
exec scripts/switch.sh --fast MODE [--agent NAME] [--dry-run]
```

`<target>` may be:
- **default/reset/unpin** — call Gateway `sessions.patch` with `model:null` so
sessions inherit config and stale `liveModelSwitchPending` is removed
- **alias** — any alias defined in `agents.defaults.models` (e.g. `opus`, `gpt`, `minimax`, `grok-4.3`, `kimi`)
- **provider/model** — a full config key (e.g. `anthropic/claude-opus-4-8`, `venice/grok-4-20`)
- **raw id** — any `provider/model` not yet in the allowlist (agnostic passthrough)
Expand All @@ -35,16 +36,26 @@ Session override flags:
`default` clears the session override so config/provider defaults win.
- `--fast MODE` sets or clears direct session `fastMode` overrides.
Modes: `on|off|auto|default`. `default` clears the session override.
- `--session-mode gateway|offline` controls how `--think` / `--fast` are
- `--set-primary` explicitly changes `agents.defaults.model.primary`. Without
it, shell-swap does not modify `openclaw.json`.
- `--pin-exact` preserves intentional hard-pin semantics even when the explicit
target equals the configured primary. It writes `source=user` and therefore
disables configured fallbacks. It cannot be combined with `--set-primary`.
- `--session-mode gateway|offline` controls how reset / `--think` / `--fast` are
written. Default is `gateway`, which calls Gateway `sessions.patch` and
updates warm in-memory sessions without a restart. `offline` edits
`sessions.json` directly and is for maintenance when Gateway is down.

### What it does

1. When a model target is provided, updates `agents.defaults.model.primary` in
`openclaw.json` to the full id
2. For every agent session store (`agents/*/sessions/sessions.json`):
1. Leaves `openclaw.json` unchanged unless `--set-primary` is supplied.
2. For `default`, or a target equal to the configured primary without
`--pin-exact`, calls Gateway `sessions.patch {model:null}` for every idle
selected session. This removes model overrides, fallback-origin state, and
`liveModelSwitchPending` without a restart. Active sessions are deferred;
rerun once they are idle.
3. For an exact non-default target—or a configured-primary target paired with
`--pin-exact`—updates every selected session store:
- sets `model` and `modelOverride` → the resolved model id
- sets `modelProvider` and `providerOverride` → the resolved provider
- sets `modelOverrideSource` → `user`
Expand All @@ -57,14 +68,15 @@ Session override flags:
are preserved when switching **into** a codex lane (provider resolves to
`agentRuntime.id == "codex"`).
- model and provider are stamped **together**, so they can never diverge
3. When `--think` / `--fast` is provided:
4. When `--think` / `--fast` is provided:
- default `gateway` mode patches every selected session through Gateway
`sessions.patch` so warm sessions update without restart
- `offline` mode edits direct session-entry fields in `sessions.json`
- invalid provider/model combinations are rejected by Gateway in warm-safe
mode and reported; those sessions are left unchanged
4. Optionally (`--crons`) rewrites `payload.model` in a legacy `cron/jobs.json`
5. Backs up each modified file (`*.bak`) and reports per-store change counts
5. Optionally (`--crons`) rewrites `payload.model` in a legacy `cron/jobs.json`
only for exact pins; reset/unpin does not create cron model pins.
6. Backs up directly modified files (`*.bak`) and reports change counts.

### What it does NOT touch

Expand All @@ -87,6 +99,15 @@ Session override flags:
# Switch the whole fleet to opus (resolves to claude-cli/claude-opus-4-8)
exec scripts/switch.sh opus

# Safely inherit the configured primary + fallbacks (no restart/config edit)
exec scripts/switch.sh default

# Intentionally pin the configured primary and disable fallbacks
exec scripts/switch.sh sol --pin-exact

# Explicitly change the configured primary, then unpin sessions to inherit it
exec scripts/switch.sh opus --set-primary

# Any provider, by alias
exec scripts/switch.sh minimax # -> venice/minimax-m25
exec scripts/switch.sh grok-4.3 # -> openrouter/x-ai/grok-4.3
Expand Down Expand Up @@ -131,25 +152,33 @@ exec scripts/switch.sh --think off --session-mode offline
leaves the global config primary unchanged; an unknown agent name aborts with
no changes. `--agent current` (or `--current-agent`) targets the active agent
via `OPENCLAW_MCP_AGENT_ID`.
- **Tests:** `bash scripts/test.sh` runs a hermetic regression suite
covering resolution, agentRuntime provider, the schema-scoped walk, `auto`
preservation, divergence repair, provenance, scoping, atomicity, backups,
pre-validation, dry-run, and session override modes. Run it before changing
the script.
- **Tests:** `/opt/homebrew/bin/bash scripts/test.sh` runs a hermetic regression
suite covering no-config-by-default behavior, Gateway null-unpin, active-run
deferral, pending-flag cleanup, same-primary `--pin-exact`, explicit config
mutation, and offline safety.
- **Thinking compatibility:** Gateway `sessions.patch` validates the selected
level against the session's effective provider/model profile. For example,
`--think max` can be rejected on an OpenAI session whose current profile only
supports `off|minimal|low|medium|high|xhigh`; shell-swap reports that failure
and leaves that session unchanged. Existing stored unsupported levels may be
remapped by OpenClaw at runtime, but the warm-safe Gateway path does not force
invalid values into live sessions.
- **Restart scope (warm vs cold):** file-surgery edits the on-disk store. A
- **Restart scope (warm vs cold):** Gateway reset via `model:null` updates warm
state and explicitly deletes pending live-switch state; idle sessions do not
require a restart. Active sessions are not patched because their later
snapshot merge could win the race; rerun after they become idle. File-surgery
edits the on-disk store. A
**cold** session (a persisted row not currently loaded in the gateway's
memory) reads the new override when it next hydrates — no restart. A **warm**
session (held in gateway memory) keeps its in-memory copy and can rewrite the
file, so a config-primary change or warm-session switch may need a gateway
restart to take effect. Cold sessions are the easy case; warm sessions are the
reason a restart is sometimes required.
- **Pending semantics:** a non-null `sessions.patch` can set
`liveModelSwitchPending` even if the session is idle at patch time, and that
flag can fire on a later user turn. Shell-swap therefore uses the native
null-reset path for default-equivalent targets and never manufactures the
flag online. Offline reset may delete it only while Gateway is stopped.
- **When to prefer the native path instead:** for a single session or a live
switch with **no restart**, use the gateway-native surfaces — `/model`, the
model picker, `session_status(model=…)`, or `sessions.patch`. They write the
Expand Down
Loading