June 25, 2026

Threads ship: the flat feed is gone

Chalie's conversation model gets rebuilt around threads instead of one long scroll, landing end-to-end from database to UI in a single day.

Today the flat conversation feed died. Every message you’d ever sent Chalie lived in one long, undifferentiated scroll — fine for a linear chat, useless once you wanted something from an hour ago without wading past everything. The fix wasn’t a UI patch: it meant redefining what a “turn” is at the database layer and rebuilding every consumer above it — transcript writes, compaction, memory recall, search, and the conversation UI.

The core move was small to describe, large to land: turn_id is now the thread id. A new topic allocates a fresh one; replies append rows carrying the existing one. That rippled everywhere — /conversation/threads returns collapsed thread previews instead of a raw row dump, /conversation/thread/<turn_id> fetches one thread’s full history on expand, and the UI’s reply path composes against an existing thread. Eight feature tests exercise allocation, append, and pagination against the real database.

The harder problem was keeping each thread’s memory its own. Before today, compaction — which keeps long conversations from blowing past context limits — watermarked on channel alone, so every thread shared one checkpoint; a reply in thread A could get compacted against a watermark drifted from thread B’s activity, leaking or losing thread-local history unpredictably. The watermark is now keyed on (channel, turn_id): each thread owns its own checkpoint, the compactor writes its summary into the thread it summarizes, and a reply’s raw context is that thread’s history plus its own checkpoint — nothing else, unless a thread’s gist is deliberately surfaced through cross-thread recall. Twelve feature tests pin the isolation end to end.

That cross-thread recall is the payoff: a dedicated async gist processor distills each thread to a short summary once it goes quiet, indexed for a new /threads/search endpoint — vector and full-text, fused. Ask Chalie about something from three threads ago and it finds it without scrolling or loading that old thread into context. The frontend rebuild makes this tangible: collapsed gist rows for finished threads expand on click, a search dialog reaches across all of them, and a reply box lives inside each open thread instead of at the bottom of one infinite page.

None of this replaced a worse feature so much as retired one that had drifted from the point. The moments/star/recall-dialog system — pin a message, star it, dig it out of a separate recall UI later — got deleted outright, backend tables and all, net around 1,650 lines gone. It solved “find something I said before” in a way threading and gist search now solve automatically, without asking you to remember to star anything. Turn-zero memory seeding also lost its HyDE rewrite step — no asking a model to hypothesize a better search query first; the raw message is the query, and a short follow-up skips recall outright, assuming the model already has the thread in view. Fewer generative steps on the hot path, a service file 200 lines lighter.

The rest of the day was the discipline that makes a rewrite this size safe to ship same-day: a tool-result nudge system now lets five more tools interpolate live results into next-step hints, a stale UTC-parsing bug making active threads look inactive got fixed, and a security pass closed a hardening batch: tokens hashed at rest, login rate-limited by IP, sessions purged on re-auth, WebSocket auth off the URL, CI Actions pinned to immutable SHAs.

Shipping a change this size and having it render correctly the same day either compounds or it doesn’t. The real test is usage: does surfacing threads and gists make Chalie feel like it remembers your day, not just its volume.

  • turn_id redefined as the thread id — replies append to existing threads instead of minting new turns

  • Compaction watermark re-keyed to (channel, turn_id); every thread now owns its own checkpoint

  • Cross-thread recall shipped: an async gist processor summarizes finished threads, indexed by a new vector+FTS /threads/search endpoint

  • Moments/star/recall-dialog feature deleted outright (~1,650 lines); HyDE removed from turn-zero memory seeding

  • Security hardening batch: session tokens hashed at rest, login rate-limited by IP, WebSocket auth moved off the URL, CI Actions pinned to immutable SHAs