Gateway
Local models
OpenClaw can install and manage a local model or connect to a server you already run. For a hardware-aware recommendation, install the llama.cpp plugin, run openclaw onboard, and choose Managed local server. Setup shows the Gateway host, model, download size, and execution backend before downloading, then verifies a real tool call before changing the default model. LM Studio and Ollama remain options when you want to manage the model separately.
This page also covers larger local stacks and custom OpenAI-compatible servers. Local models do not provide hosted providers' safety filters. Keep tool permissions and prompt-injection defenses appropriate for the model and task.
For local servers that should start only when a selected model needs them, see Local model services.
Hardware floor
Memory requirements depend on the model weights, context size, runtime, and other work on the host. Managed llama.cpp setup checks available RAM, supported GPU memory, and disk space instead of assuming a particular machine. Its curated recipes use a 64K context. The smallest has an 8 GiB host-memory floor, while larger recipes need more memory. These floors do not guarantee fit or speed. See model recommendations for the current catalog.
For custom servers, leave room for the full OpenClaw prompt, tools, history, and model output. A model that loads or answers a short prompt may still fail an agent turn. Test actual tasks before making it your default, and review local-model security.
Pick a backend
| Backend | Use when |
|---|---|
| ds4 | Local DeepSeek V4 Flash on macOS Metal with OpenAI-compatible tool calls |
| LiteLLM / OAI-proxy / custom OpenAI-compatible proxy | You front another model API and need OpenClaw to treat it as OpenAI |
| llama.cpp | Hardware-aware model selection, verified downloads, and an OpenClaw-managed server |
| LM Studio | First-time local setup, GUI loader, native Responses API |
| MLX / vLLM / SGLang | High-throughput self-hosted serving with an OpenAI-compatible HTTP endpoint |
| Ollama | CLI workflow, model library, hands-off systemd service |
Use api: "openai-responses" when the backend supports it (LM Studio does). Otherwise use api: "openai-completions". If api is omitted on a custom provider with a baseUrl, OpenClaw defaults to openai-completions.
LM Studio + large local model (Responses API)
For a separately managed local server, load a model that fits your hardware in LM Studio. Enable the local server (default http://127.0.0.1:1234). Use the Responses API to keep reasoning separate from final text.
{ agents: { defaults: { model: { primary: "lmstudio/my-local-model" }, models: { "anthropic/claude-opus-4-6": { alias: "Opus" }, "lmstudio/my-local-model": { alias: "Local" }, }, }, }, models: { mode: "merge", providers: { lmstudio: { baseUrl: "http://127.0.0.1:1234/v1", apiKey: "lmstudio", api: "openai-responses", models: [ { id: "my-local-model", name: "Local Model", reasoning: false, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 196608, maxTokens: 8192, }, ], }, }, },}Setup checklist:
- Install LM Studio: https://lmstudio.ai
- Download the largest available model build (avoid "small"/heavily quantized variants), start the server, check that
http://127.0.0.1:1234/v1/modelslists it. - Replace
my-local-modelwith the actual model ID shown in LM Studio. - Keep the model loaded. Cold-load adds startup latency.
- Adjust
contextWindow/maxTokensif your LM Studio build differs. - For WhatsApp, stick to the Responses API so only final text is sent.
- Keep
models.mode: "merge"so hosted models stay available as fallbacks.
Hybrid config: hosted primary, local fallback
{ agents: { defaults: { model: { primary: "anthropic/claude-sonnet-4-6", fallbacks: ["lmstudio/my-local-model", "anthropic/claude-opus-4-6"], }, models: { "anthropic/claude-sonnet-4-6": { alias: "Sonnet" }, "lmstudio/my-local-model": { alias: "Local" }, "anthropic/claude-opus-4-6": { alias: "Opus" }, }, }, }, models: { mode: "merge", providers: { lmstudio: { baseUrl: "http://127.0.0.1:1234/v1", apiKey: "lmstudio", api: "openai-responses", models: [ { id: "my-local-model", name: "Local Model", reasoning: false, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 196608, maxTokens: 8192, }, ], }, }, },}For local-first with a hosted safety net, swap primary/fallbacks order and keep the same providers block and models.mode: "merge".
Regional hosting / data routing
Hosted MiniMax/Kimi/GLM variants also exist on OpenRouter with region-pinned endpoints (for example, US-hosted). Pick the regional variant to keep traffic in your chosen jurisdiction while keeping models.mode: "merge" for Anthropic/OpenAI fallbacks. Local-only is still the strongest privacy path. Hosted regional routing is the middle ground when you need provider features but want control over data flow.
Other OpenAI-compatible local proxies
MLX (mlx_lm.server), vLLM, SGLang, LiteLLM, OAI-proxy, or any custom gateway works if it exposes an OpenAI-style /v1/chat/completions endpoint. Use openai-completions unless the backend explicitly documents /v1/responses support.
{ agents: { defaults: { model: { primary: "local/my-local-model" }, }, }, models: { mode: "merge", providers: { local: { baseUrl: "http://127.0.0.1:8000/v1", apiKey: "sk-local", api: "openai-completions", timeoutSeconds: 300, models: [ { id: "my-local-model", name: "Local Model", reasoning: false, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 120000, maxTokens: 8192, }, ], }, }, },}Custom/local provider entries trust their exact configured baseUrl origin for guarded model requests, including loopback, LAN, tailnet, and private DNS hosts. Metadata, link-local, and local-use NAT64 (64:ff9b:1::/48) origins remain blocked without explicit opt-in. Requests to other private origins still need models.providers.<id>.request.allowPrivateNetwork: true. Set the trust flag to false to opt out of exact-origin trust.
models.providers.<id>.models[].id is provider-local - do not include the provider prefix. For an MLX server started with mlx_lm.server --model mlx-community/Qwen3-30B-A3B-6bit:
models.providers.mlx.models[].id: "mlx-community/Qwen3-30B-A3B-6bit"agents.defaults.model.primary: "mlx/mlx-community/Qwen3-30B-A3B-6bit"
Set input: ["text", "image"] on local or proxied vision models so image attachments get injected into agent turns. Interactive custom-provider onboarding infers common vision model IDs and only asks about unknown names. Non-interactive onboarding uses the same inference, with --custom-image-input / --custom-text-input to override it.
Use models.providers.<id>.timeoutSeconds for slow local/remote model servers before raising agents.defaults.timeoutSeconds. The provider timeout covers connect, headers, body streaming, and the total guarded-fetch abort for model HTTP requests only. If the agent or run timeout is lower, raise that too. The provider timeout cannot extend the whole run.
Behavior notes for local/proxied /v1 backends:
- OpenClaw treats these as proxy-style OpenAI-compatible routes, not native OpenAI endpoints.
- Native-OpenAI-only request shaping does not apply: no
service_tier, no Responsesstore, no OpenAI reasoning-compat payload shaping, no prompt-cache hints. - Hidden OpenClaw attribution headers (
originator,version,User-Agent) are not injected on custom proxy URLs.
Compat declarations are only for the custom endpoint described by this provider row. Catalog-known routes use provider-owned capabilities instead. See the custom-provider capability guide.
Compat overrides for stricter OpenAI-compatible backends:
-
String-only content: some servers accept only string
messages[].content, not structured content-part arrays. Setmodels.providers.<provider>.models[].compat.requiresStringContent: true. -
Strict message keys: if the server rejects message entries with more than
role/content, setcompat.strictMessageKeys: true. -
Bracketed tool text: some local models emit standalone bracketed tool requests as text, like
[tool_name]followed by JSON and[END_TOOL_REQUEST]. OpenClaw promotes those to real tool calls only when the name exactly matches a registered tool for the turn. Otherwise it stays as hidden, unsupported text. -
Unstructured tool-call-looking text: a model can emit JSON, XML, or ReAct-style text that looks like a tool call but was not a structured invocation. OpenClaw then leaves it as text and logs a warning. The warning carries the run id, provider and model, detected pattern, and tool name when available. That is provider/model incompatibility, not a completed tool run.
-
Forcing tool use: tools can show up as assistant text, as raw JSON, XML, or ReAct, or as an empty
tool_callsarray. First check that the server's chat template and parser support tool calls. If the parser only works when tool use is forced, override the default proxy value oftool_choice: "auto"per model:json5 { agents: { defaults: { models: { "local/my-local-model": { params: { extra_body: { tool_choice: "required", }, }, }, }, }, },}Use this only where every normal turn should call a tool. Replace
local/my-local-modelwith the exact ref fromopenclaw models list, or set it via CLI:bash openclaw config set agents.defaults.models '{"local/my-local-model":{"params":{"extra_body":{"tool_choice":"required"}}}}' --strict-json --merge -
Extra reasoning efforts: if a custom OpenAI-compatible model accepts OpenAI reasoning efforts beyond the built-in profile, declare them in the model's compat block. Adding
"xhigh"exposes it for that model ref in/think xhigh, session pickers, Gateway validation, andllm-taskvalidation:json5 { models: { providers: { local: { baseUrl: "http://127.0.0.1:8000/v1", apiKey: "sk-local", api: "openai-responses", models: [ { id: "gpt-5.4", name: "GPT 5.4 via local proxy", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 196608, maxTokens: 8192, compat: { supportedReasoningEfforts: ["low", "medium", "high", "xhigh"], reasoningEffortMap: { xhigh: "xhigh" }, }, }, ], }, }, },}
Smaller or stricter backends
If the model loads cleanly but full agent turns misbehave, check transport first, then inspect tool use and the context budget.
-
Check the local model responds - no tools, no agent context:
bash openclaw infer model run --local --model <provider/model> --prompt "Reply with exactly: pong" --json -
Check Gateway routing - sends only the prompt. It skips transcript, AGENTS bootstrap, context-engine assembly, tools, and bundled MCP servers. It still exercises Gateway routing, auth, and provider selection:
bash openclaw infer model run --gateway --model <provider/model> --prompt "Reply with exactly: pong" --json -
Check Tool Search if both probes pass but real agent turns fail with malformed tool calls or oversized prompts. Local Ollama models, LM Studio, and managed local services automatically use structured Tool Search when
tools.toolSearchis unset. Other backends can enable it withtools.toolSearch: { mode: "tools" }. This defers schemas while preserving policy-approved capabilities. LeavelocalModelLeanunset or set it tofalseso optional tools remain available. Check the server's actual context allocation and memory use as well. -
Disable tools entirely as a last resort by setting
models.providers.<provider>.models[].compat.supportsTools: falsefor that model - the agent then runs without tool calls. -
Inspect the failing request and server logs. Check the chat template, context window, memory pressure, and server errors. A successful text-only probe does not prove that the model can reliably complete a multi-step agent task.
Troubleshooting
- Gateway can't reach the proxy?
curl http://127.0.0.1:1234/v1/models. - LM Studio model unloaded? Reload it. Cold start is a common "hanging" cause.
- Local server says
terminated,ECONNRESET, or closes the stream mid-turn? OpenClaw records a low-cardinalitymodel.call.error.failureKindplus the OpenClaw process RSS/heap snapshot in diagnostics. For LM Studio/Ollama memory pressure, match that timestamp against the server log or a macOS crash/jetsam log to check whether the model server was killed. - Context errors? OpenClaw derives context-window preflight thresholds from the detected model window or the per-model
models.providers.<provider>.models[].contextTokenscap. It warns below 20% with an 8k floor. It hard-blocks below 10% with a 4k floor. Lower that model entry'scontextTokensor raise the server/model context limit. messages[].content ... expected a string? Addcompat.requiresStringContent: trueon that model entry.validation.keys, or "message entries only allowroleandcontent"? Addcompat.strictMessageKeys: trueon that model entry.- Direct
/v1/chat/completionscalls work, butopenclaw infer model run --localfails on Gemma or another local model? Check the provider URL, model ref, auth marker, and server logs first.model runskips agent tools entirely. Ifmodel runsucceeds but larger agent turns fail, check Tool Search and the allocated context. Usecompat.supportsTools: falseonly for a model that cannot reliably call tools. - Tool calls show up as raw JSON/XML/ReAct text, or the provider returns an empty
tool_callsarray? Do not add a proxy that blindly converts assistant text into tool execution. Fix the server's chat template and parser first. If the model only works when tool use is forced, add theparams.extra_body.tool_choice: "required"override above. Use that model entry only for sessions where a tool call is expected every turn. - Safety: local models skip provider-side filters. Keep agents narrow and compaction on to limit prompt-injection blast radius.
Related
- Configuration reference
- Model failover
- CLI backends — driving a local CLI agent instead of an API model