chad-code 1.13.0__tar.gz → 2.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. chad_code-2.0.0/PKG-INFO +333 -0
  2. chad_code-2.0.0/README.md +298 -0
  3. {chad_code-1.13.0 → chad_code-2.0.0}/pyproject.toml +20 -14
  4. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/__init__.py +1 -1
  5. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/agent.py +444 -984
  6. chad_code-2.0.0/src/chad/ambient.py +640 -0
  7. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/checkpoint.py +3 -4
  8. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/cli.py +260 -115
  9. chad_code-2.0.0/src/chad/compaction.py +218 -0
  10. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/completion_engine.py +1 -1
  11. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/config.py +21 -0
  12. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/engine.py +1297 -24
  13. chad_code-2.0.0/src/chad/guardrails.py +745 -0
  14. chad_code-2.0.0/src/chad/levers.py +156 -0
  15. chad_code-2.0.0/src/chad/mlx_dflash.py +870 -0
  16. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/mlx_fastpath.py +148 -179
  17. chad_code-2.0.0/src/chad/mlx_qmm_mma.py +479 -0
  18. chad_code-2.0.0/src/chad/mlx_qsdpa.py +1522 -0
  19. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/prompt.py +59 -150
  20. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/prove.py +12 -8
  21. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/render.py +24 -87
  22. chad_code-2.0.0/src/chad/repomap.py +587 -0
  23. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/seatbelt.py +13 -14
  24. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/serve.py +2 -2
  25. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/skills.py +102 -91
  26. chad_code-2.0.0/src/chad/spill.py +162 -0
  27. chad_code-2.0.0/src/chad/syntaxgate.py +120 -0
  28. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/toolcall_parse.py +23 -6
  29. chad_code-2.0.0/src/chad/tools.py +998 -0
  30. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/tui.py +83 -26
  31. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/validate.py +54 -20
  32. chad_code-2.0.0/src/chad_code.egg-info/PKG-INFO +333 -0
  33. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad_code.egg-info/SOURCES.txt +8 -21
  34. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad_code.egg-info/requires.txt +0 -1
  35. chad_code-2.0.0/tests/test_agent.py +255 -0
  36. {chad_code-1.13.0 → chad_code-2.0.0}/tests/test_agent_e2e.py +118 -256
  37. {chad_code-1.13.0 → chad_code-2.0.0}/tests/test_agent_guards.py +41 -531
  38. chad_code-2.0.0/tests/test_ambient.py +373 -0
  39. {chad_code-1.13.0 → chad_code-2.0.0}/tests/test_cli.py +221 -155
  40. {chad_code-1.13.0 → chad_code-2.0.0}/tests/test_compaction.py +185 -1
  41. {chad_code-1.13.0 → chad_code-2.0.0}/tests/test_confirm_preview.py +0 -9
  42. {chad_code-1.13.0 → chad_code-2.0.0}/tests/test_edit.py +9 -65
  43. {chad_code-1.13.0 → chad_code-2.0.0}/tests/test_engine.py +134 -11
  44. chad_code-2.0.0/tests/test_engine_dflash.py +390 -0
  45. {chad_code-1.13.0 → chad_code-2.0.0}/tests/test_engine_kvquant.py +50 -0
  46. chad_code-2.0.0/tests/test_engine_pld_wide.py +162 -0
  47. {chad_code-1.13.0 → chad_code-2.0.0}/tests/test_gate.py +4 -12
  48. {chad_code-1.13.0 → chad_code-2.0.0}/tests/test_ignore.py +0 -3
  49. {chad_code-1.13.0 → chad_code-2.0.0}/tests/test_intent.py +49 -7
  50. chad_code-2.0.0/tests/test_levers.py +90 -0
  51. {chad_code-1.13.0 → chad_code-2.0.0}/tests/test_mcp.py +6 -5
  52. {chad_code-1.13.0 → chad_code-2.0.0}/tests/test_mlx_fastpath.py +59 -26
  53. chad_code-2.0.0/tests/test_mlx_qmm_mma.py +136 -0
  54. chad_code-2.0.0/tests/test_mlx_qsdpa.py +569 -0
  55. {chad_code-1.13.0 → chad_code-2.0.0}/tests/test_render.py +14 -26
  56. chad_code-2.0.0/tests/test_repomap.py +198 -0
  57. chad_code-2.0.0/tests/test_repomap_polyglot.py +72 -0
  58. {chad_code-1.13.0 → chad_code-2.0.0}/tests/test_seatbelt.py +23 -26
  59. {chad_code-1.13.0 → chad_code-2.0.0}/tests/test_skills.py +71 -47
  60. chad_code-2.0.0/tests/test_spill.py +106 -0
  61. chad_code-2.0.0/tests/test_syntaxgate.py +127 -0
  62. chad_code-2.0.0/tests/test_toolcall_dialect.py +84 -0
  63. {chad_code-1.13.0 → chad_code-2.0.0}/tests/test_toolcall_parse.py +43 -9
  64. chad_code-2.0.0/tests/test_tools.py +442 -0
  65. {chad_code-1.13.0 → chad_code-2.0.0}/tests/test_tui.py +42 -0
  66. chad_code-2.0.0/tests/test_validate.py +202 -0
  67. chad_code-1.13.0/PKG-INFO +0 -264
  68. chad_code-1.13.0/README.md +0 -228
  69. chad_code-1.13.0/src/chad/ambient.py +0 -453
  70. chad_code-1.13.0/src/chad/compaction.py +0 -293
  71. chad_code-1.13.0/src/chad/guardrails.py +0 -1655
  72. chad_code-1.13.0/src/chad/levers.py +0 -675
  73. chad_code-1.13.0/src/chad/lsp.py +0 -340
  74. chad_code-1.13.0/src/chad/lspclient.py +0 -341
  75. chad_code-1.13.0/src/chad/lspservers.py +0 -157
  76. chad_code-1.13.0/src/chad/mlx_moe_fused.py +0 -657
  77. chad_code-1.13.0/src/chad/mlx_qsdpa.py +0 -782
  78. chad_code-1.13.0/src/chad/profiles.py +0 -77
  79. chad_code-1.13.0/src/chad/repomap.py +0 -1105
  80. chad_code-1.13.0/src/chad/symbols.py +0 -165
  81. chad_code-1.13.0/src/chad/syntaxgate.py +0 -630
  82. chad_code-1.13.0/src/chad/tools.py +0 -2294
  83. chad_code-1.13.0/src/chad_code.egg-info/PKG-INFO +0 -264
  84. chad_code-1.13.0/tests/test_agent.py +0 -119
  85. chad_code-1.13.0/tests/test_ambient.py +0 -277
  86. chad_code-1.13.0/tests/test_compact_notice.py +0 -195
  87. chad_code-1.13.0/tests/test_done_audit.py +0 -638
  88. chad_code-1.13.0/tests/test_drift_warn.py +0 -127
  89. chad_code-1.13.0/tests/test_edit_corruption.py +0 -154
  90. chad_code-1.13.0/tests/test_garble_invariant.py +0 -86
  91. chad_code-1.13.0/tests/test_lever_bite.py +0 -971
  92. chad_code-1.13.0/tests/test_lever_instrumentation.py +0 -95
  93. chad_code-1.13.0/tests/test_levers.py +0 -147
  94. chad_code-1.13.0/tests/test_lsp.py +0 -315
  95. chad_code-1.13.0/tests/test_lsp_live.py +0 -108
  96. chad_code-1.13.0/tests/test_lspclient.py +0 -205
  97. chad_code-1.13.0/tests/test_mlx_moe_fused.py +0 -354
  98. chad_code-1.13.0/tests/test_mlx_qsdpa.py +0 -292
  99. chad_code-1.13.0/tests/test_plan_review.py +0 -95
  100. chad_code-1.13.0/tests/test_replace_lines.py +0 -424
  101. chad_code-1.13.0/tests/test_repomap.py +0 -494
  102. chad_code-1.13.0/tests/test_repomap_polyglot.py +0 -142
  103. chad_code-1.13.0/tests/test_subagent.py +0 -492
  104. chad_code-1.13.0/tests/test_symbols.py +0 -277
  105. chad_code-1.13.0/tests/test_syntaxgate.py +0 -191
  106. chad_code-1.13.0/tests/test_tools.py +0 -673
  107. chad_code-1.13.0/tests/test_validate.py +0 -246
  108. {chad_code-1.13.0 → chad_code-2.0.0}/LICENSE +0 -0
  109. {chad_code-1.13.0 → chad_code-2.0.0}/setup.cfg +0 -0
  110. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/atif.py +0 -0
  111. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/base_engine.py +0 -0
  112. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/bench.py +0 -0
  113. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/diag.py +0 -0
  114. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/ignore.py +0 -0
  115. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/mcp.py +0 -0
  116. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/mcp_oauth.py +0 -0
  117. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/parakeet/LICENSE +0 -0
  118. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/parakeet/__init__.py +0 -0
  119. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/parakeet/alignment.py +0 -0
  120. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/parakeet/attention.py +0 -0
  121. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/parakeet/audio.py +0 -0
  122. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/parakeet/cache.py +0 -0
  123. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/parakeet/conformer.py +0 -0
  124. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/parakeet/ctc.py +0 -0
  125. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/parakeet/parakeet.py +0 -0
  126. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/parakeet/rnnt.py +0 -0
  127. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/parakeet/tokenizer.py +0 -0
  128. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/parakeet/utils.py +0 -0
  129. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/session.py +0 -0
  130. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad/speech.py +0 -0
  131. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad_code.egg-info/dependency_links.txt +0 -0
  132. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad_code.egg-info/entry_points.txt +0 -0
  133. {chad_code-1.13.0 → chad_code-2.0.0}/src/chad_code.egg-info/top_level.txt +0 -0
  134. {chad_code-1.13.0 → chad_code-2.0.0}/tests/test_atif.py +0 -0
  135. {chad_code-1.13.0 → chad_code-2.0.0}/tests/test_bench.py +0 -0
  136. {chad_code-1.13.0 → chad_code-2.0.0}/tests/test_checkpoint.py +0 -0
  137. {chad_code-1.13.0 → chad_code-2.0.0}/tests/test_completion_engine.py +0 -0
  138. {chad_code-1.13.0 → chad_code-2.0.0}/tests/test_config.py +0 -0
  139. {chad_code-1.13.0 → chad_code-2.0.0}/tests/test_engine_pld_hybrid.py +0 -0
  140. {chad_code-1.13.0 → chad_code-2.0.0}/tests/test_feel_pack.py +0 -0
  141. {chad_code-1.13.0 → chad_code-2.0.0}/tests/test_log_redaction.py +0 -0
  142. {chad_code-1.13.0 → chad_code-2.0.0}/tests/test_mcp_oauth.py +0 -0
  143. {chad_code-1.13.0 → chad_code-2.0.0}/tests/test_prove.py +0 -0
  144. {chad_code-1.13.0 → chad_code-2.0.0}/tests/test_serve.py +0 -0
  145. {chad_code-1.13.0 → chad_code-2.0.0}/tests/test_session.py +0 -0
  146. {chad_code-1.13.0 → chad_code-2.0.0}/tests/test_speech.py +0 -0
  147. {chad_code-1.13.0 → chad_code-2.0.0}/tests/test_speech_tui.py +0 -0
@@ -0,0 +1,333 @@
1
+ Metadata-Version: 2.4
2
+ Name: chad-code
3
+ Version: 2.0.0
4
+ Summary: Claude-Code-style coding agent that runs entirely on a 24 GB Apple Silicon Mac (MLX, Qwen3.8-27B)
5
+ License-Expression: MIT
6
+ Project-URL: Repository, https://github.com/nathansutton/chad
7
+ Project-URL: Issues, https://github.com/nathansutton/chad/issues
8
+ Keywords: mlx,coding-agent,apple-silicon,local-llm,tui
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: MacOS
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Topic :: Software Development :: Code Generators
17
+ Requires-Python: >=3.11
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: mlx<0.33,>=0.32.0; sys_platform == "darwin"
21
+ Requires-Dist: mlx-lm<0.32,>=0.31.3; sys_platform == "darwin"
22
+ Requires-Dist: transformers<5.13,>=5.0
23
+ Requires-Dist: jinja2>=3.1
24
+ Requires-Dist: numpy<3,>=1.26
25
+ Requires-Dist: huggingface-hub>=1.22.0
26
+ Requires-Dist: prompt_toolkit>=3.0.43
27
+ Requires-Dist: tree-sitter>=0.26.0
28
+ Requires-Dist: tree-sitter-language-pack>=1.12.2
29
+ Requires-Dist: mcp<3,>=2
30
+ Provides-Extra: highlight
31
+ Requires-Dist: pygments>=2.17; extra == "highlight"
32
+ Provides-Extra: speech
33
+ Requires-Dist: sounddevice>=0.5; extra == "speech"
34
+ Dynamic: license-file
35
+
36
+ # chad — a Claude-Code-style coding agent that fits on a 24 GB MacBook
37
+
38
+ [![tests](https://github.com/nathansutton/chad/actions/workflows/tests.yml/badge.svg)](https://github.com/nathansutton/chad/actions/workflows/tests.yml)
39
+
40
+ > Claude can do anything, for anyone, anywhere. chad does one thing. 🗿
41
+ > *Coding under supervision.*
42
+
43
+ A single-user coding agent that runs **entirely on a 24 GB Apple Silicon laptop** via
44
+ [MLX](https://github.com/ml-explore/mlx). Plan mode, permission modes, a full-screen TUI,
45
+ `@file` mentions, skills, MCP — the Claude Code workflow — driven by one good open model on
46
+ your SSD. No Docker, no API key, no model picker, nothing leaves the machine. (Not
47
+ affiliated with Anthropic; the shape is borrowed, the code is not.)
48
+
49
+ ## Quickstart
50
+
51
+ Apple Silicon Mac + [uv](https://docs.astral.sh/uv/). One command — no clone, no config:
52
+
53
+ ```bash
54
+ uvx chad-code # runs chad anywhere — the command is still `chad`
55
+ uvx chad-code prove # offline smoke test: 4 tiny fix-it tasks, verified, timed 🗿
56
+ ```
57
+
58
+ First run asks, then downloads the model once (~13 GB, resumable) into the shared Hugging
59
+ Face cache. While it downloads, `cd` into a project and think of a scoped first ask —
60
+ *"fix the failing test in `tests/test_x.py`"* lands; *"improve my codebase"* flails.
61
+
62
+ > **chad targets 24 GB Apple Silicon and nothing smaller.** It runs below that and will
63
+ > tell you it is doing so, but the model needs ~13 GB resident before a single token of
64
+ > context, so a 16 GB Mac gets a window too small to work in.
65
+
66
+ > The PyPI package is **`chad-code`**; bare `chad` is an unrelated squatted package. Other
67
+ > ways in (PATH install, bleeding-edge `main`, dev clone) are in
68
+ > [Installing & upgrading](#installing--upgrading).
69
+
70
+ ## What chad is
71
+
72
+ The open models got good. [Qwen3.8-27B](https://huggingface.co/Qwen/Qwen3.8-27B) writes
73
+ correct code, uses a shell properly, and fits — quantized — on a laptop. Nobody needs
74
+ convincing of that anymore. What a laptop still lacks is a harness that makes that model
75
+ **feel** like the tool you already use. chad is that harness, and it is built around two
76
+ commitments:
77
+
78
+ 1. **A Claude-Code-shaped developer experience for one scoped task at a time.** The same
79
+ TUI moves — shift-tab through `normal` / `auto-accept` / `yolo` / `plan`, an approval
80
+ prompt that shows the whole command, `@file`, `!cmd`, `/undo`, Agent Skills, MCP — so
81
+ nothing has to be relearned. One model, one command, zero decisions before the first ask.
82
+ 2. **A 24 GB MacBook Pro is the whole machine.** ~13 GB of weights plus a ~4 GB prefill
83
+ transient leaves no slack, so the context window is sized from *live* memory, and every
84
+ token of it is spent on purpose: a ~2.8k-token system prompt, tool output clipped with a path back to
85
+ the full body, a todo list that is a markdown checklist, no skill catalog in the prompt.
86
+
87
+ Everything that serves those two commitments got the engineering budget; everything else is
88
+ deliberately plain.
89
+
90
+ **Where the budget went — tokens per second.** A dense 27B at 3-bit streams ~12 GB of
91
+ weights per token, which on an M4 Pro is ~18 tok/s serially, and an agent that decodes at
92
+ 18 tok/s while re-reading its transcript every step is a batch job, not a pair programmer.
93
+ chad owns its inference loop instead of talking to a server, and the speed comes from three
94
+ things fitted to the shipped checkpoint:
95
+
96
+ - **DFlash2 block speculation** — a bundled 1.9B drafter ([z-lab's DFlash2](https://huggingface.co/z-lab/Qwen3.8-27B-DFlash2),
97
+ ported to MLX and quantized) proposes a whole block of tokens from the main model's own
98
+ hidden states; the main model verifies the block in one batched forward; exact rejection
99
+ sampling keeps every emitted token the model's own. Decode goes ~3× on prose and code,
100
+ ~2× on real mid-session agent contexts.
101
+ - **A persistent prefix KV cache** — the transcript is kept a strict token-prefix of the
102
+ live cache, so a follow-up step prefills the ~16 tokens it appended, not the 5,000 it
103
+ already read: **~0.75 s per step instead of ~50 s**. Any server with prompt caching gets
104
+ the easy case; the work is keeping it true across compaction, truncated turns and
105
+ restarts — the stable system prefix is checkpointed to disk, so the second session in a
106
+ project starts warm (75.6 s → 5.5 s to the first tool call).
107
+ - **Fused Metal kernels** — quantized-KV attention, a small-M matmul for speculative
108
+ verify, a compiled single-token layer step — chosen per machine at load time, no knobs.
109
+
110
+ **Where it didn't — everything else is KISS.** Five tools: `bash`, `edit`, `write`,
111
+ `write_todos`, `done`. `bash` is the primary tool because the model already knows `rg`,
112
+ `sed -n`, and your test runner from pretraining, and a dedicated `read`/`grep`/`glob` family
113
+ measured no better than the shell it replaced. The todo list is a checklist the model
114
+ copies forward and ticks. There are no behavioral levers to tune. The whole harness is
115
+ readable in an afternoon.
116
+
117
+ **What it isn't.** A 3-bit 27B is not a frontier model: it lands a scoped ask and flails on
118
+ a vague one. The usable window on 24 GB is tens of thousands of tokens, not the 262k on the
119
+ label. The first turn in a *new* project spends over a minute prefilling before the warm
120
+ start exists. Thinking is on by default and is most of what it generates. It needs a 24 GB
121
+ Apple Silicon Mac, full stop. The docs quote no benchmark scores — only throughput you can
122
+ reproduce on that Mac — because that is the only machine the claim is about.
123
+
124
+ ## chad is not a baby Claude
125
+
126
+ He has some of the same moves — tool use, plan mode, a real TUI — but he's a blunter
127
+ instrument:
128
+
129
+ | | **Claude** | **chad** 🗿 |
130
+ |-----------------|-------------------------------------------------|---------------------------------|
131
+ | **Range** | every workflow, every person, incredible nuance | one job: code, on your machine |
132
+ | **Runs** | anywhere — cloud, IDE, terminal, phone | your mac. that's it. |
133
+ | **Brain** | a frontier model in a datacenter | one 27B on your SSD |
134
+ | **Disposition** | understands what you *meant* | does what you *said* |
135
+ | **Harness** | open-ended, anything you can imagine | plan. execute. nothing else. |
136
+ | **When wrong** | reasons a way out | already shipped |
137
+
138
+ ![chad fixing a failing test end to end — reason, read, edit, run pytest, confirm green, all on a local model](docs/demo.gif)
139
+
140
+ > Real session, unedited (the silent prefill is cut): a local model finds the cent that
141
+ > floor division loses, fixes it, and verifies itself — then your own `pytest`, in your
142
+ > own shell. Recorded with `--yolo` so nothing pauses for a keypress; the default mode
143
+ > stops and asks before every edit and every command.
144
+
145
+ ## Same model, same Mac, stock engine
146
+
147
+ The fair question is not "how does chad compare to Claude" — it doesn't — but "what do I
148
+ gain over pointing a generic local-model tool at the same weights?" So: Qwen3.8-27B at the
149
+ same `UD-Q3_K_XL` recipe (Unsloth's GGUF for llama.cpp, chad's MLX conversion of the same
150
+ bit map), the same M4 Pro (24 GB), one engine resident at a time, each measured with its own
151
+ benchmark on a 512-token prompt and a 128-token generation. Ollama is not a separate row:
152
+ it is llama.cpp underneath, with no speculative decoding for this model — measured once on
153
+ the same GGUF (0.32.15, Modelfile `FROM` only): 96 tok/s prefill, **10.9** decode, the
154
+ same number.
155
+
156
+ | Engine | Prefill (512-tok prompt) | Decode (128 tok) | Speculative decoding |
157
+ |---|---|---|---|
158
+ | llama.cpp `llama-bench` (stock, build 10470) | 102 tok/s | 10.9 tok/s | none for this model |
159
+ | **chad**, serial (`CHAD_NO_DFLASH=1`) | 99 tok/s | 18.1 tok/s | off |
160
+ | **chad**, default | 98 tok/s | **62 tok/s**¹ | DFlash2 block drafter |
161
+
162
+ ¹ `chad-bench`'s prompt is tiled code the drafter reads easily, so 62 is a ceiling. On ten
163
+ real mid-session contexts replayed from `~/.chad/sessions` (12–19k tokens, tool results and
164
+ schemas in place, 384-token decodes) the same engine measures **31.7 tok/s median / 21.4
165
+ floor** greedy against 14.8 serial, and **27.6 / 17.7** at the default thinking preset
166
+ against 13.9 — much of a real turn is `<think>`, where the drafter lands 35–55% of its
167
+ proposals. That ~2× is the number a session lives at. The serial gap to llama.cpp is
168
+ partly bytes (the MLX quant is ~12 GB against the GGUF's 13.1) and partly chad's fused
169
+ single-token kernels; llama.cpp's Metal path for this hybrid architecture was not profiled,
170
+ so read that row as what the fitted engine buys, not as a verdict on llama.cpp.
171
+
172
+ Method, the longer runs and the caveats are in
173
+ [Throughput & performance](docs/benchmarks.md#same-model-same-mac-stock-engine); the rows
174
+ are committed under [`benchmarks/stock/_runs/`](benchmarks/stock/); reproduce them with
175
+ `uv run python benchmarks/stock/stock.py {llama,chad}`.
176
+
177
+ ## Installing & upgrading
178
+
179
+ The one-line quickstart (`uvx chad-code`) is up top. The other ways in:
180
+
181
+ ```bash
182
+ uv tool install chad-code # install for good — then it's just `chad`
183
+ uvx --from git+https://github.com/nathansutton/chad chad # bleeding-edge main, no clone
184
+ ```
185
+
186
+ Or from a clone (the dev path):
187
+
188
+ ```bash
189
+ uv sync # install deps + the `chad` entrypoint (one time)
190
+ uv run chad # full-screen TUI
191
+ uv run chad "add a --json flag to main.py and update the tests" # one-shot, headless
192
+ uv run chad -c # resume this directory's last conversation
193
+ ```
194
+
195
+ **Optional extras.** Everything core is in the base install; two features are opt-in
196
+ because they pull deps not every install wants — `speech` (voice mode: a mic library,
197
+ no torch) and `highlight` (syntax colour in diffs/previews). An extra rides on the
198
+ **install spec**, not on a separate command, so how you add it depends on how you
199
+ installed chad:
200
+
201
+ ```bash
202
+ uv tool install --force 'chad-code[speech]' # add to an existing `uv tool` install
203
+ uvx --from 'chad-code[speech]' chad # one-off run, nothing installed
204
+ uv sync --extra speech # from a clone
205
+ ```
206
+
207
+ `/speech` in the TUI prints whichever of those matches your install, so you never have
208
+ to work it out from here.
209
+
210
+ **The model.** chad ships exactly one, downloaded once into the shared Hugging Face cache
211
+ (`~/.cache/huggingface`, reused across every project). There is no picker and no size tier.
212
+
213
+ | Model | Quant | Footprint |
214
+ |---|---|---|
215
+ | [Qwen3.8-27B `UD-Q3_K_XL-DFlash2`](https://huggingface.co/nathansutton/Qwen3.8-27B-UD-Q3_K_XL-DFlash2-MLX) | 3-bit group-64 body, 5-bit `lm_head`, bundled 4-bit DFlash2 drafter | ~13 GB resident, 262k native context |
216
+
217
+ Qwen3.8-27B is **dense** (64 layers: 48 GatedDeltaNet + 16 full attention), so every
218
+ parameter is on the critical path for every token and the quant is where decode speed comes
219
+ from. The bits go where measurement says they pay: `lm_head` is a second full 1.27B-param
220
+ tensor and is held at 5-bit; `embed_tokens`, a lookup table whose error never compounds
221
+ through a matmul, is cheapest. The name follows
222
+ [Unsloth's dynamic-quant convention](https://docs.unsloth.ai/) (`UD-…`); the quant itself is
223
+ MLX group-64 affine, not a llama.cpp k-quant. The DFlash2 drafter ships inside the same repo,
224
+ pre-quantized — one download, and speculative decoding is simply there.
225
+
226
+ **24 GB is the floor.** The context window is sized from the live Metal budget, not a
227
+ constant, so a tighter box narrows its window rather than dying — but ~13 GB of weights
228
+ plus a ~4.3 GB prefill transient spend most of a 24 GB budget before the first cached
229
+ token, which is why a 24 GB Mac lands in the tens of thousands of tokens rather than the
230
+ model's 262k. The banner states what you actually got. Below 24 GB chad warns and proceeds;
231
+ it does not gate you, but it cannot give you a usable window either.
232
+
233
+ `--model <repo or local dir>` runs different weights through the same engine, and stays a
234
+ first-class escape hatch: shipping one model is a default, not a cage. What you give up is
235
+ fit, not function — the block drafter, the fused-attention coverage, the decode fastpath
236
+ and the context governor are all fitted to the shipped checkpoint, so other weights are
237
+ slower rather than broken. The flag takes a repo id or a directory; there are no size
238
+ shorthands.
239
+
240
+ **Upgrading** — depends on how you installed: `uv tool upgrade chad-code`; `uvx --refresh
241
+ chad-code`; or `git pull && uv sync` for a clone. What changed lands in
242
+ [`CHANGELOG.md`](CHANGELOG.md). Model weights are versioned separately — a code upgrade never
243
+ re-downloads the model.
244
+
245
+ **Development.** `uv sync` once, then `uv run pytest -q` — the fast unit gate loads **no
246
+ model weights**, runs in seconds, and is what CI runs. Throughput on your own machine:
247
+ `uv run chad-bench` (see [Throughput & performance](docs/benchmarks.md)).
248
+
249
+ ## Interactive UX
250
+
251
+ `uv run chad` launches a full-screen terminal UI (built on prompt_toolkit):
252
+
253
+ - **shift-tab cycles permission modes** — `normal` (confirm each bash/write/edit) →
254
+ `auto-accept edits` (edits land silently; **terminal commands still ask**) → `yolo`
255
+ (nothing asks) → `plan mode` (read-only: investigate + propose a numbered plan) → back.
256
+ - **the approval prompt shows what you're approving** — the full command on its own lines
257
+ above the input, not a clipped single line. Approving blind isn't approving.
258
+ - **type-ahead message queue** — keep typing while the agent works; messages run in order.
259
+ - **ctrl-c interrupts the running turn** without killing the session.
260
+ - **live status line** — model, mode, context %, a state glyph + verb, elapsed seconds, and
261
+ **↑prefilled / ↓generated** token counts (with an advancing **%** on an unavoidable full
262
+ re-prefill, so it's never silent).
263
+ - **slash commands** — `/init`, `/skills`, `/<skill>`, `/mcp`, `/accept`, `/resume`,
264
+ `/compact`, `/ctx` (where the window is going, split into system prompt, tool schemas,
265
+ think residue and tool results), `/model`, `/mode`, `/help`, `/exit`, and the two that
266
+ undo the agent:
267
+ **`/undo`** reverts your files to the last edit checkpoint and **`/restore`** lists the
268
+ checkpoints so you can revert to any of them. Same set in the `--repl` line interface.
269
+ - **`@file` / `@dir` mentions** and **`!command` shell passthrough** — pull a file into
270
+ context inline, or run a shell command without invoking the model.
271
+ - **voice mode, all local** — `/speech`, then ctrl-t to talk: Parakeet-on-MLX transcribes
272
+ into the input box for you to review before Enter sends it (esc discards a take);
273
+ replies are read aloud via macOS `say`. A pre-roll buffer means your first syllable
274
+ isn't clipped, and a personal word table (`~/.chad/speech_words.json`) teaches it your
275
+ identifiers — `{"pie test": "pytest"}`. Dictation cost is linear in take length, so a
276
+ long thought is fine; `/speech` off releases both the mic and the weights. Nothing
277
+ leaves the machine. Needs the `speech` extra — just a mic library; no torch, no numba
278
+ (see [Installing & upgrading](#installing--upgrading); on a `uv tool` install that's
279
+ `uv tool install --force 'chad-code[speech]'`).
280
+
281
+ **Usage.** `uv run chad --help` is the source of truth:
282
+
283
+ | Flag | What it does |
284
+ |---|---|
285
+ | `-c, --continue` | resume this directory's **most recent** session (non-destructive) |
286
+ | `--resume` | list recent sessions, pick one by number (interactive TTY only) |
287
+ | `--plan` | start in read-only plan mode (investigate + propose, edits blocked) |
288
+ | `--yolo` | auto-approve bash/write/edit (skip confirm prompts) |
289
+ | `--no-think` | skip the model's `<think>` blocks — faster on well-scoped work |
290
+ | `--model` | `auto` (the shipped default), or any HF repo id / local model dir |
291
+ | `--repl` | plain line REPL instead of the TUI |
292
+
293
+ Plus three subcommands, each with its own `--help`:
294
+
295
+ | Command | What it does |
296
+ |---|---|
297
+ | `chad serve` | serve this Mac's model to a container or the LAN ([Configuration](docs/configuration.md#serving-the-local-model-to-a-container-chad-serve)) |
298
+ | `chad prove` | offline smoke test: 4 tiny fix-it tasks, verified, timed |
299
+ | `chad levers` | print the result-channel lever registry as JSON (for A/B ablation) |
300
+
301
+ A headless task (positional, or piped with no TTY) auto-approves mutating tools; the model
302
+ runs greedy (temp 0). Every conversation is persisted under `~/.chad/sessions/`, and every
303
+ resume forks a new branch rather than overwriting — details in
304
+ [Configuration](docs/configuration.md#sessions). The rarely-touched tuning knobs
305
+ (`CHAD_MAX_CONTEXT`, `CHAD_KV_BITS`, turn-budget/think-cap, safety opt-outs) all live in
306
+ environment variables, fully documented there.
307
+
308
+ ## Extending chad
309
+
310
+ chad speaks the same two extension formats as Claude Code:
311
+
312
+ - **[Agent Skills](https://agentskills.io)** — drop a `SKILL.md` folder in `./.claude/skills/`
313
+ and it becomes a slash command: `/ship`, `/investigate the flaky test`. Skills cost
314
+ nothing until you run one — chad puts no skill catalog in the system prompt.
315
+ - **[MCP servers](https://modelcontextprotocol.io)** — configure stdio or HTTP servers in
316
+ `./.mcp.json` to expose external tools (GitHub, Postgres, Linear, Slack, …) alongside
317
+ chad's builtins, with static-token and OAuth auth.
318
+
319
+ Both are covered in full in the [Configuration reference](docs/configuration.md).
320
+
321
+ ## Documentation
322
+
323
+ - **[Design & internals](docs/design.md)** — why prefill is the bill, the persistent prefix
324
+ cache, the trimmable/append-only trade, why the tool surface is five tools, and the ideas
325
+ borrowed from other agents.
326
+ - **[Throughput & performance](docs/benchmarks.md)** — prefill / decode / warm-step
327
+ numbers you can reproduce with `chad-bench`, the stock-engine comparison, and what the
328
+ cross-session warm start is worth.
329
+ - **[Configuration reference](docs/configuration.md)** — Agent Skills, MCP servers, the
330
+ context window, every environment variable, and the safety opt-outs.
331
+ - **[Troubleshooting](docs/troubleshooting.md)** — when a session rambles, loops, or slows:
332
+ the symptom→knob map for a small local model.
333
+ - **[Contributing](CONTRIBUTING.md)** — what lands easily, and what needs a conversation first.
@@ -0,0 +1,298 @@
1
+ # chad — a Claude-Code-style coding agent that fits on a 24 GB MacBook
2
+
3
+ [![tests](https://github.com/nathansutton/chad/actions/workflows/tests.yml/badge.svg)](https://github.com/nathansutton/chad/actions/workflows/tests.yml)
4
+
5
+ > Claude can do anything, for anyone, anywhere. chad does one thing. 🗿
6
+ > *Coding under supervision.*
7
+
8
+ A single-user coding agent that runs **entirely on a 24 GB Apple Silicon laptop** via
9
+ [MLX](https://github.com/ml-explore/mlx). Plan mode, permission modes, a full-screen TUI,
10
+ `@file` mentions, skills, MCP — the Claude Code workflow — driven by one good open model on
11
+ your SSD. No Docker, no API key, no model picker, nothing leaves the machine. (Not
12
+ affiliated with Anthropic; the shape is borrowed, the code is not.)
13
+
14
+ ## Quickstart
15
+
16
+ Apple Silicon Mac + [uv](https://docs.astral.sh/uv/). One command — no clone, no config:
17
+
18
+ ```bash
19
+ uvx chad-code # runs chad anywhere — the command is still `chad`
20
+ uvx chad-code prove # offline smoke test: 4 tiny fix-it tasks, verified, timed 🗿
21
+ ```
22
+
23
+ First run asks, then downloads the model once (~13 GB, resumable) into the shared Hugging
24
+ Face cache. While it downloads, `cd` into a project and think of a scoped first ask —
25
+ *"fix the failing test in `tests/test_x.py`"* lands; *"improve my codebase"* flails.
26
+
27
+ > **chad targets 24 GB Apple Silicon and nothing smaller.** It runs below that and will
28
+ > tell you it is doing so, but the model needs ~13 GB resident before a single token of
29
+ > context, so a 16 GB Mac gets a window too small to work in.
30
+
31
+ > The PyPI package is **`chad-code`**; bare `chad` is an unrelated squatted package. Other
32
+ > ways in (PATH install, bleeding-edge `main`, dev clone) are in
33
+ > [Installing & upgrading](#installing--upgrading).
34
+
35
+ ## What chad is
36
+
37
+ The open models got good. [Qwen3.8-27B](https://huggingface.co/Qwen/Qwen3.8-27B) writes
38
+ correct code, uses a shell properly, and fits — quantized — on a laptop. Nobody needs
39
+ convincing of that anymore. What a laptop still lacks is a harness that makes that model
40
+ **feel** like the tool you already use. chad is that harness, and it is built around two
41
+ commitments:
42
+
43
+ 1. **A Claude-Code-shaped developer experience for one scoped task at a time.** The same
44
+ TUI moves — shift-tab through `normal` / `auto-accept` / `yolo` / `plan`, an approval
45
+ prompt that shows the whole command, `@file`, `!cmd`, `/undo`, Agent Skills, MCP — so
46
+ nothing has to be relearned. One model, one command, zero decisions before the first ask.
47
+ 2. **A 24 GB MacBook Pro is the whole machine.** ~13 GB of weights plus a ~4 GB prefill
48
+ transient leaves no slack, so the context window is sized from *live* memory, and every
49
+ token of it is spent on purpose: a ~2.8k-token system prompt, tool output clipped with a path back to
50
+ the full body, a todo list that is a markdown checklist, no skill catalog in the prompt.
51
+
52
+ Everything that serves those two commitments got the engineering budget; everything else is
53
+ deliberately plain.
54
+
55
+ **Where the budget went — tokens per second.** A dense 27B at 3-bit streams ~12 GB of
56
+ weights per token, which on an M4 Pro is ~18 tok/s serially, and an agent that decodes at
57
+ 18 tok/s while re-reading its transcript every step is a batch job, not a pair programmer.
58
+ chad owns its inference loop instead of talking to a server, and the speed comes from three
59
+ things fitted to the shipped checkpoint:
60
+
61
+ - **DFlash2 block speculation** — a bundled 1.9B drafter ([z-lab's DFlash2](https://huggingface.co/z-lab/Qwen3.8-27B-DFlash2),
62
+ ported to MLX and quantized) proposes a whole block of tokens from the main model's own
63
+ hidden states; the main model verifies the block in one batched forward; exact rejection
64
+ sampling keeps every emitted token the model's own. Decode goes ~3× on prose and code,
65
+ ~2× on real mid-session agent contexts.
66
+ - **A persistent prefix KV cache** — the transcript is kept a strict token-prefix of the
67
+ live cache, so a follow-up step prefills the ~16 tokens it appended, not the 5,000 it
68
+ already read: **~0.75 s per step instead of ~50 s**. Any server with prompt caching gets
69
+ the easy case; the work is keeping it true across compaction, truncated turns and
70
+ restarts — the stable system prefix is checkpointed to disk, so the second session in a
71
+ project starts warm (75.6 s → 5.5 s to the first tool call).
72
+ - **Fused Metal kernels** — quantized-KV attention, a small-M matmul for speculative
73
+ verify, a compiled single-token layer step — chosen per machine at load time, no knobs.
74
+
75
+ **Where it didn't — everything else is KISS.** Five tools: `bash`, `edit`, `write`,
76
+ `write_todos`, `done`. `bash` is the primary tool because the model already knows `rg`,
77
+ `sed -n`, and your test runner from pretraining, and a dedicated `read`/`grep`/`glob` family
78
+ measured no better than the shell it replaced. The todo list is a checklist the model
79
+ copies forward and ticks. There are no behavioral levers to tune. The whole harness is
80
+ readable in an afternoon.
81
+
82
+ **What it isn't.** A 3-bit 27B is not a frontier model: it lands a scoped ask and flails on
83
+ a vague one. The usable window on 24 GB is tens of thousands of tokens, not the 262k on the
84
+ label. The first turn in a *new* project spends over a minute prefilling before the warm
85
+ start exists. Thinking is on by default and is most of what it generates. It needs a 24 GB
86
+ Apple Silicon Mac, full stop. The docs quote no benchmark scores — only throughput you can
87
+ reproduce on that Mac — because that is the only machine the claim is about.
88
+
89
+ ## chad is not a baby Claude
90
+
91
+ He has some of the same moves — tool use, plan mode, a real TUI — but he's a blunter
92
+ instrument:
93
+
94
+ | | **Claude** | **chad** 🗿 |
95
+ |-----------------|-------------------------------------------------|---------------------------------|
96
+ | **Range** | every workflow, every person, incredible nuance | one job: code, on your machine |
97
+ | **Runs** | anywhere — cloud, IDE, terminal, phone | your mac. that's it. |
98
+ | **Brain** | a frontier model in a datacenter | one 27B on your SSD |
99
+ | **Disposition** | understands what you *meant* | does what you *said* |
100
+ | **Harness** | open-ended, anything you can imagine | plan. execute. nothing else. |
101
+ | **When wrong** | reasons a way out | already shipped |
102
+
103
+ ![chad fixing a failing test end to end — reason, read, edit, run pytest, confirm green, all on a local model](docs/demo.gif)
104
+
105
+ > Real session, unedited (the silent prefill is cut): a local model finds the cent that
106
+ > floor division loses, fixes it, and verifies itself — then your own `pytest`, in your
107
+ > own shell. Recorded with `--yolo` so nothing pauses for a keypress; the default mode
108
+ > stops and asks before every edit and every command.
109
+
110
+ ## Same model, same Mac, stock engine
111
+
112
+ The fair question is not "how does chad compare to Claude" — it doesn't — but "what do I
113
+ gain over pointing a generic local-model tool at the same weights?" So: Qwen3.8-27B at the
114
+ same `UD-Q3_K_XL` recipe (Unsloth's GGUF for llama.cpp, chad's MLX conversion of the same
115
+ bit map), the same M4 Pro (24 GB), one engine resident at a time, each measured with its own
116
+ benchmark on a 512-token prompt and a 128-token generation. Ollama is not a separate row:
117
+ it is llama.cpp underneath, with no speculative decoding for this model — measured once on
118
+ the same GGUF (0.32.15, Modelfile `FROM` only): 96 tok/s prefill, **10.9** decode, the
119
+ same number.
120
+
121
+ | Engine | Prefill (512-tok prompt) | Decode (128 tok) | Speculative decoding |
122
+ |---|---|---|---|
123
+ | llama.cpp `llama-bench` (stock, build 10470) | 102 tok/s | 10.9 tok/s | none for this model |
124
+ | **chad**, serial (`CHAD_NO_DFLASH=1`) | 99 tok/s | 18.1 tok/s | off |
125
+ | **chad**, default | 98 tok/s | **62 tok/s**¹ | DFlash2 block drafter |
126
+
127
+ ¹ `chad-bench`'s prompt is tiled code the drafter reads easily, so 62 is a ceiling. On ten
128
+ real mid-session contexts replayed from `~/.chad/sessions` (12–19k tokens, tool results and
129
+ schemas in place, 384-token decodes) the same engine measures **31.7 tok/s median / 21.4
130
+ floor** greedy against 14.8 serial, and **27.6 / 17.7** at the default thinking preset
131
+ against 13.9 — much of a real turn is `<think>`, where the drafter lands 35–55% of its
132
+ proposals. That ~2× is the number a session lives at. The serial gap to llama.cpp is
133
+ partly bytes (the MLX quant is ~12 GB against the GGUF's 13.1) and partly chad's fused
134
+ single-token kernels; llama.cpp's Metal path for this hybrid architecture was not profiled,
135
+ so read that row as what the fitted engine buys, not as a verdict on llama.cpp.
136
+
137
+ Method, the longer runs and the caveats are in
138
+ [Throughput & performance](docs/benchmarks.md#same-model-same-mac-stock-engine); the rows
139
+ are committed under [`benchmarks/stock/_runs/`](benchmarks/stock/); reproduce them with
140
+ `uv run python benchmarks/stock/stock.py {llama,chad}`.
141
+
142
+ ## Installing & upgrading
143
+
144
+ The one-line quickstart (`uvx chad-code`) is up top. The other ways in:
145
+
146
+ ```bash
147
+ uv tool install chad-code # install for good — then it's just `chad`
148
+ uvx --from git+https://github.com/nathansutton/chad chad # bleeding-edge main, no clone
149
+ ```
150
+
151
+ Or from a clone (the dev path):
152
+
153
+ ```bash
154
+ uv sync # install deps + the `chad` entrypoint (one time)
155
+ uv run chad # full-screen TUI
156
+ uv run chad "add a --json flag to main.py and update the tests" # one-shot, headless
157
+ uv run chad -c # resume this directory's last conversation
158
+ ```
159
+
160
+ **Optional extras.** Everything core is in the base install; two features are opt-in
161
+ because they pull deps not every install wants — `speech` (voice mode: a mic library,
162
+ no torch) and `highlight` (syntax colour in diffs/previews). An extra rides on the
163
+ **install spec**, not on a separate command, so how you add it depends on how you
164
+ installed chad:
165
+
166
+ ```bash
167
+ uv tool install --force 'chad-code[speech]' # add to an existing `uv tool` install
168
+ uvx --from 'chad-code[speech]' chad # one-off run, nothing installed
169
+ uv sync --extra speech # from a clone
170
+ ```
171
+
172
+ `/speech` in the TUI prints whichever of those matches your install, so you never have
173
+ to work it out from here.
174
+
175
+ **The model.** chad ships exactly one, downloaded once into the shared Hugging Face cache
176
+ (`~/.cache/huggingface`, reused across every project). There is no picker and no size tier.
177
+
178
+ | Model | Quant | Footprint |
179
+ |---|---|---|
180
+ | [Qwen3.8-27B `UD-Q3_K_XL-DFlash2`](https://huggingface.co/nathansutton/Qwen3.8-27B-UD-Q3_K_XL-DFlash2-MLX) | 3-bit group-64 body, 5-bit `lm_head`, bundled 4-bit DFlash2 drafter | ~13 GB resident, 262k native context |
181
+
182
+ Qwen3.8-27B is **dense** (64 layers: 48 GatedDeltaNet + 16 full attention), so every
183
+ parameter is on the critical path for every token and the quant is where decode speed comes
184
+ from. The bits go where measurement says they pay: `lm_head` is a second full 1.27B-param
185
+ tensor and is held at 5-bit; `embed_tokens`, a lookup table whose error never compounds
186
+ through a matmul, is cheapest. The name follows
187
+ [Unsloth's dynamic-quant convention](https://docs.unsloth.ai/) (`UD-…`); the quant itself is
188
+ MLX group-64 affine, not a llama.cpp k-quant. The DFlash2 drafter ships inside the same repo,
189
+ pre-quantized — one download, and speculative decoding is simply there.
190
+
191
+ **24 GB is the floor.** The context window is sized from the live Metal budget, not a
192
+ constant, so a tighter box narrows its window rather than dying — but ~13 GB of weights
193
+ plus a ~4.3 GB prefill transient spend most of a 24 GB budget before the first cached
194
+ token, which is why a 24 GB Mac lands in the tens of thousands of tokens rather than the
195
+ model's 262k. The banner states what you actually got. Below 24 GB chad warns and proceeds;
196
+ it does not gate you, but it cannot give you a usable window either.
197
+
198
+ `--model <repo or local dir>` runs different weights through the same engine, and stays a
199
+ first-class escape hatch: shipping one model is a default, not a cage. What you give up is
200
+ fit, not function — the block drafter, the fused-attention coverage, the decode fastpath
201
+ and the context governor are all fitted to the shipped checkpoint, so other weights are
202
+ slower rather than broken. The flag takes a repo id or a directory; there are no size
203
+ shorthands.
204
+
205
+ **Upgrading** — depends on how you installed: `uv tool upgrade chad-code`; `uvx --refresh
206
+ chad-code`; or `git pull && uv sync` for a clone. What changed lands in
207
+ [`CHANGELOG.md`](CHANGELOG.md). Model weights are versioned separately — a code upgrade never
208
+ re-downloads the model.
209
+
210
+ **Development.** `uv sync` once, then `uv run pytest -q` — the fast unit gate loads **no
211
+ model weights**, runs in seconds, and is what CI runs. Throughput on your own machine:
212
+ `uv run chad-bench` (see [Throughput & performance](docs/benchmarks.md)).
213
+
214
+ ## Interactive UX
215
+
216
+ `uv run chad` launches a full-screen terminal UI (built on prompt_toolkit):
217
+
218
+ - **shift-tab cycles permission modes** — `normal` (confirm each bash/write/edit) →
219
+ `auto-accept edits` (edits land silently; **terminal commands still ask**) → `yolo`
220
+ (nothing asks) → `plan mode` (read-only: investigate + propose a numbered plan) → back.
221
+ - **the approval prompt shows what you're approving** — the full command on its own lines
222
+ above the input, not a clipped single line. Approving blind isn't approving.
223
+ - **type-ahead message queue** — keep typing while the agent works; messages run in order.
224
+ - **ctrl-c interrupts the running turn** without killing the session.
225
+ - **live status line** — model, mode, context %, a state glyph + verb, elapsed seconds, and
226
+ **↑prefilled / ↓generated** token counts (with an advancing **%** on an unavoidable full
227
+ re-prefill, so it's never silent).
228
+ - **slash commands** — `/init`, `/skills`, `/<skill>`, `/mcp`, `/accept`, `/resume`,
229
+ `/compact`, `/ctx` (where the window is going, split into system prompt, tool schemas,
230
+ think residue and tool results), `/model`, `/mode`, `/help`, `/exit`, and the two that
231
+ undo the agent:
232
+ **`/undo`** reverts your files to the last edit checkpoint and **`/restore`** lists the
233
+ checkpoints so you can revert to any of them. Same set in the `--repl` line interface.
234
+ - **`@file` / `@dir` mentions** and **`!command` shell passthrough** — pull a file into
235
+ context inline, or run a shell command without invoking the model.
236
+ - **voice mode, all local** — `/speech`, then ctrl-t to talk: Parakeet-on-MLX transcribes
237
+ into the input box for you to review before Enter sends it (esc discards a take);
238
+ replies are read aloud via macOS `say`. A pre-roll buffer means your first syllable
239
+ isn't clipped, and a personal word table (`~/.chad/speech_words.json`) teaches it your
240
+ identifiers — `{"pie test": "pytest"}`. Dictation cost is linear in take length, so a
241
+ long thought is fine; `/speech` off releases both the mic and the weights. Nothing
242
+ leaves the machine. Needs the `speech` extra — just a mic library; no torch, no numba
243
+ (see [Installing & upgrading](#installing--upgrading); on a `uv tool` install that's
244
+ `uv tool install --force 'chad-code[speech]'`).
245
+
246
+ **Usage.** `uv run chad --help` is the source of truth:
247
+
248
+ | Flag | What it does |
249
+ |---|---|
250
+ | `-c, --continue` | resume this directory's **most recent** session (non-destructive) |
251
+ | `--resume` | list recent sessions, pick one by number (interactive TTY only) |
252
+ | `--plan` | start in read-only plan mode (investigate + propose, edits blocked) |
253
+ | `--yolo` | auto-approve bash/write/edit (skip confirm prompts) |
254
+ | `--no-think` | skip the model's `<think>` blocks — faster on well-scoped work |
255
+ | `--model` | `auto` (the shipped default), or any HF repo id / local model dir |
256
+ | `--repl` | plain line REPL instead of the TUI |
257
+
258
+ Plus three subcommands, each with its own `--help`:
259
+
260
+ | Command | What it does |
261
+ |---|---|
262
+ | `chad serve` | serve this Mac's model to a container or the LAN ([Configuration](docs/configuration.md#serving-the-local-model-to-a-container-chad-serve)) |
263
+ | `chad prove` | offline smoke test: 4 tiny fix-it tasks, verified, timed |
264
+ | `chad levers` | print the result-channel lever registry as JSON (for A/B ablation) |
265
+
266
+ A headless task (positional, or piped with no TTY) auto-approves mutating tools; the model
267
+ runs greedy (temp 0). Every conversation is persisted under `~/.chad/sessions/`, and every
268
+ resume forks a new branch rather than overwriting — details in
269
+ [Configuration](docs/configuration.md#sessions). The rarely-touched tuning knobs
270
+ (`CHAD_MAX_CONTEXT`, `CHAD_KV_BITS`, turn-budget/think-cap, safety opt-outs) all live in
271
+ environment variables, fully documented there.
272
+
273
+ ## Extending chad
274
+
275
+ chad speaks the same two extension formats as Claude Code:
276
+
277
+ - **[Agent Skills](https://agentskills.io)** — drop a `SKILL.md` folder in `./.claude/skills/`
278
+ and it becomes a slash command: `/ship`, `/investigate the flaky test`. Skills cost
279
+ nothing until you run one — chad puts no skill catalog in the system prompt.
280
+ - **[MCP servers](https://modelcontextprotocol.io)** — configure stdio or HTTP servers in
281
+ `./.mcp.json` to expose external tools (GitHub, Postgres, Linear, Slack, …) alongside
282
+ chad's builtins, with static-token and OAuth auth.
283
+
284
+ Both are covered in full in the [Configuration reference](docs/configuration.md).
285
+
286
+ ## Documentation
287
+
288
+ - **[Design & internals](docs/design.md)** — why prefill is the bill, the persistent prefix
289
+ cache, the trimmable/append-only trade, why the tool surface is five tools, and the ideas
290
+ borrowed from other agents.
291
+ - **[Throughput & performance](docs/benchmarks.md)** — prefill / decode / warm-step
292
+ numbers you can reproduce with `chad-bench`, the stock-engine comparison, and what the
293
+ cross-session warm start is worth.
294
+ - **[Configuration reference](docs/configuration.md)** — Agent Skills, MCP servers, the
295
+ context window, every environment variable, and the safety opt-outs.
296
+ - **[Troubleshooting](docs/troubleshooting.md)** — when a session rambles, loops, or slows:
297
+ the symptom→knob map for a small local model.
298
+ - **[Contributing](CONTRIBUTING.md)** — what lands easily, and what needs a conversation first.