Persistent prefix cache
Your agent's system prompt is prefilled once, ever — saved to disk as KV state, recalled on every later turn, across restarts. Correct even for hybrid-attention models (Qwen3.5, Gemma 4) where stock servers silently miss.
daedalus is the thermally-governed inference engine for MacBook Air. Your agent's 20,000-token prompt is prefilled once, paced under thermal pressure, and never times out again.
────────────────────────────────────────────────── daedalus v0.2 — wings that don't melt ────────────────────────────────────────────────── model mlx-community/Qwen3.5-9B-MLX-4bit loaded 4.2s · 6.10 GB weights · kv cache 8-bit thermal NOMINAL · profile balanced api http://127.0.0.1:8484/v1 10:41:22 │ → f3ab21 · 8,027 tok · 4 tools · stream 10:41:22 │ f3ab21 · cache hit 8,026/8,027 (disk) — prefilling 1 10:41:24 │ ← f3ab21 · 2.4s · decode 48 tok @ 20.4 tok/s · NOMINAL 10:44:07 │ → 9c11e0 · 21,458 tok · cold prefill 10:44:39 │ 9c11e0 · prefill 52% @ 157 tok/s · MODERATE — pacing 10:46:12 │ ← 9c11e0 · 124.8s · checkpointed · thermal recovered → NOMINAL
faster warm turns
0.24s vs 52.2s TTFT
total GPU time
for identical work
time throttled,
down from 50%
tok/s sustained,
zero decay
Benchmarks — M4 MacBook Air 16GB, Qwen3.5-9B 4-bit
Four consecutive 20k-token prefills, cooled to nominal before each arm. Unpaced, the Air throttles inside the first prompt and keeps degrading. Paced, the silicon never throttles — the same work costs 20% fewer GPU-seconds.
Every number measured on real hardware — raw traces in
bench/results/, reproduce with bench/thermal_validation.py.
With the prefix cache, a given prompt pays its cold prefill exactly once, ever.
Same 8k-token agent prompt, Qwen3.5-9B 4-bit, both engines — then restart the server. llama.cpp's slot cache dies with the process; daedalus reloads the prefix from disk.
Anatomy of a request
Every prompt runs the same pipeline. The whole design goal is that steps 2–5 almost never do the expensive thing twice.
The incoming prompt is looked up in the persistent cache — RAM LRU over disk
safetensors, exact-prefix matched. A hit means the KV state is already on disk, so the
first N−1 tokens are free.
Only the uncached tail is prefilled. Coding agents resend the whole conversation each turn, so after the first request the tail is usually a handful of tokens — not 20,000.
correct even for hybrid-attention modelsIf a cold prefill is unavoidable, the governor reads the live thermal-pressure level and duty-cycles the burn — full speed at Nominal, idle gaps at Moderate, hold at Heavy — checkpointing every 4k tokens so a timeout resumes where it stopped.
no sudo · escalates instantly · hysteresis on cooldownSSE keepalives stream progress, thermal state, and ETA from the first second, so a client's idle timeout never fires mid-prefill.
progress · thermal · etaDecode streams OpenAI-format deltas with well-formed tool calls; thinking-model
chain-of-thought is split into reasoning_content. The finished KV snapshot
is saved so the next turn is a step-1 hit.
Why it exists
Every other MLX engine is benchmarked on fan-cooled Studios and Maxes. daedalus treats the fanless Air's thermal envelope as the design constraint.
Your agent's system prompt is prefilled once, ever — saved to disk as KV state, recalled on every later turn, across restarts. Correct even for hybrid-attention models (Qwen3.5, Gemma 4) where stock servers silently miss.
Checkpoints every 4k tokens. A timeout, crash, or a client's hard 300-second deadline never repeats completed work — the retry resumes exactly where it stopped.
Reads macOS's five-level thermal-pressure signal live — no sudo — and
duty-cycles prefill against it. Three profiles: performance,
balanced, cool. The first of its kind in local inference.
SSE heartbeats stream progress, thermal state, and ETA from the first second of a long prefill, so idle-timeout clients never disconnect.
OpenAI-format deltas with explicit indexes, never an empty tool_calls
array — the malformed shapes that hang agents are structurally impossible.
Chain-of-thought from thinking models is split into reasoning_content —
it never leaks into the visible reply. pi, OpenCode, and any OpenAI-compatible client
just work.
Register extra models with --swap-model; a request for another one
tears the current engine down — freeing its Metal memory before loading the target —
so only one model is ever resident on 16 GB. Admission-checked, cooldown-limited,
and it restores the previous model if a load fails.
How it compares
ollama, LM Studio, llama.cpp, and stock mlx-lm are excellent general local servers. daedalus isn't trying to replace them — it optimizes the one machine they don't design for: the fanless, memory-tight Air.
| Capability | daedalus | ollama | LM Studio | llama.cpp | mlx-lm |
|---|---|---|---|---|---|
| OpenAI-compatible server | ● | ● | ● | ● | ● |
| Native MLX (Apple GPU) | ● | ○ | ● | ○ | ● |
| KV cache survives restart | ● | ○ | ○ | ○ | ○ |
| Hybrid-attention prefix reuse | ● | ○ | ○ | ○ | partial |
| Resumable prefill on timeout | ● | ○ | ○ | ○ | ○ |
| Thermal-aware pacing | ● | ○ | ○ | ○ | ○ |
| Tuned for the fanless Air | ● | ○ | ○ | ○ | ○ |
Reflects each engine's default local-server behavior. The restart and thermal rows are the ones measured on real hardware — see the benchmarks and the docs.
Install
Then point pi, OpenCode, or any OpenAI-compatible agent at
http://127.0.0.1:8484/v1
Go deeper
CLI reference, every config flag, the HTTP API, agent setup, and how the cache and governor actually work.