[Fix] Reactions and platform events no longer supersede parked Fast turns - #2183
Merged
Conversation
…urns Every inline admission discarded the conversation's older pending inline rows on the assumption that a newer human message stands in for the earlier request. #2156 routed emoji reactions and web platform events through the same path, so a reaction while a turn was parked for a retry, or waiting to resume after an interruption, silently dropped that turn and turned its retry notice into an interruption message. Only typed human messages supersede now. The next-turn and settle reconciles also leave a retry notice alone while another durable row for the conversation is still pending, so the resumed run edits it into the answer instead of posting beside a false interruption.
Contributor
The pending-row guard now counts only a live claim or a scheduled retry on the expired-lease path, so a released or expired row whose queue wakeup never runs cannot leave a stale retry notice active forever.
daniel-lxs
approved these changes
Sep 5, 2026
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.
What the user sees today
A Fast turn hits a provider error and parks for a durable retry: the thread shows "Retrying in 30s" and the conversation lock is released while it waits. The same state exists for a few seconds after a deploy, while an interrupted turn waits for the queue to resume it.
If the user reacts with an emoji during that window (or, on the web, a setup kickoff or input response fires), the original question is gone. The reaction turn is admitted as a new inline turn, and inline admission discards every older pending turn row for the conversation. The parked turn's row is one of them. The reaction turn's entry then reconciles the "Retrying" notice into an interruption message, so the thread reads: their question, "Retrying in 30s" turned into "I was interrupted", then the reply to the reaction. The question is never answered and nothing tells them why.
This is the same rule that makes a typed message supersede an older pending turn, which is intentional: a typed message stands in for the earlier request, and the new turn is told about the request it still owes (
findFastAgentUnresolvedRequest). A reaction or platform event does neither. #2156 routed them through the same admission path without narrowing the rule.Change
persistFastAgentInlineHumanTurnonly discards older pending inline rows when the new event has no reactioninputand is not aplatform_event. A reaction or platform-event turn still gets its own durable row and runs normally; the older turn keeps its row and resumes once the conversation is idle again (the queue's drain hits the busy lock and retries every second until then).reconcileFastAgentInferenceRetryNotices) now skip when another inline row for the conversation is still pending, excluding the calling turn's own row. Without this, the reaction turn would still stamp the parked turn's notice as interrupted, and the resumed run would then post its answer beside a false interruption instead of editing the notice in place. The expired-lease reconciler gets the same guard through the shared helper; its candidate query already filtered claimed and scheduled rows.Typed-message behavior is unchanged.
What the user sees after
React to a message while a turn is retrying and you get the reaction's reply, the "Retrying" notice stays put, and the original question is answered when the retry fires, editing the notice in place as it always did.
Testing
fast-agent-human-follow-up.test.ts: a reaction and a platform event persist their own row without the supersede sweep; a typed message still supersedes; the resumption and dedupe cases are unchanged.fast-agent-conversation-repository.test.ts(real DB): a next-turn reconcile with another pending inline row returns 0 and leaves the notice active; once that row settles, the caller's own row alone does not block it and the notice is stamped.fast-agent-servicesuite (175), the drain and lock suites,pnpm lint:fast,pnpm check-types:fast,pnpm knippass. Two service assertions that checked the reconcile was not called with a given reason now inspect the reason argument directly, since the call gained a third argument.Not exercised live. The parked-turn resume path itself is the one #2040 smoke-tested; this change only stops a reaction from discarding it.