July 9, 2026
Undo Finally Means Undo
Cancelling a message now actually cancels it, thread replies stopped bleeding their status onto the main timeline, and a background reflection job that was feeding on its own output got a real gate.
Hit undo on a message mid-response before today and the spinner stopped, but the turn wasn’t actually dead — the model call kept running underneath, and if it came back late it could still write itself into the transcript after you’d already moved on. That’s not cancel, that’s a UI that lies to you.
Cancel now stamps the turn terminal — state set to cancelled, end time recorded — in one transaction and broadcasts it immediately, so the spinner clears because the turn is actually gone, not because the interface gave up waiting. A late response that does come back gets discarded at the next checkpoint instead of getting saved. The interface refetches, clears the empty shell of a cancelled turn from your history, and puts your draft back in the composer so you’re not retyping what you just cancelled.
Two more thread bugs came out of the same corner. Reply inside a thread and the main timeline used to show a duplicate “thinking” indicator for it, because the tag marking a reply was keyed off a marker that a reply’s own internal steps could flip back and forth mid-turn. Now it’s keyed off the second message in the exchange, which is fixed the moment it’s written and can’t shift under it. Separately, a scheduled task that had never fired once showed a permanent spinner with nothing behind it — “is this working” was being computed from settlement history instead of asking directly whether anything was actually running, so zero history read as forever in-flight.
The one I want to call out sits deeper than the interface. Chalie runs a background job that turns recent activity into a memory of what happened. Folding it into this week’s unified scheduler made it fire every cycle through an idle stretch — reflecting on its own prior reflection, repeatedly, with nothing new to say. Fixed by requiring a real message to have landed since the last reflection before it’s allowed to run again. The other jobs on that same scheduler just skip themselves when there’s nothing to do; this was the one job that manufactures its own input, so it needed an actual gate instead of a no-op.
A cluster of smaller things landed alongside all that. Codex CLI is now a native model option — no API key, no host, it shells out to a local codex install and reads its own login. Episode memories now index through the same declarative search path the rest of memory already uses, instead of a hand-rolled one-off. The thread panel lost an unintended pink tint it had no business having. Scheduled task labels got a fix for one-time schedules showing “Every minute” instead of “Once” — shipped, then reverted the same day once it didn’t hold up under a second look. And a new base contract for API endpoints went in: one class that generates routes and response envelopes for every endpoint built on it from here, instead of hand-writing the same routing boilerplate again per feature.
Next up is leaning on that new API base for real — migrating more of the app onto it now that undo, threads, and the reflection loop have stopped fighting the interface underneath them.
-
Undo now actually cancels a running turn — synchronous terminal state, late responses discarded, spinner clears because the turn is really gone
-
Fixed thread replies bleeding their “thinking” indicator onto the main timeline, and scheduled threads showing a permanent spinner with no work behind it
-
Gated the background reflection job so it stops re-reflecting on its own prior output through idle stretches
-
Added Codex CLI as a native, keyless model provider
-
Landed a base contract that generates routes and response envelopes for every API endpoint built on it going forward