May 18, 2026

One Chokepoint to Rule Every Message

Every message into Chalie now flows through a single dispatch function, deleting a whole shadow queueing system in the process.

Yesterday I rebuilt how steers get injected into a running turn. Today’s harder question: why does a steer need its own path at all? User messages, subagent replies, scheduled prompts, external-agent messages — four sources, four ways in. Every seam is a place a message can go missing, arrive twice, or land in the wrong turn. So I collapsed them.

dispatch_message() is now the single front door for anything that wants to talk to Chalie. It takes two flags instead of four code paths: hidden_input, which suppresses the message from the transcript (for things like disclosure pings that shouldn’t clutter the chat), and intercept, which steers into an already-active turn instead of starting a new one. Every caller — chat endpoint, subagent returns, scheduler, external-agent HITL — goes through the same function with the same guarantees. That let me delete _pending_steers, _turn_active, _deliver_envelope, _spawn_return_processor, SubagentReturnProcessor, ScheduledPromptProcessor, and all the drain/threading/broadcast plumbing built around routing decisions no longer needing four separate homes. Net minus 331 lines, and one less category of bug where “which path did this message take” is even a question.

The same instinct — stop special-casing infrastructure — showed up in policy. Tools have always needed explicit entries in allow/deny lists per context (chat, subagent, external agent), but a handful aren’t really tools the way send_email is — timer, steer, save_graph, review_tool_calls are plumbing the model uses to run itself, not capabilities a user picked. A new SYSTEM/INTERNAL class attribute lets these auto-discover and get always-allowed, instead of manual entries scattered across three allow lists and the Brain UI’s policy labels. Small change, outsized fix: the capability inventory used to crash every 30 seconds summarizing SteerAbility, which has no SUMMARY because it was never meant to be user-facing. Same root cause, one fix, applied once instead of three times.

The other fire was save_graph quietly burning reasoning iterations. The loop only exits when a turn produces zero tool calls, but save_graph.execute() swallowed its own store() result and always reported success — even on an exact duplicate. The model had no signal the fact was already recorded, so it kept re-issuing the same call, sometimes for the full 30-iteration ceiling, tying up a worker for minutes on nothing. Two fixes: a session-level dedup set that short-circuits an identical (kind, key, value) before it hits the database, and a real signal — already_stored: True — surfaced when the service reports a reinforced duplicate rather than a fresh write. The model now sees convergence and stops asking.

Not everything landed clean. I shipped a 90-second timeout on the permission-request gate to stop test runs hanging when a WebSocket client never answers a prompt — and reverted it the same day. A blanket auto-deny is the wrong shape for a gate meant to represent a real human decision; it needed scoping to non-interactive contexts, not a global change. Better to back out cleanly than ship a fix that quietly redefines what “asking permission” means for a real user.

Smaller but real: the Brain dashboard’s token metric cards were technically correct but practically confusing — a card labeled “Total Tokens” that actually meant “total in the last 24 hours” is a trap waiting to mislead someone mid-incident. Labels now reflect the active window, midnight-scoped totals are marked UTC, and cache-hit cards return null instead of a fake 0% for providers that don’t report caching.

The thread running through this week: fewer paths, fewer special cases, the ones we keep named for what they are. Next up: watching whether one dispatch chokepoint holds up under the same load that used to need four.

  • Single dispatch_message() chokepoint replaces four separate routing paths for user, subagent, scheduled, and external-agent messages, net -331 LOC

  • New SYSTEM/INTERNAL ability class auto-allows infrastructure tools, fixing a recurring capability-inventory crash and removing manual policy-list entries

  • Session-level dedup in save_graph stops the reasoning loop from burning iterations re-recording an already-stored fact

  • Reverted a same-day permission-gate timeout after recognizing a blanket auto-deny changes what “asking permission” means

  • Token metric cards on the Brain dashboard now label their actual time window instead of implying an all-time total