The short version: LM Studio ships an OpenAI-compatible HTTP server. Start it, load a tool-capable model, change your client's base URL to http://localhost:1234/v1, and the agent loop you already wrote for OpenAI runs on your own hardware — no per-token bill, no data leaving the machine. The only real decisions are which model and how you keep it resident. Here's the whole path.
1. Start the server#
Two ways. For anything scriptable — a dev container, a CI job, an agent you launch from the terminal — use the CLI:
lms server start
That brings up the local server on http://localhost:1234/v1. (In the GUI, the same switch lives under the Developer tab.) It implements the OpenAI endpoints you care about — /v1/chat/completions, /v1/completions, and /v1/models — with the same request and response shapes OpenAI uses, so your existing code doesn't know the difference. There's also an Anthropic-compatible endpoint if your stack speaks that dialect.
2. Load a tool-capable model — and keep it resident#
This is the step that decides whether your agent works, so don't skip past it. Load the model explicitly and check it's up:
lms load qwen3-coder-next # a tool-fine-tuned instruct model
lms ps # confirm it's resident + its context length
Two things matter here. First, the model must be fine-tuned for tool use. LM Studio will happily forward a tools schema to any loaded model, but only a tool-trained one returns a structured tool_calls array — a general chat model tends to ignore the schema or narrate a fake call in prose. Second, keep it resident. If you rely on just-in-time loading and the model gets evicted between turns, the next agent request eats a full cold reload. lms load up front avoids that.
3. Point your client at localhost#
No SDK swap, no new client — just the base URL and a throwaway key. In Python:
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:1234/v1",
api_key="lm-studio", # ignored on localhost, but the SDK needs a non-empty string
)
resp = client.chat.completions.create(
model="qwen3-coder-next",
messages=[{"role": "user", "content": "List the files in /tmp, then summarize."}],
tools=[{
"type": "function",
"function": {
"name": "run_shell",
"description": "Run a shell command and return stdout.",
"parameters": {
"type": "object",
"properties": {"cmd": {"type": "string"}},
"required": ["cmd"],
},
},
}],
)
msg = resp.choices[0].message
print(msg.tool_calls or msg.content)
If msg.tool_calls comes back populated with your function name and a JSON-parseable arguments string, the loop is live: execute the tool, append the result as a tool message, and call again. That's the entire agent cycle — identical to the hosted path, running locally.
The three gotchas that actually break local agents#
Every "my local agent hangs / loops / ignores its tools" report traces back to one of these:
- Wrong model. A non-tool model can't emit
tool_calls. Verify with a one-shot request (like the snippet above) before you build the loop. Model choice is not a detail here — it's the feature. - Context too short. An agent re-sends its whole growing history every turn: system prompt, prior messages, every tool result. A 4K or 8K window fills in a few steps and the model starts forgetting the task. Load the model with a longer context length and watch
lms psto confirm it took. - Eviction between turns. Idle unloading or JIT swapping means turn N+1 reloads the weights from cold. For a backend that's serving an active loop, load once and keep it pinned.
When to reach for this#
Run local when the data can't leave the box (privacy, regulated content), when you're iterating on an agent loop and want the per-token meter at zero, or when you need an offline / air-gapped path. The honest trade is throughput and top-end capability: a single machine won't match a hosted frontier model's latency under load or its hardest-problem reasoning. The pattern that gets the best of both — run the cheap, high-volume steps locally and fall back to a hosted model for the few hard ones — is the same cheap-executor / smart-advisor split that works everywhere else in the stack.
LM Studio's feature surface moves quickly — confirm endpoint behavior, CLI flags, and tool-call support against the current developer docs before you depend on any advanced feature.



