Skip to content

Add snapshot waiting and invocation tracing - #216

Merged
SandroMaglione merged 1 commit into
mainfrom
codex/wait-for-invocation-tracing
Sep 11, 2026
Merged

Add snapshot waiting and invocation tracing#216
SandroMaglione merged 1 commit into
mainfrom
codex/wait-for-invocation-tracing

Conversation

@SandroMaglione

@SandroMaglione SandroMaglione commented Sep 10, 2026

Copy link
Copy Markdown
Member

Summary

Add Machine.waitFor(ref, predicate) for external Effects and tests that need a current or subsequent published snapshot. It supports type narrowing, preserves unmatched failure Causes, distinguishes stopping from completion without a match, and releases observation on interruption. Timeouts compose through Effect; the API does not acknowledge sent events.

Effect, Stream, and timer invocations now receive Machine.invoke spans with machine, state, registered source, and invocation identity. The implementation uses Effect.withSpan without stack capture and preserves existing resource ownership. Standard Effect tracing configuration applies.

Fix indexed self-transitions and reentry for schema-less states. Add public behavior and type tests, forced generic/compiled runtime and planner coverage, documentation and agent guidance, and invocation benchmarks with tracing enabled and disabled.

Changeset

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

Minor changeset for the public observation API and invocation tracing, including the schema-less reentry fix.

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, including both invocation tracing scenarios. CI passed on f0d1f69790b7970a8f2faa820b37e4a1bb81e4b8, and both automated comparison reports were reviewed.

Type instantiations are unchanged. Across five independent runtime processes, existing machine scenarios changed by -1.7% to +1.0%, with measured heap changes no greater than +1.0%. The dedicated empty-Effect invocation lifecycle has a measurable tracing cost: throughput is 13.2% lower with tracing enabled and 12.3% lower with tracing disabled; both pass the unchanged regression guard. Disabled tracing still uses Effect's no-op span and trace-context semantics, rather than bypassing withSpan. See the automated reports below for the full comparison.

@github-actions

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,521 3,521 0 (0.0%)
Machine.make (3 states, 2 events) 9,488 9,488 0 (0.0%)
machine.handle (3 states, 2 transitions) 49,981 49,981 0 (0.0%)
declared transition (10 named branches) 63,038 63,038 0 (0.0%)
atomic construction, guards, and reentry 88,529 88,529 0 (0.0%)
registered invocation (input-taking Effect) 49,785 49,785 0 (0.0%)
machine.handle (depth 24) 197,380 197,380 0 (0.0%)
machine.handle (wide depth 16) 221,123 221,123 0 (0.0%)
machine.handle (parallel/history/choice) 95,104 95,104 0 (0.0%)
machine definition (3 independent implementations) 100,566 100,566 0 (0.0%)
machine exact input/output/error/services 85,901 85,901 0 (0.0%)
execution adapter readiness 73,376 73,376 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,466 3,466 0 (0.0%)
Machine.make (3 states, 2 events) 5,959 5,959 0 (0.0%)
machine.handle (3 states, 2 transitions) 40,493 40,493 0 (0.0%)
declared transition (10 named branches) 53,930 53,930 0 (0.0%)
atomic construction, guards, and reentry 72,720 72,720 0 (0.0%)
registered invocation (input-taking Effect) 42,088 42,088 0 (0.0%)
machine.handle (depth 24) 179,529 179,529 0 (0.0%)
machine.handle (wide depth 16) 204,050 204,050 0 (0.0%)
machine.handle (parallel/history/choice) 82,064 82,064 0 (0.0%)
machine definition (3 independent implementations) 89,344 89,344 0 (0.0%)
machine exact input/output/error/services 73,166 73,166 0 (0.0%)
execution adapter readiness 23,179 23,179 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.36s 0.35s
declared transition (10 named branches) 0.43s 0.42s
atomic construction, guards, and reentry 0.54s 0.55s
registered invocation (input-taking Effect) 0.35s 0.35s
machine.handle (depth 24) 0.66s 0.68s
machine.handle (wide depth 16) 0.72s 0.71s
machine.handle (parallel/history/choice) 0.55s 0.53s
machine definition (3 independent implementations) 0.57s 0.56s
machine exact input/output/error/services 0.55s 0.53s
execution adapter readiness 0.45s 0.45s

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

@github-actions

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 101,213 transitions/s
Drain burst with terminal fence 358,967 increments/s
Drain burst with a change observer 340,351 increments/s
Lookup and send to one child 296,282 increments/s
Start and stop a machine 110,656 machines/s
Start and stop a parent with one child 22,486 families/s
Complete an Effect invocation with tracing enabled 6,671 invocations/s
Complete an Effect invocation with tracing disabled 7,954 invocations/s
Plan transitions through a compound state 92,737 transitions/s
Plan transitions through parallel regions 74,341 transitions/s
Drain burst through a compound state 292,909 events/s
Drain burst through two parallel regions 272,066 events/s
Drain a compound-state burst with a change observer 278,529 events/s

Process runtime reference points

Scenario Effect Machine
Start and stop a raw generic process 17,751 processes/s
Start and stop a raw compiled process 59,769 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.6 KiB
Idle parent with one child 6.0 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 102,000 transitions/s 1.1% MAD 101,213 transitions/s 0.1% MAD -0.8%
Drain burst with terminal fence 365,126 increments/s 0.2% MAD 358,967 increments/s 0.3% MAD -1.7%
Drain burst with a change observer 345,929 increments/s 0.3% MAD 340,351 increments/s 1.1% MAD -1.6%
Lookup and send to one child 300,646 increments/s 0.9% MAD 296,282 increments/s 0.8% MAD -1.5%
Start and stop a machine 111,037 machines/s 0.8% MAD 110,656 machines/s 0.7% MAD -0.3%
Start and stop a parent with one child 22,561 families/s 0.6% MAD 22,486 families/s 2.3% MAD -0.3%
Complete an Effect invocation with tracing enabled 7,683 invocations/s 0.8% MAD 6,671 invocations/s 1.0% MAD -13.2%
Complete an Effect invocation with tracing disabled 9,067 invocations/s 0.3% MAD 7,954 invocations/s 0.9% MAD -12.3%
Plan transitions through a compound state 91,799 transitions/s 1.5% MAD 92,737 transitions/s 0.6% MAD +1.0%
Plan transitions through parallel regions 74,152 transitions/s 0.6% MAD 74,341 transitions/s 0.9% MAD +0.3%
Drain burst through a compound state 295,122 events/s 0.1% MAD 292,909 events/s 0.5% MAD -0.7%
Drain burst through two parallel regions 275,000 events/s 1.1% MAD 272,066 events/s 1.6% MAD -1.1%
Drain a compound-state burst with a change observer 277,886 events/s 0.1% MAD 278,529 events/s 1.7% MAD +0.2%
Idle machine heap per unit 1.9 KiB 0.6% MAD 1.9 KiB 0.2% MAD +0.4%
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.3% MAD 3.2 KiB 0.6% MAD +0.3%
Two independent idle machines heap per unit 3.5 KiB 0.0% MAD 3.6 KiB 0.0% MAD +1.0%
Idle parent with one child heap per unit 6.0 KiB 0.0% MAD 6.0 KiB 0.0% MAD +0.1%
Parent with observed child registry heap per unit 10.0 KiB 0.0% MAD 10.0 KiB 0.0% MAD +0.1%
Parent with observed invoked child snapshots heap per unit 6.5 KiB 0.2% MAD 6.5 KiB 0.2% MAD +0.1%

Process runtime reference change from base

Metric Base Base variability PR PR variability Difference
Start and stop a raw generic process 16,778 processes/s 2.3% MAD 17,751 processes/s 2.7% MAD +5.8%
Start and stop a raw compiled process 60,566 processes/s 0.4% MAD 59,769 processes/s 1.4% MAD -1.3%

Regression guard

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

Versions and interpretation
  • Effect Machine: 0.36.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 794a12a into main Sep 11, 2026
8 checks passed
@SandroMaglione
SandroMaglione deleted the codex/wait-for-invocation-tracing branch September 11, 2026 03:39
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