Uh oh!
There was an error while loading. Please reload this page.
Added module for Machine - #6429
Conversation
🦋 Changeset detectedLatest commit: c951c76 The changes in this PR will be included in the next version bump. This PR includes changesets to release 27 packages
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 |
tim-smart
commented
Jul 19, 2026
What does the cluster integration look like? |
Warning Review limit reached
Next review available in:18 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (11)
📝 WalkthroughWalkthroughA new unstable schema-first Machine API adds typed state modeling, transition planning, snapshot serialization, managed process lifecycles, child orchestration, AtomMachine reactivity, and ClusterMachine persistence. Runtime tests and compile-time type tests cover these behaviors. ChangesMachine platform and integrations
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant AtomMachine
participant MachineStart
participant machinePlanner
participant machineRuntime
participant MachineRef
AtomMachine->>MachineStart: start machine
MachineStart->>machinePlanner: plan initial state
MachineStart->>machineRuntime: create managed process
machineRuntime-->>MachineRef: publish snapshots and lifecycle operations
AtomMachine->>MachineRef: send event or stop
MachineRef-->>AtomMachine: stream runtime snapshot
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (3)
packages/effect/src/unstable/machine/internal/machineProcess.ts (2)
142-171: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDeduplicate
stopInvoke/clearInvoke.The two helpers differ only in the token guard inside
Ref.modify; theScope.close+stopChildcleanup that follows is identical. Extract the common cleanup so the two lifecycle-teardown paths can't drift.♻️ Sketch
+ const closeSession = (session: InvokeSession | undefined, exit: Exit.Exit<unknown, unknown>): Effect.Effect<void> =>+ session === undefined+ ? Effect.void+ : Scope.close(session.scope, exit).pipe(Effect.andThen(context.stopChild(session.childId))) const stopInvoke = (key: string, exit: Exit.Exit<unknown, unknown>): Effect.Effect<void> => Ref.modify(invokeSessions, (sessions) => { const current = HashMap.get(sessions, key) return Option.isSome(current) ? [current.value, HashMap.remove(sessions, key)] as const : [undefined, sessions] as const - }).pipe(- Effect.flatMap((session) =>- session === undefined- ? Effect.void- : Scope.close(session.scope, exit).pipe(- Effect.andThen(context.stopChild(session.childId))- )- )- )+ }).pipe(Effect.flatMap((session) => closeSession(session, exit)))🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/effect/src/unstable/machine/internal/machineProcess.ts` around lines 142 - 171, Deduplicate the identical cleanup in stopInvoke and clearInvoke by extracting a shared helper that removes or receives the invoke session and performs Scope.close followed by context.stopChild. Keep the token guard in clearInvoke and the unguarded lookup in stopInvoke, while routing both through the shared teardown logic.
185-192: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse
Effect.fnUntracedfor the invoke helpers that only returnEffect.gen. Both helpers currently return a bareEffect.gen, inconsistent withstartInvokesin the same file which already usesEffect.fnUntraced.
packages/effect/src/unstable/machine/internal/machineProcess.ts#L185-L192: rewritestartInvokeWatchersasEffect.fnUntraced(function*(config, child, key, token, scope) { ... }).packages/effect/src/unstable/machine/internal/machineProcess.ts#L236-L241: rewritestartInvokeasEffect.fnUntraced(function*(path, config, state, event) { ... }).As per coding guidelines: "prefer
Effect.fnUntracedover functions that only returnEffect.gen".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/effect/src/unstable/machine/internal/machineProcess.ts` around lines 185 - 192, Update startInvokeWatchers and startInvoke in packages/effect/src/unstable/machine/internal/machineProcess.ts at lines 185-192 and 236-241 to use Effect.fnUntraced with their existing generator bodies and parameters, matching the established startInvokes pattern; no other behavior should change.Source: Coding guidelines
packages/effect/src/unstable/reactivity/AtomMachine.ts (1)
28-39: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider adding a typetest for the
makeoverload'sEnsureNoExternalRequirements/ExcludeCompatibleMachineRuntimeconstraint.This conditional-type machinery decides whether the first
makeoverload (no externalAtomRuntime) is usable for a given machine, based on comparingRequirementsagainstMachine.Runtime.Requirement<Events, Emits>. It's intricate enough (nested conditionals,IsAnyguard) that a regression here would silently change which machines type-check without anAtomRuntime, and there's no dedicated typetest forAtomMachinein this cohort (onlypackages/effect/typetest/Machine.tst.tsfor the base Machine API).As per path instructions,
packages/*/typetest/**/*.{ts,tsx}is the place for type-level tests, run viapnpm test-types <filename>.Also applies to: 231-358
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/effect/src/unstable/reactivity/AtomMachine.ts` around lines 28 - 39, Add a dedicated AtomMachine typetest under the package typetest area covering the make overload’s EnsureNoExternalRequirements constraint, including compatible, incompatible, and any/unknown requirement cases governed by ExcludeCompatibleMachineRuntime. Assert which machines type-check with and without an external AtomRuntime, and run it with the package’s test-types command.Source: Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/effect/src/unstable/machine/internal/machineProcess.ts`:
- Around line 203-206: Update the snapshot-mapped send in the machine process
flow, after mapSnapshot returns a defined event, to catch and ignore
StoppedError from context.self.send. Mirror the existing Done relay’s
Effect.catchTag("StoppedError", ...) handling while preserving Effect.void for
undefined mapped events.
In `@packages/effect/src/unstable/reactivity/AtomMachine.ts`:
- Around line 191-214: Update the send and stop writable handlers to make
non-Success ref states observable instead of silently returning. When ref is
Initial or Failure, propagate an appropriate AsyncResult failure through the
atom’s own state using the existing StartError or Machine.StoppedError types;
preserve the current Effect.runCallback behavior for Success and ensure calls
are not silently dropped.
In `@packages/effect/test/unstable/machine/Machine.test.ts`:
- Around line 5468-5526: In
packages/effect/test/unstable/machine/Machine.test.ts ranges 5468-5526 and
5541-5601, fix the vacuous stoppedOutcomes checks in both invoke-lifecycle
tests: either connect stoppedOutcomes to the stopped-outcome observation path so
the zero assertion verifies behavior, or remove the unused counter and its
assert.strictEqual assertion from both tests.
---
Nitpick comments:
In `@packages/effect/src/unstable/machine/internal/machineProcess.ts`:
- Around line 142-171: Deduplicate the identical cleanup in stopInvoke and
clearInvoke by extracting a shared helper that removes or receives the invoke
session and performs Scope.close followed by context.stopChild. Keep the token
guard in clearInvoke and the unguarded lookup in stopInvoke, while routing both
through the shared teardown logic.
- Around line 185-192: Update startInvokeWatchers and startInvoke in
packages/effect/src/unstable/machine/internal/machineProcess.ts at lines 185-192
and 236-241 to use Effect.fnUntraced with their existing generator bodies and
parameters, matching the established startInvokes pattern; no other behavior
should change.
In `@packages/effect/src/unstable/reactivity/AtomMachine.ts`:
- Around line 28-39: Add a dedicated AtomMachine typetest under the package
typetest area covering the make overload’s EnsureNoExternalRequirements
constraint, including compatible, incompatible, and any/unknown requirement
cases governed by ExcludeCompatibleMachineRuntime. Assert which machines
type-check with and without an external AtomRuntime, and run it with the
package’s test-types command.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 5a901101-5411-4517-9368-c01db2f3365a
📒 Files selected for processing (14)
.changeset/public-towns-invent.mdpackages/effect/package.jsonpackages/effect/src/unstable/machine/Machine.tspackages/effect/src/unstable/machine/index.tspackages/effect/src/unstable/machine/internal/machineErrors.tspackages/effect/src/unstable/machine/internal/machineModel.tspackages/effect/src/unstable/machine/internal/machinePlanner.tspackages/effect/src/unstable/machine/internal/machineProcess.tspackages/effect/src/unstable/machine/internal/machineRuntime.tspackages/effect/src/unstable/reactivity/AtomMachine.tspackages/effect/src/unstable/reactivity/index.tspackages/effect/test/reactivity/AtomMachine.test.tspackages/effect/test/unstable/machine/Machine.test.tspackages/effect/typetest/Machine.tst.ts
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
packages/effect/src/unstable/cluster/ClusterMachine.ts (1)
304-305: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winHoist
hasInvokes(machine)out of the per-request hot path.
hasInvokesscansReflect.ownKeys(machine.handlers)on every singlesendcall, butmachine.handlersnever changes aftermake()builds the layer. Compute it once alongsidemachineId(Line 495) and capture it in the closure instead of recomputing per request.♻️ Proposed refactor
const entity = Entity.make(type, [rpc]) const machineId = machine.id ?? type + const invokesUnsupported = hasInvokes(machine)- const handle = Effect.fnUntraced(function*(request: Entity.Request<SendRpc<MachineEvents<M>>>) {- if (hasInvokes(machine)) {+ const handle = Effect.fnUntraced(function*(request: Entity.Request<SendRpc<MachineEvents<M>>>) {+ if (invokesUnsupported) { return yield* fail(Also applies to: 513-519
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/effect/src/unstable/cluster/ClusterMachine.ts` around lines 304 - 305, Move the hasInvokes(machine) computation out of the per-request send path and calculate it once during machine setup alongside machineId. Capture the resulting boolean in the closure, then update the send handling around the hasInvokes references at lines 513-519 to use that cached value while preserving existing behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/effect/src/unstable/cluster/ClusterMachine.ts`:
- Around line 317-326: Update rejectionFromCause to inspect defects via
Cause.findDefect in addition to the existing Cause.findErrorOption path, and
convert a discovered Machine.ProcessLocalError into the existing
UnsupportedProcessLocal rejection. Preserve Rejected handling and
TransitionFailure fallback for all other causes.
---
Nitpick comments:
In `@packages/effect/src/unstable/cluster/ClusterMachine.ts`:
- Around line 304-305: Move the hasInvokes(machine) computation out of the
per-request send path and calculate it once during machine setup alongside
machineId. Capture the resulting boolean in the closure, then update the send
handling around the hasInvokes references at lines 513-519 to use that cached
value while preserving existing behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 3cbfa41f-e431-490b-902e-0abbdb0cc679
📒 Files selected for processing (9)
.changeset/public-towns-invent.mdpackages/effect/src/unstable/cluster/ClusterMachine.tspackages/effect/src/unstable/cluster/index.tspackages/effect/src/unstable/machine/Machine.tspackages/effect/src/unstable/machine/internal/machineErrors.tspackages/effect/test/cluster/ClusterMachine.test.tspackages/effect/test/unstable/machine/Machine.test.tspackages/effect/typetest/ClusterMachine.tst.tspackages/effect/typetest/Machine.tst.ts
🚧 Files skipped from review as they are similar to previous changes (5)
- .changeset/public-towns-invent.md
- packages/effect/src/unstable/machine/internal/machineErrors.ts
- packages/effect/src/unstable/machine/Machine.ts
- packages/effect/test/unstable/machine/Machine.test.ts
- packages/effect/typetest/Machine.tst.ts
| const rejectionFromCause = (cause: Cause.Cause<unknown>): Rejected => { | ||
| const error = Cause.findErrorOption(cause) | ||
| if (Option.isSome(error) && error.value instanceof Rejected) { | ||
| return error.value | ||
| } | ||
| if (Option.isSome(error) && error.value instanceof Machine.ProcessLocalError) { | ||
| return reject("UnsupportedProcessLocal", `${error.value.operation} is process-local and is not supported`) | ||
| } | ||
| return reject("TransitionFailure", messageFromCause(cause)) | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bashset -euo pipefail
printf'\n== ClusterMachine.ts slices ==\n'
sed -n '300,340p' packages/effect/src/unstable/cluster/ClusterMachine.ts
printf'\n== ClusterMachine.ts raise slice ==\n'
sed -n '540,565p' packages/effect/src/unstable/cluster/ClusterMachine.ts
printf'\n== Search Cause helpers ==\n'
rg -n "findErrorOption|findDefect|failureOption|dieOption" packages/effect/src -g '!*dist*' -g '!*build*'printf'\n== Relevant Cause file candidates ==\n'
fd -a "Cause.ts" packages/effect/src | head -20
printf'\n== ClusterMachine test slice ==\n'
sed -n '560,610p' packages/effect/test/cluster/ClusterMachine.test.tsRepository: Effect-TS/effect
Length of output: 8659
🏁 Script executed:
#!/bin/bashset -euo pipefail
printf'\n== Cause.ts find helpers ==\n'
sed -n '830,970p' packages/effect/src/Cause.ts
printf'\n== ClusterMachine.ts around line 600 ==\n'
sed -n '592,628p' packages/effect/src/unstable/cluster/ClusterMachine.tsRepository: Effect-TS/effect
Length of output: 5234
ProcessLocalError needs to be read from defects too.runtime.raise throws Machine.ProcessLocalError with Effect.die, but Cause.findErrorOption only sees Fail causes. This branch misses the UnsupportedProcessLocal rejection for RaiseFromAction and falls through to TransitionFailure. Search Cause.findDefect here as well, or route ProcessLocalError through a typed failure.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/effect/src/unstable/cluster/ClusterMachine.ts` around lines 317 -
326, Update rejectionFromCause to inspect defects via Cause.findDefect in
addition to the existing Cause.findErrorOption path, and convert a discovered
Machine.ProcessLocalError into the existing UnsupportedProcessLocal rejection.
Preserve Rejected handling and TransitionFailure fallback for all other causes.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/effect/typetest/unstable/reactivity/AtomMachine.tst.ts (1)
32-36: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse
Effect.fnUntracedfor these generator handlers.
packages/effect/typetest/unstable/reactivity/AtomMachine.tst.ts#L32-L36packages/effect/typetest/unstable/reactivity/AtomMachine.tst.ts#L52-L55🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/effect/typetest/unstable/reactivity/AtomMachine.tst.ts` around lines 32 - 36, Replace the Effect.fn wrappers around both generator handlers in AtomMachine.tst.ts at lines 32-36 and 52-55 with Effect.fnUntraced, preserving each handler’s existing generator logic and behavior.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/effect/typetest/unstable/reactivity/AtomMachine.tst.ts`:
- Around line 32-36: Replace the Effect.fn wrappers around both generator
handlers in AtomMachine.tst.ts at lines 32-36 and 52-55 with Effect.fnUntraced,
preserving each handler’s existing generator logic and behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 19a55931-b92a-42db-b5b0-3d519e119a34
📒 Files selected for processing (5)
packages/effect/src/unstable/machine/internal/machineProcess.tspackages/effect/src/unstable/reactivity/AtomMachine.tspackages/effect/test/reactivity/AtomMachine.test.tspackages/effect/test/unstable/machine/Machine.test.tspackages/effect/typetest/unstable/reactivity/AtomMachine.tst.ts
💤 Files with no reviewable changes (1)
- packages/effect/test/unstable/machine/Machine.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/effect/src/unstable/machine/internal/machineProcess.ts
SandroMaglione
commented
Jul 20, 2026
I added a I am trying to find the best way to make the PR easier to review, since it contains quite a few additions. Let me know what you think I can do to ease the review |
SandroMaglione
commented
Jul 28, 2026
Published a community library based on this PR at @typeonce/effect-machine I intent to keep this in sync with the PR, so that people can test the API while it's being implemented |
tim-smart
commented
Jul 28, 2026
I think for the time being all development should happen in your repository. It is often what I do myself before introducing a new module to effect (effect atom is one example). I'm going to close this for now and we can consider bringing your package into core once v4 is released and your package has had some time to settle. |
Type
Description
Added
Machinemodule with Atom adapter:Machine("state chart")AtomMachineintegration for effect-atomThe feature is exposed under
effect/unstable/machine.Feel free to push back on any of the API decisions or to propose or ask for any change.
Related
Moved over from the
effect-smolPR.This work is inspired by
xstate, and it builds on top of #1945.Summary by CodeRabbit