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:

  1. 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.
  2. 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 ps to confirm it took.
  3. 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.