Onboard a new on-device model to the Mac Studio eval rig end-to-end — hardware size/shape feasibility gate, OpenRouter quality pre-screen, ollama pull, opencode+pi cross-harness registration in models.yml, and continuous-rotation wiring. Use when the user wants to add/test/try a new local (Ollama) model on the rig, asks whether a model is the right size/shape for the hardware, or wants to evaluate a candidate before committing rig time.
34 stars
0 votes
0 copies
2 views
Added September 2, 2026
ai-agentspythonrustgoshellbashapi
Works with
terminal
cli
api
Security analysis
C67/100
mediumUses curl or wget to download content
criticalDownloads and executes remote scripts — classic supply chain attack
Installs into .claude/skills of the current project.
Are you the author of Local Model Onboarding?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/sunholo-data-local-model-onboarding-ailang)
---
name: local-model-onboarding
description: Onboard a new on-device model to the Mac Studio eval rig end-to-end — hardware size/shape feasibility gate, OpenRouter quality pre-screen, ollama pull, opencode+pi cross-harness registration in models.yml, and continuous-rotation wiring. Use when the user wants to add/test/try a new local (Ollama) model on the rig, asks whether a model is the right size/shape for the hardware, or wants to evaluate a candidate before committing rig time.
---
# Local Model Onboarding (the rig)
End-to-end workflow for taking a **candidate model → qualified member of the
on-device eval rotation**. This ties together the pieces that already exist
(`model-manager` for models.yml/API access, `local-ollama-eval` for running on
the rig) and adds the two things that were tribal knowledge: the **hardware
size/shape gate** and the **opencode+pi cross-harness registration**.
The rig: **Mac Studio M4 Max · 128 GB unified · 40-core GPU · ~546 GB/s memory
bandwidth.** One box, shared by every eval job via a mkdir mutex (rig lock).
## Current State
- **ollama**: !'curl -s --max-time 3 http://localhost:11434/api/version >/dev/null 2>&1 && echo "✓ running" || echo "✗ not running — start it first"'
- **Unified memory**: !'echo "$(($(sysctl -n hw.memsize)/1024/1024/1024)) GB ($(sysctl -n machdep.cpu.brand_string 2>/dev/null || echo Apple Silicon))"'
- **Models on disk**: !'curl -s http://localhost:11434/api/tags 2>/dev/null | python3 -c "import sys,json;[print(\" \",m[\"name\"],\"%.1fGB\"%(m[\"size\"]/1e9)) for m in json.load(sys.stdin).get(\"models\",[])]" 2>/dev/null || echo " (ollama down)"'
- **Currently resident (VRAM)**: !'curl -s http://localhost:11434/api/ps 2>/dev/null | python3 -c "import sys,json;d=json.load(sys.stdin);[print(\" \",m[\"name\"],\"%.1fGB\"%(m.get(\"size_vram\",0)/1e9)) for m in d.get(\"models\",[])] or print(\" (none loaded)\")" 2>/dev/null || echo " (ollama down)"'
- **Rotation models**: !'grep -E "OS_FILLER_MODELS|MODELS=" tools/launchd/os-rotation-filler.sh 2>/dev/null | head -1'
- **Rig lock**: !'ls "$HOME/.ailang/state/rig.lock.d" >/dev/null 2>&1 && echo "HELD — a job is running; do heavy pulls/probes around it" || echo "free"'
> Use the state above. If ollama is down, start it before anything. If the rig
> lock is held, a scheduled job is running — `ollama pull` is fine (network/disk)
> but defer GPU probes, or acquire the lock (see `tools/launchd/rig-lock.sh`).
## The hardware envelope (read this first)
Token generation on Apple Silicon is **memory-bandwidth-bound, not
compute-bound**. Two consequences drive every decision in this skill:
1. **`--parallel 1` is the established default.** All three rig jobs
(`nightly-eval.sh`, `nightly-lang-eval.sh`, `os-rotation-filler.sh`) hard-code
it. The documented rationale (`tools/launchd/rig-lock.sh`) is operational: the
box is a single GPU / bandwidth-bound, concurrent streams thrash the ~546 GB/s
bus, and an Ollama model reload mid-run silently kills a stream — all of which
bite hard in a *multi-model* rotation. **Evidence caveat (verify before
changing):** there is **no recorded p=1-vs-p=2 head-to-head**. The only
measured parallelism data is a p=2-vs-p=4 run on a *single small* model
(gemma4:26b) that favored p=2 over p=4 — a different question. So treat p=1 as
the safe operational default, not a benchmarked optimum. If you want to raise
it, run the head-to-head first (smoke set, p=1 vs p=2, compare wall-clock,
pass rate, and TTFT-timeout count) — don't just trust the old p=2 note in
`local-ollama.md`.
2. **Prefer MoE with a small *active* parameter count.** Total params live
cheaply in unified memory (quality); only the active params compute per token
(speed). `qwen3.5:35b-a3b` = 35B total / **3B active** is the canonical shape.
A *dense* model of the same size generates tokens far slower — avoid dense
above ~14B for the rotation.
**Memory budget (single-stream, p=1):**
| | GB |
|---|---|
| Unified total | 128 |
| Usable by Metal (~75% wired default) | ~96 |
| Reserve (OS + ailang server + opencode/pi subprocs + KV headroom) | ~22 |
| **Safe ceiling for model resident** | **~74** |
| Comfortable (slack at long context) | ≤ ~50 |
Rule of thumb: **on-disk ≤ ~45 GB, total ≤ ~80B, active ≤ ~8B.** Run
`scripts/check_model_fit.sh` to get a verdict (estimate pre-pull, or measure a
pulled model's real resident VRAM).
## Workflow
### 1. Select a candidate (shape gate + generation check)
**Do not pick from memory — fetch live.** The assistant's training is months
stale and model names mislead. Use [`resources/model-watch.md`](resources/model-watch.md):
it lists the canonical sources to fetch — ranking boards **SWE-bench**,
**Terminal-Bench**, **LMArena** (Aider dropped 2026-06, unmaintained); plus
availability (Ollama library, OpenRouter, HF trending) and our own OS leaderboard.
Refresh that file's table first, then choose from it. For each promising model,
split into the **🖥 local track** (on-device build that fits the rig) and the
**☁️ cloud track** (OpenRouter screen / ceiling ref) — see model-watch.
Find a recent coding/agentic model that matches the shape rule.
**Verify the generation by RELEASE DATE, not the name** — Qwen naming is a trap:
`Qwen3-Coder-30B-A3B` is the *Qwen3.0* coder (2025) and is **older** than the
rig's incumbent `qwen3.5:35b-a3b` (Qwen 3.5, Feb 2026), despite the higher-looking
"Coder" label. As of mid-2026 the genuinely newer *same-shape* upgrade is
**Qwen 3.6 35B-A3B** (Apr 2026, MoE). `Qwen 3.6-27B` is newer but **dense** (no
A3B) → slower on this bandwidth-bound box. `Qwen 3.7-Max` is API-only (not
on-device). Always web-check the release date and confirm it actually beats the
incumbent on the axis you care about before pulling.
Skip the giant frontier-OS models (GLM-5.1, DeepSeek-V4-Pro, Kimi, MiniMax-m3) —
those stay as **OpenRouter** ceiling references; they don't fit on-device.
```bash
# Fit/shape verdict BEFORE spending rig time (estimate from the published size):
.claude/skills/local-model-onboarding/scripts/check_model_fit.sh --disk 24 --active 3
```
### 2. Quality pre-screen via OpenRouter (cheap, zero rig time)
If the model is also hosted on OpenRouter, screen its *quality* first so you only
pull winners. Use `model-manager` to add a temporary `or-<model>` entry and run a
small standard+agent pass on AILANG + the 4-lang set, then compare to incumbents.
```bash
.claude/skills/model-manager/scripts/test_model_access.sh <openrouter-id> # access + pricing
.claude/skills/model-manager/scripts/find_model_info.sh <openrouter-id>
# add or-<model> to models.yml (model-manager), then:
ailang eval-suite --models or-<model> --benchmarks <smoke set> --langs ailang,python --trials 3 --output /tmp/screen_<model>
```
If it doesn't clear the bar vs your current local models, stop here — no pull.
### 3. Pull to the rig + confirm it fits for real
```bash
ollama pull <ollama-tag>
.claude/skills/local-ollama-eval/scripts/check_ollama_model.sh <ollama-tag> # loads + sanity
.claude/skills/local-model-onboarding/scripts/check_model_fit.sh <ollama-tag> # REAL resident VRAM verdict
```
Must come back GREEN/YELLOW. RED → pick a smaller quant (mxfp8 → nvfp4/Q4) or a
lower-active variant.
### 4. Register the cross-harness pair in models.yml
Always register **two** entries — the same local model on **opencode** and on
**pi** — so the OS/Local leaderboard gets a real harness comparison. They must
share a `model_family` so the Explorer pairs them.
```yaml
opencode-<id>:
api_name: "<ollama-tag>"
provider: "ollama"
agent_cli: "opencode"
agent_model_name: "<ollama-tag>"
model_family: "<family>" # SAME on both entries → cross-harness pairing
env_var: ""
budgets: { hard_timeout_secs: 600 } # local hang protection (see below)
pricing: { input_per_1k: 0.0, output_per_1k: 0.0 }
pi-<id>:
api_name: "<ollama-tag>"
provider: "ollama"
agent_cli: "pi"
agent_model_name: "ollama/<ollama-tag>" # pi prefixes provider
model_family: "<family>"
env_var: ""
budgets: { hard_timeout_secs: 600 }
pricing: { input_per_1k: 0.0, output_per_1k: 0.0 }
```
**Both harnesses need the model in their OWN config too** — models.yml routing
is not enough. This bites silently: the harness produces **empty output / 0%**
(model-not-found) while the model is fine (the other harness passes). Register in
both:
- **opencode** — add an entry under the `ollama` provider's `models` block in
`~/.config/opencode/opencode.jsonc` (mirror an existing qwen entry: name +
temperature/top_p/max_tokens). Verify: `opencode models | grep <tag>`.
- **pi** — pi has no built-in ollama provider; add the tag's `id` to
`~/.pi/agent/models.json` (canonical copy: `tools/setup/pi-ollama-models.json`).
Verify: a one-benchmark `pi-<id>` run returns non-empty output.
If one harness smoke-fails with empty stdout while the other passes, this missing
registration is almost always why.
**hard_timeout_secs: 600** — local models hang on some hard benchmarks; the
2400s default burns 40 min per hang. 600s is the standard local backstop.
Rebuild so the embedded registry updates: `make quick-install` (the launchd jobs
use the installed binary). Verify routing:
```bash
ailang eval-suite --agent --models opencode-<id>,pi-<id> --benchmarks fizzbuzz --langs ailang --dry-run
```
### 5. Smoke-validate both harnesses (under the rig lock)
```bash
source tools/launchd/rig-lock.sh && rig_lock_acquire wait
ailang eval-suite --agent --models opencode-<id>,pi-<id> \
--benchmarks fizzbuzz,cli_args --langs ailang,python --trials 1 --parallel 1 --output /tmp/onboard_<id>
```
Confirm: passes, `cost 0`, `harness` = opencode/pi, resident VRAM under budget
(`ollama ps`). The lock releases on shell exit.
### 6. Add to the continuous rotation
Append the pair to the filler's model list — it round-robins benchmarks for all
listed models, serially at p=1:
```bash
# tools/launchd/os-rotation-filler.sh
OS_FILLER_MODELS="opencode-qwen3-5-35b-a3b-mxfp8,pi-qwen3-5-35b-a3b-mxfp8,opencode-<id>,pi-<id>"
```
The launchd plist execs the repo script directly — next fire picks it up, no
reinstall. Mind cycle wall-time: each added model multiplies runs/cycle; the rig
lock prevents overlap so cycles just lengthen.
### 7. Record the decision
Note the screen result, fit verdict, and rotation status in the model's
models.yml `notes:` and (if it's a keeper) the OS leaderboard story. Commit
models.yml + the filler change + `~/.pi/agent/models.json` mirror together.
## Gotchas
- **Never `--parallel >1`** on the rig (the whole point of this skill). Bandwidth.
- **Two entries or it's not comparable** — opencode + pi with a shared family.
- **pi needs the ollama provider block** in `~/.pi/agent/models.json`.
- **600s hard timeout** for local models, not the 2400s cloud default.
- **Blackout 04:00–07:00** — the filler skips it (nightly window). Don't schedule
heavy onboarding probes there.
- **`make quick-install` after models.yml edits** — the registry is embedded; the
launchd jobs run the installed binary, not the source tree.
- **OpenRouter screen is a quality filter only** — a model that scores well hosted
may differ on-device due to quant; always re-validate after pull (step 5), and
the opencode-vs-pi-vs-OR comparison in the Explorer surfaces that delta.
## Related
- `model-manager` — models.yml mechanics, API access, pricing (cloud + OR).
- `local-ollama-eval` — running/monitoring evals on the rig, OTEL observability.
- `tools/launchd/os-rotation-filler.sh` — the continuous rotation.
- `docs/docs/guides/evaluation/local-ollama.md` — human-facing rig reference.