Skip to content

Model machines with one typed root and simplify React ownership - #206

Merged
SandroMaglione merged 4 commits into
mainfrom
codex/root-machine-api
Sep 6, 2026
Merged

Model machines with one typed root and simplify React ownership#206
SandroMaglione merged 4 commits into
mainfrom
codex/root-machine-api

Conversation

@SandroMaglione

@SandroMaglione SandroMaglione commented Sep 6, 2026

Copy link
Copy Markdown
Member

Summary

Machines now have one explicit Machine.state root passed to Machine.make({ root }). Root fields support data-only machines and shared data that survives child transitions. Declared defaults initialize the topology; initialConfiguration separately constructs a complete startup override. Root handlers, direct constructors, non-reentering value updates, and guarded transitions reduce authoring ceremony while preserving typed topology, ownership, and initialization requirements.

The generic and indexed runtimes, history, completion, persistence, tests, and integrations use the root snapshot model. Public, internal, and emitted event protocols have fields constructors and explicit schema-import constructors. Tests accept the same deferred event inputs as production. Encoded snapshots move to version 2.

React gains typed MachineState rendering and createMachineContext(AtomMachine.factory(machine)) with isolated Provider ownership and startup-only input. Atom bridges expose result and snapshot; core MachineRef.state remains available. The root API guide and package examples cover direct migration. Devtools, lint rules, and benchmark adapters follow the new definition shape.

Changeset

  • Added a minor changeset for the breaking public API and all four packages in the fixed release group.
  • Not required because this PR does not change a publishable package

Validation

  • pnpm check
  • Automated type-performance measurement passed or was not required
  • Automated runtime- and memory-performance measurement passed or was not required

Local type-performance budgets remain unchanged. The CI comparison shows lower compiler work in complex composition, invocation, branch, and adapter cases; small root definitions and the two-transition handler fixture cost more while staying within their budgets. Focused coverage includes root initialization and reuse, guards, deferred event receipts, generic-versus-indexed execution, retained snapshots, parallel conflicts, and React ownership under StrictMode. Snapshot construction reuses immutable topology builders while keeping every startup value and configuration independent. The final five-run CI comparison passes the unchanged runtime and memory guards. Single-machine startup remains 14.1% slower than the base, within the 15% guard; the full CI report records the remaining tradeoffs.

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Type performance

Measured with TypeScript 6.0.3 and skipLibCheck=true.

Scenario Base PR Difference
Effect only 55 55 0 (0.0%)
Import effect-machine 55 55 0 (0.0%)
Machine.state (3 children) 3,039 3,555 +516 (+17.0%)
Machine.make (3 states, 2 events) 10,787 9,531 -1,256 (-11.6%)
machine.handle (3 states, 2 transitions) 28,551 46,839 +18,288 (+64.1%)
fluent transition (10 named branches) 122,777 75,241 -47,536 (-38.7%)
fluent invocation (state-dependent Effect) 108,429 67,455 -40,974 (-37.8%)
machine.handle (depth 24) 207,972 213,760 +5,788 (+2.8%)
machine.handle (wide depth 16) 236,334 267,909 +31,575 (+13.4%)
machine.handle (parallel/history/choice) 144,814 126,654 -18,160 (-12.5%)
machine definition (3 independent implementations) 143,619 93,508 -50,111 (-34.9%)
machine exact input/output/error/services 129,877 77,168 -52,709 (-40.6%)
execution adapter readiness 136,468 90,926 -45,542 (-33.4%)

Marginal instantiations are measured against the matching setup without that API call:

Scenario Base PR Difference
Import effect-machine 0 0 0
Machine.state (3 children) 2,984 3,500 +516 (+17.3%)
Machine.make (3 states, 2 events) 7,740 5,968 -1,772 (-22.9%)
machine.handle (3 states, 2 transitions) 17,764 37,308 +19,544 (+110.0%)
fluent transition (10 named branches) 112,500 58,213 -54,287 (-48.3%)
fluent invocation (state-dependent Effect) 99,018 54,223 -44,795 (-45.2%)
machine.handle (depth 24) 187,476 140,736 -46,740 (-24.9%)
machine.handle (wide depth 16) 217,045 190,768 -26,277 (-12.1%)
machine.handle (parallel/history/choice) 124,169 100,987 -23,182 (-18.7%)
machine definition (3 independent implementations) 127,578 71,667 -55,911 (-43.8%)
machine exact input/output/error/services 115,221 56,833 -58,388 (-50.7%)
execution adapter readiness 106,298 33,907 -72,391 (-68.1%)
Check times (informational)
Scenario Base PR
Effect only 0.03s 0.03s
Import effect-machine 0.03s 0.03s
Machine.state (3 children) 0.11s 0.11s
Machine.make (3 states, 2 events) 0.17s 0.16s
machine.handle (3 states, 2 transitions) 0.26s 0.36s
fluent transition (10 named branches) 0.58s 0.46s
fluent invocation (state-dependent Effect) 0.59s 0.42s
machine.handle (depth 24) 0.86s 0.65s
machine.handle (wide depth 16) 0.87s 0.81s
machine.handle (parallel/history/choice) 0.67s 0.62s
machine definition (3 independent implementations) 0.69s 0.56s
machine exact input/output/error/services 0.64s 0.49s
execution adapter readiness 0.67s 0.51s

Type instantiations are the comparison metric. Check time varies with runner load and is informational only.

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Runtime performance

Median of 5 independent benchmark processes on AMD EPYC 7763 64-Core Processor with Node v24.20.0.

Pull request baseline

Scenario Effect Machine
Plan counter transitions 110,193 transitions/s
Drain burst with terminal fence 376,520 increments/s
Drain burst with a change observer 351,611 increments/s
Lookup and send to one child 307,550 increments/s
Start and stop a machine 124,922 machines/s
Start and stop a parent with one child 25,986 families/s
Plan transitions through a compound state 97,340 transitions/s
Plan transitions through parallel regions 78,335 transitions/s
Drain burst through a compound state 309,696 events/s
Drain burst through two parallel regions 293,497 events/s
Drain a compound-state burst with a change observer 294,617 events/s

Process runtime reference points

Scenario Effect Machine
Start and stop a raw generic process 15,287 processes/s
Start and stop a raw compiled process 60,861 processes/s
Memory profile Effect Machine
Idle machine 1.9 KiB
Raw generic managed process 13.9 KiB
Raw compiled process 3.2 KiB
Two independent idle machines 3.5 KiB
Idle parent with one child 5.9 KiB
Parent with observed child registry 10.0 KiB
Parent with observed invoked child snapshots 6.5 KiB

Effect Machine change from base

Metric Base Base variability PR PR variability Difference
Plan counter transitions 123,682 transitions/s 1.5% MAD 110,193 transitions/s 1.1% MAD -10.9%
Drain burst with terminal fence 392,827 increments/s 0.1% MAD 376,520 increments/s 0.3% MAD -4.2%
Drain burst with a change observer 365,185 increments/s 0.1% MAD 351,611 increments/s 0.2% MAD -3.7%
Lookup and send to one child 318,036 increments/s 0.2% MAD 307,550 increments/s 0.6% MAD -3.3%
Start and stop a machine 145,497 machines/s 0.4% MAD 124,922 machines/s 0.6% MAD -14.1%
Start and stop a parent with one child 29,066 families/s 0.6% MAD 25,986 families/s 0.2% MAD -10.6%
Plan transitions through a compound state 106,226 transitions/s 1.5% MAD 97,340 transitions/s 1.6% MAD -8.4%
Plan transitions through parallel regions 88,129 transitions/s 1.6% MAD 78,335 transitions/s 1.6% MAD -11.1%
Drain burst through a compound state 303,843 events/s 0.8% MAD 309,696 events/s 0.9% MAD +1.9%
Drain burst through two parallel regions 302,998 events/s 0.5% MAD 293,497 events/s 0.8% MAD -3.1%
Drain a compound-state burst with a change observer 287,907 events/s 1.0% MAD 294,617 events/s 1.5% MAD +2.3%
Idle machine heap per unit 1.8 KiB 0.0% MAD 1.9 KiB 0.4% MAD +6.3%
Raw generic managed process heap per unit 13.9 KiB 0.0% MAD 13.9 KiB 0.0% MAD -0.0%
Raw compiled process heap per unit 3.2 KiB 0.5% MAD 3.2 KiB 0.1% MAD -0.2%
Two independent idle machines heap per unit 3.4 KiB 0.0% MAD 3.5 KiB 0.0% MAD +4.0%
Idle parent with one child heap per unit 5.8 KiB 0.0% MAD 5.9 KiB 0.0% MAD +2.8%
Parent with observed child registry heap per unit 9.8 KiB 0.0% MAD 10.0 KiB 0.0% MAD +1.6%
Parent with observed invoked child snapshots heap per unit 6.3 KiB 0.1% MAD 6.5 KiB 0.0% MAD +2.0%

Process runtime reference change from base

Metric Base Base variability PR PR variability Difference
Start and stop a raw generic process 15,114 processes/s 0.8% MAD 15,287 processes/s 1.9% MAD +1.1%
Start and stop a raw compiled process 61,459 processes/s 0.3% MAD 60,861 processes/s 0.2% MAD -1.0%

Regression guard

No large, noise-adjusted throughput or heap regressions detected.

Versions and interpretation
  • Effect Machine: 0.31.2

Higher throughput is better; lower heap is better. Variability is the median absolute deviation across independent processes, relative to their median. Small differences on shared GitHub-hosted hardware remain informational; the required guard rejects only large changes beyond the measured noise allowance.

@SandroMaglione
SandroMaglione merged commit 1160040 into main Sep 6, 2026
8 checks passed
@SandroMaglione
SandroMaglione deleted the codex/root-machine-api branch September 6, 2026 12:44
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