June 26, 2026

Rebuilding threads: a spine that scrolls and a memory that holds

A two-axis compaction model gives every thread its own checkpoint, while a full Weave restyle turns the thread panel into a real workspace.

Today was about making threads feel like a first-class surface instead of a bolt-on. I wanted a memory model that understands the difference between the main conversation and a side thread, and a UI that stops treating threads as an accordion you have to fight. Both landed, and together they change how Chalie holds a long, branching conversation without losing the plot.

The harder problem was underneath: what does the model see when replying inside a thread versus the main flow? I redefined the context window around a pivot I’m calling settle0 — the turn’s first assistant row that isn’t holding a tool call open. The main spine reads everything up to that pivot, bounded to a recent window; a thread reply reads only its own slice, from the pivot forward to wherever the reply started. That’s the difference between the model seeing the whole house and just the room it’s standing in — it stops a busy thread from dragging unrelated history into a reply, or a long main conversation from bleeding into a side branch. Compaction moved out of the transcript entirely, into its own table with two independent watermarks — one for the main spine, one per thread — so summarizing a thread can never shift a turn boundary. It’s invisible when it works and produces uncanny, off-topic replies when it doesn’t, so I backed it with feature tests hitting a real database on every seam, not mocks. Migration was clean: legacy compaction rows are just a cache, so dropping them costs nothing and the underlying history is untouched.

Then the whole thread panel got rebuilt on top. The old accordion pattern — cards popping open inline, pushing everything else down — is gone. Threads now open in a slide-over panel with their own pinned reply box, ⌘K opens global thread search from anywhere, and Chalie’s avatar lost its violet glow for a plain, unclipped mark that reads calmer at a glance. The reply composer inside a thread is now the exact same component as the main input dock — same voice input, same attachments, same thinking-mode toggle — so every feature added to one shows up in the other for free instead of the two drifting apart. Long user messages now collapse behind a show-more toggle, because a thread with three walls of text should read like a conversation, not a transcript dump.

The fiddly part was making a brand-new thread feel alive the instant you start it. Threads only got their id assigned once a reply fully finished, so a slow response left the card headerless and the reply box missing until a refresh — a dead-feeling UI over a system actually working fine underneath. Fixed it by having the backend announce a thread’s id the moment it starts, before any tool calls run, so the card renders its header and reply dock live from the first token. Small fix, but it’s the difference between a thread that feels responsive and one that feels broken while you wait.

None of this is finished — a two-axis memory model and a slide-over UI are foundations, not endpoints. Next is making the two sides talk cleanly: the compaction rewrite defines what the model remembers, the restyle defines how you navigate it, and the real test is a week of heavy multi-threaded use without either side surprising the other.

  • Compaction pivots on settle0 (first tool-free assistant row) with two independent watermarks — turn-keyed for the main spine, transcript-keyed per thread — so summarizing never shifts a turn boundary

  • Legacy transcript-based compaction rows dropped as a regenerable cache; full backend test suite green throughout

  • Thread UI moves from inline accordion to a slide-over panel with its own pinned reply dock, global ⌘K thread search, and a calmer unclipped Chalie avatar

  • Thread replies now use the exact same composer as the main input — voice, attachments, thinking mode included — so the two never drift apart

  • New threads bind their id the instant they start (not when they finish), so the header and reply box render live instead of waiting for a refresh