Add model hygiene workflow and enforce OpenRouter-only profiles
This commit is contained in:
parent
405433bb7f
commit
3bab4b3b4f
@ -153,8 +153,8 @@ Outputs:
|
|||||||
|
|
||||||
## 10) Acceptance Criteria
|
## 10) Acceptance Criteria
|
||||||
|
|
||||||
- AC-1: Running `bash ./scripts/model_profile_switch.sh free` changes default to `ollama/qwen3:14b`.
|
- AC-1: Running `bash ./scripts/model_profile_switch.sh free` changes default to `openrouter/qwen/qwen3-coder:free`.
|
||||||
- AC-2: Running `bash ./scripts/model_profile_switch.sh paid` restores default to `openrouter/moonshotai/kimi-k2.5`.
|
- AC-2: Running `bash ./scripts/model_profile_switch.sh paid` restores default to `openrouter/qwen/qwen3-coder:free`.
|
||||||
- AC-3: With schedule enabled (`21`/`7`), profile changes correctly by local time window.
|
- AC-3: With schedule enabled (`21`/`7`), profile changes correctly by local time window.
|
||||||
- AC-4: If operator manually drifts model during scheduled window, schedule guard re-aligns on next run.
|
- AC-4: If operator manually drifts model during scheduled window, schedule guard re-aligns on next run.
|
||||||
- AC-5: Budget guard warns and reverts when high model remains active beyond thresholds.
|
- AC-5: Budget guard warns and reverts when high model remains active beyond thresholds.
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
# OpenClaw Setup Max (Paid + Free Model Switching)
|
# OpenClaw Setup Max (Paid + Free Model Switching)
|
||||||
|
|
||||||
This workspace runs OpenClaw with:
|
This workspace runs OpenClaw with:
|
||||||
- A paid high-quality model profile (`openrouter/moonshotai/kimi-k2.5`)
|
- A paid high-quality model profile (`openrouter/qwen/qwen3-coder:free`)
|
||||||
- A free local profile (Ollama-only)
|
- A free OpenRouter profile
|
||||||
- One-command live switching
|
- One-command live switching
|
||||||
- Optional automatic day/night switching (example: free from 9pm-7am)
|
- Optional automatic day/night switching (example: free from 9pm-7am)
|
||||||
- Optional budget guard that warns and auto-reverts from expensive models
|
- Optional budget guard that warns and auto-reverts from expensive models
|
||||||
@ -27,11 +27,11 @@ Everything else is grouped by function:
|
|||||||
## 1) What You Get
|
## 1) What You Get
|
||||||
|
|
||||||
- `paid` profile:
|
- `paid` profile:
|
||||||
- Primary: `openrouter/moonshotai/kimi-k2.5`
|
- Primary: `openrouter/qwen/qwen3-coder:free`
|
||||||
- Fallbacks: `ollama/qwen3:14b`, `ollama/llama3.2:3b`
|
- Fallbacks: `openrouter/qwen/qwen3-coder-next`, `openrouter/qwen/qwen3-14b`
|
||||||
- `free` profile:
|
- `free` profile:
|
||||||
- Primary: `ollama/qwen3:14b`
|
- Primary: `openrouter/qwen/qwen3-coder:free`
|
||||||
- Fallbacks: `ollama/devstral:24b`, `ollama/llama3.2:3b`
|
- Fallbacks: `openrouter/qwen/qwen3-14b`
|
||||||
|
|
||||||
Profiles are defined in `config/model-profiles.config.json`.
|
Profiles are defined in `config/model-profiles.config.json`.
|
||||||
|
|
||||||
@ -41,6 +41,7 @@ Profiles are defined in `config/model-profiles.config.json`.
|
|||||||
- `config/model-schedule.config.json`: schedule settings (`dayProfile`, `nightProfile`, hours)
|
- `config/model-schedule.config.json`: schedule settings (`dayProfile`, `nightProfile`, hours)
|
||||||
- `config/model-budget-guard.config.json`: high-cost warning and auto-revert settings
|
- `config/model-budget-guard.config.json`: high-cost warning and auto-revert settings
|
||||||
- `scripts/model_profile_switch.sh`: manual live switch command
|
- `scripts/model_profile_switch.sh`: manual live switch command
|
||||||
|
- `scripts/model_hygiene_workflow.sh`: apply profile + prune legacy model/provider/auth config
|
||||||
- `scripts/model_schedule_guard.sh`: scheduled switch worker
|
- `scripts/model_schedule_guard.sh`: scheduled switch worker
|
||||||
- `scripts/model_budget_guard.sh`: budget guard worker
|
- `scripts/model_budget_guard.sh`: budget guard worker
|
||||||
- `scripts/install_model_schedule_guard_launchd.sh`: installs schedule LaunchAgent
|
- `scripts/install_model_schedule_guard_launchd.sh`: installs schedule LaunchAgent
|
||||||
@ -83,10 +84,16 @@ PULL_LOCAL_MODELS=false bash ./setup/setup_openclaw_ollama.sh
|
|||||||
|
|
||||||
## 4) Quick Start (Most Common)
|
## 4) Quick Start (Most Common)
|
||||||
|
|
||||||
Switch to free mode now:
|
Run the hygiene workflow (recommended after any model changes):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /Volumes/Data/openclaw-setups/openclaw-setup-max
|
cd /Volumes/Data/openclaw-setups/openclaw-setup-max
|
||||||
|
bash ./scripts/model_hygiene_workflow.sh paid
|
||||||
|
```
|
||||||
|
|
||||||
|
Switch to free mode now:
|
||||||
|
|
||||||
|
```bash
|
||||||
bash ./scripts/model_profile_switch.sh free
|
bash ./scripts/model_profile_switch.sh free
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
{
|
{
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"sessionKey": "agent:main:main",
|
"sessionKey": "agent:main:main",
|
||||||
"lowModel": "ollama/qwen3:14b",
|
"lowModel": "openrouter/qwen/qwen3-coder:free",
|
||||||
"highModels": [
|
"highModels": [
|
||||||
"openrouter/moonshotai/kimi-k2.5",
|
|
||||||
"xai/grok-4-fast",
|
"xai/grok-4-fast",
|
||||||
"xai/grok-code-fast-1"
|
"xai/grok-code-fast-1"
|
||||||
],
|
],
|
||||||
|
|||||||
@ -2,18 +2,17 @@
|
|||||||
"profiles": {
|
"profiles": {
|
||||||
"paid": {
|
"paid": {
|
||||||
"description": "High-quality paid model for deep work",
|
"description": "High-quality paid model for deep work",
|
||||||
"primary": "openrouter/moonshotai/kimi-k2.5",
|
"primary": "openrouter/qwen/qwen3-coder:free",
|
||||||
"fallbacks": [
|
"fallbacks": [
|
||||||
"ollama/qwen3:14b",
|
"openrouter/qwen/qwen3-coder-next",
|
||||||
"ollama/llama3.2:3b"
|
"openrouter/qwen/qwen3-14b"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"free": {
|
"free": {
|
||||||
"description": "All-local no-cost model stack",
|
"description": "OpenRouter free-first stack",
|
||||||
"primary": "ollama/qwen3:14b",
|
"primary": "openrouter/qwen/qwen3-coder:free",
|
||||||
"fallbacks": [
|
"fallbacks": [
|
||||||
"ollama/devstral:24b",
|
"openrouter/qwen/qwen3-14b"
|
||||||
"ollama/llama3.2:3b"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,8 +2,20 @@
|
|||||||
|
|
||||||
This setup adds two model profiles:
|
This setup adds two model profiles:
|
||||||
|
|
||||||
- `paid` -> `openrouter/moonshotai/kimi-k2.5` with local Ollama fallbacks
|
- `paid` -> `openrouter/qwen/qwen3-coder:free` with OpenRouter fallbacks
|
||||||
- `free` -> local Ollama-only stack
|
- `free` -> `openrouter/qwen/qwen3-coder:free` with OpenRouter fallback
|
||||||
|
|
||||||
|
## Recommended workflow (prevents legacy drift)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /Volumes/Data/openclaw-setups/openclaw-setup-max
|
||||||
|
bash ./scripts/model_hygiene_workflow.sh paid
|
||||||
|
```
|
||||||
|
|
||||||
|
This command:
|
||||||
|
- Applies the selected profile
|
||||||
|
- Restages/restarts guard LaunchAgents
|
||||||
|
- Prunes legacy models/providers/auth entries not referenced by current config
|
||||||
|
|
||||||
## On-demand switch (immediate)
|
## On-demand switch (immediate)
|
||||||
|
|
||||||
|
|||||||
@ -38,8 +38,6 @@ cat > "$PLIST_PATH" <<PLIST
|
|||||||
<dict>
|
<dict>
|
||||||
<key>PATH</key>
|
<key>PATH</key>
|
||||||
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
||||||
<key>OLLAMA_API_KEY</key>
|
|
||||||
<string>ollama-local</string>
|
|
||||||
<key>MODEL_GUARD_CONFIG</key>
|
<key>MODEL_GUARD_CONFIG</key>
|
||||||
<string>$MODEL_GUARD_CONFIG</string>
|
<string>$MODEL_GUARD_CONFIG</string>
|
||||||
</dict>
|
</dict>
|
||||||
|
|||||||
@ -40,8 +40,6 @@ cat > "$PLIST_PATH" <<PLIST
|
|||||||
<dict>
|
<dict>
|
||||||
<key>PATH</key>
|
<key>PATH</key>
|
||||||
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
||||||
<key>OLLAMA_API_KEY</key>
|
|
||||||
<string>ollama-local</string>
|
|
||||||
<key>MODEL_SCHEDULE_CONFIG</key>
|
<key>MODEL_SCHEDULE_CONFIG</key>
|
||||||
<string>$MODEL_SCHEDULE_CONFIG</string>
|
<string>$MODEL_SCHEDULE_CONFIG</string>
|
||||||
<key>MODEL_PROFILES_CONFIG</key>
|
<key>MODEL_PROFILES_CONFIG</key>
|
||||||
|
|||||||
189
openclaw-setup-max/scripts/model_hygiene_workflow.sh
Executable file
189
openclaw-setup-max/scripts/model_hygiene_workflow.sh
Executable file
@ -0,0 +1,189 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
PROFILES_CONFIG="${MODEL_PROFILES_CONFIG:-$ROOT_DIR/config/model-profiles.config.json}"
|
||||||
|
BUDGET_CONFIG="${MODEL_GUARD_CONFIG:-$ROOT_DIR/config/model-budget-guard.config.json}"
|
||||||
|
AUTH_STORE="${OPENCLAW_AUTH_STORE:-$HOME/.openclaw/agents/main/agent/auth-profiles.json}"
|
||||||
|
|
||||||
|
if ! command -v jq >/dev/null 2>&1; then
|
||||||
|
echo "[model-hygiene] jq is required" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! command -v openclaw >/dev/null 2>&1; then
|
||||||
|
echo "[model-hygiene] openclaw CLI is required" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'USAGE'
|
||||||
|
Usage:
|
||||||
|
bash ./scripts/model_hygiene_workflow.sh <profile> [--no-live] [--no-guards]
|
||||||
|
|
||||||
|
Behavior:
|
||||||
|
1) Applies profile via model_profile_switch.sh
|
||||||
|
2) Reinstalls guard LaunchAgents (unless --no-guards)
|
||||||
|
3) Prunes legacy models/providers/auth not referenced by config
|
||||||
|
4) Prints models status
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
bash ./scripts/model_hygiene_workflow.sh paid
|
||||||
|
bash ./scripts/model_hygiene_workflow.sh free --no-live
|
||||||
|
USAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
in_array() {
|
||||||
|
local needle="$1"
|
||||||
|
shift
|
||||||
|
local item
|
||||||
|
for item in "$@"; do
|
||||||
|
if [[ "$item" == "$needle" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
profile="${1:-}"
|
||||||
|
if [[ -z "$profile" ]]; then
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ "$profile" == "-h" || "$profile" == "--help" ]]; then
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
shift || true
|
||||||
|
|
||||||
|
apply_live="true"
|
||||||
|
install_guards="true"
|
||||||
|
while (( "$#" )); do
|
||||||
|
case "$1" in
|
||||||
|
--no-live) apply_live="false" ;;
|
||||||
|
--no-guards) install_guards="false" ;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "[model-hygiene] unknown argument: $1" >&2
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ! -f "$PROFILES_CONFIG" ]]; then
|
||||||
|
echo "[model-hygiene] missing config: $PROFILES_CONFIG" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
profile_exists="$(jq -r --arg p "$profile" '.profiles[$p] != null' "$PROFILES_CONFIG")"
|
||||||
|
if [[ "$profile_exists" != "true" ]]; then
|
||||||
|
echo "[model-hygiene] unknown profile '$profile'" >&2
|
||||||
|
jq -r '.profiles | keys[]' "$PROFILES_CONFIG" | sed 's/^/ - /'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[model-hygiene] collecting managed models/providers from config"
|
||||||
|
keep_models=()
|
||||||
|
while IFS= read -r model; do
|
||||||
|
[[ -z "$model" ]] && continue
|
||||||
|
if ! in_array "$model" "${keep_models[@]-}"; then
|
||||||
|
keep_models+=("$model")
|
||||||
|
fi
|
||||||
|
done < <(jq -r '.profiles | to_entries[] | .value.primary, (.value.fallbacks[]? // empty)' "$PROFILES_CONFIG")
|
||||||
|
|
||||||
|
if [[ -f "$BUDGET_CONFIG" ]]; then
|
||||||
|
while IFS= read -r model; do
|
||||||
|
[[ -z "$model" ]] && continue
|
||||||
|
if ! in_array "$model" "${keep_models[@]-}"; then
|
||||||
|
keep_models+=("$model")
|
||||||
|
fi
|
||||||
|
done < <(jq -r '.lowModel // empty, (.highModels[]? // empty)' "$BUDGET_CONFIG")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ${#keep_models[@]} -eq 0 ]]; then
|
||||||
|
echo "[model-hygiene] no models discovered in config; aborting" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
keep_providers=()
|
||||||
|
for model in "${keep_models[@]}"; do
|
||||||
|
provider="${model%%/*}"
|
||||||
|
[[ -z "$provider" || "$provider" == "$model" ]] && continue
|
||||||
|
if ! in_array "$provider" "${keep_providers[@]-}"; then
|
||||||
|
keep_providers+=("$provider")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "[model-hygiene] applying profile '$profile'"
|
||||||
|
switch_args=("$profile" "--no-status")
|
||||||
|
if [[ "$apply_live" != "true" ]]; then
|
||||||
|
switch_args+=("--no-live")
|
||||||
|
fi
|
||||||
|
bash "$SCRIPT_DIR/model_profile_switch.sh" "${switch_args[@]}"
|
||||||
|
|
||||||
|
if [[ "$install_guards" == "true" ]]; then
|
||||||
|
echo "[model-hygiene] reinstalling guard LaunchAgents"
|
||||||
|
bash "$SCRIPT_DIR/install_local_model_guardrails.sh" >/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[model-hygiene] pruning legacy configured models"
|
||||||
|
current_models_json="$(openclaw config get agents.defaults.models 2>/dev/null || echo '{}')"
|
||||||
|
while IFS= read -r model; do
|
||||||
|
[[ -z "$model" ]] && continue
|
||||||
|
if ! in_array "$model" "${keep_models[@]-}"; then
|
||||||
|
openclaw config unset "agents.defaults.models[$model]" >/dev/null || true
|
||||||
|
fi
|
||||||
|
done < <(jq -r 'keys[]? // empty' <<<"$current_models_json")
|
||||||
|
|
||||||
|
for model in "${keep_models[@]}"; do
|
||||||
|
openclaw config set --json "agents.defaults.models[$model]" '{}' >/dev/null || true
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "[model-hygiene] pruning legacy aliases"
|
||||||
|
while IFS=$'\t' read -r alias target; do
|
||||||
|
[[ -z "$alias" || -z "$target" ]] && continue
|
||||||
|
if ! in_array "$target" "${keep_models[@]-}"; then
|
||||||
|
openclaw models aliases remove "$alias" >/dev/null || true
|
||||||
|
fi
|
||||||
|
done < <(openclaw models status --json 2>/dev/null | jq -r '.aliases // {} | to_entries[] | "\(.key)\t\(.value)"')
|
||||||
|
|
||||||
|
echo "[model-hygiene] pruning legacy provider blocks"
|
||||||
|
providers_json="$(openclaw config get models.providers 2>/dev/null || echo '{}')"
|
||||||
|
while IFS= read -r provider; do
|
||||||
|
[[ -z "$provider" ]] && continue
|
||||||
|
if ! in_array "$provider" "${keep_providers[@]-}"; then
|
||||||
|
openclaw config unset "models.providers.$provider" >/dev/null || true
|
||||||
|
fi
|
||||||
|
done < <(jq -r 'keys[]? // empty' <<<"$providers_json")
|
||||||
|
|
||||||
|
echo "[model-hygiene] pruning legacy auth profiles in openclaw config"
|
||||||
|
auth_profiles_json="$(openclaw config get auth.profiles 2>/dev/null || echo '{}')"
|
||||||
|
while IFS=$'\t' read -r profile_id provider; do
|
||||||
|
[[ -z "$profile_id" || -z "$provider" ]] && continue
|
||||||
|
if ! in_array "$provider" "${keep_providers[@]-}"; then
|
||||||
|
openclaw config unset "auth.profiles[$profile_id]" >/dev/null || true
|
||||||
|
fi
|
||||||
|
done < <(jq -r 'to_entries[]? | "\(.key)\t\(.value.provider // empty)"' <<<"$auth_profiles_json")
|
||||||
|
|
||||||
|
if [[ -f "$AUTH_STORE" ]]; then
|
||||||
|
echo "[model-hygiene] pruning legacy auth profiles in auth store"
|
||||||
|
keep_providers_json="$(printf '%s\n' "${keep_providers[@]-}" | jq -R . | jq -s .)"
|
||||||
|
tmp_auth="$(mktemp)"
|
||||||
|
jq --argjson keep "$keep_providers_json" '
|
||||||
|
.profiles |= with_entries(select((.value.provider // "") as $p | ($keep | index($p)) != null))
|
||||||
|
| .lastGood |= with_entries(select(.key as $p | ($keep | index($p)) != null))
|
||||||
|
| .usageStats |= with_entries(select((.key | split(":")[0]) as $p | ($keep | index($p)) != null))
|
||||||
|
' "$AUTH_STORE" > "$tmp_auth"
|
||||||
|
mv "$tmp_auth" "$AUTH_STORE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[model-hygiene] complete"
|
||||||
|
echo "[model-hygiene] providers kept: ${keep_providers[*]}"
|
||||||
|
echo "[model-hygiene] models kept: ${keep_models[*]}"
|
||||||
|
echo ""
|
||||||
|
openclaw models status
|
||||||
Loading…
Reference in New Issue
Block a user