Skip to content

Workspace shows another turn's reply when two turns run on the same thread #3166

Closed

Description

@obasilakis

Problem

When two turns are sent to the same Workspace thread (same thread open in two browser tabs), the second tab can display the first turn's reply as its own answer. The server runs both turns correctly and stores the correct answers; only the display is wrong.

Cause

After a turn's stream ends, PortalConversation.vue::deliver calls awaitPersistedReply, which polls thread history and accepts the reply via replyFromHistory (src/frontend/src/components/portal/portalUtils.js:2058): the latest assistant message that differs from the baseline taken before dispatch. Nothing ties that message to the turn's execution.

Turn B's execution goes terminal a few ms before its assistant message is persisted. If turn A's reply landed in the thread after B's baseline was taken, B's first poll sees A's reply as "new", returns it, and stops polling. B's real answer is never shown in that tab until reload.

enterprise_portal_messages has no execution id column, so the client cannot match a reply to its turn today.

Repro

  1. Open one Workspace thread in two browser tabs.
  2. Tab A: send a message. Before it is answered, tab B: send "what was my last message?".
  3. Tab B shows tab A's reply under its question.

Observed on a preview stack (times UTC):

Turn Ran Answer persisted
A "count slowly from 1 to 15" 16:08:45 – 16:08:49.34 16:08:49.417
B "what was my last message?" 16:09:05 – 16:09:08.741 16:09:08.834

Tab B rendered "1 … 15". Stored answer for B: Your last message was: "count slowly from 1 to 15" (correct).

Present on dev. Any agent can hit it (the second turn waits on the conversation's resume lock); agents that queue turns for a worker widen the window.

Fix options

  • Store the execution id on each assistant message (enterprise_portal_messages, SQLite migration + Alembic revision) and have awaitPersistedReply accept only the reply carrying its own execution id.
  • Or read the turn's own result by execution id once the stream ends, instead of "newest reply in thread".

Acceptance criteria

  • With two turns in flight on one thread, each tab shows the reply produced by its own turn.
  • Unit test for the reply matcher: a newer reply from a different execution is not accepted.

Activity

  1. obasilakis commented on Oct 1, 2026

    @obasilakis
    ContributorAuthor

    Reload shows the correct answer, so the stored data is fine and the defect is display-only. After reload the thread renders by save time: the three user messages, then both answers (A's reply sits directly under B's question). Replies are not linked to the question they answer, so interleaved turns read out of order even after reload. The execution-id fix should also place each reply under its own question.

  2. added
    status-readyGreenlit and ready for development (vetted; counterpart to status-incubating)
    on Oct 5, 2026
  3. added and removed
    status-readyGreenlit and ready for development (vetted; counterpart to status-incubating)
    on Oct 7, 2026
  4. self-assigned this
    on Oct 7, 2026
  5. added
    status-in-devMerged to dev, awaiting release cut to main
    and removed on Oct 8, 2026
  6. removed
    status-in-devMerged to dev, awaiting release cut to main
    on Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions