<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Michael</title>
    <description>The latest articles on DEV Community by Michael (@wango).</description>
    <link>https://dev.to/wango</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4112487%2F29545d71-5136-4bfc-9104-a91787d62978.jpg</url>
      <title>DEV Community: Michael</title>
      <link>https://dev.to/wango</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wango"/>
    <language>en</language>
    <item>
      <title>How to Run Local LLMs on Apple Silicon</title>
      <dc:creator>Michael</dc:creator>
      <pubDate>Wed, 23 Sep 2026 07:18:45 +0000</pubDate>
      <link>https://dev.to/wango/how-to-run-local-llms-on-apple-silicon-3m9e</link>
      <guid>https://dev.to/wango/how-to-run-local-llms-on-apple-silicon-3m9e</guid>
      <description>&lt;p&gt;This is about the model, on its own — what tool to install, what format to pull, and how to actually talk to it. Not wrapped in a VM, not wired up to a coding agent. If you want that part too, &lt;a href="https://www.veloworkspaces.com/blog/zero-tax-virtualization/" rel="noopener noreferrer"&gt;the complete AI agent guide&lt;/a&gt; covers it in depth; this page is the more basic thing underneath it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four names, four different things
&lt;/h2&gt;

&lt;p&gt;These get used almost interchangeably in casual conversation, and they're not the same kind of tool:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;llama.cpp&lt;/strong&gt; is the inference engine underneath most of this ecosystem — a C++ implementation that runs GGUF-format models across NVIDIA, AMD, Intel, and plain CPU, not Apple-specific at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MLX&lt;/strong&gt; is Apple's own array framework and inference library, built specifically for Apple Silicon's unified memory architecture, running its own MLX-format models.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ollama&lt;/strong&gt; is a friendly CLI and local API server wrapped around an inference backend — historically llama.cpp underneath, though that changed recently and specifically for Apple Silicon (more below).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LM Studio&lt;/strong&gt; is a desktop GUI app that runs both GGUF and MLX models side by side in the same interface, and is the easiest on-ramp if you don't yet know which format or engine you want.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A real, recent change worth knowing about
&lt;/h2&gt;

&lt;p&gt;As of Ollama 0.19 (March 2026), Ollama replaced its Metal-backed llama.cpp inference path with Apple's own MLX framework on Macs with 32GB or more of unified memory — a substantial speed improvement on supported hardware. 8GB and 16GB Macs still run the older Metal/llama.cpp path unchanged. If you're on a 32GB+ Mac, "MLX vs. Ollama" is a less clean-cut choice than it used to be: for many models, Ollama is now MLX under the hood, not a genuinely separate engine. Worth checking your installed Ollama version and hardware tier before assuming older comparisons still describe your setup exactly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Model formats: GGUF vs. MLX, briefly
&lt;/h2&gt;

&lt;p&gt;GGUF is llama.cpp's own model container format — broad, well-supported, with the longest tail of available conversions (older models, fine-tunes, merges) because the ecosystem around it is the largest. MLX-format models are built specifically for Apple's framework, and on the same chip an MLX build is commonly meaningfully faster than the equivalent GGUF for supported models. Neither format is strictly better — GGUF wins on breadth and portability, MLX wins on Apple Silicon-specific speed for models that have an MLX conversion available. Quantization — the 4-bit, 8-bit tradeoffs that actually determine how much RAM a given model needs — is its own topic: see &lt;a href="https://dev.to/wango/llm-quantization-explained-for-mac-users-3cm0"&gt;LLM quantization explained for Mac users&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where models actually come from
&lt;/h2&gt;

&lt;p&gt;MLX-format models are published on &lt;a href="https://huggingface.co/models?library=mlx-lm" rel="noopener noreferrer"&gt;Hugging Face&lt;/a&gt;, generally under the mlx-community organization; Ollama pulls from its own &lt;a href="https://ollama.com/library" rel="noopener noreferrer"&gt;library&lt;/a&gt;. Both move fast — new releases and their MLX/GGUF conversions can lag each other by days to weeks, so treat any specific model tag you read (including on this blog) as a snapshot rather than a permanent recommendation, and check the source directly before pulling.&lt;/p&gt;

&lt;h2&gt;
  
  
  How much RAM you actually need
&lt;/h2&gt;

&lt;p&gt;This blog already has a detailed RAM-to-model-size table, broken down from 16GB through 128GB+ Macs with specific MLX and Ollama tags per tier — see &lt;a href="https://www.veloworkspaces.com/blog/zero-tax-virtualization/" rel="noopener noreferrer"&gt;Section 3 of the complete AI agent guide &lt;/a&gt;rather than a second copy of the same table here. The short version: reserve roughly 20–25% of total unified memory for the OS and display compositor before budgeting the rest for model weights and KV cache, since both the model and macOS itself draw from the same pool. For the actual math behind those numbers — and how to work out a model that isn't already tabulated — see &lt;a href="https://dev.to/wango/how-much-ram-do-you-need-for-local-llms-on-a-mac-5b0m"&gt;how much RAM local LLMs actually need&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using a model with nothing else involved
&lt;/h2&gt;

&lt;p&gt;None of this requires a VM or an agent. LM Studio gives you a chat window the moment a model finishes downloading. Ollama's own &lt;code&gt;ollama run &amp;lt;model&amp;gt;&lt;/code&gt; drops you into a REPL directly in Terminal. Either one also exposes an OpenAI-compatible local API — &lt;code&gt;http://127.0.0.1:11434/v1&lt;/code&gt; for Ollama, whatever port you configure for MLX or LM Studio's server mode — that any script, editor plugin, or app that speaks the OpenAI API format can point at, with nothing more exotic than an environment variable pointing somewhere on your own machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you'll actually want isolation
&lt;/h2&gt;

&lt;p&gt;The model itself is inert — it's the code an agent generates and executes against that model that creates risk, and that's a different problem from "how do I run a model at all," worth its own guide rather than a section here. If what you're actually building is an AI coding agent, not just a chat window, see &lt;a href="https://www.veloworkspaces.com/blog/zero-tax-virtualization/" rel="noopener noreferrer"&gt;the complete AI agent guide&lt;/a&gt; for the full VM-isolated setup, &lt;a href="https://www.veloworkspaces.com/blog/ai-bridge-architecture/" rel="noopener noreferrer"&gt;the AI Bridge architecture&lt;/a&gt; for how the model stays on the host GPU while the agent runs isolated, and &lt;a href="https://www.veloworkspaces.com/blog/mlx-vm-setup-guide/" rel="noopener noreferrer"&gt;the MLX + OpenCode setup guide&lt;/a&gt; for one concrete, complete walkthrough.&lt;/p&gt;

&lt;p&gt;Related reading: &lt;a href="https://www.veloworkspaces.com/blog/zero-tax-virtualization/" rel="noopener noreferrer"&gt;running AI agents safely in an isolated VM&lt;/a&gt;, &lt;a href="https://www.veloworkspaces.com/blog/ai-bridge-architecture/" rel="noopener noreferrer"&gt;the AI Bridge architecture&lt;/a&gt;, and &lt;a href="https://www.veloworkspaces.com/blog/vm-sandboxing-cost/" rel="noopener noreferrer"&gt;what isolation actually costs&lt;/a&gt;, benchmarked across MLX and Ollama. Or &lt;a href="https://apps.apple.com/app/apple-store/id6805509975?pt=129339260&amp;amp;ct=homepage&amp;amp;mt=8" rel="noopener noreferrer"&gt;download Velo Workspaces&lt;/a&gt; and try it yourself.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>apple</category>
      <category>localai</category>
    </item>
    <item>
      <title>LLM Quantization Explained for Mac Users</title>
      <dc:creator>Michael</dc:creator>
      <pubDate>Wed, 23 Sep 2026 07:16:01 +0000</pubDate>
      <link>https://dev.to/wango/llm-quantization-explained-for-mac-users-3cm0</link>
      <guid>https://dev.to/wango/llm-quantization-explained-for-mac-users-3cm0</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/wango/how-much-ram-do-you-need-for-local-llms-on-a-mac-5b0m"&gt;The RAM math post&lt;/a&gt; treats quantization as an input — "4-bit is roughly 0.5 bytes per parameter" — and moves on, on purpose. This is what's actually behind that number: what quantization does to a model's weights, why "4-bit" isn't one single thing once you look at real GGUF filenames, and how to pick a level instead of copying whatever a guide happened to recommend.&lt;/p&gt;

&lt;h2&gt;
  
  
  What quantization actually is
&lt;/h2&gt;

&lt;p&gt;A model's weights start out as 16-bit (or occasionally 32-bit) floating-point numbers — that's the precision they were trained and typically distributed in. Quantization replaces those with lower-precision representations: fewer bits per weight, grouped into blocks that share a small amount of extra data (a scale factor, sometimes a minimum value) so the reduced-precision numbers can still be mapped back to something close to the original range. Fewer bits per weight means less memory and less data to move through the chip per token generated — which is why quantized models are both smaller and faster, not just smaller.&lt;/p&gt;

&lt;p&gt;The cost is precision, not correctness in any simple sense: a quantized model doesn't become "wrong" the way a training bug would make it wrong. It becomes a slightly blurrier version of the same model — individual outputs can differ from the full-precision original, more so at lower bit depths, but there's no bit width at which the model suddenly starts producing garbage across the board. The practical question is always "how much quality am I willing to trade for how much size and speed," not "is this broken."&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "4-bit" isn't one thing
&lt;/h2&gt;

&lt;p&gt;Open an actual GGUF download list and the names are more specific than "4-bit" or "8-bit" — &lt;code&gt;Q4_K_M&lt;/code&gt;, &lt;code&gt;Q5_K_M&lt;/code&gt;, &lt;code&gt;Q4_0&lt;/code&gt;, &lt;code&gt;Q8_0&lt;/code&gt;, and several more. The number is the average bits per weight; the letters describe the quantization scheme, and they're not interchangeable at the same bit count. The older, plain &lt;code&gt;Q4_0&lt;/code&gt;/&lt;code&gt;Q4_1&lt;/code&gt; schemes quantize uniformly, block by block, with no per-layer nuance. The newer k-quants (&lt;code&gt;Q4_K_M&lt;/code&gt;, &lt;code&gt;Q5_K_M&lt;/code&gt;, and similar) are more deliberate about it — allocating more precision to weights that empirically matter more to output quality and less to weights that don't, rather than treating every block identically. At the same nominal bit width, a k-quant variant is close to universally the better choice over the legacy scheme it replaced; llama.cpp still ships both mainly for compatibility with older tooling.&lt;/p&gt;

&lt;p&gt;The trailing letter on a k-quant (&lt;code&gt;_S&lt;/code&gt;, &lt;code&gt;_M&lt;/code&gt;, &lt;code&gt;_L&lt;/code&gt;) is a small additional lever within that same bit width — small, medium, large — trading a bit more size for a bit more fidelity without changing the headline bit count. When two model cards both say "Q4," check the full tag before assuming they mean the same tradeoff.&lt;/p&gt;

&lt;h2&gt;
  
  
  Picking a level, not defaulting to one
&lt;/h2&gt;

&lt;p&gt;A workable default rather than a rule: &lt;code&gt;Q4_K_M&lt;/code&gt; is the level most of the ecosystem converges on as the practical sweet spot — a meaningful size and speed win over 8-bit, with a quality gap that's genuinely hard to notice in normal use for most tasks. Move up from there deliberately, not reflexively:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Q5_K_M&lt;/strong&gt; or &lt;strong&gt;Q6_K&lt;/strong&gt; when the task is precision-sensitive — code generation and anything involving exact syntax or careful multi-step reasoning tends to show quantization loss sooner than open-ended writing does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Q8_0&lt;/strong&gt;, close to full precision, when you have the memory headroom to spare and want the smallest possible gap from the unquantized model, accepting the larger download and footprint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Q3_K&lt;/strong&gt; or lower only when memory is the hard constraint and running a smaller parameter count at a higher bit level isn't an option — a heavily quantized larger model is not reliably better than a lightly quantized smaller one at the same memory budget, and often worse. Test both rather than assuming bigger-but-blurrier wins.
MLX-format models use their own bit-depth conventions rather than the GGUF/k-quant naming above, but the underlying tradeoff — bits per weight traded against quality — is the same idea wearing a different label. &lt;a href="https://www.veloworkspaces.com/blog/local-llms-apple-silicon/" rel="noopener noreferrer"&gt;The tools and formats post&lt;/a&gt; covers where each format actually comes from.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What this changes about the RAM math
&lt;/h2&gt;

&lt;p&gt;Going up one quantization tier doesn't just cost more memory for weights — it changes the whole budget covered in &lt;a href="https://www.veloworkspaces.com/blog/llm-ram-requirements-mac/" rel="noopener noreferrer"&gt;the RAM requirements post&lt;/a&gt;: weights scale directly with bits per parameter, and KV cache size is independent of quantization level entirely, computed from the model's architecture rather than its weight precision. A model that just barely fits at Q4_K_M with room for a long agentic session may not have any KV cache headroom left at all at Q8_0, even though the weights themselves are still well within memory. Check both numbers before moving up a tier, not just whether the weights alone fit.&lt;/p&gt;

&lt;p&gt;Related reading: &lt;a href="https://www.veloworkspaces.com/blog/llm-ram-requirements-mac/" rel="noopener noreferrer"&gt;how much RAM local LLMs actually need&lt;/a&gt;, the math this post's bit-width numbers feed into, and &lt;a href="https://www.veloworkspaces.com/blog/local-llms-apple-silicon/" rel="noopener noreferrer"&gt;the local LLM tools and formats landscape&lt;/a&gt; for where to actually find quantized models. Or &lt;a href="https://apps.apple.com/app/apple-store/id6805509975?pt=129339260&amp;amp;ct=homepage&amp;amp;mt=8" rel="noopener noreferrer"&gt;download Velo Workspaces&lt;/a&gt; and try it yourself.&lt;/p&gt;

</description>
      <category>localai</category>
      <category>llm</category>
      <category>ai</category>
      <category>macos</category>
    </item>
    <item>
      <title>How Much RAM Do You Need for Local LLMs on a Mac?</title>
      <dc:creator>Michael</dc:creator>
      <pubDate>Wed, 23 Sep 2026 07:15:00 +0000</pubDate>
      <link>https://dev.to/wango/how-much-ram-do-you-need-for-local-llms-on-a-mac-5b0m</link>
      <guid>https://dev.to/wango/how-much-ram-do-you-need-for-local-llms-on-a-mac-5b0m</guid>
      <description>&lt;p&gt;This blog already has &lt;a href="https://www.veloworkspaces.com/blog/zero-tax-virtualization/" rel="noopener noreferrer"&gt;a tier-by-tier&lt;/a&gt; table for the specific models it recommends. This is the math behind it — so you can work out the answer for any model, not just the ones already tabulated, and understand why a model that fit fine when you started a session can run out of room an hour later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two numbers, not one
&lt;/h2&gt;

&lt;p&gt;A model's memory footprint isn't a single static figure — it's two genuinely different things added together: the &lt;strong&gt;weights&lt;/strong&gt;, which are fixed the moment you pick a model and a quantization level, and the &lt;strong&gt;KV cache&lt;/strong&gt;, which grows as a conversation gets longer. Sizing for weights alone is the mistake that makes a model work fine in a quick test and then run out of memory in a real session.&lt;/p&gt;

&lt;h2&gt;
  
  
  Weights: the simple part
&lt;/h2&gt;

&lt;p&gt;Weight memory is roughly parameter count times bytes-per-parameter. A model's name tells you the first number — "7B" means 7 billion parameters — and quantization tells you the second: 4-bit quantization is roughly 0.5 bytes per parameter, so a 7B model at 4-bit lands around 3.5GB just for weights. Double the bit-width and you roughly double that figure. (For what's actually behind that bytes-per-parameter number — the different quantization schemes, and why they're not interchangeable at the same bit width — see &lt;a href="https://www.veloworkspaces.com/blog/llm-quantization-explained-mac/" rel="noopener noreferrer"&gt;LLM quantization explained for Mac users&lt;/a&gt;.) This is the part every model card and download page already tells you, directly or indirectly, and it's the part most "how much RAM" answers stop at — which is exactly the gap that causes surprises later.&lt;/p&gt;

&lt;h2&gt;
  
  
  KV cache: the part that grows on you
&lt;/h2&gt;

&lt;p&gt;The key-value cache stores intermediate attention state for every token in the current context, and unlike weights, it scales with how long the conversation gets. The actual formula: &lt;code&gt;2 × layers × KV-heads × head-dimension × tokens × bytes-per-element&lt;/code&gt; — each of those numbers is in the model's own config, and most inference tools will report the resulting figure directly rather than making you compute it by hand. The practical upshot is what matters here: for a short exchange, weights dominate and KV cache is nearly irrelevant. For a long agentic session with a large context window — exactly the kind of multi-turn, tool-calling conversation an AI coding agent produces — KV cache can grow to rival or exceed the weights themselves. That's the mechanism behind "it worked when I started, then got slow or crashed later": nothing changed about the model, the context just kept growing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The other consumer: macOS itself
&lt;/h2&gt;

&lt;p&gt;Unified memory means the model isn't the only thing drawing from the pool — the OS and display compositor need their share too, which is why &lt;a href="https://www.veloworkspaces.com/blog/zero-tax-virtualization/" rel="noopener noreferrer"&gt;the existing model-sizing table&lt;/a&gt; reserves roughly 20–25% of total RAM for that overhead before budgeting the rest. That reserve isn't a made-up safety margin; it's covering real, measured system usage that doesn't show up in a naive "weights + KV cache" calculation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it together
&lt;/h2&gt;

&lt;p&gt;A workable estimate for any model, not just the ones already tabulated on this blog: take the parameter count times bytes-per-parameter for weights, add a KV cache estimate scaled to how long a session you actually expect (short chat: small; long agentic session: meaningfully more), then make sure the total leaves that 20–25% headroom for macOS. If a model's weights alone already eat most of your unified memory, there's no room left for KV cache to grow into — that's the model to size down, not the one to fight with swap.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it looks like when you get it wrong
&lt;/h2&gt;

&lt;p&gt;Memory pressure on a Mac doesn't usually announce itself as a hard crash — it shows up as swapping, and an inference engine that was fast a minute ago suddenly isn't, with no error message pointing at the actual cause. If a model that felt snappy at the start of a session gets progressively slower rather than staying consistent, check context length and KV cache growth before assuming something's wrong with the model or the engine itself.&lt;/p&gt;

&lt;p&gt;Related reading: &lt;a href="https://www.veloworkspaces.com/blog/zero-tax-virtualization/" rel="noopener noreferrer"&gt;the tier-by-tier model table this post explains the math behind&lt;/a&gt;, &lt;a href="https://www.veloworkspaces.com/blog/local-llms-apple-silicon/" rel="noopener noreferrer"&gt;the broader landscape of local LLM tools&lt;/a&gt;, and &lt;a href="https://www.veloworkspaces.com/blog/how-much-memory-linux-vm-mac/" rel="noopener noreferrer"&gt;how much memory to give a Linux VM&lt;/a&gt; for non-LLM workloads, including the memory-balloon device that handles the VM side of memory allocation. Or &lt;a href="https://apps.apple.com/app/apple-store/id6805509975?pt=129339260&amp;amp;ct=homepage&amp;amp;mt=8" rel="noopener noreferrer"&gt;download Velo Workspaces&lt;/a&gt; and try it yourself.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>localllm</category>
      <category>ai</category>
      <category>apple</category>
    </item>
    <item>
      <title>Zero-Tax Virtualization: Running AI Agents Safely in Velo Workspaces</title>
      <dc:creator>Michael</dc:creator>
      <pubDate>Wed, 16 Sep 2026 17:24:00 +0000</pubDate>
      <link>https://dev.to/wango/zero-tax-virtualization-running-ai-agents-safely-in-velo-workspaces-4mfa</link>
      <guid>https://dev.to/wango/zero-tax-virtualization-running-ai-agents-safely-in-velo-workspaces-4mfa</guid>
      <description>&lt;h2&gt;
  
  
  1. Introduction: The Security vs. Performance Dilemma
&lt;/h2&gt;

&lt;p&gt;Running autonomous AI coding agents natively on your primary macOS machine introduces severe security liabilities. Modern agents execute terminal commands, install unverified dependencies via &lt;code&gt;pip&lt;/code&gt; and &lt;code&gt;npm&lt;/code&gt;, modify arbitrary files, and can be coerced through prompt injection attacks into exfiltrating environment variables, dotfiles, or macOS Keychain secrets.&lt;/p&gt;

&lt;p&gt;The standard industry remedy is sandboxing agents inside a Linux virtual machine or container. On Apple Silicon, however, this immediately collides with a hypervisor-level barrier:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No Compute Passthrough:&lt;/strong&gt; Apple's native &lt;code&gt;Virtualization.framework&lt;/code&gt; does not expose the host Metal GPU to Linux guests. Linux VMs receive only a 2D paravirtualized framebuffer (&lt;code&gt;virtio-gpu&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The "Virtualization Tax":&lt;/strong&gt; Attempting to run an LLM directly inside a Linux VM forces model inference onto virtualized CPU cores. Generation speeds crater by 80%+, making multi-turn agentic loops unusable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Containers Share the Same Limitation:&lt;/strong&gt; Docker Desktop, OrbStack, and Colima run atop Linux guest kernels and face the exact same GPU compute void.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Decoupled Solution: Velo Workspaces AI Bridge
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.veloworkspaces.com" rel="noopener noreferrer"&gt;Velo Workspaces&lt;/a&gt; resolves this trade-off by separating the &lt;strong&gt;agent execution environment&lt;/strong&gt; from the &lt;strong&gt;model inference engine&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Model on the Host:&lt;/strong&gt; An inference server — &lt;strong&gt;Ollama&lt;/strong&gt; or &lt;strong&gt;Apple MLX&lt;/strong&gt; — runs natively on macOS, retaining full access to unified memory bandwidth and Metal GPU acceleration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent in the VM:&lt;/strong&gt; The agent framework executes inside an isolated Ubuntu Linux guest, restricting all file modifications and terminal execution to a sandboxed filesystem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VirtIO-vsock Transport:&lt;/strong&gt; Traffic travels across hypervisor memory buffers via vsock rather than a traditional virtualized NAT network stack, reducing bridge overhead to single-digit milliseconds.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This guide covers &lt;strong&gt;both&lt;/strong&gt; engines side by side. Pick one in Section 5 — everything downstream (VM setup, the vsock bridge, agent configuration) works identically either way, substituting the port your chosen engine listens on.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;MLX&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Ollama&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Default port&lt;/td&gt;
&lt;td&gt;&lt;code&gt;8080&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;11434&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model source&lt;/td&gt;
&lt;td&gt;&lt;a href="https://huggingface.co/models?library=mlx-lm" rel="noopener noreferrer"&gt;huggingface.co/models?library=mlx-lm&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://ollama.com/library" rel="noopener noreferrer"&gt;ollama.com/library&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best for&lt;/td&gt;
&lt;td&gt;Apple Silicon-native performance, the widest current selection of day-one MLX-quantized releases&lt;/td&gt;
&lt;td&gt;The simplest one-command setup and model management (&lt;code&gt;ollama pull&lt;/code&gt;, &lt;code&gt;ollama run&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  2. Architecture Topology
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgn3sqd1kcc1vr46hcbh4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgn3sqd1kcc1vr46hcbh4.png" alt="Architecture topology diagram: an external LAN client reaches the macOS host through a Caddy port forward; on the host, the inference server (Ollama or MLX) and the Velo AI Bridge Swift agent talk over host loopback TCP; the AI Bridge reaches the Linux guest VM over a VirtIO-vsock channel, where an AI forwarding proxy (socat) relays to the agent runtime, with an optional agent web UI reachable directly or via the LAN port forward." width="800" height="978"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Note: Inside the VM, the agent's traffic is forwarded over a VirtIO-vsock channel to the AI Bridge on the macOS host, which relays it to the host's inference server (Ollama or MLX). A separate Caddy port forward lets an external LAN client reach the VM's optional web UI.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Network Flow Breakdown
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Inference Path:&lt;/strong&gt; The agent sends standard OpenAI-compatible HTTP requests to &lt;code&gt;127.0.0.1:&amp;lt;PORT&amp;gt;&lt;/code&gt; inside the VM — &lt;code&gt;8080&lt;/code&gt; for MLX, &lt;code&gt;11434&lt;/code&gt; for Ollama. The local &lt;code&gt;socat&lt;/code&gt; proxy routes this payload across vsock to the host. The Swift AI Bridge receives the connection and relays it to whichever engine you selected as the workspace's Host Provider.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Host UI Access:&lt;/strong&gt; The host browser connects directly to the guest's virtual interface via &lt;code&gt;http://&amp;lt;VM_IP&amp;gt;:4096&lt;/code&gt; over the standard hypervisor bridge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;External LAN Access:&lt;/strong&gt; Because external machines cannot route directly to the VM's private virtual subnet, the host forwards an external port (&lt;code&gt;8081&lt;/code&gt;) to the guest's Web UI port (&lt;code&gt;4096&lt;/code&gt;). Port &lt;code&gt;8081&lt;/code&gt; is used to avoid colliding with whichever inference port (&lt;code&gt;8080&lt;/code&gt; or &lt;code&gt;11434&lt;/code&gt;) the host is already using.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  3. Choose Your Model (Purpose and Host RAM Requirements)
&lt;/h2&gt;

&lt;p&gt;Both engines use macOS Unified Memory dynamically. Because the OS, display compositor, and the model's KV cache share this pool, reserve at least 20–25% of total host RAM for operating overhead.&lt;/p&gt;

&lt;p&gt;Model identifiers below are verified as of this writing — always confirm current availability and exact tags at &lt;a href="https://huggingface.co/models?library=mlx-lm" rel="noopener noreferrer"&gt;huggingface.co/models?library=mlx-lm&lt;/a&gt; (MLX) or &lt;a href="https://ollama.com/library" rel="noopener noreferrer"&gt;ollama.com/library&lt;/a&gt; (Ollama) before pulling, since libraries change.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mac Unified RAM&lt;/th&gt;
&lt;th&gt;MLX (Hugging Face)&lt;/th&gt;
&lt;th&gt;Ollama (&lt;code&gt;ollama pull ...&lt;/code&gt;)&lt;/th&gt;
&lt;th&gt;Quantization&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;16 GB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mlx-community/Qwen2.5-Coder-7B-Instruct-4bit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;qwen2.5-coder:7b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;4-bit&lt;/td&gt;
&lt;td&gt;Fast code completion, lightweight script generation, single-file edits.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;24 GB / 32 GB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;mlx-community/Qwen2.5-Coder-32B-Instruct-4bit&lt;/code&gt;&lt;br&gt;&lt;code&gt;mlx-community/Mistral-Small-24B-Instruct-4bit&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;qwen2.5-coder:32b&lt;/code&gt;&lt;br&gt;&lt;code&gt;mistral-small&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;4-bit&lt;/td&gt;
&lt;td&gt;Multi-file reasoning, refactoring, and debugging complex logic.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;36 GB / 48 GB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;mlx-community/Qwen3.8-27B-4bit&lt;/code&gt;*&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;qwen3.8:27b&lt;/code&gt;*&lt;/td&gt;
&lt;td&gt;4-bit&lt;/td&gt;
&lt;td&gt;Advanced agentic tasks, architectural design, repository-wide indexing.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;64 GB / 96 GB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mlx-community/Llama-3.3-70B-Instruct-4bit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;llama3.3:70b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;4-bit&lt;/td&gt;
&lt;td&gt;Deep reasoning, zero-shot full repository synthesis, complex planning.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;128 GB+&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;mlx-community/Qwen3.8-2.4T-A95B-*bit&lt;/code&gt;&lt;em&gt;&lt;br&gt;&lt;code&gt;deepseek-ai/DeepSeek-V3&lt;/code&gt;&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;deepseek-v3&lt;/code&gt;*&lt;/td&gt;
&lt;td&gt;1-bit to 4-bit&lt;/td&gt;
&lt;td&gt;Full-scale autonomous pipelines, heavy concurrent agent swarms.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;* These are very recent (2026) or very large releases — check the library link for the exact current tag/quantization before pulling; MLX and Ollama conversions can lag a new release by days to weeks.&lt;/p&gt;


&lt;h2&gt;
  
  
  4. VM Sizing Guidelines
&lt;/h2&gt;

&lt;p&gt;Because the heavy LLM weights and KV caches stay in macOS unified memory, the Linux VM only needs enough resources to execute the code generated by the agent.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important (Session Longevity):&lt;/strong&gt; Long-running autonomous sessions gradually leak resources. Agents continuously generate temporary files, compile dependencies, bloat &lt;code&gt;pip&lt;/code&gt;/&lt;code&gt;npm&lt;/code&gt; caches, and retain execution logs. Allocate extra memory buffers for long-lived environments to keep the Linux Out-Of-Memory (OOM) killer from terminating tasks.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;vCPUs&lt;/th&gt;
&lt;th&gt;Memory&lt;/th&gt;
&lt;th&gt;Storage&lt;/th&gt;
&lt;th&gt;Primary Workloads &amp;amp; Rationale&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ephemeral / Light Automation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2 vCPUs&lt;/td&gt;
&lt;td&gt;2 GB – 3 GB&lt;/td&gt;
&lt;td&gt;15 GB – 20 GB&lt;/td&gt;
&lt;td&gt;CLI automation and simple scripts. Ideal for short-lived, disposable tasks.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Full-Stack Web Development&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4 vCPUs&lt;/td&gt;
&lt;td&gt;4 GB – 6 GB&lt;/td&gt;
&lt;td&gt;30 GB – 40 GB&lt;/td&gt;
&lt;td&gt;Node.js, Django, SQLite, Vite. Accommodates build tools and background servers.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Long-Running Agent Sessions&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4 – 6 vCPUs&lt;/td&gt;
&lt;td&gt;8 GB – 12 GB&lt;/td&gt;
&lt;td&gt;50 GB – 60 GB&lt;/td&gt;
&lt;td&gt;Continuous autonomous loops. Buffers memory against cached artifacts and logs.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;System Programming &amp;amp; Docker&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;6 – 8 vCPUs&lt;/td&gt;
&lt;td&gt;12 GB – 16 GB&lt;/td&gt;
&lt;td&gt;60 GB – 80 GB&lt;/td&gt;
&lt;td&gt;Rust/Go/C++ compilation, Docker-in-VM services. Prevents compilation lockups.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h2&gt;
  
  
  5. Install the Inference Engine on the macOS Host
&lt;/h2&gt;

&lt;p&gt;Pick &lt;strong&gt;one&lt;/strong&gt; engine. All steps in this section run in the &lt;strong&gt;macOS Terminal&lt;/strong&gt; on the host.&lt;/p&gt;
&lt;h3&gt;
  
  
  5.1 Option A — MLX
&lt;/h3&gt;

&lt;p&gt;Install the official Apple MLX language model package using Python (3.10+):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create and activate an isolated virtual environment&lt;/span&gt;
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv ~/.mlx-env
&lt;span class="nb"&gt;source&lt;/span&gt; ~/.mlx-env/bin/activate

&lt;span class="c"&gt;# Install the MLX LM server package&lt;/span&gt;
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--upgrade&lt;/span&gt; mlx-lm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Launch the server bound to loopback (&lt;code&gt;127.0.0.1&lt;/code&gt;) on port &lt;code&gt;8080&lt;/code&gt;. It downloads the model from Hugging Face automatically on first run. &lt;code&gt;mlx-community/Qwen2.5-Coder-7B-Instruct-4bit&lt;/code&gt; below is just an example — swap in whichever tag you picked for your RAM tier in &lt;strong&gt;Section 3&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mlx_lm.server &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--model&lt;/span&gt; mlx-community/Qwen2.5-Coder-7B-Instruct-4bit &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--host&lt;/span&gt; 127.0.0.1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--port&lt;/span&gt; 8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a second macOS terminal, verify it's responding to OpenAI-compatible requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://127.0.0.1:8080/v1/models | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"id"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5.2 Option B — Ollama
&lt;/h3&gt;

&lt;p&gt;Install Ollama from &lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;ollama.com&lt;/a&gt; (or &lt;code&gt;brew install ollama&lt;/code&gt;). &lt;code&gt;qwen2.5-coder:7b&lt;/code&gt; below is just an example — pull whichever tag you picked for your RAM tier in &lt;strong&gt;Section 3&lt;/strong&gt; instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;ollama
ollama serve &amp;amp;          &lt;span class="c"&gt;# or just launch the Ollama app — it runs this for you&lt;/span&gt;
ollama pull qwen2.5-coder:7b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ollama listens on &lt;code&gt;127.0.0.1:11434&lt;/code&gt; by default. Verify it's responding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://127.0.0.1:11434/v1/models | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"id"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  6. Create a VM in Velo Workspaces
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Launch &lt;strong&gt;Velo Workspaces&lt;/strong&gt; on macOS.&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;AI Workspace&lt;/strong&gt; in the sidebar, click &lt;strong&gt;+&lt;/strong&gt; in the upper right.&lt;/li&gt;
&lt;li&gt;Pick an image source: download an official image, choose a local OS image file, or boot from an existing virtual disk. Let's pick Ubuntu 26.04 LTS (Server or Desktop).&lt;/li&gt;
&lt;li&gt;Choose the &lt;strong&gt;AI Sandbox&lt;/strong&gt; profile, click &lt;strong&gt;"Show Advanced Settings"&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Assign hardware parameters based on the sizing guidelines in Section 4 (e.g., 4 vCPUs, 6 GB RAM, 40 GB Disk). Ensure &lt;strong&gt;AI Bridge&lt;/strong&gt; is checked and &lt;strong&gt;Host Provider&lt;/strong&gt; matches whichever engine you started in Section 5 — &lt;strong&gt;MLX&lt;/strong&gt; or &lt;strong&gt;Ollama&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjs0my9h3u40bismol4nv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjs0my9h3u40bismol4nv.png" alt="AI Sandbox configuration example, with AI Bridge enabled and Host Provider set to MLX" width="800" height="654"&gt;&lt;/a&gt;&lt;br&gt;
   &lt;em&gt;Example: AI Sandbox profile with AI Bridge enabled and Host Provider set to MLX. Pick Ollama here instead if that's what you started in Section 5.&lt;/em&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;Continue&lt;/strong&gt;, install the guest OS, then start the virtual machine and log in.&lt;/li&gt;
&lt;/ol&gt;


&lt;h2&gt;
  
  
  7. Install and Configure AI Agents inside the VM
&lt;/h2&gt;

&lt;p&gt;All steps in this section run in the &lt;strong&gt;Ubuntu Linux Terminal&lt;/strong&gt; inside the VM. Throughout, &lt;code&gt;&amp;lt;PORT&amp;gt;&lt;/code&gt; is &lt;code&gt;8080&lt;/code&gt; for MLX or &lt;code&gt;11434&lt;/code&gt; for Ollama — whichever you picked in Section 5.&lt;/p&gt;
&lt;h3&gt;
  
  
  7.1 Establish the Guest Vsock Proxy
&lt;/h3&gt;

&lt;p&gt;Rather than typing this by hand, open the running workspace's &lt;strong&gt;AI Bridge&lt;/strong&gt; tab in Velo Workspaces — it shows your exact port already filled in, with a Copy button on each command block. Run &lt;strong&gt;step 1&lt;/strong&gt;, then either &lt;strong&gt;step 2&lt;/strong&gt; (forwards for the life of this terminal session — simplest, good for a quick test) or &lt;strong&gt;step 3&lt;/strong&gt; (installs it as a systemd service that survives reboots — better for anything you'll come back to):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnjmmy0qd1xs7qdj8z436.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnjmmy0qd1xs7qdj8z436.png" alt="The AI Bridge tab inside a running workspace, showing the Environment Variables and the four copy-pasteable setup commands" width="800" height="607"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Install socat&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; socat

&lt;span class="c"&gt;# 2. Forward the port for this session&lt;/span&gt;
socat &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; TCP-LISTEN:&amp;lt;PORT&amp;gt;,fork,reuseaddr,bind&lt;span class="o"&gt;=&lt;/span&gt;127.0.0.1,nodelay VSOCK-CONNECT:2:&amp;lt;PORT&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;— or, to keep it running across reboots —&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 3. Install as a systemd service instead of step 2&lt;/span&gt;
&lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/systemd/system/velo-ai-bridge.service &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
[Unit]
Description=Velo Workspaces AI Bridge (127.0.0.1:&amp;lt;PORT&amp;gt; to the host over the high speed channel)
After=network.target

[Service]
ExecStart=/usr/bin/socat TCP-LISTEN:&amp;lt;PORT&amp;gt;,fork,reuseaddr,bind=127.0.0.1,nodelay VSOCK-CONNECT:2:&amp;lt;PORT&amp;gt;
Restart=always
RestartSec=2

[Install]
WantedBy=multi-user.target
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl daemon-reload
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;velo-ai-bridge
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart velo-ai-bridge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, either way, verify guest-to-host connectivity across the vsock boundary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 4. Check it from inside the workspace&lt;/span&gt;
curl &lt;span class="nt"&gt;-sS&lt;/span&gt; http://127.0.0.1:&amp;lt;PORT&amp;gt;/v1/models
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that returns your model's info, the bridge is live — every agent below points at &lt;code&gt;http://127.0.0.1:&amp;lt;PORT&amp;gt;/v1&lt;/code&gt;, and each one configures that itself in the next section, so there's no separate global environment-variable setup needed here.&lt;/p&gt;




&lt;h3&gt;
  
  
  7.2 Configure Agent Frameworks
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Agent Option A: OpenCode (CLI &amp;amp; Web UI)
&lt;/h4&gt;

&lt;p&gt;OpenCode provides both an automated CLI agent and an interactive web workspace.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install OpenCode and its build dependencies:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; curl git build-essential
   curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://opencode.ai/install | bash
   &lt;span class="nb"&gt;source&lt;/span&gt; ~/.bashrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Register your inference engine as a provider.&lt;/strong&gt; Create &lt;code&gt;~/.config/opencode/opencode.json&lt;/code&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/.config/opencode
   nano ~/.config/opencode/opencode.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste this, substituting &lt;code&gt;&amp;lt;PORT&amp;gt;&lt;/code&gt; and the model name for your chosen engine (from Section 3):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://opencode.ai/config.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"local"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"npm"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@ai-sdk/openai-compatible"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Local Server"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"options"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
           &lt;/span&gt;&lt;span class="nl"&gt;"baseURL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://127.0.0.1:&amp;lt;PORT&amp;gt;/v1"&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"models"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
           &lt;/span&gt;&lt;span class="nl"&gt;"&amp;lt;model-name&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;Display Name&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
           &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"local/&amp;lt;model-name&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Run in non-interactive CLI mode&lt;/strong&gt; — &lt;code&gt;--auto&lt;/code&gt; auto-approves tool execution inside the sandbox:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   opencode run &lt;span class="nt"&gt;--auto&lt;/span&gt; &lt;span class="s2"&gt;"Write a python script to benchmark disk I/O, execute it, and print the results."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Or launch the interactive TUI&lt;/strong&gt;, then connect via its wizard instead of (or in addition to) the config file:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   opencode
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside the TUI, type &lt;code&gt;/connect&lt;/code&gt;, select &lt;strong&gt;Local Server&lt;/strong&gt;, and when prompted for an API key, type anything (e.g. &lt;code&gt;local&lt;/code&gt;) — the local server doesn't check it.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Or run the web interface&lt;/strong&gt;, bound to &lt;code&gt;0.0.0.0&lt;/code&gt; so it's reachable across the hypervisor network:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   opencode web &lt;span class="nt"&gt;--port&lt;/span&gt; 4096 &lt;span class="nt"&gt;--hostname&lt;/span&gt; 0.0.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From your Mac's browser: &lt;code&gt;http://&amp;lt;VM_IP&amp;gt;:4096&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accessing the Web UI from another PC on your LAN:&lt;/strong&gt; external machines can't route directly into the VM's private subnet, so forward a port on the host. On the &lt;strong&gt;macOS host&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   brew &lt;span class="nb"&gt;install &lt;/span&gt;caddy
   caddy reverse-proxy &lt;span class="nt"&gt;--from&lt;/span&gt; :8081 &lt;span class="nt"&gt;--to&lt;/span&gt; &amp;lt;VM_IP&amp;gt;:4096
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any machine on the LAN can then navigate to &lt;code&gt;http://&amp;lt;HOST_IP&amp;gt;:8081&lt;/code&gt;.&lt;/p&gt;




&lt;h4&gt;
  
  
  Agent Option B: Open Interpreter
&lt;/h4&gt;

&lt;p&gt;Open Interpreter provides a direct terminal agent loop designed for code execution.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   pip &lt;span class="nb"&gt;install &lt;/span&gt;open-interpreter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Launch against the local endpoint&lt;/strong&gt; — &lt;code&gt;-y&lt;/code&gt; auto-approves code execution without a confirmation prompt each time:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   interpreter &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;--api_base&lt;/span&gt; http://127.0.0.1:&amp;lt;PORT&amp;gt;/v1 &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;--model&lt;/span&gt; &amp;lt;model-name&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;--api_key&lt;/span&gt; &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h4&gt;
  
  
  Agent Option C: Aider
&lt;/h4&gt;

&lt;p&gt;Aider is designed specifically for Git-integrated pair programming and repository modifications. It reads the endpoint from environment variables rather than dedicated CLI flags, and needs the &lt;code&gt;openai/&lt;/code&gt; prefix on the model name so it routes through its OpenAI-compatible path:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   python3 &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install &lt;/span&gt;aider-chat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Run inside a Git repository:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;cd&lt;/span&gt; /path/to/project
   &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPENAI_API_BASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://127.0.0.1:&amp;lt;PORT&amp;gt;/v1
   &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;local
   &lt;/span&gt;aider &lt;span class="nt"&gt;--model&lt;/span&gt; openai/&amp;lt;model-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h4&gt;
  
  
  Agent Option D: Goose
&lt;/h4&gt;

&lt;p&gt;Goose is an extensible open-source autonomous agent developed by Block.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://github.com/block/goose/releases/download/stable/download_cli.sh | &lt;span class="nv"&gt;CONFIGURE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false &lt;/span&gt;bash
   &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.local/bin:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Configure a custom OpenAI-compatible provider:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   goose configure
   &lt;span class="c"&gt;# Select: Add Provider → Custom / OpenAI-compatible&lt;/span&gt;
   &lt;span class="c"&gt;# Base URL: http://127.0.0.1:&amp;lt;PORT&amp;gt;/v1&lt;/span&gt;
   &lt;span class="c"&gt;# API Key:  local&lt;/span&gt;
   &lt;span class="c"&gt;# Model:    &amp;lt;model-name&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Start a one-shot autonomous run:&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
bash
   goose run --text "Audit this directory, find security misconfigurations in JSON files, and correct them."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>ai</category>
      <category>macos</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>An Architecture to Run AI Agents Safely and Efficiently in a Linux VM on Apple Silicon</title>
      <dc:creator>Michael</dc:creator>
      <pubDate>Wed, 09 Sep 2026 14:10:23 +0000</pubDate>
      <link>https://dev.to/wango/an-architecture-to-run-ai-agents-safely-and-efficiently-in-a-linux-vm-on-apple-silicon-1hni</link>
      <guid>https://dev.to/wango/an-architecture-to-run-ai-agents-safely-and-efficiently-in-a-linux-vm-on-apple-silicon-1hni</guid>
      <description>&lt;p&gt;I'm the developer of &lt;a href="https://www.veloworkspaces.com" rel="noopener noreferrer"&gt;Velo Workspaces&lt;/a&gt;, a native macOS app for disposable Linux and macOS VMs on Apple Silicon. This post is about a specific problem I kept hitting while building it, how to let an AI agent run generated code safely in a VM without giving up the GPU acceleration? I ended up with the architecture AI Bridge based on vsock, and supported by real benchmark numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;If you want to run an AI coding agent locally on your Mac today, you may choose one of the following two options.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option A: run it directly on your Mac.&lt;/strong&gt; Most agent frameworks, like LangChain, AutoGen, SWE-Agent and Open Interpreter, execute LLM-generated code via subprocess or &lt;code&gt;exec()&lt;/code&gt; with full access to your filesystem, network, and credentials. Docker's own engineering blog has documented real &lt;a href="https://www.docker.com/blog/ai-coding-agent-horror-stories-security-risks/" rel="noopener noreferrer"&gt;AI coding agent security incidents&lt;/a&gt; resulting from exactly this. A recent dev.to post lays out the same concern plainly: &lt;a href="https://dev.to/serenitiesai/ai-agents-run-unsandboxed-code-how-to-fix-it-2026-1np4"&gt;AI Agents Run Unsandboxed Code — How to Fix It&lt;/a&gt;. Indirect prompt injection makes this worse than it sounds, and a malicious instruction hidden in a file the agent reads can trigger commands with your full permissions, not just the ones you asked for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option B: run it in a VM or container for isolation.&lt;/strong&gt; This is the "correct" answer on paper, and it runs straight into a wall specific to Apple Silicon: &lt;strong&gt;Apple's Virtualization.framework does not expose the host GPU to a Linux guest like the way it does for macOS guests.&lt;/strong&gt; This isn't a rumor, it's confirmed directly by Apple's own container team in their public repos, in response to people asking for exactly this: &lt;a href="https://github.com/apple/container/discussions/62" rel="noopener noreferrer"&gt;GPU passthrough availability?&lt;/a&gt; and &lt;a href="https://github.com/apple/containerization/issues/480" rel="noopener noreferrer"&gt;Paravirtualized Graphics via virtio-gpu&lt;/a&gt;. A macOS guest gets a purpose-built Metal-based GPU driver; a Linux guest gets &lt;code&gt;virtio-gpu&lt;/code&gt;, a paravirtualized 2D device with no path to the host's compute GPU. Running a 7B model inside the VM is dramatically slower than the same model on the host.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root cause:&lt;/strong&gt; on Apple Silicon, "safe" (inside a VM) and "fast" (on the GPU) currently pull in opposite directions, because the GPU only exists on the host side in the case of Linux VMs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution: split the agent from the model
&lt;/h2&gt;

&lt;p&gt;The fix isn't to pick a side, it's to stop treating "run the agent" and "run the model" as a same problem. The dangerous part (executing LLM-generated code) and the expensive part (running the model) don't have to live in the same place:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;agent&lt;/strong&gt; runs inside the isolated, disposable Linux VM, and when you're done, the whole VM (and anything it wrote to disk) is gone.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;model&lt;/strong&gt; runs on the host, on Ollama or MLX, with full native Metal GPU access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI Bridge&lt;/strong&gt; connects the two over a direct vsock channel, so the agent inside the VM talks to the model exactly like it would talk to any local server.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌───────────────────────────────────────────────────────────────────┐
│                            macOS HOST                             │
│                                                                   │
│      Ollama / MLX Engine   ───►  Storage: ~/.ollama/models        │
│      (Native Metal GPU)          (single 10GB–50GB copy)          │
│              ▲                                                    │
│              │  Velo Workspaces AI Bridge                         │
│              │  proxies to localhost:&amp;lt;port&amp;gt;                       │
│              ▼                                                    │
│      ┌─────────────────────────────────────────────────────┐      │
│      │                     LINUX VM                        │      │
│      │  socat 127.0.0.1:&amp;lt;port&amp;gt;  ⇄  vsock CID 2             │      │
│      │  Env: OPENAI_API_BASE=http://127.0.0.1:&amp;lt;port&amp;gt;/v1    │      │
│      │  Dev apps / Docker containers / VS Code / Python    │      │
│      └─────────────────────────────────────────────────────┘      │
└───────────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A relay bridges a local TCP port inside the guest to the host's model-server port over vsock (CID 2 is the host, in Apple's vsock addressing). The port isn't hardcoded to Ollama, so the bridge works for MLX or any other OpenAI-compatible local server on whatever port it's actually listening on. From inside the VM, you set &lt;code&gt;OPENAI_API_BASE&lt;/code&gt; (or whatever your tool calls it) to &lt;code&gt;http://127.0.0.1:&amp;lt;port&amp;gt;/v1&lt;/code&gt; and every OpenAI-compatible client will work, such as Open Interpreter, LangChain, or a raw &lt;code&gt;curl&lt;/code&gt;. No code changes need to be done, and no awareness that a VM boundary exists at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The value this targets specifically:&lt;/strong&gt; get real isolation for the part that's actually risky (arbitrary generated code execution) without paying a GPU-virtualization tax for the part that's actually expensive (inference), and without copying a 10–50GB model file into every VM you spin up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The results
&lt;/h2&gt;

&lt;p&gt;I wanted to know how close this actually gets to "no tax" so I benchmarked it properly rather than asserting it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setup:&lt;/strong&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Host: Mac mini, Apple M4, 10 cores, 16GB RAM, macOS 26.6.2. &lt;/li&gt;
&lt;li&gt;Guest: Ubuntu Server 26.04 (minimized), 4 vCPU, 4GB RAM.&lt;/li&gt;
&lt;li&gt;Model: &lt;code&gt;qwen2.5-coder:7b&lt;/code&gt; (Q4_K_M) served by Ollama on the host. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bridge tuned with a larger socat buffer and &lt;code&gt;nodelay&lt;/code&gt; set. Both sides on Python 3.12.&lt;/p&gt;

&lt;p&gt;Three tests, &lt;a href="https://github.com/xgz2025/veloworkspace-tools" rel="noopener noreferrer"&gt;scripts here&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ai_perf_test.py&lt;/code&gt;&lt;/strong&gt; — single-request time-to-first-token and throughput, 5 runs, deterministic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ai_load_test.py&lt;/code&gt;&lt;/strong&gt; — 8 concurrent clients (&lt;code&gt;OLLAMA_NUM_PARALLEL=2&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;oi_perf_test.py&lt;/code&gt;&lt;/strong&gt; — full agentic-loop latency via Open Interpreter: prompt → generated code → execution → result, 5 runs × 3 tasks&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;One methodology lesson that mattered more than anything else:&lt;/strong&gt; the first time I ran these, I switched between the host test and the VM test without being disciplined about fully stopping the VM and restarting Ollama between them. The agentic-loop numbers were a mess on host, wildly bimodal, some iterations 10x slower than others. The fix was to &lt;strong&gt;stop all VMs, stop Ollama, restart &lt;code&gt;ollama serve&lt;/code&gt; fresh, before every single run&lt;/strong&gt; for both host and VM tests. Once I did that consistently, the bimodal pattern disappeared completely. A leftover process from the previous run contending for CPU/scheduling with the one actually being measured was enough to produce numbers that looked dramatic but meant nothing. All results below are from the controlled re-run.&lt;/p&gt;

&lt;h3&gt;
  
  
  Raw single-request speed — this is the actual "tax," and it's small
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Host (loopback)&lt;/th&gt;
&lt;th&gt;VM (AI Bridge)&lt;/th&gt;
&lt;th&gt;Delta&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Time to first token&lt;/td&gt;
&lt;td&gt;56 ms&lt;/td&gt;
&lt;td&gt;59 ms&lt;/td&gt;
&lt;td&gt;+6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Throughput&lt;/td&gt;
&lt;td&gt;22.09 tok/s&lt;/td&gt;
&lt;td&gt;21.78 tok/s&lt;/td&gt;
&lt;td&gt;−1%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These numbers reveal the actual cost of this setup: exactly one network hop over vsock. Because the model stays firmly anchored to the host's hardware, a single-digit percentage drop is practically as close to a "zero virtualization tax" as physics will allow. You aren't paying a penalty for slow GPU emulation simply because there is no GPU emulation happening at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  8 concurrent clients — the VM won
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Host (loopback)&lt;/th&gt;
&lt;th&gt;VM (AI Bridge)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Total wall time (8 clients)&lt;/td&gt;
&lt;td&gt;15.04s&lt;/td&gt;
&lt;td&gt;12.73s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avg per-client time&lt;/td&gt;
&lt;td&gt;9.43s&lt;/td&gt;
&lt;td&gt;8.28s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The VM finished ~15% faster via the bridge！ Initially, I thought this was purely about CPU isolation on the host, the benchmark client and the Ollama server are fighting for the exact same physical cores. In the VM, the client gets its own dedicated vCPU slice. But digging into the network architecture reveals a deeper mechanical advantage.  When you run 8 concurrent Python clients natively on macOS, Python relies on kqueue for async I/O. It works, but it isn't quite as efficient as Linux's epoll event loop for handling rapid socket creation. More importantly, when the VM routes traffic over vsock, it lands in Velo Workspaces's native Swift app on the host, which uses Apple's Grand Central Dispatch (GCD) to proxy the connection to Ollama. GCD acts like a highly optimized shock-absorber at the Apple Silicon hardware level. It batches and streams those requests to localhost far more efficiently than a raw Python script hammering the port directly. You are effectively putting an enterprise-grade reverse proxy in front of the model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real agentic loop — the VM won consistently, by a believable margin
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Host mean&lt;/th&gt;
&lt;th&gt;Host std dev&lt;/th&gt;
&lt;th&gt;VM mean&lt;/th&gt;
&lt;th&gt;VM std dev&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Math computation&lt;/td&gt;
&lt;td&gt;6.45s&lt;/td&gt;
&lt;td&gt;±1.16s&lt;/td&gt;
&lt;td&gt;5.38s&lt;/td&gt;
&lt;td&gt;±1.28s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;System info&lt;/td&gt;
&lt;td&gt;7.14s&lt;/td&gt;
&lt;td&gt;±1.12s&lt;/td&gt;
&lt;td&gt;5.82s&lt;/td&gt;
&lt;td&gt;±1.07s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File I/O&lt;/td&gt;
&lt;td&gt;9.08s&lt;/td&gt;
&lt;td&gt;±1.22s&lt;/td&gt;
&lt;td&gt;6.56s&lt;/td&gt;
&lt;td&gt;±1.38s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The VM ran 17–28% faster, with both sides showing tight, comparable variance and no bimodal weirdness once background contention was controlled for. Seeing the VM consistently beat native macOS on tasks like File I/O and System Info might look counter-intuitive, but it makes perfect sense when you look at how agent frameworks actually execute code.  Open Interpreter relies heavily on spawning background terminal shells and Python REPLs, writing temporary files, and reading stdout. Every time a script does this natively on macOS, Apple's background security daemons (XProtect, Gatekeeper, and Endpoint Security) briefly intercept the execution to scan for malicious behavior. It is a tiny delay that compounds massively over the course of an agentic loop. Inside the minimized Ubuntu VM, none of that desktop antivirus overhead exists. Linux's process creation (fork()) is fundamentally lighter than macOS's (posix_spawn), and writing to a raw ext4 filesystem is instantaneous. The vsock bridge penalty is so remarkably small that it gets completely swallowed by the sheer execution speed of Linux.&lt;/p&gt;

&lt;h3&gt;
  
  
  How close to the ideal outcome?
&lt;/h3&gt;

&lt;p&gt;The goal was "near without virtualization tax" — not literally zero, since a network hop is unavoidable, but close enough that it isn't the thing you'd notice. A single-digit-percent difference on the raw inference path is that outcome. What I didn't expect going in was that the &lt;em&gt;system-level&lt;/em&gt; numbers — concurrent load, real agentic workflows — would come out ahead in the VM rather than merely close. The isolation you get for safety turns out to also isolate you from host-side resource contention, which is a genuine bonus on top of the safety case, not just a wash.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Biggest lesson overall:&lt;/strong&gt; environment hygiene between benchmark runs matters as much as the architecture you're testing. A leftover process from the previous run can produce numbers dramatic enough to completely mislead you about which side actually won.&lt;/p&gt;




&lt;p&gt;Scripts are at &lt;a href="https://github.com/xgz2025/veloworkspace-tools" rel="noopener noreferrer"&gt;github.com/xgz2025/veloworkspace-tools&lt;/a&gt; if you want to reproduce this on your own hardware or model. Velo Workspaces, which implements this architecture as AI Bridge, is at &lt;a href="https://www.veloworkspaces.com" rel="noopener noreferrer"&gt;veloworkspaces.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>virtualizaiton</category>
      <category>llm</category>
      <category>macos</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
