Skip to content

Stale buffered delivery drains before the new message when a session is rehydrated after a dropped connection #547

Description

@rmolines

Summary

When a client connection dies while a delivery is pending, that delivery stays in the session's bufferedDeliveries queue in workflow durable storage. A later POST /eve/v1/session/:id rehydrates the session and drains the stale queued delivery before the message that was just sent — the new turn starts with message.received equal to the old text, and the just-sent message runs as a subsequent turn.

From the user's point of view: they send a message today and the agent first answers the message they sent yesterday (whose stream had died), producing a confusing transcript and a run that none of our UI surfaces expected.

Environment

  • eve 0.19.0 (npm), conversation mode, Next.js app proxying /eve/v1/* to eve start (nitro server)
  • Also inspected 0.20.0: session-delivery-hook.js, turn-control-receiver.js, workflow-entry.js, turn-workflow.js, and route-child-delivery.js in the published dist are byte-identical to 0.19.0, so the behavior should be unchanged there.

Reproduction shape

  1. Send a message on a session; while the turn is in flight (or parked), the client connection dies with a delivery still pending — the payload lands in the per-session bufferedDeliveries queue and is persisted with the session.
  2. Later (we observed this across a day boundary), send a new message to the same session id.
  3. The rehydrated driver drains the buffered (stale) delivery first: the new run's message.received is the old text; the new text executes as the following turn.

Where we traced it (published dist, 0.19.0)

  • workflow-entry.jswaitForNextDeliver() drains bufferedDeliveries.splice(0) (coalesced) before reading fresh deliveries from the session delivery hook.
  • turn-control-receiver.jsserviceDeliveryRequest() does bufferedDeliveries.shift() before racing the control hook against fresh hook deliveries; bufferTurnDeliveries() unshifts a turn's buffered deliveries back to the front.
  • The queue is part of the durable session state, so it survives the dropped connection and is not reconciled against the delivery that arrives with the rehydrating POST.

Expected

One of (whichever matches your intended semantics):

  • A delivery whose originating connection died is dropped (or surfaced as such) on rehydration instead of silently replaying ahead of the live message; or
  • The rehydrating POST's delivery is ordered before (or coalesced with) stale buffered deliveries; or
  • The channel gets a way to inspect/flush pending buffered deliveries so the app can decide.

Happy to provide more detail from our traces if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions