What You'll Build
A local browser computer-use agent (CUA) running entirely on Apple Silicon: Microsoft's Fara1.5-4B served by llama-server on Metal with the vision projector loaded, so the model actually sees browser screenshots, driven by Microsoft's own fara-cli harness against a local Playwright Chromium.
Hardware data: Apple M4 Max (48GB unified memory) · Q8_0 + f16 projector = 4.812 GiB of weights, 13.008 GiB total at the model's full 262,144-token context (derived below) · See benchmark data
⚠️ This model clicks, types and submits in a real browser — and
fara-clidoes not sandbox it. That is not boilerplate; it is the operating requirement, and it is the one thing to get right before anything else on this page. The model card is blunt: "Don't run this model with unrestricted browser access on a machine that has anything sensitive on it." Fara also reads whatever is on the page, so a hostile page is a prompt-injection surface.Be clear about what the tooling gives you.
fara-clidrives a plain local Playwright Chromium — no container, no allow-list, no pause control. The Docker sandbox belongs to Magentic-UI, which the harness README describes as "a sandboxed browser environment with auditable action logging and user prompts at critical points" and offers as what you use "instead of fara-cli you can use Magentic-UI". The vendor's position is a recommendation, not a guarantee shipped in the CLI: "We recommend running it in a sandboxed environment, monitoring its execution, and avoiding sensitive data or high-risk domains."On a Mac this matters more than usual, because the Mac you would run this on is very likely your daily machine — the same one holding your mail, your keychain and your logged-in sessions. If you follow the
fara-clipath below, you are the sandbox. Practically: run it under a throwaway macOS user account or a VM, let Playwright use its own fresh browser profile (never your daily Chrome), stay logged out of everything, keep tasks on low-stakes public sites, and watch the run rather than leaving it unattended. If you want the container, allow-lists, watch-mode and pause the model card asks for, use Magentic-UI instead — see Running.
ℹ️ Vision is the product, not a bonus. Per the model card, "The model is vision-only at perception time: it sees the browser through screenshots, not the DOM or accessibility tree." A Fara install that only serves text is a broken install — the
--mmprojstep below is mandatory, and Step 4 exists to prove an image really reached the model before you let it drive anything. This is also the single reason the MLX build a reader would reach for first is the wrong one; see Alternative runtime.
⚠️ Runtime status — read before you commit an evening to this. Metal support for every piece of this model is verifiable statically and this recipe shows that evidence, but no published report shows Fara1.5 specifically captioning an image under
llama-serveron Apple Silicon. Treat the first run as the test. There is also no published speed measurement for this pair, and none is invented here (see Results).
Requirements
| Component | Minimum | This recipe |
|---|---|---|
| GPU | 24GB unified memory (Apple Silicon, Metal) | Apple M4 Max, 48GB unified — not measured; the budget below is derived (/contribute) |
| RAM | unified with the GPU — see budget below | — |
| Storage | 5.17 GB (Q8_0 + f16 projector) | 5.17 GB decimal / 4.812 GiB |
| Software | macOS 14+, llama.cpp b10087+ (Metal), Python 3.10+, Playwright | — |
Why 48GB does not change this recipe, and why "does it fit" is the wrong question
Apple has no dedicated VRAM. Metal's recommendedMaxWorkingSetSize caps what the GPU may address, and below 64GB that lands near 66% of unified memory — roughly 32 GiB safe on this 48GB machine, perhaps 36 GiB optimistically. That figure is extrapolated from the documented rule rather than read off a 48GB machine's own log, so it is the conservative end that gets used below. Either way /check's raw 48 GB is optimistic by about a third.
Even so, a 4B model is nowhere near that wall: the heaviest configuration on this page is 16.7 GiB, about half the conservative pool. No iogpu.wired_limit_mb raise is needed for anything here, and if a Fara recipe tells you to raise it, it has copied a caveat that does not apply.
So fit is not the axis, and the honest headline is that nothing material differs from the 64GB configuration — the model's full native context and the top of the quality ladder both still fit, at the same settings, with roughly 19 GiB of the addressable pool untouched. What this particular chip contributes is not capacity but memory bandwidth: at 546 GB/s the 48GB M4 Max is the highest-bandwidth part of the four Apple configurations this site covers, and token generation is bandwidth-bound, so this is the Mac on which the byte-streaming argument below pays off best. The M4 Max ships in two bins (Apple tech specs) — a 14-core-CPU/32-core-GPU part at 410 GB/s and a 16-core-CPU/40-core-GPU part at 546 GB/s — and Apple sells 48 GB only on the latter, so a 48GB M4 Max is unambiguously the 546 GB/s die. That is a published specification, not a measurement of this model — see Results.
Weights — byte counts from the bartowski GGUF tree:
| Component | Bytes | GiB |
|---|---|---|
Fara1.5-4B-Q8_0.gguf | 4,493,954,144 | 4.185 |
mmproj-Fara1.5-4B-f16.gguf | 672,423,584 | 0.626 |
| Q8_0 pair | 5,166,377,728 | 4.812 |
Fara1.5-4B-bf16.gguf | 8,424,393,568 | 7.846 |
mmproj-Fara1.5-4B-bf16.gguf | 675,569,312 | 0.629 |
| bf16 pair | 9,099,962,880 | 8.475 |
KV cache — and this is where Fara1.5 is unusual, and where the context decision gets cheap. Its qwen3_5 architecture is a hybrid. The source config.json spells the split out layer by layer in text_config.layer_types: 24 of 32 layers are linear_attention, only 8 are full_attention (full_attention_interval: 4). llama.cpp models this as a hybrid memory — llm_arch_is_hybrid() in src/llama-arch.cpp returns true for LLM_ARCH_QWEN35, and llama-memory-hybrid.cpp filters the growing cache with !hparams.is_recr(il). Only those 8 layers carry a cache that scales with context.
With num_key_value_heads: 4 and head_dim: 256 from the same config.json:
per layer per token = (4 x 256) + (4 x 256) = 2,048 elements
x 8 full-attention layers = 16,384 elements/token
at f16 = 32 KiB/token
x 262,144 tokens (the model's full context) = 8.00 GiB
The other 24 layers hold a fixed-size recurrent state, sized by llama.cpp's n_embd_r() / n_embd_s() in src/llama-hparams.cpp from linear_conv_kernel_dim: 4, linear_num_value_heads: 32 x linear_value_head_dim: 128 (= 4096), linear_num_key_heads: 16 and linear_key_head_dim: 128:
n_embd_r = (4-1) x (4096 + 2 x 16 x 128) = 24,576
n_embd_s = 128 x 4096 = 524,288
(24,576 + 524,288) x 24 layers x 4 bytes = 50.25 MiB per sequence
x 4 sequences (llama-server's default) = 201.0 MiB = 0.196 GiB
That state is allocated per sequence, not once: src/llama-model.cpp passes /* recurrent_rs_size */ std::max((uint32_t) 1, cparams.n_seq_max), and tools/server/server.cpp resolves its auto default with params.n_parallel = 4, so a plain llama-server reserves four slots' worth. It is a rounding error against this machine's pool; it is stated at its real size because it costs nothing to be right.
Totals against a ~32 GiB addressable pool:
| Config | Weights + projector | KV @ 262,144 | Recurrent state | Total |
|---|---|---|---|---|
| Q8_0 + f16 mmproj | 4.812 GiB | 8.000 GiB | 0.196 GiB | 13.008 GiB |
| bf16 + bf16 mmproj | 8.475 GiB | 8.000 GiB | 0.196 GiB | 16.671 GiB |
| Q8_0, context halved to 131,072 | 4.812 GiB | 4.000 GiB | 0.196 GiB | 9.008 GiB |
Compute buffers and the vision graph are not in the table — they are allocated at load time and this recipe does not derive them. Read the buffer-size lines llama-server prints on startup for the real figures on your build, and report what you see.
Why Q8_0 and not bf16, when both fit twice over. Token generation is bandwidth-bound, and even 546 GB/s is a fixed budget. The bf16 pair streams 1.87x the bytes of the Q8_0 pair (8,424,393,568 / 4,493,954,144) for a step that is conventionally treated as near-lossless. So Q8_0 leads. bf16 stays documented above as the reference-fidelity option — and unlike the 8 GB sibling, where stepping up a quant tier is a rationing decision, here it costs you nothing but throughput. That is the concrete thing 48GB buys on this model: the full 262K context and the top of the quality ladder at the same time, instead of one or the other.
How much context one screenshot costs
A CUA sends a screenshot every step, so this is the number that decides whether the context budget above is honest. It is not the 768 you may see quoted for this projector — that value derives from num_position_embeddings: 2304 (√2304 x 16 = 768) as a reference for position-embedding interpolation, and llama.cpp treats this projector as dynamic-resolution.
The harness fixes the browser viewport at 1440x900 (fara15_agent.py: viewport_width: int = 1440, viewport_height: int = 900), matching the card's note that "The screen resolution Fara is most commonly trained with is" 1440x900. It then applies Qwen's smart_resize with patch_size: 16 and spatial_merge_size: 2, so the alignment factor is 32 and each 2x2 patch block merges to one token:
900 -> round(900/32)*32 = 896 1440 -> round(1440/32)*32 = 1440
merged grid = (1440/16)/2 x (896/16)/2 = 45 x 28
image tokens = 1,260 tokens per screenshot
The harness keeps max_n_images: int = 3, so 3,780 tokens of any prompt are screenshots; its own conservative default image_token_estimate: int = 1500 sits just above the derived 1,260, which is a useful sanity check on the arithmetic. Against the 262,144-token window above, screenshots are rounding error — the reason to keep the window wide is the accumulated reasoning and action history on a long task, not the images. The repo's floor still applies as a lower bound: "Please ensure that context length is set to at least 15000 tokens and temperature to 0 for best results."
Installation
1. Install llama.cpp with Metal
Metal needs no flags. Per docs/build.md: "On MacOS, Metal is enabled by default. Using Metal makes the computation run on the GPU." There is no CUDA toolkit, no ROCm, and no flash-attn wheel in this recipe — none of them exist on Apple Silicon.
brew install llama.cpp
Homebrew is a documented install channel for macOS in docs/install.md and its formula tracks releases. If you would rather build from source — worth doing here, because the Metal path for this architecture is young and still being tuned:
git clone https://github.com/ggml-org/llama.cpp
cd llama.cpp
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release -j
Use b10087 or newer — that is the release the GGUF below was quantised on, per the quantiser's card.
What makes this work on Metal, since nobody has published a Fara-on-Mac run. Every op class this model needs has a Metal kernel in ggml-metal.metal: kernel_rope_multi and kernel_rope_vision for the interleaved multimodal RoPE this architecture uses, kernel_gated_delta_net_impl for the 24 linear-attention layers, kernel_ssm_conv_* / kernel_ssm_scan_f32 for their recurrent state, and kernel_im2col / kernel_conv_2d for the vision patch embedding. The projector is not stranded on the CPU either: clip.cpp asks for a generic GPU backend at context creation and logs which one it got (CLIP using <backend> backend) — on a Metal build that line should read Metal, and it is the first thing to check in the startup log. The gated-delta-net kernels landed in PR #20361 (metal : add GDN kernel, merged by a maintainer, superseding the unmerged community PR #20244), and a maintainer is still optimising that path in the open PR #25788, whose benchmark table runs qwen35 models on an Apple M3 — an older and much lower-bandwidth chip than this one, so read that table for direction only. For the vision half specifically, a community contributor's open PR #21443 profiles the image-encoding path of a Qwen3.5 sibling on the Metal backend and reports where the time goes, so that encoder demonstrably runs there.
That is static and circumstantial evidence, not a tested Fara run. Step 4 below is the test.
2. Download the weights and the projector
Both files are required. The projector is a separate artifact — downloading only the text GGUF gives you a model that cannot see.
pip install -U huggingface_hub
hf download bartowski/Fara1.5-4B-GGUF \
Fara1.5-4B-Q8_0.gguf mmproj-Fara1.5-4B-f16.gguf \
--local-dir ~/models/fara1.5-4b
Microsoft publishes no first-party GGUF for Fara1.5 — the microsoft org ships safetensors for all three sizes (and an ONNX repo only for the previous-generation Fara-7B, a different model). Four independent community quantisers have converted the 4B including its vision tower — bartowski, prithivMLmods, DevQuasar and runanywhere all ship an mmproj file. Four independent conversions succeeding is the strongest available evidence that the vision path converts cleanly; it is still not a runtime test.
3. Serve it
./build/bin/llama-server \
-m ~/models/fara1.5-4b/Fara1.5-4B-Q8_0.gguf \
--mmproj ~/models/fara1.5-4b/mmproj-Fara1.5-4B-f16.gguf \
-ngl 99 -c 262144 --port 5000 \
--image-min-tokens 1024 \
--reasoning-format none \
--temp 0
Four of those flags are load-bearing and worth understanding rather than pasting:
--reasoning-format none. This is the one that decides whether the agent loop works at all, and it is maintainer-acknowledged. Fara opens a<think>tag and never closes it, so the server's default reasoning extraction files the whole reply — thoughts and the<tool_call>block — as reasoning, leavingmessage.contentempty; harnesses that readcontentthen get nothing.noneis documented incommon/arg.cppas "none: leaves thoughts unparsed inmessage.content", which is what the harness expects. The report is Fara1.5-27B discussion #4, reproduced on b10107, where the reporter states plainly that "It emits a<think>opening tag but never a closing</think>." and that with the default the whole reply — thoughts and tool call together — "lands inreasoning_content, andmessage.contentcomes back as" an empty string. A Microsoft team member replied there asking for it to be filed upstream; it now sits as the open fara#82. Nothing about this is Apple-specific — it is a property of the model's chat template, so it bites every serving stack, including the MLX one below.--image-min-tokens 1024. The projector ships noimage_min_pixelskey, so llama.cpp falls back to a low default floor and warns: "Qwen-VL models require at minimum 1024 image tokens to function correctly on grounding tasks" (clip.cpp). Fara is a grounding model — it emits pixel coordinates — so this is precisely the case the warning is about. At the harness's 1440x900 viewport the image is already 1,260 tokens and the floor never binds; it protects you if you shrink the viewport.-c 262144. The model's native window, costing 8.00 GiB of KV per the table above. The 8 GB NVIDIA sibling has to cap this at 32,768 and the 16GB M2 Pro at 65,536; you do not. Halve it to131072if you would rather keep the total under 9.1 GiB.-ngl 99. Explicit rather than necessary — a Metal build already runs the computation on the GPU by default, perdocs/build.mdabove.
Two things you will notice are missing, deliberately. There is no --cache-type-k q8_0 KV-quantisation step: it exists, but at 8 GiB of KV against ~32 GiB addressable it buys nothing and costs precision on a model whose job is coordinates. And there is no sudo sysctl iogpu.wired_limit_mb raise, for the reason given in Requirements.
4. Prove the vision path works before going further
This is the step that turns the static Metal evidence above into a fact about your machine. Check the server advertises multimodal, confirm the CLIP backend line in the startup log, then send a real image. The server README instructs clients to check for the capability this way, and documents image_url as a first-class content part whose URL "can be a remote URL, base64 (raw or URI-encoded via" a data URI "or path to local file".
curl -s http://localhost:5000/v1/models | grep -o multimodal
curl -s http://localhost:5000/v1/chat/completions -H "Content-Type: application/json" -d '{
"model": "fara",
"temperature": 0,
"messages": [{"role": "user", "content": [
{"type": "text", "text": "Describe this image in one sentence."},
{"type": "image_url", "image_url": {"url": "https://raw.githubusercontent.com/ggml-org/llama.cpp/master/media/llama0-logo.png"}}
]}]
}' | python3 -m json.tool
If the reply describes the picture, the projector is wired up and Metal is running it. If it answers as though no image were present, or the server never logged an mmproj load, stop here — the agent loop below will silently produce nonsense actions rather than fail loudly. And if content comes back empty while a reasoning field is full, you missed --reasoning-format none.
5. Install the harness — and skip the extra the README hands you
git clone https://github.com/microsoft/fara.git
cd fara
python3 -m venv .venv && source .venv/bin/activate
pip install -e .
playwright install chromium
Use pip install -e ., not the README's pip install -e .[vllm]. That extra pins a CUDA serving stack — vllm==0.19.1 plus nvidia-cutlass-dsl==4.5.2, both visible in pyproject.toml — which is neither installable nor useful on Apple Silicon. The README notes the plain install as the option for people not self-hosting on their own GPU: "If you plan on hosting with Microsoft Foundry only, you can skip the [vllm] and just do pip install -e .". You are self-hosting, just not with vLLM, so take the plain install and point the client at llama-server.
The rest of the harness is genuinely portable: pyproject.toml declares Operating System :: OS Independent and the runtime dependencies are playwright==1.51, openai, pillow and other pure-Python packages, with no CUDA anywhere. Playwright 1.51 publishes a macosx_11_0_arm64 wheel, so Chromium runs native on Apple Silicon with no Rosetta. Screenshots are captured through the browser (page.screenshot() in playwright_controller.py), not off the desktop, so macOS Screen Recording permission is never requested and never needed. The harness's Xvfb code — the usual source of Linux-only breakage in headful browser agents — is gated behind platform.system() == "Linux" in environment.py, so --headful on macOS simply opens a Chromium window.
Running
Two client paths share the same llama-server endpoint. Path A (fara-cli) is the smoke test — unsandboxed. Path B (Magentic-UI) is what the vendor recommends for anything real. Pick deliberately; they are not equivalent in risk.
Path A — fara-cli, for a first smoke test
fara-cli speaks to any OpenAI-compatible endpoint. Point it at llama-server:
fara-cli \
--base_url http://localhost:5000/v1 \
--api_key sk-no-key-required \
--model fara \
--task "how many pages does wikipedia have" \
--output_folder ./runs/first
Each run writes per-step screenshots and a data_point.json recording the task, actions, observations and outcome to --output_folder. Watch that folder on the first few runs — it is how you tell a grounding failure (clicks landing in the wrong place) from a reasoning failure.
Runs are headless by default; --headful opens the window. Either way this drives a real, unsandboxed Chromium on your Mac — the playwright install chromium in step 5 is what put it there. Keep the task on a low-stakes public site, stay signed out, and stay at the keyboard. Fara is trained to stop and ask before entering personal information, making payments, submitting forms, signing in or sending messages; if you see it sail through one of those, stop the run — that is a wiring problem in your prompt path, not a model quirk to tolerate.
Path B — Magentic-UI, for the sandbox
Everything the model card asks for — the containerised browser, domain allow-lists, watch-mode and a pause control — lives in Magentic-UI, not in fara-cli. This is where a Docker requirement would come from; there is no Docker step in Path A because Path A genuinely does not use one.
Magentic-UI takes the same OpenAI-compatible endpoint you started above, so nothing on this page changes except the client: per the harness README, "instead of fara-cli you can use Magentic-UI". Its setup is documented in its own repository rather than reproduced here, because this recipe has not verified those steps end to end on this machine — follow microsoft/magentic-ui and give it http://localhost:5000/v1 as the model endpoint.
Alternative runtime: MLX-VLM
Apple recipes on this site normally lead with MLX. This one leads with llama.cpp-Metal, and the reason is worth stating precisely, because it is not a framework gap: mlx-vlm implements this architecture natively — mlx_vlm/models/qwen3_5/ exists, and its vision.py is a one-line subclass of the Qwen3-VL vision model. It also ships a real OpenAI-compatible server (mlx_vlm.server) whose /v1/chat/completions accepts image_url content parts, which is exactly what fara-cli --base_url needs. The call here is about build quality and provenance, not capability:
- The MLX build a reader would reach for first is text-only.
mlx-community/Fara1.5-4B-OptiQ-4bitkeeps its vision tower in a separateoptiq/optiq_vision.safetensorsand says so on its own card: "The one repo loads text-only under stockmlx-lmand full image+text under OptiQ." Vision requires installing the third-partyoptiqpackage. For a model whose entire job is looking at a screenshot, a text-only load is not a degraded install, it is a broken one — and it carries themlx-communityname that normally signals "safe default". - The one clean MLX build is very new and very small.
runanywhere/Fara1.5-4B-mlx-4bitis made with the right converter and passes a tensor census: 1,221 tensors of which 297 are vision, matching the 297 vision tensors in Microsoft's ownmodel.safetensors.index.jsonexactly, all resolving to its singlemodel.safetensorsrather than a side artifact, with the vision tower left unquantised. Its card states it "Loads through MLX VLM stacks that supportqwen3_5". But it is a single-author repo days old with double-digit downloads, it offers one quant tier, and its accuracy claims are the author's own self-report with no independent check. - The GGUF side has a size ladder and a vetted redistributor. bartowski ships 24 quant tiers from IQ2_M up to full bf16, each with a matching projector — which is precisely what makes the "run it at Q8_0, or at bf16 if you want the reference" choice on this page possible.
If you would rather stay in MLX, the parallel path is real and short:
pip install -U mlx-vlm
mlx_vlm.server --model runanywhere/Fara1.5-4B-mlx-4bit --host 127.0.0.1 --port 8080
Then point the harness at http://127.0.0.1:8080/v1. Two things to know before you do:
--host 127.0.0.1is not optional. The server's own default is0.0.0.0, which publishes an unauthenticated model endpoint — one that drives a browser — to your whole network.- The
<think>problem exists here too, and the default already handles it.mlx-vlm's server splits thinking out of the reply exactly as llama.cpp does, and its splitter returns an emptycontentwhen it finds an opening tag with no closing one — the same dead agent loop. What saves you is the default:enable_thinkingdefaults toFalse, and Fara's chat template branches on it to emit a pre-closed<think>\n\n</think>block in the prompt, so the generated text contains no thinking tag to strip. Do not pass--enable-thinking— it re-opens the exact failure that--reasoning-format noneexists to prevent on the llama.cpp side.
One more thing that makes both servers safe here: fara-cli sends the tool schema inside the system prompt and passes no OpenAI tools array (the agent's call args are just {"temperature": 0} plus your extras), and it parses the raw text itself, splitting on <tool_call>. Servers that would otherwise rewrite <tool_call> blocks into a structured tool_calls field leave the text alone when no tools array is sent — which is why the harness's raw-text parser keeps working against a stack Microsoft never tested.
Results
- Speed: omitted. No published measurement exists for Fara1.5-4B on an M4 Max, on any Apple Silicon chip, or on any consumer GPU; the only hardware the vendor names is datacenter class ("A6000, A100, H100, and B200 have been tested"), and forwarding a number from it would be misleading. Nor can one be forwarded across Apple chips: token generation is bandwidth-bound and this chip's 546 GB/s is a substantially larger budget than the ~400 GB/s M2 Max and M3 Max parts or the ~200 GB/s M2 Pro, so a figure from any of them would understate this machine — by an amount nobody has measured, which is exactly why no number appears here. If you run this, please contribute your numbers so this section can be replaced with a measurement.
- Unified memory usage: 4.812 GiB of weights and projector; 13.008 GiB including KV and recurrent state at the full 262,144-token context, derived above from
config.jsonand llama.cpp's memory formulas. That sits comfortably inside the ~32 GiB a 48GB M4 Max makes GPU-addressable. This is a derivation, not an observed peak — compute and vision buffers are additional. See /check/fara1-5-4b/m4-max. - Quality notes: the vendor's published scores are for the bf16 model on datacenter hardware. Q8_0 is a lossy quantisation of a model whose main job is predicting exact pixel coordinates, and no published evaluation measures how much grounding accuracy that costs at any tier. The advantage of this machine is that the fix is free: if clicks land near-but-not-on targets, switch to
Fara1.5-4B-bf16.ggufwithmmproj-Fara1.5-4B-bf16.ggufand re-test before blaming the model. - Language: English only — the card's out-of-scope list names "Languages other than English (training data is English-only)".
For the full benchmark data, see /check/fara1-5-4b/m4-max.
Troubleshooting
The server starts but the log says CLIP using CPU backend
The projector fell back to the CPU. Vision will still work, but slowly, and it means the GPU backend failed to initialise. Confirm you are on a Metal build (a source build without -DGGML_METAL=OFF, or the Homebrew formula) and that --mmproj points at a file that exists. clip.cpp prints this line at context creation, so it is visible before the first request — check it during step 4 rather than after a confusing agent run.
llama-server exits immediately when you add --mmproj
Check whether you have taken the GPU out of the picture. The one report in llama.cpp's tracker of an Apple M4 llama-server refusing to start with a --mmproj on the command line — issue #23631, a different vision-language model, exiting with code -5 — runs its repro with --device none, i.e. deliberately CPU-only, and the reporter is chasing a KleidiAI CPU path rather than Metal. It was closed by a staleness bot with no maintainer answer, so it is not evidence about the Metal path either way. The actionable inverse is simply: do not force a CPU-only device selection on a Mac. The command in step 3 passes no --device, which lets Metal be chosen, and the CLIP using <backend> backend line in the startup log is how you confirm it was.
ollama run fara does nothing — there is no Ollama entry
There is no Ollama library entry for Fara1.5 in any form: fara1.5, fara, fara1-5, fara1.5-4b and fara-4b all 404. The GGUF/LM Studio/Ollama line in the harness README refers to Fara-7B, the previous generation, reached with a --fara-7b flag — it is not about Fara1.5. That older path is not trouble-free either: fara#58 is an open community report of fara-cli failing on its first model call against an Ollama-hosted Fara-7B, with the reporter suspecting the OpenAI image_url content type. Whatever server you choose, step 4's image test is the check that matters.
The model answers but ignores the screenshot
Almost always the projector. Confirm --mmproj is on the command line, that the server logged an mmproj load stage, and that step 4's curl describes the image. A text-only build paired with no projector loads and runs perfectly happily — it simply cannot see, which in a CUA presents as confidently wrong actions rather than an error. If you came here from MLX, check first that you are not on the OptiQ build under a stock loader, which is text-only by construction.
The agent dies on round one with an empty response
message.content is empty and everything is in a reasoning field. On llama.cpp that is the missing --reasoning-format none; on mlx_vlm.server it is --enable-thinking being passed. Both are the same underlying behaviour and both are described in step 3.
Clicks land in the wrong place
Check the viewport first. The harness defaults to 1440x900 because that is what the model was mostly trained at; a different window size changes both the grounding distribution and the token cost derived above. Then confirm --image-min-tokens 1024 is set, since llama.cpp's own warning ties sub-1024-token images to degraded grounding. Only after both, suspect the quantisation — and on this machine, testing that is just a re-download at bf16.
Keyboard shortcuts do nothing on pages that expect them
A macOS-only asymmetry. The harness intercepts a fixed set of Control-based chords itself before they reach the browser — refresh, back, forward and find, in _BROWSER_CHROME_DISPATCH and _FIND_CHORD in environment.py — so those work on any OS. Other editing chords the model emits as Control+key are passed through to Chromium, where macOS expects Meta (Cmd) instead, and quietly do nothing. The key map in key_mapping.py does translate cmd to Meta, so a run that phrases the chord that way works; there is no setting to flip. Prefer tasks that do not lean on copy/paste shortcuts, and read data_point.json when an action seems to have been swallowed.
The output is a wall of @@@@ instead of an answer
Worth knowing what this is not. There is one Metal-specific defect in llama.cpp's multimodal path that produces exactly this symptom — issue #23986, where a pipeline/dispatch mismatch in Metal's im2col broke audio encoders from build b9433 onward — and it does not apply to Fara. A second contributor reproduced it at the op level on an M1 Pro and scoped it precisely: "it really is isolated to large-IC 1D conv", and "The 2D path already agrees so it stays unaffected." Fara's vision patch embedding is a 2D convolution, and the original reporter checked the vision half explicitly: "Image / vision inputs are also unaffected (verified with the same mmproj path on the latest build, b9453)." The issue was closed by a staleness bot rather than by a fix, so it is still live for audio models — but it is also the closest thing to independent community confirmation that llama.cpp's Metal mmproj path serves images correctly, which is worth having on a page that otherwise rests on a static kernel census.
Generation is slower than a 4B model should be
The Metal path for this architecture's linear-attention layers is newer than the CUDA one and still being tuned — an open maintainer PR (#25788) reports gains on qwen35 models on an Apple M3 from a single cache-fusion change, with a mixed table that includes regressions on some quants, and llama.cpp's own op-support table still lists GATED_DELTA_NET on Metal as "Partially supported by this backend". Build from a recent master before drawing conclusions, and compare two builds with llama-bench rather than trusting a single impression. This chip's bandwidth advantage does not help if the kernel is the bottleneck. No measurement for this chip exists either way — contribute one.