Lifecycles are machines

Goal: move a lifecycle out of boolean flags and into named states, with side effects in declared actors.

A machine is a definition of named states and the events that move between them. Use an XState machine for the application’s named states, retries, cancellation, or a lifecycle you need to resume or inspect. Model the states and the events that move between them instead of accumulating booleans whose combinations have no meaning.

An actor is a running unit that performs declared work. A side effect is work outside the state description, such as reading a file. A Scope35 owns cleanup when that work ends.

Worked example: inspecting a file

packages/core/src/inspect-machine.ts is the working example. It reads a file and ends in one of two final states.

1. Declare the side effect as an actor

packages/core/src/inspect-machine.ts · a3f5a148 · readStats is the only code that touches the file
const readStats = createEffectLogic({
  effect: ({ input }) =>
    FileInspector.use((inspector) => inspector.inspect(input.path)),
  schemas: { input: Schema.Struct({ path: Schema.String }) },
});

createEffectLogic defines an Effect-backed side effect with an input Schema30.

2. Let the machine choose transitions

packages/core/src/inspect-machine.ts · a3f5a148 · reading invokes readStats, then lands in a final state
  initial: "reading",
  output: ({ context }) => context.outcome,
  states: {
    inspected: { type: "final" },
    reading: {
      invoke: {
        input: ({ context }) => ({ path: context.path }),
        onDone: {
          context: ({ event }) => ({
            outcome: inspectOutcome.Inspected({ stats: event.output }),
          }),
          target: "inspected",
        },
        onError: {
          context: ({ event }) => ({
            outcome: inspectOutcome.Unreadable({ cause: event.error.cause }),
          }),
          target: "unreadable",
        },
        src: "readStats",
      },
    },
    unreadable: { type: "final" },
  },

setupEffect declares actors. The machine invokes readStats by name in the reading state. The machine chooses transitions and records its outcome. The invoked actor performs the file read.

A read failure lands in unreadable with its cause. The failure is a state you can test.

3. Start it inside Effect and wait for the outcome

packages/core/src/inspect-machine.ts · a3f5a148 · createEffectActor starts it; watchActor reports it
export const runInspectMachine = Effect.fn("runInspectMachine")(
  function* runInspectMachine(path: string) {
    const actor = yield* createEffectActor(inspectMachine, { input: { path } });
    yield* watchActor("inspectMachine", actor);

createEffectActor starts the machine inside Effect’s scope35. watchActor makes the run visible to devtools. join waits for the final outcome. Effect.orDie treats a machine-level failure as a programming error. The domain read failure remains an explicit machine state.

Why a machine

Two booleans, loading and failed, allow four combinations. Only three of them mean anything here. This machine has exactly three states: reading, inspected, and unreadable.

The fence50 keeps side effects in declared actors. oxlint.config.ts loads the lint plugin that @xstate/effect ships and sets xstate-effect/no-inline-effect to error.

That rule matches two cases. It flags an Effect returned from an inline enq or enqueue callback. It flags inline Effect logic passed to enq.spawn. Rule source

The stack pins XState 6.0.0-beta.1 and @xstate/effect 1.0.0-beta.0 together. The migration note records the beta API changes and persistence audit.

Next

Use the add-a-lifecycle-machine skill to build one.

See one capability, every surface21 for how a capability can own a machine and the fence50 for the rule that protects it.

A machine in memory ends with its process. Restart must reconstruct known state9 and supervision chooses what restarts11 cover recovery.

Sources

  1. @xstate/effect 1.0.0-beta.0 oxlint plugin. unpkg. no-inline-effect reports Effects returned from inline enq callbacks and inline Effect logic passed to enq.spawn. Accessed 2026-11-08.
  2. rat-stack/AGENTS.md at main · joelhooks/rat-stack · GitHub. GitHub joelhooks/rat-stack. Lifecycle rule; used for declared Effect actors and explicit states. Accessed 2026-10-01.
  3. rat-stack oxlint configuration. GitHub joelhooks/rat-stack. Loads the @xstate/effect/oxlint plugin and sets xstate-effect/no-inline-effect to error. Accessed 2026-11-08.
  4. Inspection machine at a3f5a14. GitHub joelhooks/rat-stack. Pinned source for the excerpts on this page. Accessed 2026-11-08.
  5. rat-stack/packages/core/src/inspect-machine.ts at main · joelhooks/rat-stack · GitHub. GitHub joelhooks/rat-stack. Inspection machine; used as the working actor and scope example. Accessed 2026-10-01.
  6. rat-stack/packages/core/test/inspect-machine.test.ts at main · joelhooks/rat-stack · GitHub. GitHub joelhooks/rat-stack. Machine tests; used for the inspection lifecycle's behavior. Accessed 2026-10-01.

Last changed 2026-11-08 in b8f4ccf. Source on GitHub. Change log.

Linked from

  1. add-a-capability. Learn how one contract and its handler become a command, HTTP route, MCP tool, browser RPC, and sandbox call.

    Keep it small; put real work in a service or lifecycle machine.¶

  2. add-a-lifecycle-machine. Learn how XState owns a lifecycle while Effect owns its work, errors, and services.

    A lifecycle machine defines named states and the events that permit movement between them.

  3. Agent guide. An Effect stack so pure (aspirational) Kit Langton will blush.

    Lifecycles are machines — XState owns finite lifecycle states while declared Effect actors own side effects.

  4. Agent rules for rat-stack (AGENTS.md). What you may change, which commands to run, and which changes need approval.

    Role: Shared inspectFile, search, and read contracts; inspectFile handler, lifecycle machine, and FileInspector

  5. Build and run rat-stack (README.md). What is in the repo, how the example works, and how to run it.

    Role: Domain example: the inspectFile contract, handler, and lifecycle machine

  6. Change log. What changed in the files served here, newest first.

    Lifecycles are machines — new page: Initial commit from joelhooks/rat-stack template

  7. Circuit breakers have state. A breaker stops doomed calls and controls probes before restoring traffic.

    Connect this to the existing rat-stack guidance.¶

  8. Code snippets quote a pinned source. Shipped design brief: pinned Git objects, typed diagnostics, and one build-only snippet model for both views.

    Lifecycles are machines applies when remote acquisition adds retries, cancellation, or resumability.

  9. Concurrent work needs an owner and a bound. Limit active work, retain result order, and stop child work when its owning lifetime ends.

    In rat-stack, machines own finite domain retries and cancellation.

  10. Context and requirements. Effect requirements make a computation's needed services visible in its type.

    The actor that does the read asks for that service by name:¶

  11. Durability. Seven durable Effect patterns, each with its seam, its fence, and the incident that taught it.

    Service flags port with Layer adapters and a declared lifecycle machine.¶

  12. Durability starts with a recovery record. A reading order for storage, replay, worker ownership, overload, and recovery tests.

    Lifecycles are machines.¶

  13. Durable timers need recovery. Persist scheduled intent when losing a worker must not lose its deadlines.

    Connect this to the existing rat-stack guidance.¶

  14. Effect 4 study: September 2026. Dated Effect 4 source studies from September 2026; current versions live in pins.md.

    ts, and lifecycles are machines.

  15. Full agent guide. An Effect stack so pure (aspirational) Kit Langton will blush.
  16. Handle expected failures without hiding defects. Declare expected failures, preserve their context, and recover by tag without turning crashes into successful outcomes.
    Link context

    The inspection machine uses it where the machine’s result is handed back to Effect because its expected file failure already travels through the Unreadable outcome.

  17. HTTP success needs a status check and a decoder. Supply the client, check the response status, decode accepted data, and name failures at the adapter boundary.

    In rat-stack, machines own finite retry lifecycles.

  18. Infrastructure as effects. Sam Goodwin relates application operations to infrastructure requirements in one typed program.

    Use an XState machine for named states, retries, cancellation, or a lifecycle to resume or inspect.

  19. Model-based testing. Describe what a system should do as a small model, generate action sequences, and check the real system against the model.

    Machines in this repo are lifecycles, so each one is a ready-made test model.

  20. One capability, every surface. A schema-typed contract and one handler feed every interface rat-stack keeps.

    The handler runs the inspection machine.¶

  21. Rat Stack lore | rat-stack. Short, source-grounded notes on the ideas and decisions behind rat-stack.

    Lifecycles are machines — XState owns finite lifecycle states while declared Effect actors own side effects.

  22. Restart must reconstruct known state. Execution recovery and state recovery need separate contracts.

    A new fiber does not restore old local variables or machine memory.¶

  23. Schema projections: 2026-09-18 history. Historical Effect rc.115 proposal and implementation receipts from 2026-09-18; use the one-capability-every-surface lore page for the current pattern.

    packages/core: FileStats became a Schema; inspectFile is the first capability; the lifecycle machine moved here from the CLI so every surface runs it.

  24. Scopes release resources when work ends. Acquire resources lazily, keep their use inside the owner, and release them after success, failure, or interruption.

    In rat-stack, machines own finite business lifecycles.

  25. Source change history (log.md). The generated change log as Markdown.

    Lifecycles are machines — new page: Initial commit from joelhooks/rat-stack template

  26. Structure an Effect codebase by domain. Start with folders for application work, services and Layers in the same file, and startup code that supplies implementations.

    In rat-stack, a machine owns finite modes, cancellation, or legal event sequences.

  27. Supervision chooses what restarts. Restart policy follows dependency shape and bounds repeated recovery.

    Connect this to the existing rat-stack guidance.¶

Unlinked mentions (25)