Skip to content

Add lazy actors owned by parent state scopes - #85

Merged
cevr merged 1 commit into
mainfrom
feat/actor-host
Sep 7, 2026
Merged

Add lazy actors owned by parent state scopes#85
cevr merged 1 commit into
mainfrom
feat/actor-host

Conversation

@cevr

@cevr cevr commented Sep 7, 2026

Copy link
Copy Markdown
Owner

Problem / Intent

A consumer that requests a lazy child actor must not own its startup or lifetime. Applications currently need their own request sharing and state-scope cleanup code.

Approach

Add ActorHost.make({ identity, spawn }). The parent state hosts one generation. Consumers share the first matching request and receive an actor only after it starts. State exit or host service shutdown closes the generation. Consumers receive a typed closed error before actor cleanup; consumer cancellation leaves startup running.

parent state scope
  host(input)
    generation scope
      acquire(input) → shared startup → actor
      close notification → actor cleanup → remove generation

The factory keeps its typed failures and captured services. Both Machine.spawn and system.spawn factories work. Session validation stays in the application.

Validation: full gate passed, including 395 tests and 902 assertions, typecheck, lint, build, and all examples. Ten real actor tests cover cancellation, native reentry, first-request input, slow cleanup, nested host errors, and direct actor startup. Two Counsel rounds are complete; all accepted findings are fixed. Includes a minor changeset.

Comment thread src/actor-host.ts
return value;
}),
);
const scope = yield* Scope.fork(generation);

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

  1. State ownership and shutdown order
+ host(input)
+   generation scope
+     inner scope: startup fiber and actor
+   close generation
+     mark closed; fail waiting consumers
+     close inner scope; stop actor
+     remove the matching generation

Finalizers run in reverse registration order. The slow-cleanup test verifies that consumers fail closed and a new host remains rejected until cleanup ends. This trace comes from the source. calldiff could not resolve the Effect.fn generator entry point.

Comment thread src/actor-host.ts
entry.actor,
Deferred.await(entry.requested).pipe(
Effect.flatMap((requested) =>
Machine.scoped(options.spawn(requested).pipe(Effect.tap((actor) => actor.start))),

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

  1. Shared lazy startup
+ acquire(input)
+   select matching generation
+   supply the first request input
+   await shared actor
+ host startup fiber
+   Machine.scoped(factory(input))
+   actor.start
+   publish shared result

Consumer cancellation only ends that consumer's wait. The generation scope owns startup. Tests cover real recovery cancellation, direct Machine.spawn startup, and typed failures from a nested host. This trace comes from the source; the calldiff entry point was not resolved.

@cevr
cevr merged commit a210b82 into main Sep 7, 2026
1 check passed
@github-actions github-actions Bot mentioned this pull request Sep 7, 2026
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