Field report · Apple Silicon, 32 GB unified memory · October 2026

Local Coding LLMs on a 32 GB Mac

Which local model, inference backend and coding agent actually get work done on a 32 GB Mac? We ran three test series overnight: 7 models on 12 coding tasks, 9 coding agents on the same model, and finally 8 real bug fixes from popular open-source libraries.

Setup: Mac mini with Apple Silicon and 32 GB unified memory, LM Studio as the only inference server, every task in a fresh git repo, judged only by hidden tests the agent never sees. Each agent run had a 10-minute limit per task.

TL;DR

Qwen3.6-35B-A3B (4-bit) on LM Studio's Splash backend, driven by OpenCode, with a short brief and an acceptance test
  • Model: Qwen3.6-35B-A3B was the best model that runs stably on 32 GB. Dense 24–28B models and models with classic attention ran out of memory or were far too slow.
  • Backend: LM Studio's Splash runtime (speculative decoding with a built-in draft model) generated 170 tok/s instead of 68 tok/s with MLX, with the same weights and the same quality.
  • Agent: OpenCode and pi tied on targeted tasks; on real open-source code OpenCode was clearly more reliable.
  • Workflow: With only the issue text, the local model fixed about half of 8 real bugs. With a short brief plus an acceptance test written by a stronger model, it fixed all 8.
170 tok/sQwen3.6 on Splash (MLX: 68)
7.5 / 12average over 11 runs, custom tasks
10 / 12OpenCode and pi on agent-specific tasks
8 / 8real bug fixes with brief + acceptance test

1 · Which model fits on 32 GB?

Twelve tasks with hidden tests: bug hunts from a README spec, implementing from spec, refactoring, a CLI feature that has to follow existing patterns, debugging from a production stack trace, a performance rewrite, and two honesty tasks where a referenced function or config key does not exist. Half the tasks are JavaScript, half Python. Everything ran through OpenCode against LM Studio with 65k context.

Model (4-bit)tok/sScoreTime for 12Verdict
Qwen3.6-35B-A3B · Splash1706–9 / 12 (avg 7.5, 11 runs)15–25 minwinner
Qwen3.6-35B-A3B · MLX688 / 1231 minsame quality, 2.5× slower decode
Nemotron 3.5 Lightning 30B-A3B637 / 1272 mingood debugger, thinks very long, invents files
Gemma 4 26B-A4B525 / 1239 minmany requests per task, timeouts
GLM-4.7-Flash 30B-A3B540 / 4 (aborted)–memory at the limit, crawled
Qwen3-Coder 30B-A3B67out of memory–GPU OOM even at 32k context
Devstral Small 2 24B (dense)–not run–KV cache would need ~25 GB at 65k
A 28B dense GGUF model90 / 2 (aborted)–too slow for agent work

2 · Tuning prompts, sampling and max tokens

Variant (Qwen3.6 Splash, OpenCode)RunsScoreHonesty taskVerdict
Default (max_tokens 16k, server default sampling)8, 77.50 / 2baseline
Short working-rules prompt9, 6, 87.73 / 3use it
max_tokens 32k9, 780 / 2neutral
max_tokens 4k770 / 1too small
Official sampling (T 1.0, top_p 0.95, top_k 20)770 / 1neutral
Long checklist prompt770 / 1neutral

3 · Same model, nine coding agents

Eight tasks built to expose differences between agents: a one-function edit in a 2,100-line file, preserving tabs and CRLF line endings, renaming a feature flag across 314 files (including JSON, YAML and a concatenated string), npm test hanging in watch mode, project rules that exist only in AGENTS.md, a registry built from 40 plugin files, a test loop against 26 visible tests, and cleaning a git-ignored build/ folder without touching data/ or .env. Plus four general tasks.

AgentScoreTimeMain weakness
OpenCode10 / 1220 minabout 60% slower than pi on these tasks
pi (engine of JetBrains ThinkRail)10 / 1212 minno permission system of its own; see real bug fixes below
Qwen Code8 / 1218 minfile tools hide git-ignored files; ignored AGENTS.md
Claude Code (pointed at the local model)8 / 1215 minreads CLAUDE.md, not AGENTS.md
Codex CLI8 / 1216 minmissed the concatenated string; edited outside the target function
Crush4 / 5*3 minfollowed only 2 of 3 AGENTS.md rules
mini-swe-agent3 / 5*4 minwandered into another directory without a fixed cwd; no project rules
Goose2 / 5*2 minimprecise edits, ignored AGENTS.md and git-ignored files
Aider1 / 6*9 minheadless it cannot run shell commands, so it never tests

* quick run on the five most discriminating tasks. Every agent ran once with an isolated, empty config directory. Claude Code works locally because LM Studio also serves an Anthropic-compatible /v1/messages endpoint; Codex used LM Studio's Responses endpoint.

Reads AGENTS.md

OpenCode, pi, Codex (and Crush partly). Claude Code and Qwen Code need their own CLAUDE.md / QWEN.md.

Hides git-ignored files

Qwen Code and Goose. Careful with tasks around build folders, logs or .env.

Headless pitfall

OpenCode auto-rejects the permission prompt for paths outside the project and ends the whole run. Setting "permission": {"external_directory": "deny"} returns an error to the model instead, and the run continues.

4 · Real bug fixes from open-source history

SWE-bench style: eight real, merged fixes and small features from yargs-parser, qs, humanize, python-tabulate and python-dotenv. The repo is reset to the commit before the fix, the task is a paraphrase of the real issue, and grading uses the tests the maintainers added with the fix. Five of the eight fixes are from 2026, so the model cannot know them from training. All agent runs and all foreign test code ran inside a macOS sandbox (writes only inside the test folder, network only to the local model server).

How the local model was usedR1R2R3R4R5R6R7R8Score
Issue text only · OpenCode**✗✓✗✗✓✓✓✗≈ 4 / 8
Issue text only · pi✗✗✗✗––––0 / 4
Brief + acceptance test · pi✗✗✓✓✓✓✓✓6 / 8 · 25 min
Brief + acceptance test · OpenCode✓✓✓✓✓✓✓✓8 / 8 · 34 min

** In this run OpenCode's search tool was broken by the sandbox (it tried to download ripgrep at first start), so this is a lower bound. The brief + test runs were repeated after the fix. R1 negative decimals in yargs-parser · R2 unpaired UTF-16 surrogates in qs (cause in a different file) · R3 new WHATWG output format in qs · R4 time-zone handling in humanize · R5 infinite loop with wide CJK characters in tabulate · R6 CRLF handling in python-dotenv · R7 opt-out env variable in python-dotenv · R8 alias plus dot-notation in yargs-parser.

The workflow that works

  1. A stronger model (or you) reads only the relevant code and writes a 3–5 sentence brief: file, function, root cause, approach.
  2. It writes an acceptance test from the examples and edge cases in the issue and checks that it fails on the current code.
  3. The local agent works in an isolated git worktree until the test passes. If the test is still red, it gets the test output and one more attempt.
  4. Only the test result and the diff are reviewed before merging.

In our runs this cost the stronger model roughly 3,000 tokens per task, while all exploration, editing and test runs (15 to 90 model requests per task) stayed on the local machine. That is an estimate, not a precise measurement.

Example brief (qs, unpaired surrogates):
Cause in lib/utils.js, function encode, surrogate branch at the end of the loop:
it assumes c is a high surrogate and the next char a low surrogate, but never
checks. Check both; if it is not a valid pair, emit %EF%BF%BD and do not
consume the next character.

Setup that worked

(version 1)(allow default)(deny file-write*)
(allow file-write* (subpath "/path/to/project") (subpath "/private/tmp")
                   (subpath "/private/var/folders") (subpath "/dev"))
(deny network*)(allow network* (local unix))
(allow network* (remote ip "localhost:*"))

Limits