Repository navigation
[REA-6854] Deliver commands and reactor events to the model in posted order - #231
Merged
Dere-Wah merged 1 commit intoOct 5, 2026
Conversation
Contributor
Author
This was referenced Oct 3, 2026
tempusfrangit
approved these changes
Oct 3, 2026
tempusfrangit
left a comment
Contributor
There was a problem hiding this comment.
Reviewed the ordered inbound dispatch and the session-start regression tests. Commands and lifecycle events retain posted order, including commands between sessions.
ggoldens
approved these changes
Oct 5, 2026
Contributor
Author
… order Commands and reactor events reached the model on two queues with two dispatch loops. When a lifecycle hook held the step lock, a command posted after a session start could take the lock before that start and run while self.state was still None, so its value was lost. The model now has one inbound queue and one dispatch loop. Commands and reactor events are handled one at a time in the order they were posted: a command sent with a session start runs after @session_started returns, and a session end runs after the commands sent before it. A handler or hook must not wait for a later reactor event, since that event is queued behind it. Signed-off-by: Dere-Wah <derexcontact@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Dere-Wah
force-pushed
the
dere/rea-6854-deliver-commands-and-reactor-events-in-order
branch
from
October 5, 2026 18:07
415024a to
60738a2
Compare
Dere-Wah
deleted the
dere/rea-6854-deliver-commands-and-reactor-events-in-order
branch
October 5, 2026 18:10
douglaseel
added a commit
that referenced
this pull request
Oct 5, 2026
…#240) ## Why Releases what has landed since 3.6.1. It adds new public surface and moves nothing on the wire, which makes it a minor release: - **Data-channel chunking** (#228–#230). `WebRtcConfig.dc_chunking` is on by default. The runtime answers clients that ask for chunked data channels, so a command or a model message of up to 64 MiB passes, instead of being capped at 256 KiB. Refused frames are logged once per channel, and a teardown sends what a chunked channel still has queued. A client that doesn't ask keeps the plain channel. - **Step sessions** (#231–#239): a session's starting input and step count on the start body, `complete_step()`, closing a session at its steps, the `runtime.step_results` manifest block, and step output saved as an MP4 or a folder of files, served over HTTP. - **Client stats** (#201, #224): client-observed WebRTC stats decoded in the transport and journaled as metric self-loops. - **reactor-webrtc 0.21.0** (from 0.19.1). - 0.21.0 keeps per-frame metadata on its own frame when libwebrtc drops a frame before render. Before, metadata was matched by queue order, so after a dropped frame every later frame got its predecessor's metadata. - It also keeps a `FrameTransform`'s `replace_data` through the metadata step. - 0.20.0 is additive. - Neither changes the Python API the runtime uses. - Dependency bumps (#216–#220). ## What Changed Two commits: - `uv version 3.7.0`, which updates the version in `pyproject.toml` and `uv.lock`; - `reactor-webrtc==0.21.0`, relocked. Only that package moves in `uv.lock`. Merging tags `v3.7.0` and publishes. Checked locally with a locked install of the published reactor-webrtc 0.21.0 wheel: - lint, typecheck and the HTTP spec check pass; - unit tests pass (1917); - integration tests pass (12). And with the release workflow's own gates: - `mise run http-breaking-release`: "3.6.1 -> 3.7.0 (minor) satisfies the mandated patch"; - `mise run wire-check-release`: the pin matches `proto/`; - `mise run build`: builds the 3.7.0 wheel and sdist. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
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.

Why
Commands and reactor events reached the model on two queues, each drained by its own loop. Both loops take the step lock, but
asyncio.Lockhands the lock to whoever has waited longest, so a command could overtake a reactor event that was posted before it. When a lifecycle hook held the lock, a command posted right afterSessionStartedcould take the lock first and run whileself.statewas stillNone: a generated setter then does nothing, and the value is lost with no error.Today that needs unlucky timing. A session that brings its own commands on the start request, which the rest of this stack adds, posts them right behind
SessionStartedon every start, so the order has to be guaranteed.What Changed
ReactorCorenow has one inbound queue holdingCommandEnvelope | ReactorEvent, andReactorAppdrains it with a single loop that dispatches each item before taking the next. The model therefore handles commands and reactor events in exactly the order the runtime posted them: a command sent after a session start runs after@session_startedreturns, and a session end runs after the commands sent before it. A command sent between two sessions is not held for the next one, because no session start was posted before it.One rule follows, and it is now in the dispatch loop's docstring: a handler or hook must not wait for a later reactor event, since that event is queued behind it. The bundled examples wait on
connectedonly insiderun(), which is unaffected.The tests run both loops on a real event loop. One reproduces the race (a
@connectedhook holds the lock whileSessionStartedand aset_speedcommand are posted); it fails onmainwith the setter's value lost and passes here. Another checks that a command sent between sessions does not reach the next session's state.