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:

Bar chart comparing host vs VM single-request speed. Time to first token: 172.21 ms on host vs 174.27 ms via AI Bridge (+1%). Throughput: 24.09 tokens/sec on host vs 23.88 tokens/sec via AI Bridge (-1%).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

Bar chart comparing host vs VM under 8-client concurrent load. Total wall time: 7.04 seconds on host vs 7.38 seconds via AI Bridge. Average per-client time: 6.24 seconds on host vs 6.39 seconds via AI Bridge.Click to enlarge
Bar chart comparing host vs VM on three OpenCode agentic tasks. Math computation: 8.40s host vs 8.82s VM. System info: 4.50s host vs 4.55s VM. File I/O: 5.58s host vs 5.70s VM.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)

Time to first token 56 ms host 59 ms VM (AI Bridge) +6%
Throughput 22.09 tok/s host 21.78 tok/s VM (AI Bridge) −1%

With eight concurrent clients, it flips:

8 parallel clients

Total wall time 15.04 s host 12.73 s VM (AI Bridge) 15% faster
Avg. per-client time 9.43 s host 8.28 s VM (AI Bridge) 12% faster

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

Math computation 6.45 s ±1.16s host 5.38 s ±1.28s VM 17% faster
System info 7.14 s ±1.12s host 5.82 s ±1.07s VM 18% faster
File I/O 9.08 s ±1.22s host 6.56 s ±1.38s VM 28% faster

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.