Part 5 of a series on the tools I actually run.

The setup, in one sentence: Qwen3.6 runs in LM Studio on my MacBook Air, LM Studio serves it over my tailnet, and OpenCode talks to it from whichever machine I happen to be sitting in front of.

That last part is the bit worth reading for. Everything before it is fairly standard local-LLM stuff. The thing that made it genuinely pleasant to use was realising the machine running the model and the machine running the agent don’t have to be the same machine.

Why bother running it locally

I’m not going to pretend a local model beats a frontier one. It doesn’t, and anyone telling you otherwise is selling something.

What it does is remove the meter. There’s no per-token cost, so I stop rationing. I’ll point it at a repo and let it read the whole thing. I’ll let it take three wrong turns before it finds the right one. I’ll leave it running against a directory of old scripts just to see what it says. None of that is something I’d do if each attempt had a price tag, and a surprising amount of useful work lives in exactly that space.

The other thing is that the code stays here. Some of what I work on isn’t mine to send anywhere.

Which Qwen3.6, and why

The 3.6 family has a dense 27B and a 35B-A3B mixture-of-experts. The MoE has 35 billion parameters total but only about 3 billion active per token.

On a 32GB Air the MoE is the obvious pick, and it’s not close. Dense 27B means every one of those 27 billion parameters gets touched for every token you generate. The MoE touches roughly a ninth of that. You’re paying 35B in memory and 3B in compute, which is a very good trade on a machine with fast unified memory and a modest GPU.

At 4-bit that lands around 20GB. Which sounds comfortable on a 32GB machine right up until you remember the KV cache.

The context window will eat your RAM

This is the part that caught me out, and it’s specific to agentic coding.

Qwen3.6 has a 256K native context. Agentic coding tools genuinely use long context, because they’re stuffing in file contents and tool output and prior turns. But the KV cache scales with how much of that window you actually fill, and it comes out of the same pool as the weights.

So: 20GB of weights, plus a KV cache that grows as the session does, on a machine that also wants to run a browser and a terminal and everything else. It gets tight. When it gets too tight macOS starts swapping and everything falls apart at once, in a way that feels like the machine has died rather than like you’ve run out of memory.

Two things help. Set a context limit in LM Studio that’s realistic rather than maximal. And macOS by default only lets the GPU wire down about 75% of system RAM, which you can raise:

sudo sysctl iogpu.wired_limit_mb=26624

That’s 26GB, leaving 6 for everything else. It doesn’t survive a reboot unless you make it permanent, and I’d rather it didn’t, because it’s the kind of setting you want to think about deliberately each time.

LM Studio in server mode

Load the model, go to the Developer tab, hit the toggle to start the server. It comes up on port 1234 and speaks the OpenAI API, which is the entire reason any of this works: every tool in this space speaks that dialect.

The setting everyone misses is “Serve on Local Network.” Leave it off and LM Studio binds to 127.0.0.1, which means it’s reachable from precisely nothing. Turn it on and it binds to all interfaces, and now your tailnet address works.

Check it from another machine:

curl http://your-mac.your-tailnet.ts.net:1234/v1/models

If that returns JSON, you’re done with the hard part.

Worth being clear-eyed about what “all interfaces” means: it’s not only your tailnet, it’s also whatever LAN you’re on, with no authentication in front of it. On my home network I don’t care. On hotel wifi I would care a great deal. Tailscale ACLs can lock down who on the tailnet can reach it, but they can’t do anything about the coffee shop, so either turn the server off when you’re out or put the Mac’s firewall in front of the port.

Tailscale is doing the boring important work

Nothing clever here, which is the point. Every machine gets a stable 100.x address and a MagicDNS name that follows it between networks. The Air is at home, or tethered, or at the office, and the name resolves the same either way. Traffic is encrypted end to end. No port forwarding, no dynamic DNS, no reverse proxy, no certificates.

So the model has an address on my network in the same way a printer does, and everything else in this post is just pointing things at that address.

Wiring up OpenCode

OpenCode takes a custom provider block. Global config lives at ~/.config/opencode/opencode.json:

{
  "$schema": "https://opencode.ai/config.json",
  "provider": {
    "lmstudio": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "LM Studio (tailnet)",
      "options": {
        "baseURL": "http://your-mac.your-tailnet.ts.net:1234/v1"
      },
      "models": {
        "qwen/qwen3.6-35b-a3b": {
          "name": "Qwen3.6 35B A3B"
        }
      }
    }
  }
}

@ai-sdk/openai-compatible is the adapter for anything speaking the OpenAI API. The model key has to match exactly what the server expects in the model field, so if it doesn’t work, curl the /v1/models endpoint and copy the id straight out of the response rather than typing what you think it’s called.

Start opencode, run /models, pick it. That’s the whole integration.

The bit I’m actually pleased about

Here’s the thing I didn’t see coming.

For a while I ran opencode on the same Mac that was serving the model. Which works. But the agent isn’t free. It’s a Node process holding a TUI, assembling context, reading files, tracking a session, and all of that wants memory and CPU on a machine that is already flat out doing inference with 20GB of weights resident.

Then it occurred to me that opencode only needs to reach the model over HTTP, and HTTP doesn’t care which machine it comes from.

So now I SSH to another box on the tailnet and run opencode there. It talks back across the tailnet to the Air. The Air does nothing but inference. The other machine does all the agent work with its own RAM and its own cores.

It’s noticeably better. Not because anything got faster in isolation, but because the two workloads stopped fighting over the same 32GB.

And it solves the file problem sideways

The other thing this gets you, almost by accident.

opencode works on files wherever it’s running. So running it over SSH on the server means it’s working on the server’s files. If the repo lives on my Linux box, I don’t sync it anywhere or mount anything. I SSH in, run opencode, and it’s right there in the working directory, with the model coming from a laptop somewhere else entirely.

Which means every machine on the tailnet becomes somewhere I can work, and none of them need to be able to run a model. The little Debian box with 8GB that couldn’t load a 20GB model in its worst nightmare is a perfectly good place to run a coding agent against one.

Combine it with Tailscale SSH and you don’t even manage keys for it.

What it’s actually like to use

Honest assessment.

Good at: reading an unfamiliar codebase and explaining it. Writing self-contained scripts. Mechanical refactors. Tests. Anything where I’d otherwise be looking up syntax. The kind of long grinding task I’d feel bad about spending API credits on.

Less good at: long multi-step agentic work where it has to keep a plan straight over many tool calls. It’s better at this than the last generation was, the 3.6 release made a real point of agentic coding, but it still loses the thread on genuinely hard problems in a way the frontier models mostly don’t.

Prompt processing is the real bottleneck, not generation. Generation is quick, because only 3B parameters are active. But when a session has built up a large context and the agent sends the lot back, there’s a pause while it chews through the prefill. That’s the thing that makes long sessions feel sluggish, and it’s a compute limit, not something you can configure your way out of.

It’s a laptop. Sustained inference on a fanless Air is a thermal question eventually. And when I close the lid, the model goes away for everyone on the tailnet, which is a strange thing to have to remember. If this became load-bearing I’d move it to something that stays put.

Would I set this up again

Yes, and it took an afternoon.

The individual pieces are all boring. LM Studio serves an OpenAI-compatible endpoint. Tailscale gives it a name. OpenCode points at the name. Nothing here is clever.

What makes it good is the decoupling. The model is a service on my network rather than a thing installed on one computer. Once you see it that way, “which machine runs the agent” becomes a question with an interesting answer instead of an assumed one, and the answer turns out to be “whichever one has the files and some spare memory.”


Next in the series: the dotfiles repo that keeps the Mac and the servers in sync.