May 26, 2026
Optional Deps Go Runtime, Reasoning Loop Gets Steerable
Voice and browser automation now install on demand instead of bloating every image, and mid-conversation redirects finally cancel cleanly instead of racing the model.
Two things had been bugging me: the install was heavier than it needed to be for most people, and interrupting Chalie mid-thought was clumsier than it should be. Today I fixed both.
Voice and browser automation serve a meaningful slice of people, not everyone, and baking both into every image punished the majority for the minority. I built a RuntimeDepsService that moves both to background installation: Playwright installs itself automatically on first boot, and voice installs the moment someone actually flips it on — during onboarding or later from settings. GPU detection that used to live in shell scripts now runs in Python and picks the right dependency group (CPU, CUDA, or ROCm) with one install command instead of juggling swaps after the fact. The payoff is blunt: the base image is 60-70% smaller — faster pulls, faster first boots, less disk pressure on anyone running Chalie at home. Onboarding now has a dedicated voice setup step with a clear enable-or-skip choice, so the tradeoff is explicit instead of silently baked into install time.
The other half of the day was the reasoning loop — the part of Chalie that lets you interrupt a running turn and redirect it. The old steering mechanism was a separate ability (SteerAbility) bolted onto the chat pipeline, and it showed: extra plumbing in the dispatcher, the websocket layer, and the renderer, all to support a feature that could be simpler. I deleted it outright and rebuilt interruption as cooperative cancellation. Send a message while Chalie’s mid-turn now, and it POSTs to /chat/interrupt, which cancels the active turn, cleans up its half-finished tool calls and transcript rows, and starts a fresh turn seeded with your original message plus the new one — nothing you said gets lost in the handoff. The stop control moved from the chat input box into the reasoning-cycle bubble itself, right where the running work is visible, instead of a spot that had nothing to do with what it was stopping.
While I was in there I killed a long-standing failure mode: send-on-Enter, a constant source of half-typed messages firing early, especially on mobile. The send button is now the only way to submit, on every device — one behavior, no platform exceptions.
I also caught a bug quietly making failures worse than they needed to be. When a tool ability returned an error, the dispatcher’s success-path handler read only the text key and threw away error entirely — so a blocked URL, a browser timeout, or an unavailable capability all came back to the model as an empty string. Nothing looked wrong, so the model just retried. Now errors surface as [ERROR] ... in what the model sees, get logged properly, and the turn’s status reflects reality — the difference between Chalie recovering from a problem and quietly looping on a dead end.
Closed the day with the unglamorous but necessary part: rewriting the architecture and message-flow docs to match the new interrupt model, and clearing a backlog of test debt — dead mocks, tests that only checked something was absent, and a rebuild of the processor invariant suite so it reflects the tool scopes each processor actually has today. 1142 unit tests green at the end of it, on a net negative line count.
None of this is roadmap-shifting alone, but it’s the kind of day that makes everything after it faster: a lighter install means more people reach a working Chalie sooner, and a dispatcher that tells the truth about failures means the next feature built on top doesn’t inherit a silent lie.
- RuntimeDepsService moves voice and Playwright installation to background threads, cutting base image size 60-70%
- Reasoning loop rebuilt around cooperative cancellation: mid-turn messages cancel cleanly and merge into a fresh turn instead of racing
- SteerAbility deleted entirely; the stop control now lives inside the running reasoning-cycle bubble
- Send-on-Enter removed everywhere — the send button is the only way to submit, on any device
- Dispatcher bug fixed where ability error messages were silently dropped, causing the model to retry blindly on failures