Skip to content

Unify transition construction, guards, and reentry - #207

Merged
SandroMaglione merged 1 commit into
mainfrom
codex/consistent-transition-builders
Sep 6, 2026
Merged

Unify transition construction, guards, and reentry#207
SandroMaglione merged 1 commit into
mainfrom
codex/consistent-transition-builders

Conversation

@SandroMaglione

@SandroMaglione SandroMaglione commented Sep 6, 2026

Copy link
Copy Markdown
Member

Summary

Make construction consistent across ordinary transitions, retained-owner updates, and atomic destination-plus-owner updates:

Save: (to) => to.local.Saving().updating(to.root)
  .guard(({ current }) => current.draft.length > 0)
  .from(({ current }) => ({
    target: { text: current.draft },
    update: { ...current, attempts: current.attempts + 1 }
  }))

.from constructs both values from schema inputs; .decoded accepts decoded values. .resolve remains available for explicit child configurations, mixed construction methods, and commands. Guards decline before construction or commands and preserve ancestor fallback.

Reentry becomes one chainable modifier:

- to.local.Saving().resolve(callback, { reenter: true })
+ to.local.Saving().reenter().resolve(callback)

It also composes with .from, .decoded, guards, and .updating. Named branches use .branches(...).reenter().resolve(...); to.none.reenter() remains complete without a resolver. The lint autofix preserves modifiers. Reentry still applies to the handler source, and owner updates still require complete values.

Public runtime and type tests cover atomic validation, lifecycle behavior, guard fallback, explicit construction requirements, and removed options. Differential tests compare both indexed planners with the generic planner and verify that guarded transitions retain the generic path. A dedicated type-performance fixture measures the new chains; the runtime benchmark adapter supports both base and head APIs.

Changeset

  • Added or updated for a library or package-metadata change
  • Not required because this PR does not change a publishable package

Minor core changeset for public additions and the breaking reentry migration; lint fix included in the synchronized release group.

Validation

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

Local pnpm perf:types and pnpm perf:runtime passed. Reviewed the CI base-versus-PR reports: existing type scenarios are effectively unchanged, and the new construction fixture passes its budget. The five-run runtime comparison passes the noise-adjusted regression guard; throughput differences range from -1.0% to -4.5%, and retained heap is essentially unchanged.

@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,555 3,555 0 (0.0%)
Machine.make (3 states, 2 events) 9,531 9,531 0 (0.0%)
machine.handle (3 states, 2 transitions) 46,839 46,813 -26 (-0.1%)
fluent transition (10 named branches) 75,241 75,251 +10 (+0.0%)
atomic construction, guards, and reentry 112,926 new
fluent invocation (state-dependent Effect) 67,455 67,427 -28 (-0.0%)
machine.handle (depth 24) 213,760 213,760 0 (0.0%)
machine.handle (wide depth 16) 267,909 267,909 0 (0.0%)
machine.handle (parallel/history/choice) 126,654 126,654 0 (0.0%)
machine definition (3 independent implementations) 93,508 93,482 -26 (-0.0%)
machine exact input/output/error/services 77,168 77,142 -26 (-0.0%)
execution adapter readiness 90,926 90,926 0 (0.0%)

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) 3,500 3,500 0 (0.0%)
Machine.make (3 states, 2 events) 5,968 5,968 0 (0.0%)
machine.handle (3 states, 2 transitions) 37,308 37,282 -26 (-0.1%)
fluent transition (10 named branches) 58,213 58,223 +10 (+0.0%)
atomic construction, guards, and reentry 87,782 new
fluent invocation (state-dependent Effect) 54,223 54,195 -28 (-0.1%)
machine.handle (depth 24) 140,736 140,736 0 (0.0%)
machine.handle (wide depth 16) 190,768 190,768 0 (0.0%)
machine.handle (parallel/history/choice) 100,987 100,987 0 (0.0%)
machine definition (3 independent implementations) 71,667 71,641 -26 (-0.0%)
machine exact input/output/error/services 56,833 56,807 -26 (-0.0%)
execution adapter readiness 33,907 33,907 0 (0.0%)
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.16s 0.16s
machine.handle (3 states, 2 transitions) 0.35s 0.36s
fluent transition (10 named branches) 0.48s 0.47s
atomic construction, guards, and reentry 0.66s
fluent invocation (state-dependent Effect) 0.43s 0.42s
machine.handle (depth 24) 0.65s 0.66s
machine.handle (wide depth 16) 0.78s 0.77s
machine.handle (parallel/history/choice) 0.63s 0.64s
machine definition (3 independent implementations) 0.55s 0.57s
machine exact input/output/error/services 0.50s 0.49s
execution adapter readiness 0.52s 0.54s

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 112,068 transitions/s
Drain burst with terminal fence 378,189 increments/s
Drain burst with a change observer 350,113 increments/s
Lookup and send to one child 309,824 increments/s
Start and stop a machine 126,662 machines/s
Start and stop a parent with one child 25,838 families/s
Plan transitions through a compound state 98,791 transitions/s
Plan transitions through parallel regions 77,615 transitions/s
Drain burst through a compound state 313,062 events/s
Drain burst through two parallel regions 294,921 events/s
Drain a compound-state burst with a change observer 294,295 events/s

Process runtime reference points

Scenario Effect Machine
Start and stop a raw generic process 15,292 processes/s
Start and stop a raw compiled process 62,657 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.4 KiB

Effect Machine change from base

Metric Base Base variability PR PR variability Difference
Plan counter transitions 114,434 transitions/s 1.2% MAD 112,068 transitions/s 1.2% MAD -2.1%
Drain burst with terminal fence 382,033 increments/s 0.4% MAD 378,189 increments/s 0.9% MAD -1.0%
Drain burst with a change observer 355,166 increments/s 0.5% MAD 350,113 increments/s 1.3% MAD -1.4%
Lookup and send to one child 313,208 increments/s 0.5% MAD 309,824 increments/s 0.5% MAD -1.1%
Start and stop a machine 128,452 machines/s 0.7% MAD 126,662 machines/s 1.0% MAD -1.4%
Start and stop a parent with one child 26,890 families/s 1.0% MAD 25,838 families/s 0.4% MAD -3.9%
Plan transitions through a compound state 101,047 transitions/s 0.7% MAD 98,791 transitions/s 1.1% MAD -2.2%
Plan transitions through parallel regions 81,229 transitions/s 0.6% MAD 77,615 transitions/s 1.9% MAD -4.5%
Drain burst through a compound state 317,904 events/s 0.9% MAD 313,062 events/s 0.8% MAD -1.5%
Drain burst through two parallel regions 304,045 events/s 0.1% MAD 294,921 events/s 1.0% MAD -3.0%
Drain a compound-state burst with a change observer 303,265 events/s 0.4% MAD 294,295 events/s 1.2% MAD -3.0%
Idle machine heap per unit 1.8 KiB 0.1% MAD 1.9 KiB 0.3% MAD +0.2%
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.1% MAD 3.2 KiB 0.0% MAD -0.0%
Two independent idle machines heap per unit 3.5 KiB 0.0% MAD 3.5 KiB 0.0% MAD +0.0%
Idle parent with one child heap per unit 5.9 KiB 0.0% MAD 5.9 KiB 0.0% MAD -0.0%
Parent with observed child registry heap per unit 10.0 KiB 0.0% MAD 10.0 KiB 0.0% MAD -0.0%
Parent with observed invoked child snapshots heap per unit 6.5 KiB 0.1% MAD 6.4 KiB 0.0% MAD -0.0%

Process runtime reference change from base

Metric Base Base variability PR PR variability Difference
Start and stop a raw generic process 15,694 processes/s 1.6% MAD 15,292 processes/s 2.5% MAD -2.6%
Start and stop a raw compiled process 62,972 processes/s 1.7% MAD 62,657 processes/s 1.0% MAD -0.5%

Regression guard

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

Versions and interpretation
  • Effect Machine: 0.32.0

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 3255f24 into main Sep 6, 2026
8 checks passed
@SandroMaglione
SandroMaglione deleted the codex/consistent-transition-builders branch September 6, 2026 15:18
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