chad-code 2.0.2__tar.gz → 2.1.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 (111) hide show
  1. {chad_code-2.0.2/src/chad_code.egg-info → chad_code-2.1.0}/PKG-INFO +63 -46
  2. {chad_code-2.0.2 → chad_code-2.1.0}/README.md +60 -44
  3. {chad_code-2.0.2 → chad_code-2.1.0}/pyproject.toml +57 -2
  4. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/__init__.py +1 -1
  5. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/agent.py +241 -156
  6. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/ambient.py +69 -30
  7. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/atif.py +53 -26
  8. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/base_engine.py +11 -27
  9. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/bench.py +35 -8
  10. chad_code-2.1.0/src/chad/checkpoint.py +231 -0
  11. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/cli.py +243 -171
  12. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/completion_engine.py +30 -160
  13. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/diag.py +16 -9
  14. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/engine.py +1184 -1183
  15. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/guardrails.py +50 -69
  16. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/ignore.py +0 -5
  17. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/mcp.py +169 -131
  18. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/mcp_oauth.py +61 -44
  19. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/mlx_dflash.py +78 -33
  20. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/mlx_fastpath.py +49 -25
  21. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/mlx_qmm_mma.py +35 -9
  22. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/mlx_qsdpa.py +133 -106
  23. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/prompt.py +21 -10
  24. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/prove.py +67 -23
  25. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/render.py +24 -17
  26. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/repomap.py +91 -21
  27. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/seatbelt.py +132 -102
  28. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/session.py +60 -19
  29. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/skills.py +9 -11
  30. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/speech.py +44 -27
  31. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/spill.py +8 -6
  32. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/toolcall_parse.py +6 -6
  33. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/tools.py +113 -25
  34. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/tui.py +100 -12
  35. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/validate.py +58 -21
  36. {chad_code-2.0.2 → chad_code-2.1.0/src/chad_code.egg-info}/PKG-INFO +63 -46
  37. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad_code.egg-info/SOURCES.txt +4 -3
  38. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad_code.egg-info/requires.txt +1 -1
  39. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_agent_e2e.py +257 -13
  40. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_agent_guards.py +42 -23
  41. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_ambient.py +66 -45
  42. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_atif.py +33 -12
  43. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_bench.py +22 -14
  44. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_checkpoint.py +73 -4
  45. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_cli.py +163 -158
  46. chad_code-2.1.0/tests/test_cli_modes.py +235 -0
  47. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_completion_engine.py +30 -15
  48. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_config.py +23 -0
  49. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_confirm_preview.py +23 -0
  50. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_edit.py +42 -30
  51. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_engine.py +240 -98
  52. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_engine_dflash.py +148 -15
  53. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_engine_kvquant.py +1 -3
  54. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_engine_pld_hybrid.py +0 -1
  55. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_engine_pld_wide.py +32 -9
  56. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_feel_pack.py +6 -8
  57. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_gate.py +45 -43
  58. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_intent.py +80 -91
  59. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_log_redaction.py +18 -0
  60. chad_code-2.1.0/tests/test_matrix_bench.py +484 -0
  61. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_mcp.py +70 -64
  62. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_mcp_oauth.py +9 -10
  63. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_mlx_qsdpa.py +22 -30
  64. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_prove.py +68 -1
  65. chad_code-2.1.0/tests/test_repomap.py +295 -0
  66. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_seatbelt.py +101 -82
  67. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_session.py +106 -22
  68. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_skills.py +2 -3
  69. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_speech_tui.py +165 -149
  70. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_spill.py +3 -4
  71. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_toolcall_parse.py +17 -0
  72. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_tools.py +138 -0
  73. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_tui.py +41 -0
  74. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_validate.py +36 -3
  75. chad_code-2.1.0/tests/test_warm_prefix_tiers.py +263 -0
  76. chad_code-2.0.2/src/chad/checkpoint.py +0 -149
  77. chad_code-2.0.2/src/chad/serve.py +0 -814
  78. chad_code-2.0.2/tests/test_repomap.py +0 -198
  79. chad_code-2.0.2/tests/test_serve.py +0 -815
  80. {chad_code-2.0.2 → chad_code-2.1.0}/LICENSE +0 -0
  81. {chad_code-2.0.2 → chad_code-2.1.0}/setup.cfg +0 -0
  82. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/compaction.py +0 -0
  83. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/config.py +0 -0
  84. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/levers.py +0 -0
  85. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/parakeet/LICENSE +0 -0
  86. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/parakeet/__init__.py +0 -0
  87. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/parakeet/alignment.py +0 -0
  88. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/parakeet/attention.py +0 -0
  89. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/parakeet/audio.py +0 -0
  90. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/parakeet/cache.py +0 -0
  91. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/parakeet/conformer.py +0 -0
  92. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/parakeet/ctc.py +0 -0
  93. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/parakeet/parakeet.py +0 -0
  94. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/parakeet/rnnt.py +0 -0
  95. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/parakeet/tokenizer.py +0 -0
  96. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/parakeet/utils.py +0 -0
  97. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad/syntaxgate.py +0 -0
  98. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad_code.egg-info/dependency_links.txt +0 -0
  99. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad_code.egg-info/entry_points.txt +0 -0
  100. {chad_code-2.0.2 → chad_code-2.1.0}/src/chad_code.egg-info/top_level.txt +0 -0
  101. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_agent.py +0 -0
  102. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_compaction.py +0 -0
  103. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_ignore.py +0 -0
  104. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_levers.py +0 -0
  105. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_mlx_fastpath.py +0 -0
  106. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_mlx_qmm_mma.py +0 -0
  107. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_render.py +0 -0
  108. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_repomap_polyglot.py +0 -0
  109. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_speech.py +0 -0
  110. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_syntaxgate.py +0 -0
  111. {chad_code-2.0.2 → chad_code-2.1.0}/tests/test_toolcall_dialect.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chad-code
3
- Version: 2.0.2
3
+ Version: 2.1.0
4
4
  Summary: Claude-Code-style coding agent that runs entirely on a 24 GB Apple Silicon Mac (MLX, Qwen3.8-27B)
5
5
  License-Expression: MIT
6
6
  Project-URL: Repository, https://github.com/nathansutton/chad
@@ -13,11 +13,12 @@ Classifier: Operating System :: MacOS
13
13
  Classifier: Programming Language :: Python :: 3.11
14
14
  Classifier: Programming Language :: Python :: 3.12
15
15
  Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
16
17
  Classifier: Topic :: Software Development :: Code Generators
17
18
  Requires-Python: >=3.11
18
19
  Description-Content-Type: text/markdown
19
20
  License-File: LICENSE
20
- Requires-Dist: mlx==0.32.0; sys_platform == "darwin"
21
+ Requires-Dist: mlx!=0.32.1,<=0.32.2,>=0.32.0; sys_platform == "darwin"
21
22
  Requires-Dist: mlx-lm<0.32,>=0.31.3; sys_platform == "darwin"
22
23
  Requires-Dist: transformers<5.13,>=5.0
23
24
  Requires-Dist: jinja2>=3.1
@@ -43,9 +44,13 @@ Dynamic: license-file
43
44
  > *Coding under supervision.*
44
45
 
45
46
  chad is a single-user coding agent that runs entirely on an Apple Silicon Mac via
46
- [MLX](https://github.com/ml-explore/mlx). One 27B model and no API key. Every other
47
- harness assumes a datacenter on the other end of a socket; chad assumes a laptop,
48
- and the whole design falls out of that. (Not affiliated with Anthropic.)
47
+ [MLX](https://github.com/ml-explore/mlx). One 27B model and no API key. (Not affiliated
48
+ with Anthropic.)
49
+
50
+ Plenty of harnesses run local models now, and [pi](https://pi.dev) is a fantastic default
51
+ for the same reason llama.cpp is: it works with everything. I was steering chad in the
52
+ opposite direction. One model and one set of silicon, taken to the max. Swap out your
53
+ `CHAD_MODEL` and it still runs, you just leave the drafter and the kernels behind.
49
54
 
50
55
  ## Try it
51
56
 
@@ -73,32 +78,36 @@ The PyPI package is `chad-code`. Bare `chad` is an unrelated squatted package.
73
78
 
74
79
  ## Same model, same Mac, stock engine
75
80
 
76
- The question worth answering: what do you gain over pointing a generic local-model tool at
77
- the same weights? Qwen3.8-27B at the same `UD-Q3_K_XL` recipe (Unsloth's GGUF for
81
+ What do you gain over pointing a generic local-model tool at the same weights?
82
+ Qwen3.8-27B at the same `UD-Q3_K_XL` recipe (Unsloth's GGUF for
78
83
  llama.cpp, chad's MLX conversion of the same bit map), the same M4 Pro (24 GB), one engine
79
84
  resident at a time, each measured with its own benchmark on a 512-token prompt and a
80
85
  128-token generation.
81
86
 
82
87
  | Engine | Prefill (512-tok prompt) | Decode (128 tok) | Speculative decoding |
83
88
  |---|---|---|---|
84
- | llama.cpp `llama-bench` (stock, build 10470) | 102 tok/s | 10.9 tok/s | none for this model |
85
- | **chad**, serial (`CHAD_NO_DFLASH=1`) | 99 tok/s | 18.1 tok/s | off |
86
- | **chad**, default | 98 tok/s | **62 tok/s**¹ | DFlash2 block drafter |
89
+ | llama.cpp `llama-bench` (stock, build 10470) | 102 tok/s | 10.9 tok/s | off in this benchmark |
90
+ | llama.cpp `llama-server` (build 10917), serial | 97 tok/s | 11.3 tok/s | off |
91
+ | llama.cpp `llama-server` (build 10917) | 95 tok/s | 11.1 tok/s² | DFlash2 drafter (Q4_K_M GGUF) |
92
+ | **chad**, serial (`CHAD_NO_DFLASH=1`) | 100 tok/s | 17.9 tok/s | off |
93
+ | **chad**, default | 101 tok/s | **62.9 tok/s**¹ | DFlash2 block drafter |
87
94
 
88
- A 200-token function body takes roughly 18 seconds at 10.9 tok/s and 3 at 62. That is the
89
- difference between a batch job and a pair programmer, and closing it is what the project is
90
- for.
95
+ A 200-token function body takes roughly 18 seconds at 10.9 tok/s and 3 at 63. You wait for
96
+ the first one and you talk to the second.
91
97
 
92
- Ollama does not get its own row: it is llama.cpp underneath with no speculative decoding for
93
- this model, and on the same GGUF (0.32.15, Modelfile `FROM` only) it measures 96 tok/s
98
+ Ollama does not get its own row: it is llama.cpp underneath, measured without speculative
99
+ decoding, and on the same GGUF (0.32.15, Modelfile `FROM` only) it measures 96 tok/s
94
100
  prefill and the same **10.9** decode.
95
101
 
96
- ¹ 62 is a ceiling: `chad-bench`'s prompt is tiled code the drafter reads easily. Replayed
97
- against ten real mid-session contexts from `~/.chad/sessions` (12–19k tokens, tool results in
102
+ ¹ 63 is a ceiling: `chad-bench`'s prompt is tiled code the drafter reads easily. Replayed
103
+ against ten real mid-session contexts from `~/.chad/sessions` (12-19k tokens, tool results in
98
104
  place, 384-token decodes) the same engine measures **31.7 tok/s median / 21.4 floor** greedy
99
105
  against 14.8 serial, and **27.6 / 17.7** thinking against 13.9. That ~2× is what a session
100
- lives at. llama.cpp's Metal path for this hybrid architecture was not profiled, so read its
101
- row as what a fitted engine buys, not as a verdict on llama.cpp.
106
+ lives at.
107
+
108
+ ² llama.cpp has run DFlash2 since build 10658. Here it accepts 96.5% of drafted tokens and still
109
+ gains nothing: verifying 8 tokens costs ~6.4 serial steps on this GGUF and Mac
110
+ ([details](docs/benchmarks.md#same-model-same-mac-stock-engine)).
102
111
 
103
112
  Method, the longer runs and the caveats are in
104
113
  [Throughput & performance](docs/benchmarks.md#same-model-same-mac-stock-engine); the rows are
@@ -110,17 +119,17 @@ committed under [`benchmarks/stock/_runs/`](benchmarks/stock/); reproduce them w
110
119
  chad owns its inference loop instead of talking to a server, and the engine is fitted to the
111
120
  one checkpoint it ships:
112
121
 
113
- - **DFlash2 block speculation.** A bundled 1.9B drafter ([z-lab's DFlash2](https://huggingface.co/z-lab/Qwen3.8-27B-DFlash2),
122
+ - DFlash2 block speculation. A bundled 1.9B drafter ([z-lab's DFlash2](https://huggingface.co/z-lab/Qwen3.8-27B-DFlash2),
114
123
  ported to MLX and quantized) proposes a whole block of tokens from the main model's own
115
124
  hidden states. The main model verifies the block in one batched forward, and exact
116
125
  rejection sampling keeps every emitted token the model's own.
117
- - **A persistent prefix KV cache.** The transcript is kept a strict token-prefix of the live
126
+ - A persistent prefix KV cache. The transcript is kept a strict token-prefix of the live
118
127
  cache, so a follow-up step prefills the ~16 tokens it appended instead of the 5,000 it
119
- already read: **~0.75 s per step instead of ~50 s**. Any server with prompt caching gets
128
+ already read: **~0.55 s per step instead of ~48 s**. Any server with prompt caching gets
120
129
  the easy case; the work is holding it true across compaction, truncated turns and restarts.
121
- The system prefix is checkpointed to disk, so the second session in a project starts warm
130
+ The system prefix is checkpointed to disk, so the second session anywhere starts warm
122
131
  (75.6 s → 5.5 s to the first tool call).
123
- - **Fused Metal kernels.** Quantized-KV attention, a small-M matmul for speculative verify,
132
+ - Fused Metal kernels. Quantized-KV attention, a small-M matmul for speculative verify,
124
133
  and a compiled single-token layer step, chosen per machine at load time, no knobs.
125
134
 
126
135
  ## What chad gives up
@@ -146,16 +155,21 @@ budget; everything else is deliberately plain.
146
155
 
147
156
  `uv run chad` launches a full-screen terminal UI (built on prompt_toolkit):
148
157
 
149
- - **shift-tab cycles permission modes**: `normal` (confirm each bash/write/edit) →
158
+ - shift-tab cycles permission modes: `normal` (confirm each bash/write/edit) →
150
159
  `auto-accept edits` (edits land silently, **terminal commands still ask**) → `yolo`
151
160
  (nothing asks) → `plan mode` (read-only: investigate and propose a numbered plan) → back.
152
- - **type-ahead message queue.** Keep typing while the agent works; messages run in order.
153
- - **ctrl-c interrupts the running turn** without killing the session. **↑prefilled /
161
+ A finished plan lands in `./plans/`; ctrl-g (or `/accept`) clears the context and starts
162
+ implementing it ([details](docs/configuration.md#plan-mode)).
163
+ - Type-ahead message queue. Keep typing while the agent works; messages run in order.
164
+ - ctrl-c interrupts the running turn without killing the session. **↑prefilled /
154
165
  ↓generated** token counts show an advancing **%** on an unavoidable full re-prefill, so it
155
166
  is never silent.
156
- - **`@file` / `@dir` mentions** and **`!command` shell passthrough.** Pull a file into
167
+ - `@file` / `@dir` mentions and `!command` shell passthrough. Pull a file into
157
168
  context inline, or run a shell command without invoking the model.
158
- - **voice mode, all local.** `/speech`, then ctrl-t to talk: Parakeet-on-MLX transcribes into
169
+ - Standing project instructions. A `CLAUDE.md` (or `AGENTS.md`) in the working directory is
170
+ appended to the system prompt, and `/init` reads the project and writes one for you
171
+ ([details](docs/configuration.md#project-instructions-claudemd--agentsmd)).
172
+ - Voice mode, all local. `/speech`, then ctrl-t to talk: Parakeet-on-MLX transcribes into
159
173
  the input box for you to review before Enter sends it, and replies are read aloud via macOS
160
174
  `say`. A word table teaches it your identifiers. Needs the `speech` extra
161
175
  ([details](docs/configuration.md#voice-mode-speech)).
@@ -169,12 +183,13 @@ budget; everything else is deliberately plain.
169
183
  | `--plan` | start in read-only plan mode (investigate and propose, edits blocked) |
170
184
  | `--yolo` | auto-approve bash/write/edit (skip confirm prompts) |
171
185
  | `--no-think` | skip the model's `<think>` blocks, faster on well-scoped work |
186
+ | `--think-budget N` | soft-cap each step's `<think>` at N tokens, force-close it and carry on (off by default) |
187
+ | `--backend llama` | run the same harness against a remote llama.cpp server, with `--base-url`, `--tokenizer` and `--api-key-env` ([details](docs/configuration.md#alternate-backend-remote)) |
172
188
  | `--model` | `auto` (the shipped default), or any HF repo id / local model dir |
173
189
  | `--repl` | plain line REPL instead of the TUI |
174
190
 
175
- Three subcommands, each with its own `--help`: `chad prove` (the offline smoke test),
176
- `chad serve` ([serve this Mac's model to a container or the LAN](docs/configuration.md#serving-the-local-model-to-a-container-chad-serve)),
177
- and `chad levers` (print the result-channel lever registry as JSON, for A/B ablation).
191
+ Two subcommands, each with its own `--help`: `chad prove` (the offline smoke test) and
192
+ `chad levers` (print the result-channel lever registry as JSON, for A/B ablation).
178
193
 
179
194
  A headless task (positional, or piped with no TTY) auto-approves mutating tools and runs
180
195
  greedy (temp 0). Every conversation is persisted under `~/.chad/sessions/`, and every resume
@@ -220,7 +235,7 @@ uv run chad "add a --json flag to main.py and update the tests" # one-shot, he
220
235
  uv run chad -c # resume this directory's last conversation
221
236
  ```
222
237
 
223
- **Optional extras.** Two features are opt-in because they pull deps not every install wants:
238
+ Two features are opt-in because they pull deps not every install wants:
224
239
  `speech` (voice mode: a mic library, no torch) and `highlight` (syntax colour in diffs and
225
240
  previews). An extra rides on the install spec, not on a separate command, so how you add it
226
241
  depends on how you installed chad:
@@ -234,12 +249,12 @@ uv sync --extra speech # from a clone
234
249
  `/speech` in the TUI prints whichever of those matches your install, so you never have to
235
250
  work it out from here.
236
251
 
237
- **Upgrading** depends on how you installed: `uv tool upgrade chad-code`, `uvx --refresh
252
+ Upgrading depends on how you installed: `uv tool upgrade chad-code`, `uvx --refresh
238
253
  chad-code`, or `git pull && uv sync` for a clone. What changed lands in
239
254
  [`CHANGELOG.md`](CHANGELOG.md). Model weights are versioned separately, so a code upgrade
240
255
  never re-downloads the model.
241
256
 
242
- **Development.** `uv sync` once, then `uv run pytest -q`. The fast unit gate loads **no model
257
+ For development, `uv sync` once, then `uv run pytest -q`. The fast unit gate loads **no model
243
258
  weights**, runs in seconds, and is what CI runs. For throughput on your own machine, use
244
259
  `uv run chad-bench` (see [Throughput & performance](docs/benchmarks.md)).
245
260
 
@@ -247,11 +262,12 @@ weights**, runs in seconds, and is what CI runs. For throughput on your own mach
247
262
 
248
263
  chad speaks the same two extension formats as Claude Code:
249
264
 
250
- - **[Agent Skills](https://agentskills.io).** Drop a `SKILL.md` folder in
251
- `./.claude/skills/` and it becomes a slash command: `/ship`, `/investigate the flaky test`.
252
- Skills cost nothing until you run one, because chad puts no skill catalog in the system
253
- prompt.
254
- - **[MCP servers](https://modelcontextprotocol.io).** Configure stdio or HTTP servers in
265
+ - [Agent Skills](https://agentskills.io). Drop a `SKILL.md` folder in `./.agents/skills/`
266
+ or `./.claude/skills/` (or under `~/` for every project) and it becomes a slash command:
267
+ `/ship`, `/investigate the flaky test`. Skills cost nothing until you run one, because
268
+ chad puts no skill catalog in the system prompt. Precedence and the full list of roots
269
+ are in the [Configuration reference](docs/configuration.md#agent-skills-agentskillsio).
270
+ - [MCP servers](https://modelcontextprotocol.io). Configure stdio or HTTP servers in
255
271
  `./.mcp.json` to expose external tools (GitHub, Postgres, Linear, Slack, …) alongside
256
272
  chad's builtins, with static-token and OAuth auth.
257
273
 
@@ -259,16 +275,17 @@ Both are covered in full in the [Configuration reference](docs/configuration.md)
259
275
 
260
276
  ## Documentation
261
277
 
262
- - **[Design & internals](docs/design.md)** covers why prefill is the bill, the persistent
278
+ - [Design & internals](docs/design.md) covers why prefill is the bill, the persistent
263
279
  prefix cache, the trimmable/append-only trade, why the tool surface is five tools, and the
264
280
  ideas borrowed from other agents.
265
- - **[Throughput & performance](docs/benchmarks.md)** has the prefill, decode and warm-step
281
+ - [Throughput & performance](docs/benchmarks.md) has the prefill, decode and warm-step
266
282
  numbers you can reproduce with `chad-bench`, the stock-engine comparison, and what the
267
283
  cross-session warm start is worth.
268
- - **[Configuration reference](docs/configuration.md)** documents Agent Skills, MCP servers,
269
- the context window, every environment variable, and the safety opt-outs.
270
- - **[Troubleshooting](docs/troubleshooting.md)** is the symptom→knob map for when a session
284
+ - [Configuration reference](docs/configuration.md) documents project instructions, Agent
285
+ Skills, MCP servers, plan mode, the slash commands, the context window, every environment
286
+ variable, and the safety opt-outs.
287
+ - [Troubleshooting](docs/troubleshooting.md) maps symptoms to knobs for when a session
271
288
  rambles, loops, or slows.
272
- - **[Contributing](CONTRIBUTING.md)** says what lands easily and what needs a conversation
289
+ - [Contributing](CONTRIBUTING.md) says what lands easily and what needs a conversation
273
290
  first.
274
291
 
@@ -8,9 +8,13 @@
8
8
  > *Coding under supervision.*
9
9
 
10
10
  chad is a single-user coding agent that runs entirely on an Apple Silicon Mac via
11
- [MLX](https://github.com/ml-explore/mlx). One 27B model and no API key. Every other
12
- harness assumes a datacenter on the other end of a socket; chad assumes a laptop,
13
- and the whole design falls out of that. (Not affiliated with Anthropic.)
11
+ [MLX](https://github.com/ml-explore/mlx). One 27B model and no API key. (Not affiliated
12
+ with Anthropic.)
13
+
14
+ Plenty of harnesses run local models now, and [pi](https://pi.dev) is a fantastic default
15
+ for the same reason llama.cpp is: it works with everything. I was steering chad in the
16
+ opposite direction. One model and one set of silicon, taken to the max. Swap out your
17
+ `CHAD_MODEL` and it still runs, you just leave the drafter and the kernels behind.
14
18
 
15
19
  ## Try it
16
20
 
@@ -38,32 +42,36 @@ The PyPI package is `chad-code`. Bare `chad` is an unrelated squatted package.
38
42
 
39
43
  ## Same model, same Mac, stock engine
40
44
 
41
- The question worth answering: what do you gain over pointing a generic local-model tool at
42
- the same weights? Qwen3.8-27B at the same `UD-Q3_K_XL` recipe (Unsloth's GGUF for
45
+ What do you gain over pointing a generic local-model tool at the same weights?
46
+ Qwen3.8-27B at the same `UD-Q3_K_XL` recipe (Unsloth's GGUF for
43
47
  llama.cpp, chad's MLX conversion of the same bit map), the same M4 Pro (24 GB), one engine
44
48
  resident at a time, each measured with its own benchmark on a 512-token prompt and a
45
49
  128-token generation.
46
50
 
47
51
  | Engine | Prefill (512-tok prompt) | Decode (128 tok) | Speculative decoding |
48
52
  |---|---|---|---|
49
- | llama.cpp `llama-bench` (stock, build 10470) | 102 tok/s | 10.9 tok/s | none for this model |
50
- | **chad**, serial (`CHAD_NO_DFLASH=1`) | 99 tok/s | 18.1 tok/s | off |
51
- | **chad**, default | 98 tok/s | **62 tok/s**¹ | DFlash2 block drafter |
53
+ | llama.cpp `llama-bench` (stock, build 10470) | 102 tok/s | 10.9 tok/s | off in this benchmark |
54
+ | llama.cpp `llama-server` (build 10917), serial | 97 tok/s | 11.3 tok/s | off |
55
+ | llama.cpp `llama-server` (build 10917) | 95 tok/s | 11.1 tok/s² | DFlash2 drafter (Q4_K_M GGUF) |
56
+ | **chad**, serial (`CHAD_NO_DFLASH=1`) | 100 tok/s | 17.9 tok/s | off |
57
+ | **chad**, default | 101 tok/s | **62.9 tok/s**¹ | DFlash2 block drafter |
52
58
 
53
- A 200-token function body takes roughly 18 seconds at 10.9 tok/s and 3 at 62. That is the
54
- difference between a batch job and a pair programmer, and closing it is what the project is
55
- for.
59
+ A 200-token function body takes roughly 18 seconds at 10.9 tok/s and 3 at 63. You wait for
60
+ the first one and you talk to the second.
56
61
 
57
- Ollama does not get its own row: it is llama.cpp underneath with no speculative decoding for
58
- this model, and on the same GGUF (0.32.15, Modelfile `FROM` only) it measures 96 tok/s
62
+ Ollama does not get its own row: it is llama.cpp underneath, measured without speculative
63
+ decoding, and on the same GGUF (0.32.15, Modelfile `FROM` only) it measures 96 tok/s
59
64
  prefill and the same **10.9** decode.
60
65
 
61
- ¹ 62 is a ceiling: `chad-bench`'s prompt is tiled code the drafter reads easily. Replayed
62
- against ten real mid-session contexts from `~/.chad/sessions` (12–19k tokens, tool results in
66
+ ¹ 63 is a ceiling: `chad-bench`'s prompt is tiled code the drafter reads easily. Replayed
67
+ against ten real mid-session contexts from `~/.chad/sessions` (12-19k tokens, tool results in
63
68
  place, 384-token decodes) the same engine measures **31.7 tok/s median / 21.4 floor** greedy
64
69
  against 14.8 serial, and **27.6 / 17.7** thinking against 13.9. That ~2× is what a session
65
- lives at. llama.cpp's Metal path for this hybrid architecture was not profiled, so read its
66
- row as what a fitted engine buys, not as a verdict on llama.cpp.
70
+ lives at.
71
+
72
+ ² llama.cpp has run DFlash2 since build 10658. Here it accepts 96.5% of drafted tokens and still
73
+ gains nothing: verifying 8 tokens costs ~6.4 serial steps on this GGUF and Mac
74
+ ([details](docs/benchmarks.md#same-model-same-mac-stock-engine)).
67
75
 
68
76
  Method, the longer runs and the caveats are in
69
77
  [Throughput & performance](docs/benchmarks.md#same-model-same-mac-stock-engine); the rows are
@@ -75,17 +83,17 @@ committed under [`benchmarks/stock/_runs/`](benchmarks/stock/); reproduce them w
75
83
  chad owns its inference loop instead of talking to a server, and the engine is fitted to the
76
84
  one checkpoint it ships:
77
85
 
78
- - **DFlash2 block speculation.** A bundled 1.9B drafter ([z-lab's DFlash2](https://huggingface.co/z-lab/Qwen3.8-27B-DFlash2),
86
+ - DFlash2 block speculation. A bundled 1.9B drafter ([z-lab's DFlash2](https://huggingface.co/z-lab/Qwen3.8-27B-DFlash2),
79
87
  ported to MLX and quantized) proposes a whole block of tokens from the main model's own
80
88
  hidden states. The main model verifies the block in one batched forward, and exact
81
89
  rejection sampling keeps every emitted token the model's own.
82
- - **A persistent prefix KV cache.** The transcript is kept a strict token-prefix of the live
90
+ - A persistent prefix KV cache. The transcript is kept a strict token-prefix of the live
83
91
  cache, so a follow-up step prefills the ~16 tokens it appended instead of the 5,000 it
84
- already read: **~0.75 s per step instead of ~50 s**. Any server with prompt caching gets
92
+ already read: **~0.55 s per step instead of ~48 s**. Any server with prompt caching gets
85
93
  the easy case; the work is holding it true across compaction, truncated turns and restarts.
86
- The system prefix is checkpointed to disk, so the second session in a project starts warm
94
+ The system prefix is checkpointed to disk, so the second session anywhere starts warm
87
95
  (75.6 s → 5.5 s to the first tool call).
88
- - **Fused Metal kernels.** Quantized-KV attention, a small-M matmul for speculative verify,
96
+ - Fused Metal kernels. Quantized-KV attention, a small-M matmul for speculative verify,
89
97
  and a compiled single-token layer step, chosen per machine at load time, no knobs.
90
98
 
91
99
  ## What chad gives up
@@ -111,16 +119,21 @@ budget; everything else is deliberately plain.
111
119
 
112
120
  `uv run chad` launches a full-screen terminal UI (built on prompt_toolkit):
113
121
 
114
- - **shift-tab cycles permission modes**: `normal` (confirm each bash/write/edit) →
122
+ - shift-tab cycles permission modes: `normal` (confirm each bash/write/edit) →
115
123
  `auto-accept edits` (edits land silently, **terminal commands still ask**) → `yolo`
116
124
  (nothing asks) → `plan mode` (read-only: investigate and propose a numbered plan) → back.
117
- - **type-ahead message queue.** Keep typing while the agent works; messages run in order.
118
- - **ctrl-c interrupts the running turn** without killing the session. **↑prefilled /
125
+ A finished plan lands in `./plans/`; ctrl-g (or `/accept`) clears the context and starts
126
+ implementing it ([details](docs/configuration.md#plan-mode)).
127
+ - Type-ahead message queue. Keep typing while the agent works; messages run in order.
128
+ - ctrl-c interrupts the running turn without killing the session. **↑prefilled /
119
129
  ↓generated** token counts show an advancing **%** on an unavoidable full re-prefill, so it
120
130
  is never silent.
121
- - **`@file` / `@dir` mentions** and **`!command` shell passthrough.** Pull a file into
131
+ - `@file` / `@dir` mentions and `!command` shell passthrough. Pull a file into
122
132
  context inline, or run a shell command without invoking the model.
123
- - **voice mode, all local.** `/speech`, then ctrl-t to talk: Parakeet-on-MLX transcribes into
133
+ - Standing project instructions. A `CLAUDE.md` (or `AGENTS.md`) in the working directory is
134
+ appended to the system prompt, and `/init` reads the project and writes one for you
135
+ ([details](docs/configuration.md#project-instructions-claudemd--agentsmd)).
136
+ - Voice mode, all local. `/speech`, then ctrl-t to talk: Parakeet-on-MLX transcribes into
124
137
  the input box for you to review before Enter sends it, and replies are read aloud via macOS
125
138
  `say`. A word table teaches it your identifiers. Needs the `speech` extra
126
139
  ([details](docs/configuration.md#voice-mode-speech)).
@@ -134,12 +147,13 @@ budget; everything else is deliberately plain.
134
147
  | `--plan` | start in read-only plan mode (investigate and propose, edits blocked) |
135
148
  | `--yolo` | auto-approve bash/write/edit (skip confirm prompts) |
136
149
  | `--no-think` | skip the model's `<think>` blocks, faster on well-scoped work |
150
+ | `--think-budget N` | soft-cap each step's `<think>` at N tokens, force-close it and carry on (off by default) |
151
+ | `--backend llama` | run the same harness against a remote llama.cpp server, with `--base-url`, `--tokenizer` and `--api-key-env` ([details](docs/configuration.md#alternate-backend-remote)) |
137
152
  | `--model` | `auto` (the shipped default), or any HF repo id / local model dir |
138
153
  | `--repl` | plain line REPL instead of the TUI |
139
154
 
140
- Three subcommands, each with its own `--help`: `chad prove` (the offline smoke test),
141
- `chad serve` ([serve this Mac's model to a container or the LAN](docs/configuration.md#serving-the-local-model-to-a-container-chad-serve)),
142
- and `chad levers` (print the result-channel lever registry as JSON, for A/B ablation).
155
+ Two subcommands, each with its own `--help`: `chad prove` (the offline smoke test) and
156
+ `chad levers` (print the result-channel lever registry as JSON, for A/B ablation).
143
157
 
144
158
  A headless task (positional, or piped with no TTY) auto-approves mutating tools and runs
145
159
  greedy (temp 0). Every conversation is persisted under `~/.chad/sessions/`, and every resume
@@ -185,7 +199,7 @@ uv run chad "add a --json flag to main.py and update the tests" # one-shot, he
185
199
  uv run chad -c # resume this directory's last conversation
186
200
  ```
187
201
 
188
- **Optional extras.** Two features are opt-in because they pull deps not every install wants:
202
+ Two features are opt-in because they pull deps not every install wants:
189
203
  `speech` (voice mode: a mic library, no torch) and `highlight` (syntax colour in diffs and
190
204
  previews). An extra rides on the install spec, not on a separate command, so how you add it
191
205
  depends on how you installed chad:
@@ -199,12 +213,12 @@ uv sync --extra speech # from a clone
199
213
  `/speech` in the TUI prints whichever of those matches your install, so you never have to
200
214
  work it out from here.
201
215
 
202
- **Upgrading** depends on how you installed: `uv tool upgrade chad-code`, `uvx --refresh
216
+ Upgrading depends on how you installed: `uv tool upgrade chad-code`, `uvx --refresh
203
217
  chad-code`, or `git pull && uv sync` for a clone. What changed lands in
204
218
  [`CHANGELOG.md`](CHANGELOG.md). Model weights are versioned separately, so a code upgrade
205
219
  never re-downloads the model.
206
220
 
207
- **Development.** `uv sync` once, then `uv run pytest -q`. The fast unit gate loads **no model
221
+ For development, `uv sync` once, then `uv run pytest -q`. The fast unit gate loads **no model
208
222
  weights**, runs in seconds, and is what CI runs. For throughput on your own machine, use
209
223
  `uv run chad-bench` (see [Throughput & performance](docs/benchmarks.md)).
210
224
 
@@ -212,11 +226,12 @@ weights**, runs in seconds, and is what CI runs. For throughput on your own mach
212
226
 
213
227
  chad speaks the same two extension formats as Claude Code:
214
228
 
215
- - **[Agent Skills](https://agentskills.io).** Drop a `SKILL.md` folder in
216
- `./.claude/skills/` and it becomes a slash command: `/ship`, `/investigate the flaky test`.
217
- Skills cost nothing until you run one, because chad puts no skill catalog in the system
218
- prompt.
219
- - **[MCP servers](https://modelcontextprotocol.io).** Configure stdio or HTTP servers in
229
+ - [Agent Skills](https://agentskills.io). Drop a `SKILL.md` folder in `./.agents/skills/`
230
+ or `./.claude/skills/` (or under `~/` for every project) and it becomes a slash command:
231
+ `/ship`, `/investigate the flaky test`. Skills cost nothing until you run one, because
232
+ chad puts no skill catalog in the system prompt. Precedence and the full list of roots
233
+ are in the [Configuration reference](docs/configuration.md#agent-skills-agentskillsio).
234
+ - [MCP servers](https://modelcontextprotocol.io). Configure stdio or HTTP servers in
220
235
  `./.mcp.json` to expose external tools (GitHub, Postgres, Linear, Slack, …) alongside
221
236
  chad's builtins, with static-token and OAuth auth.
222
237
 
@@ -224,16 +239,17 @@ Both are covered in full in the [Configuration reference](docs/configuration.md)
224
239
 
225
240
  ## Documentation
226
241
 
227
- - **[Design & internals](docs/design.md)** covers why prefill is the bill, the persistent
242
+ - [Design & internals](docs/design.md) covers why prefill is the bill, the persistent
228
243
  prefix cache, the trimmable/append-only trade, why the tool surface is five tools, and the
229
244
  ideas borrowed from other agents.
230
- - **[Throughput & performance](docs/benchmarks.md)** has the prefill, decode and warm-step
245
+ - [Throughput & performance](docs/benchmarks.md) has the prefill, decode and warm-step
231
246
  numbers you can reproduce with `chad-bench`, the stock-engine comparison, and what the
232
247
  cross-session warm start is worth.
233
- - **[Configuration reference](docs/configuration.md)** documents Agent Skills, MCP servers,
234
- the context window, every environment variable, and the safety opt-outs.
235
- - **[Troubleshooting](docs/troubleshooting.md)** is the symptom→knob map for when a session
248
+ - [Configuration reference](docs/configuration.md) documents project instructions, Agent
249
+ Skills, MCP servers, plan mode, the slash commands, the context window, every environment
250
+ variable, and the safety opt-outs.
251
+ - [Troubleshooting](docs/troubleshooting.md) maps symptoms to knobs for when a session
236
252
  rambles, loops, or slows.
237
- - **[Contributing](CONTRIBUTING.md)** says what lands easily and what needs a conversation
253
+ - [Contributing](CONTRIBUTING.md) says what lands easily and what needs a conversation
238
254
  first.
239
255
 
@@ -4,7 +4,7 @@
4
4
  # import name, and command name are independent. `uvx chad-code` runs the alias
5
5
  # script added under [project.scripts].
6
6
  name = "chad-code"
7
- version = "2.0.2"
7
+ version = "2.1.0"
8
8
  description = "Claude-Code-style coding agent that runs entirely on a 24 GB Apple Silicon Mac (MLX, Qwen3.8-27B)"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -23,6 +23,7 @@ classifiers = [
23
23
  "Programming Language :: Python :: 3.11",
24
24
  "Programming Language :: Python :: 3.12",
25
25
  "Programming Language :: Python :: 3.13",
26
+ "Programming Language :: Python :: 3.14",
26
27
  "Topic :: Software Development :: Code Generators",
27
28
  ]
28
29
  dependencies = [
@@ -42,7 +43,11 @@ dependencies = [
42
43
  # A/B-confirmed 2026-08-23: same wheel, same prompt, sane on ==0.32.0, garbled on
43
44
  # ==0.32.1). Point releases must be re-gated against the custom QSDPA/DFlash2/MMA
44
45
  # kernels before widening this pin.
45
- "mlx==0.32.0; sys_platform == 'darwin'",
46
+ # mlx 0.32.2: re-gated and admitted — greedy output byte-identical to 0.32.0 on the
47
+ # shipped model, `chad prove` 4/4, full suite green, decode within noise (M4 Pro;
48
+ # independently on M5 Pro, #50). The range admits only gated releases; the upper
49
+ # bound moves when the next point release is gated, not before.
50
+ "mlx>=0.32.0,!=0.32.1,<=0.32.2; sys_platform == 'darwin'",
46
51
  "mlx-lm>=0.31.3,<0.32; sys_platform == 'darwin'",
47
52
  # mlx-lm requires transformers>=5.0, but 5.13.0 regressed AutoTokenizer.register:
48
53
  # it now does `key.__module__.startswith("transformers.")` with no hasattr guard, and
@@ -138,6 +143,8 @@ testpaths = ["tests"]
138
143
 
139
144
  [tool.ruff]
140
145
  line-length = 100
146
+ # Vendored Exercism exercises (benchmarks/matrix/tasks/) are upstream files, kept byte-identical.
147
+ extend-exclude = ["benchmarks/matrix/tasks", "tools/anti_slop"] # vendored linter: not chad's style
141
148
  src = ["src", "tests"]
142
149
 
143
150
  [tool.ruff.lint]
@@ -192,3 +199,51 @@ ignore_errors = true
192
199
  # that is likewise Any on Linux.
193
200
  module = ["chad.mlx_fastpath", "chad.mlx_qmm_mma", "chad.engine"]
194
201
  warn_unused_ignores = false
202
+
203
+ # anti-slop: the third lane of the gate, next to ruff (style/correctness) and mypy
204
+ # (types). It rejects the evidence-discarding patterns a coding agent reaches for when a
205
+ # checker complains — `Any`/`object` contracts, unexplained `cast`/`# type: ignore`,
206
+ # string-target mocking, `getattr` dispatch — which ruff and mypy both permit by
207
+ # construction. Vendored (tools/anti_slop, MIT, TinyFrontier/anti-slop-py) so it needs
208
+ # no dependency and runs on the stdlib alone; it does need Python 3.12+, so `make slop`
209
+ # runs it under `uv run --python 3.12` regardless of the 3.11 project pin.
210
+ # Run `uv run --python 3.12 --no-project python tools/anti_slop --explain <rule>` for
211
+ # the reasoning behind any finding.
212
+ [tool.anti-slop]
213
+ include = ["src", "tests", "benchmarks"]
214
+ exclude = [
215
+ ".agents/**",
216
+ ".claude/**",
217
+ "src/chad/parakeet/**", # vendored upstream, not held to chad's gate (see ruff/mypy)
218
+ "benchmarks/matrix/tasks/**", # vendored Exercism exercises, kept byte-identical
219
+ "tools/anti_slop/**",
220
+ ]
221
+ # Pre-existing findings live in the baseline (regenerate: `make slop-baseline`). A run
222
+ # fails only on what is NEW; edit a baselined line and its finding comes back, so the
223
+ # debt retires as the code it sits on is touched. Never regenerate to hide a new finding.
224
+ baseline = ".anti-slop-baseline.json"
225
+
226
+ [tool.anti-slop.rules]
227
+ # Escape-hatch tier: every one at error. These discard evidence the checker already had.
228
+ no-any-parameters = "error"
229
+ no-any-returns = "error"
230
+ no-any-type-aliases = "error"
231
+ no-chained-casts = "error"
232
+ no-conditional-empty-dict-spread = "error"
233
+ no-dynamic-dispatch = "error"
234
+ no-known-value-widening = "error"
235
+ no-unsafe-dict-values = "error"
236
+ no-widen-then-cast = "error"
237
+ require-safety-comment = "error"
238
+ # Architectural tier: policy, also at error, with the two exceptions below.
239
+ no-object-parameters = "error"
240
+ no-string-attribute-access = "error"
241
+ no-module-mocking = "error"
242
+ # chad's domain objects ARE untyped model output: tool args arrive as JSON the model
243
+ # wrote, and validate.py/mcp.py decode them at that boundary with isinstance — which is
244
+ # the recipe this rule prescribes, not the pattern it bans (the linter's own repository
245
+ # turns it off for the same reason over ast nodes). Kept at warn so a new inline
246
+ # isinstance deep in agent code still reports, without failing the run.
247
+ no-adhoc-isinstance = "warn"
248
+ # Tensor shapes are domain vocabulary in the MLX kernel modules; `shape` names the thing.
249
+ no-shape-in-symbol-names = { level = "error", terms = [] }
@@ -4,7 +4,7 @@ A flat collection of cooperating modules behind one console script (``chad``):
4
4
  the inference engine, the tool layer, the agent loop, and the terminal UI.
5
5
  """
6
6
 
7
- __version__ = "2.0.2"
7
+ __version__ = "2.1.0"
8
8
 
9
9
  # chad sets no MLX_* runtime vars. MLX_METAL_FAST_SYNCH, MLX_MAX_OPS_PER_BUFFER
10
10
  # and MLX_MAX_MB_PER_BUFFER were each measured end-to-end on the 35B and every