Run Effect at the integration boundary

A service is a named interface. A Layer describes how to build a service and supply its dependencies. A runtime executes Effects. A managed runtime keeps constructed services until disposal.

Keep service methods as Effects. Run them where a process, framework, or callback requires execution. The integration boundary owns the runtime’s lifetime.

Use the calling framework’s Effect integration

Calling framework or processSuitable execution boundary
One CLI processCompose the program, then use NodeRuntime.runMain
Effect-native HTTP routerAdapt its Layer with HttpRouter.toWebHandler
Framework that expects native Promises rather than EffectsReuse a ManagedRuntime owned by that integration
Effect-aware testLet it.effect or it.layer execute the test

A managed runtime is one integration tool. It is not required inside every Effect application.

The basics page17 shows CLI, Worker and test execution boundaries. The CLI’s composition root16 supplies requirements before running its program.

Do not replace an existing Effect bridge

The development backend already uses a router bridge:

apps/web/src/dev/backend.ts · 65e9465f
const { handler } = HttpRouter.toWebHandler(
  devtoolsRoutes(contentCapabilities).pipe(Layer.provide(nodeContentLayer)),
  {
    disableLogger: true,
  }
);

export const backend: BackendFetch = handler;

export const devtoolsBackend: BackendFetch = handler;

toWebHandler also returns a disposal function. This module keeps only the handler; the excerpt does not prove explicit disposal on development-server shutdown. Do not present that missing shutdown check as established resource cleanup.

Own a foreign runtime explicitly

The checked adapter exposes a small Promise API to a hypothetical foreign framework. It creates one managed runtime from a counted resource Layer.

packages/capability/test/fixtures/wiki/foreign-runtime-boundary.ts · 4019cffb
export const makeForeignRuntimeBoundary = (options: {
  readonly acquired: Ref.Ref<number>;
  readonly released: Ref.Ref<number>;
}) => {
  const runtime = ManagedRuntime.make(BoundaryResource.layer(options));

  const read = Effect.gen(function* readBoundaryResource() {
    const resource = yield* BoundaryResource;

    return yield* resource.read;
  });

  return {
    close: runtime.dispose.bind(runtime),
    // @effect-diagnostics-next-line asyncFunction:off -- This foreign framework boundary exposes a native Promise API.
    read: async () => await runtime.runPromise(read),
  };
};

Effect stays inside the adapter. Callers receive a Promise-facing read and an explicit close.

The Layer builds on first use. Later calls reuse the built service context. close disposes the runtime and releases its resources. An integration must connect that close operation to its own shutdown path.

Keep native async functions at the foreign boundary. Domain methods return Effects.

Prove disposal through the foreign interface

The fixture’s test harness uses it.effect. The adapter under test owns the manual runtime.

packages/capability/test/runtime-boundary.test.ts · 4019cffb
      const acquired = yield* Ref.make(0);
      const released = yield* Ref.make(0);
      yield* Effect.acquireUseRelease(
        Effect.sync(() => makeForeignRuntimeBoundary({ acquired, released })),
        (boundary) =>
          Effect.gen(function* useForeignBoundary() {
            expect(yield* Ref.get(acquired)).toBe(0);
            expect(yield* Effect.tryPromise(boundary.read)).toBe("ready");
            expect(yield* Effect.tryPromise(boundary.read)).toBe("ready");
            expect(yield* Ref.get(acquired)).toBe(1);
            yield* Effect.promise(boundary.close);
            yield* Effect.promise(boundary.close);
            expect(yield* Ref.get(released)).toBe(1);

            const reused = yield* Effect.tryPromise(boundary.read).pipe(
              Effect.result
            );

            expect(Result.isFailure(reused)).toBe(true);
          }),
        (boundary) => Effect.promise(boundary.close)

The test wraps the subject’s native Promises with Effect adapters. Its acquire/use/release cleanup also closes the subject if an assertion fails. Check the running server’s shutdown behavior separately.

Integrate one calling framework at a time

  1. Identify the process or framework that requires execution.
  2. Keep the job’s input, output and failures typed inside Effect.
  3. Build the needed services at the integration boundary.
  4. Translate native callbacks or Promises at that edge.
  5. Connect disposal to the host’s shutdown path.
  6. Test the foreign interface and its cleanup.

Common mistakes

Effect idiom and house rule

Read scopes20 when deciding what the integration owns. Read services16 before extracting the job from its host. Return to the Effect reading order17.

Sources

  1. Effect contributors. 2026. ManagedRuntime. Effect 4.0.0.. Effect-TS. make builds a Layer lazily, caches its context, owns resources and running fibers, and refuses reuse after disposal. Accessed 2026-10-06.
  2. Effect contributors. 2026. Using ManagedRuntime with existing applications.. Effect-TS. A foreign framework can reuse one managed runtime while application behavior stays in services and Layers. Accessed 2026-10-06.
  3. Effect contributors. 2026. HttpRouter.. Effect-TS. toWebHandler adapts an Effect router Layer to native Request/Response handling and supplies a disposal function. Accessed 2026-10-06.
  4. rat-stack contributors. 2026. Development backend bridge.. rat-stack. The backend uses HttpRouter.toWebHandler rather than creating its own ManagedRuntime. The excerpt does not establish explicit shutdown handling. Accessed 2026-10-06.
  5. rat-stack contributors. 2026. Foreign runtime boundary fixture.. rat-stack. Checks lazy construction, repeated calls sharing one service, one release, and rejection after disposal through a Promise-facing test subject. Accessed 2026-10-06.
  6. Langton, Kit. 2026. Incremental Adoption. Effect Solutions.. Kit Langton. The proposed outline frames integration questions. It is not a completed recipe or API reference. Accessed 2026-10-06.

Last changed 2026-10-07 in f1a00f2. Source on GitHub. Change log.

Linked from

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

    Run Effect at the integration boundary — Build the runtime where a calling framework enters, reuse its services, and dispose it when that integration ends.

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

    Run Effect at the integration boundary — edited: feat(learn): teach cards 63 to 82 with plain lines, diagrams and snippets

  3. Effect basics. An Effect describes work, expected failures, and required services before a runtime executes it.

    Run Effect at the integration boundary covers foreign code, runtime reuse, and disposal.¶

  4. Full agent guide. An Effect stack so pure (aspirational) Kit Langton will blush.
  5. Lint and type escape ledger (debt.md). A source-linked count of repo-owned lint and type escapes.
  6. Rat Stack lore | rat-stack. Short, source-grounded notes on the ideas and decisions behind rat-stack.

    Run Effect at the integration boundary — Build the runtime where a calling framework enters, reuse its services, and dispose it when that integration ends.

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

    Run Effect at the integration boundary — edited: feat(learn): teach cards 63 to 82 with plain lines, diagrams and snippets

  8. Trace operations where they mean something. Name useful operations, add safe context, and distinguish creating a span from delivering it to a backend.

    Next, run Effect at the integration boundary and connect observability lifetime to its runtime owner.

Content updated .