Repository navigation
Inbox: a row is a thread (send_message joins the session's thread; optional thread name) - #1752
Merged
Merged
Conversation
…tional thread name) Owner ruling 2026-10-07: a row is a thread, the project is a label and a filter. send_message without reply_to now joins the sender session's open default thread in the project, or the open thread named by the new optional 'thread' (1..120 chars, case-folded, across sessions); resolved means a new thread; reply_to still wins. Routing reads only the log (indexes built from each root's first line in seq order), so replays route identically and step-1 stores replay unchanged. The list model is now one row per thread, placed in the approved sections (Stopped on you, Holding up work, Waiting on you, Sent, New since you looked, Quiet) with the project as a label and ?project= filter on GET /api/inbox/threads; /api/inbox/projects becomes the sidebar folders. POST /api/inbox/threads/:id/seen records a look; an agent reading the person's reply moves Sent to Quiet. Additive record fields only. Proof: live MCP SDK client against the running server, a step-1 store fixture written by step 1's own code, and a fast-check oracle replaying the log.
…st cost - Thread names: refuse bidi controls and invisible-only names; compare by NFKC with default-ignorable code points dropped and case folded upper-then-lower, so look-alike spellings are one thread. - A name joins its newest OPEN thread (one the person reopened counts). - An idempotency key reused with another thread name is refused, like a different body or reply_to. - wait_for_reply without a cursor counts replies after the CALLER's last message, so another session writing in a shared thread cannot hide one; a returned reply records only itself as checked. - /seen writes only when an agent message is new to the person. - The list is linear: questions indexed by message (4,000 threads: 627 ms to 11 ms), and /api/inbox/threads builds its rows once. - send_message's lead is one short sentence, so Claude Code's 2,048-char description cut keeps the question guide through "Ask only what you cannot decide alone"; pinned by a test. - Live tests for each, through the MCP SDK client and real fetch.
1 task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Inbox step 1c. Owner ruling 2026-10-07 ("The row model, ruled"): a row is a thread, and the project is a label on the row and a filter. Before this change, every
send_messagewithoutreply_tostarted a new thread.Routing (
InboxStore.sendMessage)The first rule that matches wins:
reply_to: the message goes into that message's thread as a reply, andthreadis ignored. Same as today.thread(a name): trimmed, 1..120 characters, no control characters or line breaks. Otherwise the refusal isvalidation_error: thread: ….agent_sessionstarted in this project, while that thread is open. If there is none, or it is resolved, the message starts a new thread. Resolved means done.subjectis dropped.thread_name, plusperson_seen_seq,agent_checked_seqandagent_checked_aton thread roots. A step-1 store replays unchanged.The list: sections per thread
GET /api/inbox/threads[?project=prj_…]returns all six sections in the approved order: Stopped on you, Holding up work, Waiting on you, Sent, New since you looked, Quiet.project: { id, name },thread_name,section,unread,answered_not_sent,waiting_since,unseenandsent./api/inbox/projectsnow returns the sidebar folders, with thread and unread counts.packages/shared/inbox/list.ts, a pure module that reads Workspaces'inbox-model.tsper thread. Sort order inside each section also follows Workspaces.POST /api/inbox/threads/:id/seenrecords that the person looked. It uses the same-origin andserverSessionguards. The stub page sends it when a thread opens.wait_for_replyreturns it, orread_threadreads the thread), the row moves from Sent to Quiet, matching the approved Sent band.MCP
send_messagehas a newthreadparameter. Its description now explains the routing, and its result addsnew_threadandthread_name.read_thread's list mode returns list rows for the threads the session sent in: threads it started, joined or replied in.Proof
Proved live, with no mocks:
packages/server/inbox-threads.test.tsruns the official MCP SDK client against a running Inbox and covers:thread: "b"→ a second thread;" B "→ joins it;reply_tolands in the replied-to thread, even withthreadset;tests/test-fixtures/inbox-step1/was written by step 1's ownstore.ts(taken fromorigin/main). It replays every record unchanged and lists one row per thread in the right sections, also with the filter. Its session's next message joins that session's newest open root, and a restart gives the same list.store.test.ts): an independent oracle walks the log in seq order and assigns every message the same thread the store did. A copy replayed from disk routes every next send the same way. A mutation check confirmed the property fails when name case-folding is broken.bun run typecheckpasses.bun test packages/server packages/shared packages/core apps/hook: 3486 pass, 2 skip, 0 fail.Judgement calls
read_thread) is new;readOnlyHintstays, because the agent cannot observe the change./api/inbox/projectschanged shape to become the sidebar folders. Nothing outside the stub, the tests and DAST read it; the DAST seeds now include/api/inbox/threads.