Cache. Pace. Fly.

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 serve — 127.0.0.1:8484
──────────────────────────────────────────────────
  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
218×

faster warm turns
0.24s vs 52.2s TTFT

−20%

total GPU time
for identical work

21%

time throttled,
down from 50%

157

tok/s sustained,
zero decay

Benchmarks — M4 MacBook Air 16GB, Qwen3.5-9B 4-bit

The middle path is the fast path.

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.

Stock engine (governor off)647s GPU · rate decays 133 → 119 tok/s

50% of the run throttled at HEAVY thermal pressure. Every round slower than the last.

daedalus (governor on)515s GPU · stable 157 tok/s

21% throttled. Recovers to NOMINAL between rounds. Rate never decays.

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.

llama.cpp — TTFT after restart78.6s · re-runs the whole prefill

Every daemon restart, crash, or reboot re-pays the full cold prefill — on a chassis its own last run just heated.

daedalus — TTFT after restart0.55s · reloads KV from disk

143× faster. The persistent cache outlives the process. Reproduce with bench/head_to_head.py.

Anatomy of a request

Five things happen before your agent waits.

Every prompt runs the same pipeline. The whole design goal is that steps 2–5 almost never do the expensive thing twice.

1

Match the prefix

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.

cache · RAM + disk · survives restarts
2

Prefill only what's new

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 models
3

Pace against the chassis

If 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 cooldown
4

Keep the client alive

SSE keepalives stream progress, thermal state, and ETA from the first second, so a client's idle timeout never fires mid-prefill.

progress · thermal · eta
5

Stream a clean reply

Decode 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.

tool_calls always well-formed · reasoning separated

Why it exists

Built for the machine everyone actually owns.

Every other MLX engine is benchmarked on fan-cooled Studios and Maxes. daedalus treats the fanless Air's thermal envelope as the design constraint.

▲▲

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.

⟲

Resumable prefill

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.

◐

Thermal governor

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.

≋

Keepalives with telemetry

SSE heartbeats stream progress, thermal state, and ETA from the first second of a long prefill, so idle-timeout clients never disconnect.

ƒ

Strict tool-call streaming

OpenAI-format deltas with explicit indexes, never an empty tool_calls array — the malformed shapes that hang agents are structurally impossible.

◔

Reasoning separation

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.

⇄

Single-resident model swap · on main, v0.3

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

Great engines exist. None target this laptop.

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

Up in three commands.

$uv tool install https://github.com/therahul-yo/Daedalus/releases/download/v0.2.0/daedalus-0.2.0-py3-none-any.whl
$daedalus serve --model mlx-community/Qwen3.5-9B-MLX-4bit --port 8484

Then point pi, OpenCode, or any OpenAI-compatible agent at http://127.0.0.1:8484/v1

Go deeper

The full manual is one click away.

CLI reference, every config flag, the HTTP API, agent setup, and how the cache and governor actually work.