Skip to content

Chat Message Lifecycle

This page summarizes the current chat message lifecycle after the durable queued-turn refactor. Canonical product requirements still live in docs/features/chat/.

  • Which layer owns executed transcript history versus queued turn state.
  • How direct sends, queued sends, and active-turn interrupts behave.
  • What phase 1 intentionally does not do yet.
  • Mastra memory remains the canonical store for executed branch transcript history.
  • The API app owns branch lineage, active-branch selection, queued-turn persistence, share sanitization, feedback, HITL policy, and interrupt behavior.
  • Each new send creates a ChatTurnRequest.
  • If the thread is idle, that turn request is claimed and streamed immediately through the existing AI SDK SSE path.
  • If the thread already has an active turn, the new send becomes a persisted queued turn request instead of being rejected.
  • Thread detail exposes queue state through turnQueue.activeTurn and ordered turnQueue.items.
  • Queued items render outside chatClient.messages in a sticky Queue Dock above the composer.
  • Stopping an active turn is a server interrupt, not only a browser abort.
  • Stop & send on the queued head interrupts the active server turn, stops the local stream, waits for the active lock to clear, and then claims that queued head.
  • The Stop & send claim passes a non-visible model instruction that the prior assistant turn was interrupted and the queued message is now the user’s priority. That lets the model treat the queued message as clarification, correction, or added context for the interrupted request when appropriate, answer the revised original request from scratch when the interrupted draft is incomplete, and keep ordinary clarification wording separate from attempts to reveal or change hidden instructions.
  • The Send now handoff is covered by the deterministic chat-queue-interrupt-ci Mastra eval profile, including add-context, clarification, operational-priority clarification, and subject-change queued messages.
  • If visible assistant text already streamed, that partial reply remains in the transcript and its activity ends as interrupted.
  • Plain Stop does not cancel or delete queued turns; after the active interrupt settles, the queue head becomes eligible again if no HITL blocker remains.
  • Phase 1 queue draining is viewer-claimed. Queued turns do not auto-run in the background when no chat viewer is attached.
  • The queue is capped at five queued turns per thread, excluding the currently running turn.
  • Only the queue head can Send now or Stop & send.
  • Queued-turn editing happens in place inside the Queue Dock; the main composer is disabled during that edit state.
  • Failed queue heads do not auto-skip. The user must edit or delete them before later queued work can continue.
  • Pending HITL blocks queued-turn execution until the request resolves terminally.
  • Queue state is owner-only thread state and is not exposed in shared-chat snapshots.

For branch-editing checks, use one new private chat thread:

  • Send an initial user message, edit it into a new version, and confirm the original branch is not mutated.
  • Switch between the original and edited versions and confirm each version shows only its own descendant assistant reply.
  • Continue from the edited branch, then delete the latest user turn. The delete should remove only that branch tail and leave the sibling branch intact.
  • Reload while a non-root branch is selected and confirm the active branch and version controls persist.
  • docs/features/chat/spec.md
  • docs/features/chat/stories/chat-queued-turns-and-interrupts.stories.md
  • docs/features/chat/tests/chat-queue.feature
  • docs/features/chat/decisions/ADR-009-chat-turn-request-and-queue-execution.md
  • apps/api/src/chats/
  • apps/web/app/pages/chat/[id].vue
  • Mastra memory reference: Memory.cloneThread() and Memory.deleteMessages()
  • Mastra agent streaming and abort-signal guidance
  • AI SDK chat streaming and stop/abort handling