Skip to content

[REA-6854] Deliver commands and reactor events to the model in posted order - #231

Merged
Dere-Wah merged 1 commit into
mainfrom
dere/rea-6854-deliver-commands-and-reactor-events-in-order
Oct 5, 2026
Merged

Dere-Wah merged 1 commit into
mainfrom
dere/rea-6854-deliver-commands-and-reactor-events-in-order

Conversation

@Dere-Wah

@Dere-Wah Dere-Wah commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

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.Lock hands 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 after SessionStarted could take the lock first and run while self.state was still None: 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 SessionStarted on every start, so the order has to be guaranteed.

What Changed

ReactorCore now has one inbound queue holding CommandEnvelope | ReactorEvent, and ReactorApp drains 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_started returns, 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 connected only inside run(), which is unaffected.

The tests run both loops on a real event loop. One reproduces the race (a @connected hook holds the lock while SessionStarted and a set_speed command are posted); it fails on main with the setter's value lost and passes here. Another checks that a command sent between sessions does not reach the next session's state.

@Dere-Wah
Dere-Wah requested a review from a team as a code owner October 3, 2026 00:36
@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

[codex-review] No issues found. This PR looks good.

Scope: full (69640ad..415024a).

View workflow run.

@tempusfrangit tempusfrangit left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the ordered inbound dispatch and the session-start regression tests. Commands and lifecycle events retain posted order, including commands between sessions.

Dere-Wah commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor Author

Merge activity

  • Oct 5, 6:07 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Oct 5, 6:07 PM UTC: Graphite rebased this pull request as part of a merge.
  • Oct 5, 6:10 PM UTC: @Dere-Wah merged this pull request with Graphite.

… 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
Dere-Wah force-pushed the dere/rea-6854-deliver-commands-and-reactor-events-in-order branch from 415024a to 60738a2 Compare October 5, 2026 18:07
@Dere-Wah
Dere-Wah merged commit 5e6b70c into main Oct 5, 2026
10 checks passed
@Dere-Wah
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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants