Skip to content

Side conversation thread support #628

Description

@jtenniswood

studio: back the "reply in thread" indicator with a real, daemon-persisted side conversation

Problem

studio/ already has UI for Slack-style side threads, built as part of the Atrium vendor
drop (#618) but never wired to anything real:

  • ReplyIndicator renders
    the avatar stack / "N replies" / "Last reply HH:MM" row — but it reads
    message.replies, a field (AgentMessage.replies)
    that nothing in the codebase ever populates. It never renders today.
  • ThreadPanel opens on
    "Reply in thread" and lets you type — but the "assistant" reply is a hardcoded string
    ("Picking this up in the thread…"), not a real model turn, and the whole exchange lives
    in local React state only. Reload the page, or open the session on another device, and
    it's gone.

That's a direct contradiction of ADR 0229,
which the rest of studio/ follows strictly: the daemon's session store is the single
source of truth, and "a second browser must see nothing" is treated as the bug class this
ADR exists to close. This thread stub is prototype residue that slipped past the #618
import filtering.

Separately, real delegation activity (Subagent/Team/Parallel — see
use-agent-chat.ts) is turned into
inline badges live during a stream, but isn't reconstructed from GET /transcript on
reload, so it also disappears on refresh. Worth fixing alongside this, since it's the same
"activity that happened off the main line isn't durable" shape.

Proposal

Make "reply in thread" a real feature: a side conversation anchored to one message, with a
real model reply, that survives reload and is visible from any client — matching what
ReplyIndicator already implies it can do.

Recommended shape — a thread is a child session anchored to a message.

Reuse the existing session-store/resumability machinery instead of inventing a new engine
primitive:

  • A thread is an ordinary daemon session, tagged with its parent (sessionId, messageId).
    Creating one seeds its history from the parent message (± surrounding context, mirroring
    the existing Subagent fork: true seeding pattern) so the model has context without a
    new prompt-assembly path.
  • New wire surface: something like POST /v1/sessions/{id}/messages/{turnId}/thread to
    create/get a thread, and the thread session then behaves like any other session (real
    POST /prompt turns, GET /transcript, listed in GET /v1/sessions).
  • Thread sessions are filtered out of the flat top-level sidebar the same way subagent/team
    member rows already are — via a decoder-level inspect_only_kind-style reason (see
    sessions.ts) — not id-prefix guessing.
  • The indicator's data comes almost for free: "N replies" = the thread session's own
    messageCount, "Last reply" = its updatedAt, participants = the same role/avatar data
    MessageBubble already renders for ordinary messages. No new engine-level "reply count"
    or "avatar" concept needed.
  • Thread creation is lazy — only on first actual send — so browsing a session with no
    threads costs nothing extra.

Cheaper fallback, if backend scope is too big for v1 — drop the human-authoring half
and re-skin existing delegation participants (Subagent/Team/Parallel) as the "repliers" for
a turn, Studio-only, no engine change. This gets the visual pattern but not the "user
chooses to branch a side conversation" behavior implied by the mock, and still needs the
reload-persistence fix for delegation events called out above.

Open questions for whoever picks this up

  • Does a thread reply actually re-invoke the full agent (tools, permissions) or a
    lighter-weight text-only completion scoped to the parent message? The former is more
    useful but reintroduces permission-ask UI inside a side panel.
  • Should threads be creatable on any message (including a user's own), or only on
    assistant turns, as the mock suggests?
  • Given the size, this may want to go through /to-acceptance-plan rather than being
    implemented as one shot — flagging here rather than pre-deciding it.

Non-goals (v1)

  • No change to Subagent/Team/Parallel delegation semantics themselves — this is additive,
    human-initiated, and orthogonal to model-initiated delegation.
  • No multi-level threads (a thread cannot itself have a side thread).
  • No mecatui (TUI) equivalent in this issue — the TUI's fleet/roster overlays already cover
    a related-but-distinct need there; worth its own issue if wanted.

Acceptance

  • ReplyIndicator renders only for a message with a real, persisted thread that has at
    least one reply, sourced from daemon data — never from local-only state.
  • Sending a message in ThreadPanel produces a real model turn (not a canned string) and
    survives a full page reload.
  • Opening the same session from a second browser/tab shows the same thread state.
  • A session's transcript, once reloaded, still shows delegation badges for turns that had
    them (closing the related live-only gap).
  • Thread sessions do not appear as extra rows in the top-level chat sidebar.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    WebUIApplies to the WebUI interfaceenhancementNew feature or requestux

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions