name: local-llm-curator description: Audit, maintain, and serve local LLM coding models on the user's Apple Silicon machine. Use when asked to choose, install, compare, inventory, update, prune, or sync local coding models across MLX, Ollama, LM Studio, llama.cpp/GGUF, or Hugging Face caches — and when asked to run, start, stop, or troubleshoot a local OpenAI-compatible endpoint (omlx on 127.0.0.1:8000) that an agent or harness points at.
local-llm-curator
Use this skill to keep local coding models current without loading model-maintenance procedure on every turn.
Workflow
- Run
scripts/model-sync.sh auditto inventory local models and caches. It covers ollama, oMLX, LM Studio, and the Hugging Face cache, and reports a size per entry — on a fixed-RAM machine size is the decision, so lead with it. Pipe throughcolumn -t -s$'\t'to read it. - For recommendations or installs, verify current availability and benchmarks from primary sources — official model cards, release posts, technical reports, runtime docs — before naming a model. Recognizing a model name is not knowing its current state; local coding models turn over within months, so search the name as the user wrote it even when it looks familiar.
- Apply the user's retention policy from
~/AGENTS.md: keep one daily driver, one fast fallback, and optionally one experimental model; prune superseded near-duplicates. - Prefer MLX on Apple Silicon when a suitable build exists. Fall back to GGUF/Ollama/LM Studio when easier deployment or compatibility is the deciding factor.
- Before deleting anything, run
scripts/model-sync.sh prune-plan --want <file>and present the removal commands for approval. - Only run destructive removal commands after explicit user approval.
Serving a Model Over HTTP
Inventorying a model does not make it reachable. Agents that talk to a "local
model" (Clankie's model add-local, Codex, opencode) need an OpenAI-compatible
endpoint, and omlx (uv tool, ~/.local/bin/omlx) is the server on this
machine — defaults to 127.0.0.1:8000, which is what those configs point at.
omlx serve --port 8000 # foreground, multi-model; background it yourself
omlx diagnose
curl -s http://127.0.0.1:8000/v1/models # the only honest list of what it serves
omlx start/stop/restart are advertised in --help but only work for the
macOS-app and Homebrew installs. This machine's omlx is a uv tool, where
they exit with "Background start is available for the macOS app and Homebrew
installs." Use omlx serve and detach it (nohup … &, or a herdr pane).
omlx serve discovers models from subdirectories of model_dir (default
~/.omlx/models), so a model id is a directory name, not a Hugging Face repo
id and not a slug — populate it by pointing model_dir at the download or
placing the snapshot there. Never trust a config's declared model ids; curl
/v1/models while the server is up and reconcile.
Two things that read as "the client is broken" when the server is fine:
-
~/.omlx/settings.jsonsetsauth.api_keywithskip_api_key_verification: falseby default. A caller sending no key, or the wrong one, gets a 401 — including agent harnesses that assume a local endpoint is keyless, and including client-side model probes that then report the endpoint as unreachable rather than as unauthorized. Read the key out of settings and store it in the caller's own credential store, or setskip_api_key_verification: truefor a loopback-only server. -
Cold decode is not the model's speed. The first request after load pages weights in off SSD; a published tok/s only shows up once warm. Measure the second request, not the first.
-
A model 50x slower than its card is swapping, not misconfigured.
iogpu.wired_limit_mbis unset by default, so MLX buffers are pageable and macOS evicts model weights under pressure — decode then pays a disk read per token. Measured here: DeepSeek-V4-Flash-0731-2.4bit-mixed at 0.7 tok/s against a published 36 tok/s on the same machine, withsysctl vm.swapusageshowing 51 GB of 52 GB used. Check swap first; it is the single most likely cause of a wildly slow local model, and no cache or scheduler setting will fix it.sysctl vm.swapusage # used ~= total means thrashing sudo sysctl iogpu.wired_limit_mb=124518 # omlx prints the right number for the machine at startupTwo traps when reading memory during this: a loaded MLX model shows a small
psRSS (Metal allocations are not RSS, soomlx-serverreports ~17 GB while holding 79 GB), andmemory_pressurereporting most memory free right after a request proves the weights were paged out, not that there is headroom. Trustvm.swapusageand the server's ownLoaded model: … (actual: …)line. -
A large quant evicts everything else.
~/.omlx/logs/server.logshows the real reason a request stalls or the engine restarts, e.g.Settle barrier timed out ...: freed=78.62GB (need>=86.24GB). Check that log before blaming the client. The memory guard ceiling lives undermemoryin settings.json. A ~79 GB model wants the machine to itself — close the browser/Electron pile before benchmarking or the measurement is of swap, not the model.
Desired-Model Files
Use a plain text file for --want, one model identifier per line. Blank lines and # comments are ignored. Identifiers may be exact installed names or substrings such as:
# daily
Qwen3-Coder-Next
# fast fallback
Qwen3-Coder-30B
Substring matching keeps every model containing the identifier, so a want entry
that is a prefix of another installed model silently keeps both (e.g.
qwen3.6:27b-mlx also keeps qwen3.6:27b-mlx-bf16). Always check the
prune-plan output for unintended keep rows and remove those models explicitly.
A model oMLX serves is an install like any other: it lives as a directory under
its model_dir and is invisible to ollama list and to the Hugging Face cache
listing, while being the largest thing on the machine. The audit covers it; do
not reach for a per-runtime command instead.
The script intentionally does not encode a permanent "best model" list. Current model choice belongs in the skill workflow, after fresh research.
