What changed, and why
One environment variable in the committed reference copy of the Mac Studio's com.zo.ollama LaunchAgent. Ollama's default unloads a model after five idle minutes, and the workloads that call it — ynab-agent's bursty email cadence, froot's periodic loops — are exactly the traffic shape that goes quiet for five minutes constantly. Every quiet stretch made the next call pay a multi-second cold load before a single token moved. OLLAMA_KEEP_ALIVE=-1 disables the idle timer.
The one block that changed
The variable joins the existing serving tuning (OLLAMA_FLASH_ATTENTION, OLLAMA_KV_CACHE_TYPE) in the agent's environment dict. The comment carries the operational reasoning, since a plist can't.
The environment dict; KEEP_ALIVE=-1 is the addition.
infra/k8s/tailscale/macstudio/com.zo.ollama.plist · 60 lines
infra/k8s/tailscale/macstudio/com.zo.ollama.plist
⋯ 22 lines hidden (lines 1–22)
1<?xml version="1.0" encoding="UTF-8"?>
2<!-- LaunchAgent that runs Ollama on the Mac Studio so it survives reboots.
3 Reference copy of ~/Library/LaunchAgents/com.zo.ollama.plist. Install:
5 cp infra/k8s/tailscale/macstudio/com.zo.ollama.plist ~/Library/LaunchAgents/
6 launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.zo.ollama.plist
7 brew services stop ollama # avoid the formula's LaunchAgent fighting this one
9 We use a dedicated agent (not `brew services`) because the Homebrew ollama
10 formula hardcodes its service env block, so brew regenerates the plist on
11 every upgrade and would wipe any added vars. This agent preserves the
12 formula's perf tuning AND survives `brew upgrade ollama`. -->
13<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
14<plist version="1.0">
15<dict>
16 <key>Label</key>
17 <string>com.zo.ollama</string>
18 <key>ProgramArguments</key>
19 <array>
20 <string>/opt/homebrew/opt/ollama/bin/ollama</string>
21 <string>serve</string>
22 </array>
23 <key>EnvironmentVariables</key>
24 <dict>
25 <!-- Loopback bind: `tailscale serve` dials 127.0.0.1:11434 and owns the
26 tailnet IP on :11434, so Ollama must NOT bind 0.0.0.0 (would collide
27 with serve, and is pointless — the macOS firewall blocks direct
28 non-loopback hits regardless). The cluster's Host header is rewritten
29 to `localhost` by an in-cluster proxy so Ollama's DNS-rebind check
30 accepts it. Local `ollama` CLI keeps working on the default port. -->
31 <key>OLLAMA_HOST</key>
32 <string>127.0.0.1:11434</string>
33 <!-- Preserved from the Homebrew ollama formula's service block. -->
34 <key>OLLAMA_FLASH_ATTENTION</key>
35 <string>1</string>
36 <key>OLLAMA_KV_CACHE_TYPE</key>
37 <string>q8_0</string>
38 <!-- Never idle-unload a loaded model. The default unloads after 5
39 minutes, so a quiet stretch (ynab-agent's email cadence is bursty)
40 pays a multi-second cold load on the next call — the largest
41 single source of tail latency. -1 disables the timer; loading a
42 different model can still evict via the normal LRU path. -->
43 <key>OLLAMA_KEEP_ALIVE</key>
44 <string>-1</string>
45 </dict>
⋯ 15 lines hidden (lines 46–60)
46 <key>RunAtLoad</key>
47 <true/>
48 <key>KeepAlive</key>
49 <true/>
50 <!-- Aqua = run in the logged-in GUI session so Metal/GPU is available. -->
51 <key>LimitLoadToSessionType</key>
52 <string>Aqua</string>
53 <key>StandardOutPath</key>
54 <string>/opt/homebrew/var/log/ollama.log</string>
55 <key>StandardErrorPath</key>
56 <string>/opt/homebrew/var/log/ollama.log</string>
57 <key>WorkingDirectory</key>
58 <string>/opt/homebrew/var</string>
59</dict>
60</plist>