The problem
Most people running an AI coding agent locally give it a subprocess or exec() call with full access to their files, network and credentials. Docker's own engineering blog has documented real incidents from exactly this. Indirect prompt injection makes it worse: a hostile instruction hidden in a file the agent reads can run commands with all your permissions.
The right fix is to run the agent in a VM. On Apple silicon that hits a wall: Virtualization.framework doesn't give a Linux guest the Mac's GPU, as Apple's own container team has confirmed. A Linux guest gets virtio-gpu, a paravirtualized 2D framebuffer with no path to the host's Metal GPU. Run a 7B model inside the VM and it's dramatically slower than on the Mac.
Update, September 2026: the wall is current, not necessarily permanent. The Cua project has published a software shim that changes GPU capability flags and reports benchmarked speedups inside a Linux guest, with no change from Apple. It doesn't affect what's measured below.
So on Apple silicon, "safe" (in a VM) and "fast" (on the GPU) pull in opposite directions, unless you stop treating "run the agent" and "run the model" as one problem.
The fix: split agent from model
The dangerous part (running generated code) and the expensive part (running the model) don't have to live in the same place. The model stays on the Mac, served by Ollama or MLX; the agent runs in the VM. A thin socat proxy in the guest forwards 127.0.0.1:<port> over a virtio-vsock channel, a direct hypervisor channel rather than a virtual network card, to a small Swift listener on the Mac, which passes it to whichever engine is running. Inside the VM you set OPENAI_API_BASE to http://127.0.0.1:<port>/v1, and any OpenAI-compatible tool works with no code changes.
That's the whole mechanism. The question is what it costs.
These benchmarks treat MLX and Ollama as independent engines, which they were at the time. Since Ollama 0.19 (March 2026), Ollama runs on MLX on Macs with 32 GB or more, so this exact gap may not hold on that hardware today.
Benchmark 1: MLX, single request, near zero
Mac mini (Apple M4, 10 cores, 16GB RAM, macOS 26.6.2). Guest: Ubuntu Server 26.04 LTS, 4 vCPU, 4GB RAM. Model: mlx-community/Qwen2.5-Coder-7B-Instruct-4bit via mlx_lm.server. 5-run average, host vs. through the vsock bridge:
Click to enlarge
That's within run-to-run noise. For single requests, the vsock hop costs nothing measurable.
Under load and in a real agent loop: small, real, consistent
Click to enlarge
Click to enlarge
With eight parallel clients, averaged over four runs, the VM trails by about 5% on total wall time. A real agent loop (OpenCode, five iterations per task) goes the same way: the VM trails by 1–5% on all three tasks, with a standard deviation under 0.2 s on each. A small, repeatable gap.
So: near zero for single requests, a small single-digit tax under concurrency. The boring, expected result.
Benchmark 2: Ollama, same questions, the opposite answer under load
A different session on the same Mac, with the model server swapped for Ollama (qwen2.5-coder:7b, Q4_K_M) and the same three tests. Single-request speed tells the same unremarkable story:
Mac mini (Apple M4), qwen2.5-coder:7b (Q4_K_M)
With eight concurrent clients, it flips:
8 parallel clients
The VM finished about 15% faster than the host. In a real agent loop (Open Interpreter, five runs of three tasks):
Open Interpreter, 5 runs × 3 tasks, mean ± std dev
The VM won by 17–28%, consistently, with tight variance. I didn't expect that, and I don't trust any single explanation. My working theory has three parts. On the host, the benchmark client and Ollama compete for the same cores, while in the VM the client has its own vCPUs. Requests from the VM arrive at Velo Workspaces' Swift bridge, which hands connections to Ollama through Grand Central Dispatch, rather than eight Python clients hitting Ollama's port directly. And in the agent loop, launching many short-lived processes is cheaper in a minimal Linux guest than on macOS, where new executables and scripts can be checked by the system's security services.
That's a theory that fits the data, not a proven mechanism.
Why I'm showing both rather than picking one
With only the Ollama run, I'd be telling you sandboxing is free and faster, which sounds too good to be true and deserves that skepticism. With only the MLX run, I'd be telling you there's a small, unavoidable tax under load: clean, modest and easy to believe. Neither is dishonest, but reporting only one would hide how much the inference engine's own I/O model seems to matter, possibly more than the VM boundary does. That's the real finding, and I only found it by keeping the inconvenient first result when the second didn't match.
One methodology mistake to admit: the first time I ran the Ollama numbers, I didn't fully stop all VMs and restart the model server between the host and VM runs. The agent-loop numbers came out wildly bimodal, with some iterations 10× slower than others. A leftover process competing for the scheduler was enough to produce dramatic numbers that meant nothing. Stopping and restarting everything before every run made the pattern disappear. Every number above comes from the careful re-run.
Caveats
These results are specific to this hardware (M4, 16 GB), this model (7B, 4-bit) and this workload. They won't necessarily hold for a 70B model, an 8 GB Mac or a different agent framework. If you want to check, or have a better explanation for the Ollama result than mine, the scripts and raw output are here: ai_perf_test.py, ai_load_test.py and opencode_perf_test.py, all three tests, both engines.
This is the architecture behind AI Bridge, part of Velo Workspaces, the Mac app I built for disposable Linux and macOS VMs on Apple silicon; I'm saying so plainly because it's why I ran these benchmarks. Related reading: the architecture behind AI Bridge, a setup guide for MLX or Ollama with AI agents in a VM, how the approach compares with Parallels Desktop and UTM, and why ARM's virtualization extensions keep these numbers small.