[Question]: Race condition between step and wait_for_event #16

Closed
opened 2026-02-16 02:16:06 -05:00 by yindo · 7 comments
Owner

Originally created by @mrityu-jha on GitHub (Aug 31, 2025).

Question Validation

  • I have searched both the documentation and discord for an answer.

Question

I wanted to understand the intended behavior for the below case:

If step A is awaiting on wait_for_event(HumanResponseEvent, ..) but we also have step B which accepts HumanResponseEvent.

In this case, once we do ctx.send_event(HumanResponseEvent()) is it guaranteed that wait_for_event will receive it always?

I tried a couple of times and everytime the response was received by awaiting event itself.

Originally created by @mrityu-jha on GitHub (Aug 31, 2025). ### Question Validation - [x] I have searched both the documentation and discord for an answer. ### Question I wanted to understand the intended behavior for the below case: If step A is awaiting on wait_for_event(HumanResponseEvent, ..) but we also have step B which accepts HumanResponseEvent. In this case, once we do ctx.send_event(HumanResponseEvent()) is it guaranteed that wait_for_event will receive it always? I tried a couple of times and everytime the response was received by awaiting event itself.
yindo added the question label 2026-02-16 02:16:06 -05:00
yindo closed this issue 2026-02-16 02:16:06 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Aug 31, 2025):

When you use ctx.send_event(HumanResponseEvent()) and have one step awaiting wait_for_event(HumanResponseEvent, ...) while another step also accepts HumanResponseEvent, the event will be delivered to the step awaiting wait_for_event—but only one consumer will actually receive it. This is by design: events are queued and dispatched to waiting steps, but unless the event is broadcasted, only one consumer (the first to await) will consume it. This means that if step A is awaiting wait_for_event, it will reliably receive the event, and step B (if not awaiting) will not consume it in parallel. This behavior is robust and has been tested to avoid race conditions between steps and event delivery, with recent fixes ensuring proper queue management and event dispatching source.

If you want multiple steps to process the same event, you should design your workflow so that both steps are triggered by the event independently, rather than relying on wait_for_event in both places. If you encounter unexpected behavior, make sure you are on a recent version, as there were previous bugs with event consumption that have since been fixed source.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

@dosubot[bot] commented on GitHub (Aug 31, 2025): <!-- Answer --> When you use `ctx.send_event(HumanResponseEvent())` and have one step awaiting `wait_for_event(HumanResponseEvent, ...)` while another step also accepts `HumanResponseEvent`, the event will be delivered to the step awaiting `wait_for_event`—but only one consumer will actually receive it. This is by design: events are queued and dispatched to waiting steps, but unless the event is broadcasted, only one consumer (the first to await) will consume it. This means that if step A is awaiting `wait_for_event`, it will reliably receive the event, and step B (if not awaiting) will not consume it in parallel. This behavior is robust and has been tested to avoid race conditions between steps and event delivery, with recent fixes ensuring proper queue management and event dispatching [source](https://github.com/run-llama/llama_index/discussions/18245). If you want multiple steps to process the same event, you should design your workflow so that both steps are triggered by the event independently, rather than relying on `wait_for_event` in both places. If you encounter unexpected behavior, make sure you are on a recent version, as there were previous bugs with event consumption that have since been fixed [source](https://github.com/run-llama/llama_index/discussions/18215). <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/a08864a6-6ebe-4791-bf5d-fab20ab291ad?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/a08864a6-6ebe-4791-bf5d-fab20ab291ad?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/a08864a6-6ebe-4791-bf5d-fab20ab291ad?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/a08864a6-6ebe-4791-bf5d-fab20ab291ad?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/a08864a6-6ebe-4791-bf5d-fab20ab291ad?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/a08864a6-6ebe-4791-bf5d-fab20ab291ad?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/a08864a6-6ebe-4791-bf5d-fab20ab291ad?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/a347f61c-1d6a-4435-903f-654845df5b7b/ask?utm_source=github)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/run-llama/llama_index/issues/19767)
Author
Owner

@mrityu-jha commented on GitHub (Aug 31, 2025):

@dosu unable to follow here.

As per my understanding, step B registers itself at initialization itself that it can accept HumanResponseEvent. And since the event queue is shared, how is it being ensured that step A (wait_for_event) will always get it?

@mrityu-jha commented on GitHub (Aug 31, 2025): @dosu unable to follow here. As per my understanding, step B registers itself at initialization itself that it can accept HumanResponseEvent. And since the event queue is shared, how is it being ensured that step A (wait_for_event) will always get it?
Author
Owner

@dosubot[bot] commented on GitHub (Aug 31, 2025):

Great question! The current design is that when you send a HumanResponseEvent, only one consumer will receive it—either a step awaiting wait_for_event or a step registered to accept the event, but not both. If step A is actively awaiting wait_for_event(HumanResponseEvent, ...), it will reliably get the event, and step B (registered at initialization) will not consume it in parallel. This is because wait_for_event consumes the event from the shared queue as soon as it matches, removing it so other consumers can't see it. This behavior is intentional, but there are known bugs and ongoing work to improve how multiple consumers can observe events (see discussion and maintainer comment). If you want multiple steps to process the same event, you'll need to design your workflow so each step is triggered independently, or wait for future updates that may change this behavior.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

@dosubot[bot] commented on GitHub (Aug 31, 2025): <!-- Answer --> Great question! The current design is that when you send a `HumanResponseEvent`, only one consumer will receive it—either a step awaiting `wait_for_event` or a step registered to accept the event, but not both. If step A is actively awaiting `wait_for_event(HumanResponseEvent, ...)`, it will reliably get the event, and step B (registered at initialization) will not consume it in parallel. This is because `wait_for_event` consumes the event from the shared queue as soon as it matches, removing it so other consumers can't see it. This behavior is intentional, but there are known bugs and ongoing work to improve how multiple consumers can observe events (see [discussion](https://github.com/run-llama/llama_index/discussions/18245) and [maintainer comment](https://github.com/run-llama/llama_index/discussions/18245#discussioncomment-12688601)). If you want multiple steps to process the same event, you'll need to design your workflow so each step is triggered independently, or wait for future updates that may change this behavior. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/95831045-af74-4a50-9971-e7e1b2d73d9b?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/95831045-af74-4a50-9971-e7e1b2d73d9b?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/95831045-af74-4a50-9971-e7e1b2d73d9b?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/95831045-af74-4a50-9971-e7e1b2d73d9b?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/95831045-af74-4a50-9971-e7e1b2d73d9b?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/95831045-af74-4a50-9971-e7e1b2d73d9b?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/95831045-af74-4a50-9971-e7e1b2d73d9b?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/a347f61c-1d6a-4435-903f-654845df5b7b/ask?utm_source=github)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/run-llama/llama_index/issues/19767)
Author
Owner

@mrityu-jha commented on GitHub (Aug 31, 2025):

From the code what I have understood is,

send_event(..) puts the given event in all the queues if step is not mentioned, each step then discards this event if it doesn't accept it.

wait_for_event create a new entry in self._queues with waiter_id as key and start waiting on it with the specified event type.

By above understanding, it seems that step B will also receive this HumanResponseEvent.

@dosu

@mrityu-jha commented on GitHub (Aug 31, 2025): From the code what I have understood is, `send_event(..)` puts the given event in all the queues if step is not mentioned, each step then discards this event if it doesn't accept it. `wait_for_event` create a new entry in `self._queues` with `waiter_id` as key and start waiting on it with the specified event type. By above understanding, it seems that step B will also receive this HumanResponseEvent. @dosu
Author
Owner

@logan-markewich commented on GitHub (Aug 31, 2025):

@mrityu-jha in the case you are describing, both would get the event. This is why its helpful to subclass HumanResponseEvent to customize and control the event flow further

@logan-markewich commented on GitHub (Aug 31, 2025): @mrityu-jha in the case you are describing, both would get the event. This is why its helpful to subclass HumanResponseEvent to customize and control the event flow further
Author
Owner

@logan-markewich commented on GitHub (Aug 31, 2025):

Alternatively, you can add some tag or ID to the HumanResponseEvent so that your workflow knows when/how to process it

Really it's just a matter of understanding how the workflow works (which it sounds like you do) and designing your code appropriately for that

@logan-markewich commented on GitHub (Aug 31, 2025): Alternatively, you can add some tag or ID to the HumanResponseEvent so that your workflow knows when/how to process it Really it's just a matter of understanding how the workflow works (which it sounds like you do) and designing your code appropriately for that
Author
Owner

@mrityu-jha commented on GitHub (Aug 31, 2025):

thanks @logan-markewich for confirming.

@mrityu-jha commented on GitHub (Aug 31, 2025): thanks @logan-markewich for confirming.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: run-llama/workflows-py#16