Skip to content

Releases: nathansutton/chad

1.0.9

Choose a tag to compare

@nathansutton nathansutton released this 30 Jul 23:59
6810a63

Decode is 5–7% faster on the 35B. The MoE block β€” the largest remaining
inefficiency in the per-token decode step β€” now runs as two custom Metal kernels
(JIT-compiled at load, no wheel rebuild): one dispatch computes gate|up for all 8
routed experts plus the shared expert with the SiLU epilogue in-register, the other
folds every down-projection, the routing weights, the shared-expert gate, and the
residual add into a single pass. Routing itself is bit-identical to stock; measured
on the shipped checkpoint: 70.7 β†’ 74.3 tok/s at 8k context, decode-path perplexity
a hair better (the fused combine accumulates in fp32), prefill unchanged
(756 tok/s @7k vs 723 baseline), peak memory unchanged. Engages only on the exact
35B geometry, falls back to the stock graph anywhere else; opt out with
CHAD_NO_MOE_FUSED=1.

1.0.8

Choose a tag to compare

@nathansutton nathansutton released this 30 Jul 00:10
bed29b5
  • /speech now names an install command that can actually work. The voice-mode
    install hint was hardcoded to uv sync --extra speech β€” true only in a git clone, so
    every published install (uvx chad-code, uv tool install chad-code) was sent to a
    command with no project to sync. An extra rides on the install spec, not on a
    separate step, and speech.install_hint() now derives the right form from how chad was
    actually installed: uv tool install --force 'chad-code[speech]', uvx --from 'chad-code[speech]' chad, uv sync --extra speech in a clone, or uv pip install 'chad-code[speech]' in a plain venv.
  • Extras documented in one place. The README install section now states the
    install-spec rule with all three commands side by side, so the PyPI project page
    carries it too. Also drops a stale line claiming LSP-precise find-refs/rename need an
    lsp extra β€” that extra is gone and precision ships in the base install as of 1.0.7.

1.0.7

Choose a tag to compare

@nathansutton nathansutton released this 29 Jul 23:25
e1ad82a
  • Precision code intelligence now ships in the base install. find_refs /
    rename_symbol precision used to live behind an optional lsp extra
    (serena-agent, 33 transitive dependencies) that uvx chad-code never installed β€”
    so every published install ran name-match only. chad now drives language servers
    through its own ~350-line LSP client (lspclient.py) plus a declarative registry
    (lspservers.py, the nvim-lspconfig shape): pyright via uvx (guaranteed present on
    the uvx install path), the TypeScript 7 native LSP via npx, gopls / rust-analyzer /
    clangd / solargraph / intelephense from PATH. Zero new Python dependencies; the
    lsp extra and serena-agent are gone. Servers that would answer confidently-wrong
    empties without project context (clangd without compile_commands.json, TS without
    a tsconfig) are refused, keeping the honestly-labeled name-match fallback.
  • Qualified symbol paths in every language. view_symbol("Engine/process"),
    replace_symbol("Engine.process", …) etc. now resolve the method β€” not a same-named
    free function β€” in Python, TS/JS, Go, Rust, Java, Ruby, C#, PHP, Kotlin and C++
    alike, via scope chains derived from tree-sitter spans (plus receiver/impl context
    for Go/Rust/C++, whose methods don't nest lexically). This was Python-only via
    jedi before; jedi is deleted and every language takes one code path (verified
    against jedi on this repo: 230/230 span-identical). A qualified path whose
    container doesn't match is now a miss instead of silently editing the bare name.
  • Broken grammars fixed for 4 of 12 fixture languages β€” and shell added outright.
    First polyglot measurement found the language pack ships NO tags query for
    TypeScript/TSX, a PHP query that doesn't compile against the current grammar, C
    with no reference captures, and C++ missing out-of-class method definitions. chad
    now carries its own tags queries for those (repomap._TAGS_OVERRIDE), plus a new
    bash query the pack never had: shell functions and top-level variables now appear
    in repo_map/overview, find_refs finds invocations across scripts, and
    replace_symbol edits a shell function by name β€” shell is the one surface nearly
    every real repo (and 89 of 91 terminal-bench-2.1 tasks) contains. All pinned by a
    13-language coverage test.
  • Post-edit typecheck in the edit result. When a language server is warm, every
    edit/symbol-edit result appends the server's type errors (errors only, capped,
    ~50 tokens) β€” a type error surfaces immediately instead of one failed test run
    later. Edits never pay a cold server start (lever: post_edit_diagnostics).
  • New hover tool. The resolved type signature + docs of one symbol via the
    language server β€” one line instead of opening the defining file.
  • mcp 2.x, single code path. The mcp<2 cap (below) was serena-agent collateral;
    with serena gone chad runs natively on mcp 2.0 (protocol 2026-07-28). The two
    renames that fail silently through getattr defaults (isError→is_error,
    readOnlyHint→read_only_hint) are each pinned by a test that fails on the 1.x
    spelling.
  • Fixes a broken install. uvx chad-code (and any other fresh resolve) crashed on
    startup with ImportError: cannot import name 'streamablehttp_client'. The mcp
    dependency was unbounded, so a clean resolve picked up the SDK's 2.0 major, which
    removed the HTTP transport chad uses, moved to httpx2, and split the wire types into
    a separate distribution. (The cap has since become the deliberate 2.x port above.)
  • A broken MCP SDK can no longer take the agent down. Every other MCP failure already
    degraded β€” a server that's missing, misconfigured, or crashing costs you its tools and
    nothing else β€” but the SDK import itself ran unguarded at chad.mcp load, which happens
    on every Agent. It's guarded now: an incompatible SDK means no MCP tools plus one
    warning line in /mcp, and the rest of chad runs. Relatedly, /mcp no longer reports
    "no MCP servers configured" when servers are configured but all failed β€” it shows the
    warnings that explain why.
  • CI now catches this class of break. The fresh-resolve job and the weekly install
    canary only ran chad --help, which exits inside argparse and never imports the modules
    that load on the first turn. Both now import every chad submodule against a clean
    resolve and fail if the MCP SDK guard fires.

1.0.6

Choose a tag to compare

@nathansutton nathansutton released this 29 Jul 00:17
80bbae6
  • Voice mode, all local. /speech in the TUI turns on push-to-talk: ctrl-t opens the
    mic (a red ● rec pin in the status line β€” an open mic you can't see is a bug), ctrl-t
    again transcribes into the input box. The transcript is reviewed text, not a sent
    message β€” you read what the model heard and Enter submits, same supervision contract as
    every edit. Replies are read aloud (code blocks dropped, cut at a sentence boundary;
    ctrl-c hushes). STT is Parakeet TDT v3 on MLX β€” the engine
    Hex ships as its default, faster than Whisper per
    clip, more accurate on dictation, and without Whisper's repetition-loop hallucination
    mode β€” via a copy vendored into chad.parakeet (Apache-2.0, from
    parakeet-mlx); TTS is macOS say. Nothing
    leaves the machine and the audio never touches disk. Vendoring is deliberate: the PyPI
    packages drag torch (mlx-whisper, ~2 GB) or librosa's numba/scipy tree (parakeet-mlx)
    for code paths MLX-format weights never run, so uv sync --extra speech adds exactly
    one package β€” the mic library (sounddevice). The tokenizer vocabulary rides inside the
    model repo, so no data blobs ship in chad. Dictation hygiene is built in:
    trailing/leading silence is trimmed before the model sees it, and a repetition-collapse
    pass keeps any decode loop out of the input box. The weights are quantized to 8-bit
    at load
    (no separate quant repo): A/B'd against bf16 on a 24-clip multi-voice
    battery β€” transcripts identical up to case/spacing, twice more correct β€” for 786 MB
    resident instead of 1296 and ~40% faster decode. Tune with CHAD_STT_MODEL (default
    parakeet-tdt-0.6b-v3, multilingual; ~2.5 GB fetched on first use), CHAD_STT_QUANT
    (8 default Β· 4 halves memory again, clean on the battery but unproven on noisy
    mics Β· none for bf16), CHAD_VOICE, CHAD_SPEECH_RATE.
  • Dictation length is linear, not quadratic. The encoder ships as rel_pos with
    att_context_size [-1,-1] β€” full global attention, so a take's cost grows with the
    square of its own length, on the same GPU the coding model is resident on. chad now
    switches it to the banded local kernel at load: the score matrix goes from TΓ—T to
    (T, 2w+1), and w=256 encoder frames is Β±20.5s of audio at 12.5 frames/s, so a
    dictation take still sees its whole self. The transcript is byte-identical to full
    attention on a real clip (and 8-bit local is byte-identical to bf16 local). A 359s take
    now decodes in 3.9s with peak memory flattening (58s→2.0 GB, 175s→3.1, 359s→3.3) where
    full attention keeps climbing. The switch happens before quantization, because
    set_attention_model rebuilds the attention modules and load_weights rejects a
    quantized layer's scales/biases. A take is also capped at 600s as a backstop: recording
    doesn't stop and the status line says so, the take just stops growing β€” a mic left open
    is truncated, never lost.
  • /speech off gives the memory back, and enable tells you what's broken. Turning
    voice mode off now unloads the STT weights (747 MB β†’ 0 measured), not just the mic β€”
    it won't fire mid-transcription, where the worker still holds the model. Enable-time
    validation covers the two failures that were previously invisible until after you'd
    spoken: a misspelled CHAD_VOICE (say exits nonzero, so nothing ever raised β€” you
    just got silence forever; now it names the typo and suggests the nearest real voice)
    and a bad CHAD_STT_QUANT (which only raised from inside the decode). Both warn rather
    than block β€” a machine without say can still dictate. And a transcript that finishes
    after you've turned /speech off is discarded with a note instead of typing itself
    into an input box in a mode you already left.
  • Voice mode capture + correction, borrowed from the best. Three ideas adapted from
    Hex (MIT), the open-source macOS dictation app:
    while /speech is on the mic stays warm feeding a ~1s ring buffer, and each take is
    seeded with 0.45s of pre-roll from before the keypress β€” opening a CoreAudio device
    takes 100–300ms, so a cold-opened mic clips your first syllable ("ix the failing
    test"). The status line says mic open the whole time, because an open mic you can't
    see is a bug; /speech off releases it. esc during a take discards it instead of
    transcribing β€” a mistaken recording shouldn't cost a decode plus an input-box cleanup.
    And a personal word table (~/.chad/speech_words.json, or CHAD_SPEECH_WORDS) of
    {"heard": "meant"} fixes what no ASR model can β€” your identifiers: {"pie test": "pytest", "you vee": "uv"}. Case-insensitive, whole-word, longest match first,
    loaded fresh each take; /speech reports the count (or the parse error) at enable.
  • numpy 2 allowed. The <2 cap dated to 1.0.0 and encoded no known break β€” it also
    blocked modern ASR dependencies that floor at numpy 2. Now >=1.26,<3, verified on
    numpy 2.4.6: full unit gate, live on-device transcription, and chad prove 4/4
    (Ornith 9B smoke model) at normal throughput.
  • You can see what you're approving. The confirm prompt used to render into the
    one-row status line with newlines flattened to ⏎ and a 160-character clip, so a
    multi-line bash command was unreadable β€” approving it was approving blind. Approvals
    now open a panel above the input that shows the command on its own lines (bounded, so a
    giant heredoc can't swallow the screen), names in plain words what is about to happen
    ("chad wants to run a terminal command"), and puts the destructive-command warning next
    to the command instead of in scrollback.
  • New demo GIF. The old one predated the banner entirely (no logo, a dead model id,
    and a fixture that no longer matched the checked-in tape). The new one is a 35B session
    end to end in 51 seconds: banner, type-ahead while the weights load, the model finding
    the cent that floor division loses, fixing it, verifying itself, and your own pytest
    confirming it stuck.
  • auto-accept edits now means what it says. It auto-approved everything, bash
    included. It now auto-approves file edits only β€” terminal commands and mutating MCP
    tools still ask, which is the mode most people actually wanted. The old
    approve-everything behavior is unchanged but now has its own name, yolo, still where
    --yolo starts you and now reachable with shift-tab: normal β†’ auto-accept edits β†’ yolo β†’ plan. A sub-agent's tools: "all" request is honored only under yolo, since
    all includes the shell

1.0.5

Choose a tag to compare

@nathansutton nathansutton released this 26 Jul 20:35

The release that makes a 24 GB Mac a first-class target: it runs the 35B now, a Linux container can drive the real local MLX model through chad serve instead of a different quantization on a remote box, and the CLI surface is smaller and honest β€” serve, prove and levers are real subcommands, and five harness-only governor flags became CHAD_* env vars. Plus completion and reasoning-budget accuracy, long-running commands that survive their timeout, a destructive-command guard that stops crying wolf, and long-context decode throughput that ships to a uvx install β€” measured end-to-end on stock PyPI mlx, no custom wheel anywhere. Each behavior change is individually reversible via CHAD_DISABLE.

24 GB Macs now get the 35B. The 35B's floor was 32 GB because its working set SIGKILLed a 24 GB machine mid-turn. The fused attention kernel and the 8-bit-from-the-start KV cache it enables cut the per-token cache cost enough to give the headroom back, and the compaction trigger sizes itself from the live Metal budget rather than a fixed constant, so a tight box narrows its window instead of dying. 16/18 GB still get the 9B, and --model 9b puts a 24 GB box back on the small model.
--model replaces reaching for an env var to change model. Takes 35b, 9b, auto, or any Hugging Face repo id / local model dir; works on chad and chad serve. CHAD_MODEL still works and the flag outranks it, so a globally exported var can't pin every run, and --model auto forces the RAM-aware pick even when the env var is set. Forcing 35b on a box whose RAM is too small (or unreadable) is honored but warns first β€” chad advises, you decide.
serve, prove and levers are real subcommands with their own --help. They were dispatched on the literal task string, so chad --help never mentioned them while still listing --host/--port as "chad serve only" β€” flags that parsed fine and were then silently ignored on every other invocation. --host/--port now belong to chad serve and are rejected elsewhere. chad --levers still works as an alias.
chad --help lists 12 flags instead of 19. The unattended-run governor knobs (--think-ceiling, --turn-budget-tokens, --turn-budget-s, --auto-continue, --review-pass) are set by benchmark harnesses, never by hand, and every one already had a CHAD_* twin, so the flags are removed rather than hidden β€” passing one is now an error instead of a silent no-op. CHAD_AUTO_CONTINUE fills in the one twin that was missing, and the bundled TB2 adapter (benchmarks/tb2/harbor_chad_tb2.py) sets all three it used through the CHAD_* env dict it was already building. --think-budget stays on the CLI: unlike the rest it is a capability trade a person reaches for interactively.
A dead remote backend explains itself. An unreachable --base-url exited through a 22-line traceback ending in chad.base_engine.BackendError, which reads as a chad crash rather than "the server you pointed me at isn't up". Now problem/cause/fix, like the model-load and download failures.
Removed the draft-model code path. chad has been single-model since before 1.0; Engine(draft_id=...) was never set by any caller, but its branches still shaped cache construction, KV-quant resolution, warm-prefix eligibility and prefill. Prompt-lookup decoding is unaffected β€” it shares the word "draft" and none of the machinery.
chad serve β€” drive the local MLX model from a Linux container. chad's remote arm (--backend llama) exists because MLX can't run in a benchmark container, which in practice meant measuring a different quantization of the model on a remote GPU box. chad serve exposes the in-process MLX engine β€” real prefix cache and all β€” over the same llama.cpp /completion protocol that arm already speaks, so the container drives the model people actually run, unchanged and with exact server-side timings. --host/--port (CHAD_SERVE_HOST/CHAD_SERVE_PORT), loopback and unauthenticated by default; CHAD_SERVE_API_KEY guards a wider bind. One KV cache means one agent at a time; concurrent clients queue.
Cache quarantine and warm-prefix come back over the wire. Against a stock llama.cpp server these are unavoidable no-ops (its cache is opaque and its disk isn't ours). A chad serve server advertises both in /props and the client feature-detects, so a sub-agent no longer evicts the main transcript's prefix and the stable system+tools prefix warm-starts from disk. Nothing changes against a real llama-server, and a failed call degrades to the old no-op β€” these are latency, never correctness.
A client that stops reading can no longer take the server down. Streaming wrote each token to the socket from the one thread allowed to touch MLX, with no timeout on it β€” so a client that stopped draining without disconnecting (a suspended container, a slept laptop) blocked that thread indefinitely and no other request could be served, while /health still answered ok. A blocked write never raises, so the hung-up-client cancel couldn't fire either. Generation now hands SSE lines to the request's own thread through a bounded queue: the engine runs ahead of a slow reader instead of being paced by it, and a peer that stops reading is written off the same way a disconnected one is.
A request no longer silently zeroes the server's sampler config. min_p, top_p and temperature absent from a /completion body were defaulted to 0.0 and applied anyway, wiping a server started with CHAD_MIN_P/CHAD_TOP_P on essentially every request (the client only sends those knobs when they're armed) and forcing greedy decoding. Absent now means "leave it alone"; an explicit 0.0 still disarms a knob for that request only.
Generated token ids on the wire are the engine's own. They were recovered by slicing the engine's cache past the prompt, which only holds on the main decode path β€” the prompt-lookup path came up one token short every turn, and after a Metal OOM the client was told a prefix was resident when the cache had been dropped. The engine now reports what it generated, and says so when it reset the cache, so the client's mirror and its prefill estimates stay honest on every path.
Long turns stopped paying quadratic CPU to watch for stop markers. The remote backend re-scanned the entire generated text after every token; at 16k tokens that was 846ms of pure scanning on the thread draining the stream. Now incremental: 4.5ms.
A long cold prefill no longer kills the turn it is working on. The stream stayed byte-silent from the response headers until the first token, and HTTP clients measure a stall in bytes, not in your progress β€” chad's own passes its timeout to urlopen, which applies it per read. A big prefill therefore timed out just before the first token, and because a socket timeout looks transient the agent re-issued the step and paid for the same prefill again. The stream now sends an SSE comment during silence; stock clients discard it, so nothing on the wire changes for llama.cpp.
A prompt that cannot fit is a 400, not a Metal OOM. Nothing compared the prompt against the context window before prefilling it, and n_predict was never clamped to the room left beside the prompt β€” so a client that mis-budgeted (a stale pinned context, a plain curl) walked the engine into an allocation it might not survive, taking every other client with it. /completion and /warm now refuse an oversized prompt up front and clamp the generation budget to what the window can hold. The admission wall is live, not the number pinned at load: the Metal budget is blind to other processes, so a docker stack or a second model started afterwards takes physical pages the KV cache needs 1:1, and a prompt that fit an hour ago no longer does. /props keeps advertising the stable window clients budget against; /health reports safe_ctx and ctx_pressure so a tightened wall is visible before a request bounces off it.
chad serve honors CHAD_TEMP / CHAD_MIN_P / CHAD_TOP_P. It built its own engine and read none of them, so a server started with a sampler config sampled as if it had none β€” the one drift the server exists to eliminate. The three now travel as a single shared call the CLI and the server both make, so they cannot drift apart again.
Long-context decode is faster on both models. The fused attention kernel's split factor now widens past 16k context instead of staying flat: measured end-to-end (one process, interleaved arms, adopted only where the spread cleared 1.000), the 35B decodes +1.5% at 32k and +0.6% at 16k, the 9B +0.5% at 32k, both unchanged at 8k where flat was already optimal. It is the same math on a different grid β€” the split only regroups the partials pass 2 combines β€” and the widened tier gets its own kernel-vs-reference test plus a retile-agreement case at that size.
CHAD_FUSED_LAYER is removed. The opt-in fused GDN+MoE compiled region was measured on real weights against the two-call path it replaced and moved decode 1.001x-1.002x β€” indistinguishable from noise β€” so the code is gone rather than shipped as a dead knob. The measurement lives in the source so the idea isn't re-derived. Setting the var now does nothing.
CHAD_NO_MLX_TUNING is removed, along with the tuning it disabled. The MLX_* runtime knobs (MLX_METAL_FAST_SYNCH, MLX_MAX_OPS_PER_BUFFER, MLX_MAX_MB_PER_BUFFER) were swept on the 35B and every arm lost 1-4% at both 8k and 32k, so chad now sets none of them and the module that applied them is gone rather than shipped as an empty table with a flag to switch it off. mlx reads these vars directly if you want to experiment.
A done with nothing landed gets the task's own requirements back, once. Previously, ending a turn with no landed-and-verified change stopped the turn immediately and banked a progress note β€” so the done-audit, which quotes the task statement's requirement lines and reports what actually exists on disk, never ran on exactly the turns that needed it most. That ending now hands off to a single audit bounce first, in context with the work the turn already did; a second empty ending stops as before....

Read more

1.0.4

Choose a tag to compare

@nathansutton nathansutton released this 23 Jul 21:15

Tool-result economics: three additions, each individually reversible via CHAD_DISABLE.

Edits survive typographic-punctuation drift. A fourth edit-match rung: when
exact, escape-normalized, and whitespace-flexible matching all miss, the edit
retries with curly quotes, en/em dashes, ellipsis, and non-breaking spaces folded
to ASCII on both sides β€” the drift when the model re-types prose or docstrings it
saw rendered (in either direction). A unique match is still required, and the
result discloses the recovery.
Duplicate read-only output is elided. When a read/grep/glob/symbol-tool
result comes back byte-identical to a result still in the transcript, chad appends
a short pointer to the earlier copy instead of re-sending the full body β€” on a
non-trimmable prefix cache every duplicate body is prefill paid again on every
later step. Byte-equality against the live transcript is the safety proof: a
changed file, different arguments, or a compaction rewrite all break equality, so
content is only elided while a verbatim copy is provably still in context.
Sub-agent reports with no evidence are flagged. A task sub-agent that
returns a confident, non-empty report having dispatched zero tools answered from
model memory, not this repository; its folded result now carries an explicit
"verify with grep/read before relying on it" warning. Warn-not-reject on purpose:
a re-spawn doubles the local GPU cost, and a wrongly-rejected report breaks the
turn while a wrongly-accepted one merely restores the old behavior.
Plus a privacy default flip:

Local diagnostic traces are now opt-in (privacy-first default: off). The
readable ~/.chad/session.log trace β€” user query, tool-call args (bash commands,
write/edit content), and result previews β€” and the persistent input history at
~/.chad/history are no longer written by default. chad is a local, single-user
agent so nothing ever left the machine, but the log still landed plaintext previews
under ~/.chad, so it's now enabled only when you opt in with CHAD_SESSION_LOG=1.
CHAD_NO_SESSION_LOG=1 still works as a hard kill switch and wins over the opt-in.
And a session-start orientation change:

A lightweight workspace map replaces the flat file listing. The system-prompt
tail now opens with a small, ranked repo_map digest β€” signatures only, ranked by
reference centrality β€” instead of a flat file listing, so the model orients
structurally at session start rather than burning a reflexive step-1 repo_map
call (the tool stays for a wider or focused map). It's built once per session from
the on-disk mtime cache, so it doesn't churn the KV prefix or add per-turn cost, and
degrades cleanly to the old flat listing wherever repomap is unavailable (e.g.
wheel-less platforms). On by default; reversible via CHAD_DISABLE.

1.0.3

Choose a tag to compare

@nathansutton nathansutton released this 21 Jul 23:05

[1.0.3] β€” 2026-07-21
Guardrail-interaction fixes: three cases where the model was doing fine but overlapping guardrails ended the turn early or accepted a non-answer.

A malformed tool call is never accepted as a final answer. When the model slips into a wrong tool-call dialect, garbles now get their own re-nudge budget (separate from the token-cap-truncation counter), a canonical call exemplar once it's stuck, a scrub of the previous garbled message so the model stops few-shotting its own broken dialect, and β€” if the budget runs out β€” a clean hard stop with a banked progress note instead of shipping the garble as the answer. On by default; reversible via CHAD_DISABLE.

Done-audit re-bounce for still-missing deliverables. If the audit flags a task-named output path as absent and it's still absent when the model tries to finish (with real time left on the budget), chad bounces once more naming only the missing path, then accepts unconditionally. Stops a required file from going unwritten while the turn ends "successfully."

Investigation gate exempts ops commands. A bash step that isn't provably read-only (git merge, apt-get install, redirects, make, …) now counts as action, not investigation, so the "you've only been looking, make an edit" gate no longer fires in the middle of a legitimate ops workflow.

1.0.2

Choose a tag to compare

@nathansutton nathansutton released this 20 Jul 21:30

[1.0.2] β€” 2026-07-20

Reliability tuning for long, budgeted tasks, plus one interactive papercut fix.

  • Syntax gate no longer flags prose & data files. Plain-text deliverables β€” a
    .txt answer file, requirements.txt, markdown, CSV/TSV β€” were being run through a
    tree-sitter grammar (the language pack maps .txt β†’ VIMDOC) and warned on, sometimes
    reverted, at exactly the write that produced the deliverable. The gate now polices
    code languages only; prose/data formats are skipped uniformly across warn, edit-revert,
    and write-reject.
  • run-style tasks no longer bail with prose. System-state asks ("start the
    service", "boot the image", "install X") are completable with zero file edits, and
    used to fall through to the weakest completion path β€” a give-up in prose could ship
    nothing (qemu-startup did, with most of the wall unspent). They now get their own
    intent class that arms the anti-bail nudges, while still completing cleanly with zero
    edits (the no-empty-diff "done" gate only ever applied to edit tasks).
  • Reasoning budget throttles instead of muting. Once a turn's cumulative
    reasoning-token budget is spent, chad now forces one no-think action step per ~3k
    further reasoning tokens (a duty cycle) rather than muting thinking for the rest of the
    turn β€” thinking is restored as soon as the model stops over-spending, avoiding the
    garbled tool-call tails a blanket mute produced.
  • Wall-aware auto-continue. When most of a task's wall budget is still unspent, an
    exhausted turn is granted a fresh relaunch (bounded), instead of giving up after a
    fixed two attempts with the clock barely touched.
  • Rejected "done" no longer poisons the relaunch. When a completion claim is
    rejected for landing no verified change, the carried-forward progress note now leads
    with a warning that the claim was rejected and drops any hypothesis that itself asserts
    completion β€” so a relaunch stops inheriting "already complete and verified" as fact and
    re-confirming it.