# Thread titles

How an unnamed thread gets a label, and why we don't ask a model to invent one.

## The problem

Thread creation is fresh by default, so a house soon holds several unnamed threads with the same people in them. A list shows who is in each one and the latest line, and that is not enough: five threads with ada, all ending in "ok, done", look identical.

## What a title is here

A thread has one real title: the `name` a person typed. It is stable, addressable as `#name` in chat, and it decides whether a new conversation reuses an old one. Nothing derived from messages ever touches those three things. Whatever else a list prints is presentation.

## How would you title a chat that keeps evolving?

You wouldn't. A chat has people (its identity) and an "about" that drifts. Slack names a DM by its people forever, gives a channel a topic that is expected to change, and shows a thread as its opening message plus the latest reply. Coding-agent tools (opencode, t3code) title a session once from the opening message and never retitle it. Both answers are the same: the stable thing about a conversation is how it started.

A model-written title from message two goes stale by message twenty; a title that tracks the conversation changes under you. Once the label is "how it started", neither problem exists.

## What arbe does

**The opening line.** The first previewable message is stored on the thread next to the latest one, written once and never regenerated. It is the instant, free seed: the moment someone speaks, a list can tell this thread from its siblings. `arbe thread view` prints it as `started:`.

**The polished title.** When the thread director finishes a pass and finds an unnamed thread that has started but has no title, it rewrites the opening line once into a short phrase — "can we ship the release on friday?" becomes "Friday release" — on a cheap model, paid by arbe rather than the house, capped at 50 characters and kept in the message's language. The prompt follows opencode's title generator and t3code's subject-and-outcome rule (drop "hey, can someone…", keep names, numbers and dates); the output is sanitized the same way (first line, quotes off, clamped). It is written only while the title is still empty and the thread still unnamed, so a name typed meanwhile wins.

It is never regenerated on its own. A person can ask for a fresh one — "Regenerate title" in the thread menu, `arbe thread title <id> --regenerate`, or `PATCH /api/threads/:id { "generated_title": null }` — which forgets the title and wakes the director to write it again. That is the whole regeneration story: forgetting is the request, and the director is the only writer.

## Display order

Every surface uses the same helper (`@arbe/core/thread-display`): explicit name → generated title → participants → short id for the row label. The opening line shows underneath as the differentiator until a title has been written from it, and never when it is also the latest line. The house palette is a people picker, so it labels unnamed threads by their people regardless.

See [threads](system/chat/threads.md) for the row, reuse, and the participant filter.
