Organizing and continuing conversations
Delivery scope: partition grouping, the complete inbox, reversible closing and durable external task reuse are independent of AI preparation. First-prompt summaries and related-conversation suggestions remain planned until the naming-service connection is approved and installed. Delivery status records validation and deployment evidence.
The inbox groups conversations by their partition name, like projects in a repository sidebar. Each partition has a count and can be collapsed. Delegated work belongs under its parent conversation and is collapsed until expanded. Open, Closed and All views use the complete accessible catalog; a display limit must never silently hide older conversations. Search includes partition names, titles, descriptions, categories and delegated work. The same inbox is available from the desktop chat and the phone's Threads link. Select visible and bulk close act only on expanded, matching conversations; collapsing a partition clears its selection.
Grouping uses the conversation's original context partition, falling back to its stored partition when no context is available. A conversation kept in the user's home can appear under the partition it discusses without changing its storage path or access rules. This is a local projection, so existing conversations group immediately without model calls or metadata backfills. Categories remain optional secondary labels; changing a label does not move a conversation between partitions.
The composer shows a context chip only while the viewer can read the context node. If that node is deleted, the chip clears and the conversation remains usable; deleting the context does not delete the conversation or its messages. Other context-read failures still reach the view's error boundary.
Planned: first message and continuation
When the preparation service is available, the composer prepares a title and a short description of the first prompt. Partition grouping is derived from context. The original draft, selections and attachments remain available throughout preparation. A relevant open conversation can be suggested, with two explicit choices:
- Continue thread appends the draft to the selected conversation after rechecking that it is still an accessible, caller-owned, open root conversation.
- Start new creates one conversation with the prepared metadata and the original first message.
Current deployed adapters use normal creation while the service is absent; they do not issue an unhandled preparation request or delay sending. The optional choice flow is tested with a fake preparation service and is enabled only by a future integration.
The choice happens before creating a runnable thread. Matching never merges conversations, closes an older conversation, or launches its task. Titles and descriptions from other conversations are matching data, never instructions. Failure leaves the draft intact. A new edit invalidates a pending suggestion. Retrying an uncertain submission keeps its operation identifiers.
MeshNode.Description describes the original request. It is distinct from Thread.Summary, the
latest result used by delegation. A continued conversation retains its existing title, description
and category. Manual edits remain authoritative. Thread paths remain stable when names change.
Closing and Undo
Close moves a conversation to the Closed view without deleting its messages or changing its
children. Bulk close reports each result independently. Conversations with running or queued work,
including active delegated work, are skipped. Selection and counts describe root conversations.
Delegated child paths are rejected by both the batch operation and the owning thread handler;
older root paths without an _Thread segment remain supported. Accessible orphaned children stay
visible, but cannot be selected for root-conversation close.
The parent's running and queued state is checked atomically by its owner. The active-child check uses a query snapshot, because multiple thread owners do not share an atomic transaction. A child may start after that snapshot. Closing never cancels or edits it; the parent remains in Open while any accessible child is active, even when the parent's own status is closed.
Undo carries the specific close operation and persisted version. It restores the prior idle or cancelled state only while that exact close is still current. Later input, edits, reopening, or a different close invalidate the receipt. A close retry with the same operation identity recovers the original receipt, including its original persisted version; it cannot mint a fresh Undo receipt for an intervening edit. Accepted close identities remain recorded after reopening, Undo, and new input, so an old close request cannot close resumed work again. Explicitly sending new input to a closed conversation reopens it; retrying an already accepted input does not.
The owning thread hub evaluates the condition and performs the update. A remote mirror's
per-property merge cannot safely enforce a close or Undo precondition: it can merge a stale
Done write with newly queued input. The request/response boundary also gives clients an actual
acceptance receipt instead of guessing that a fire-and-forget write succeeded.
In the personal inbox
The personal inbox (/{user}/Inbox, the platform's InboxLayoutArea) lists everything that reaches
a person in one place — notifications, mail and conversations — as one selectable grid with a
search box, an order select, a switch per source, and Delete and Undo. The inbox knows nothing about threads: the Threads app BRINGS its part
(ThreadInboxContribution, registered with the engine's services), and the inbox runs it.
| Brought | What it is |
|---|---|
| one leg per channel | WhatsApp, Teams, iMessage — the viewer's open conversations that arrived over that channel; each is its own switch in the inbox, listed only once a conversation ever arrived over it (below) |
the Chat leg |
the viewer's open conversations no channel claimed |
the Conversations leg (Running band) |
the conversations an agent is answering right now |
ThreadInboxItemHandler |
what Delete and Undo mean for a thread |
- One conversation, one row. A channel stamps the conversations it starts
(
Thread.ConversationOrigin.Client, aConversationChannelconstant); each channel leg matches its own stamp and theChatleg negates all of them. A conversation started before the stamp existed carries none and is listed underChat. - Delete is this page's Close; Undo is its Reopen. The handler calls the same
CloseThreadsthe conversation list uses, so the same refusals apply (a running round, queued input, a delegated child) and each is reported in the inbox's outcome line with its reason. Nothing is deleted: a closed conversation keeps its history and remains under Closed in the Threads app. Undo is this page's own conditional undo (UndoThreadClosures) with the close receipt the conversation carries: it restores the status the close recorded (a cancelled conversation comes back cancelled), and it is refused once the conversation changed after the close — never a direct status write. - Each channel package requires
AI@^1.28.0, the version that bringsConversationChannel: WhatsApp, Teams and iMessage stamp it on every conversation they start. - An unopened reply is bold. A round that completes stamps
Thread.UnreadByOwnerat the moment its "is ready" notification goes out; opening the conversation clears it — only when the viewer is the person it is addressed to — and marks that viewer's notifications about it read ({viewer}/_Notification,TargetNodePath= the thread). Each channel therefore brings an UNREAD leg (content.unreadByOwner:true, shown bold) and a READ leg (the defaultfalseis omitted from stored JSON, so it is negated). Portal chats bring ONLY the unread leg: a chat its owner has read is history, not an obligation, and listing every open chat ever started buried the inbox under months-old conversations (owner, 2026-10-14). Read chats stay in the Threads app. Every source carries its symbol: 🟢 WhatsApp, 🟣 Teams, 🔵 iMessage, 💬 Chat, ⏳ the conversations being answered now. - Reading never reorders. A conversation is shown and ordered by its last reply
(
Thread.LastReplyAt, stamped with the unread mark), never byLastModified— clearing the mark on open is a write, and timing by it would move an old conversation ahead of newer ones (coreInboxQueryLeg.TimeField). The legs still choose their rows byLastModified: conversations stored before the field existed lack it, and a descending sort puts a missing value first on Postgres. - A channel nobody wrote in over is not listed (owner, 2026-10-15: "i see a lot of stuff that's
not even configured ==> must get only configured sources"). Each channel leg names its connection
(
InboxQueryLeg.ConnectedWhen,ThreadInboxContribution.ConnectedWhenFor):namespace:{viewer}/_Thread nodeType:Thread content.conversationOrigin.client:{channel} limit:1 select:path— any conversation that arrived over the channel, a closed one included. Core's inbox lists the channel's switch only when that finds a node.ChatandConversationsname none and are always listed. - Only the viewer's own
_Threadis listed. Every leg reads{viewer}/_Thread— one partition. That is where every inbound channel threads into, and where a chat started from the viewer's own home lives. A conversation started on a page in another partition lives beside that page; finding it would need the cross-partition read this list uses (InboxThreads), which the personal inbox must never issue on a render. Those conversations stay here, in the Threads app. Idleis never matched positively. It is the status default and is omitted from stored JSON, so the open legs negateDone,ExecutingandStartingExecution, and the running leg matches the two stamped states it wants.
Reuse from Codex and other clients
A durable external task identifies its conversation using the authenticated user, client name and task id. The conversation lives in that user's home; the page or namespace discussed is message context and can change without creating another conversation. Reconnects and transport session ids do not change the mapping. Forked tasks have distinct task ids. Starting an intentionally separate conversation requires a new task id.
Each send also carries its own stable request id. Creation and message retries recover the same conversation and receipt. A repeated request id with a different payload is rejected. Accepted message fingerprints remain after ingestion so an old retry cannot enqueue the same work again. None of these identifiers grant access: ordinary mesh permissions and the persisted origin are checked before reuse. Read-only MCP calls do not create conversations.
Implementation boundaries
New thread paths use a readable slug from the first message plus 20 random hex characters
(80 bits). The slug is capped at 40 characters, so different control-plane tasks often share it;
four random hex characters caused a production collision between unrelated pull-request reviews
(Plugins #2842). Existing thread paths stay as they are. CreateNodeRequest rejects an occupied
path, and StartThread reports that failure; its only fallback handles access denial into the
caller's home partition. A failed create never adopts a node whose MainNode may point to another
work item.
ThreadPreparation is a read-only protocol. ContinueThreadRequest is the explicit suggested-choice
commit; SubmitThreadMessageRequest is an acknowledged ordinary follow-up, including shared and
delegated conversations. CloseThreadsRequest and UndoThreadClosuresRequest share the same
lifecycle operations across native, React and Blazor clients. Standard layout controls render the
shared user inbox at {user}/Threads, also embedded by the shared Threads landing page.
MCP callers retain operationId with the selected paths when retrying an uncertain close_threads
response; omitting it deliberately starts a new close action.
Tests cover catalogs larger than 100 rows, hidden children, queued work, closed-state transitions, stale Undo receipts, retries after ingestion, changed retry payloads, preservation of drafts and attachments, and both choices in the actual phone composer. The typed and voice entry points use the same preparation flow.