June 28, 2026

Threads get a real REST contract, and the wrapper API dies

The thread system lands its consolidated send/read contract behind one send chokepoint, while a full generation of dormant API surface gets torn out for good.

Yesterday’s restyle gave threads a slide-over panel and a shared composer. Today was about the contract underneath it — making the actual send/read path as clean as the UI now looks, and then using the momentum to clear out every piece of API surface that’s been quietly rotting since earlier stages of the product.

The thread system gets its consolidated REST contract: POST /api/thread to start one, POST /api/thread/<turn_id> to reply, both returning a bare 201 and firing a lean created WebSocket signal that carries nothing but the new turn id. Every surface that needs the actual content fetches it fresh — one getter, Transcript.by_turn, is now the only way any code path reads a channel’s rows, whether that’s a REST read, the batch endpoint, or a WebSocket refetch. No more parallel query paths that can silently drift apart; consumers filter and shape what they get back in plain Python instead of bespoke SQL, which is the difference between one flow you can reason about and three you’d have to re-verify every time the schema shifts. The old POST /chat route and its /chat/stop alias, fully superseded once the CRUD contract landed, are gone.

Per-thread gists get sharper too: instead of a generic summary, each thread now gets a terse, topical label — three to five words, written from exactly two messages, the opener and the first message past its settle point. Small until you have a dozen threads open in the activity panel and need to tell them apart at a glance. Keyword search over threads stopped being a separate feature and became the existing thread feed with a query filter — same shape, same code path, capped to five results.

The bigger story is subtraction. A whole generation of wrapper/agent-to-agent API surface — built for an early integration model that never got a live consumer — came out: the outbound intent-push stack, the inbound state-mutation endpoints, the cognitive-state query and passive-signal-ingest routes, half a dozen placeholder stubs, and the unused context endpoint. With the query/signals routes gone, wrapper tokens stopped needing a permissions model at all — they’re now what they should have been from the start, opaque bearer credentials for authentication only. None of this touched the live agent integration path. Net effect: over a thousand lines of dead surface removed in a single day, each piece verified to have zero real callers before it went.

Alongside the deletions, the API layer took a step toward type safety: a Pydantic-based DTO boundary landed, and the lists endpoints became the first namespace fully migrated onto it — nine id-addressed CRUD operations, strictly typed bodies, and swagger docs checked against what the handlers actually return rather than hand-maintained prose that drifts. That’s the template the rest of the API follows next.

Test hygiene got the same subtraction treatment. Every test touching the new thread and compaction model went through a mutation audit — breaking each invariant on purpose and confirming the test notices — which found tests that looked like coverage but proved nothing, and others passing for the wrong reasons. Dropping the dead ones and fixing the silent ones is worth more than adding new tests on top of a foundation you haven’t checked.

None of this is showy. But a product built on threads and memory only earns trust if the read path is boring and predictable — one getter, one send chokepoint, a wrapper layer that finally does only what it says. Next is putting real usage on top of that foundation and seeing where it still creaks.

  • Consolidated thread contract: POST /api/thread[/<turn_id>] returns 201 empty; created WS signal carries only the new turn id; all reads flow through one Transcript.by_turn getter

  • Thread gists rewritten as terse 3-5 word topical labels from exactly two messages; keyword search folded into the existing thread feed

  • Dormant wrapper/agent-to-agent API stack removed — over 1,000 lines net deleted, wrapper tokens simplified to plain bearer auth

  • Lists API becomes the first namespace fully migrated to a typed Pydantic DTO boundary

  • Mutation-audit pass on the thread/compaction test suite drops tests that proved nothing, fixes ones passing for the wrong reason