Skip to content

refactor(runtime): Effect-native dispatcher and stream pipeline (wave 3.5 stage 2) - #156

Merged
ScriptedAlchemy merged 3 commits into
mainfrom
wave35/effect-dispatcher
Sep 1, 2026
Merged

refactor(runtime): Effect-native dispatcher and stream pipeline (wave 3.5 stage 2)#156
ScriptedAlchemy merged 3 commits into
mainfrom
wave35/effect-dispatcher

Conversation

@ScriptedAlchemy

@ScriptedAlchemy ScriptedAlchemy commented Sep 1, 2026

Copy link
Copy Markdown
Owner

Summary

Stage 2 of #152: the #145 dispatcher pipeline rewritten on Effect Stream with dispatch()/stream() byte-compatible.

  • Flight bytes: pull-gated Stream.unfold that waits for event-stream demand before reader.read(); pending boundaries via Stream.paginate; contract bounds as the emit stage; progress via Stream.merge(..., { haltStrategy: 'left' }) + Stream.takeUntil(complete).
  • streamToReadableStream in the boundary module owns the runFork/interrupt edge: pull-gated Latch, a terminal state so post-failure reads reject with the typed error, Stream.tapError to fail the controller immediately, and a forked (never blocking) interrupt on cancel().
  • No effect/unstable/* adoptions (rationale recorded in docs/effect-conventions.md).

Event-count hang root cause (verified by bisect + instrumentation)

enforces event-count bounds on the live stream hung because the old decodeFlightRoot teardown called bytes.cancel() on the Flight ReadableStream from an acquireRelease finalizer while React's Flight client (createFromReadableStream) held its reader. cancel() on a locked stream rejects with ERR_INVALID_STATE: ReadableStream is locked; Effect.promise converts that rejection into a defect mid-scope-close, which wedged the public event-stream fiber's exit — the controller was never errored and the consumer's pending read() never settled. It was not a blocking-interrupt deadlock: the underlying source cancel() never ran. Fix: scopedAbortSignal + Stream.interruptWhen interrupts the Flight source on scope close without touching the locked ReadableStream. Each candidate change was bisected against the failing baseline; this swap alone fixes it, and swallowing the locked-cancel rejection alone also fixes it, confirming the mechanism.

Notes for stage-3 workers (helped / hurt)

Helped

  • scopedAbortSignal (Effect.abortSignal) is the right bridge for tearing down web streams a consumer may have locked — interrupt the source, never cancel() the ReadableStream.
  • Latch (openUnsafe/closeUnsafe from sync web-stream callbacks) is a clean pull-gate; the terminal-state pattern keeps post-failure reads rejecting deterministically.
  • Stream.tapError to fail the controller immediately, without waiting for the fiber exit observer.
  • Stream.merge with haltStrategy: 'left' + Stream.takeUntil(complete) for a side-channel queue (progress) that must not keep the stream alive.

Hurt / gotchas

  • Effect.promise rejections become defects (die), and a defect raised in a scope-close finalizer can silently wedge the closing fiber's exit and hide the original typed error. Keep finalizers infallible.
  • Never block a finalizer or ReadableStream.cancel() on runPromise(Fiber.interrupt(...)) — fork it.
  • Stream.callback rejected for progress because a failed callback producer does not fail the stream (recorded in docs/effect-conventions.md).

Parity (all local, scoped)

Refs #152.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 1, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-01T16:53:52.316524Z 5899ae5 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@changeset-bot

changeset-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 28044bb

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@agent-bundle/runtime Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Sep 1, 2026

Copy link
Copy Markdown
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle@156
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/create-agent-bundle@156
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/@agent-bundle/runtime@156

commit: 41975e2

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5899ae5cd7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/rsc-runtime/src/reconciler.ts
Comment thread packages/rsc-runtime/src/reconciler.ts
Comment thread packages/rsc-runtime/src/dispatcher.ts
@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

CI note: RSC runtime micro-eval is green. Remaining Verify / Examples / Release-gates failures are workbench playground e2e (stateStoreId), audiobook-curator examples:check, and MCP request timeouts — not the #145 dispatcher contract suite. Main is already red on the same class of post-#154 Verify jobs (dts order; this PR also fixes runtime-before-agent-bundle in the root build script). Not merging until those required checks are green or explicitly waived.

… 3.5 stage 2)

Rewrite the #145 Flight decode and bound-event pipeline on Effect Streams and fibers while keeping dispatch() and stream() byte-compatible. Refs #152.
agent-bundle's test helpers import @agent-bundle/runtime; CI was typechecking that package before runtime dist existed, which is why main's post-#154 Verify jobs fail in ~30s.
…n comments

The maxEvents hang was not a blocking-interrupt deadlock: the acquireRelease
finalizer called cancel() on the Flight readable while the Flight client held
its reader, the locked-stream rejection became an Effect.promise defect during
scope close, and the event stream's exit never surfaced. Document that at the
scopedAbortSignal fix site and correct the boundary cancel() comment.
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.

1 participant