# Run Effect at the integration boundary

> For agents: start with the [agent guide](https://ratstack.sh/llms.txt). Every page is Markdown by default; add `Accept: text/html` for HTML.

Free workshop: [how to burn a trillion tokens and get good results](/tokenmaxx#interested).

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 bridge the host needs

| Host                            | Suitable execution boundary                         |
| ------------------------------- | --------------------------------------------------- |
| One CLI process                 | Compose the program, then use `NodeRuntime.runMain` |
| Effect-native HTTP router       | Adapt its Layer with `HttpRouter.toWebHandler`      |
| Foreign Promise-based framework | Reuse a `ManagedRuntime` owned by that integration  |
| Effect-aware test               | Let `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 page](/lore/effect-basics) shows CLI, Worker and test execution boundaries.
The CLI's composition root 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 at 65e9465f38092e24486392f22c08f45d61230c20; lines 9-18; highlighted none.

```typescript
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 receipt 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 at 4019cffbcbdb282d6f006854ff5b1f26d3b407e0; lines 6-23; highlighted none.

```typescript
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 at 4019cffbcbdb282d6f006854ff5b1f26d3b407e0; lines 10-30; highlighted none.

```typescript
      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 outer bracket also closes the subject if an assertion fails.
Check the running server's shutdown behavior separately.

## Adopt one edge 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

- Create a runtime for each domain method. Construction and cleanup lose a clear owner.
- Return native Promises throughout the domain. Typed failures and requirements no longer compose through the Effect description.
- Forget to dispose a long-lived runtime.
- Assume a framework's shutdown is proven because the adapter returns a close function.

## Effect idiom and house rule

- Runners and framework bridges execute Effects; runtime owners manage their Layers and disposal.
- In rat-stack, [capabilities remain the shared action edge](/lore/one-capability-every-surface). A new runner does not authorize a raw bypass.
- In rat-stack, test harnesses use [Effect-aware tests](/lore/tests-that-earn-their-place), not manually created runtimes.

Read [scopes](/lore/scopes-own-resources) when deciding what the integration owns.
Read [services](/lore/services-capture-dependencies) before extracting the job from its host.
Return to the [Effect reading order](/lore/effect-basics#read-next).

## Sources

1. [Effect contributors. 2026. ManagedRuntime. Effect 4.0.0.](https://github.com/Effect-TS/effect/blob/67ba4e46a11ccda0b6761578bfd22c04ae00167d/packages/effect/src/ManagedRuntime.ts)
   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.](https://github.com/Effect-TS/effect/blob/67ba4e46a11ccda0b6761578bfd22c04ae00167d/ai-docs/src/04_integration/10_managed-runtime.ts)
   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.](https://github.com/Effect-TS/effect/blob/67ba4e46a11ccda0b6761578bfd22c04ae00167d/packages/effect/src/http/HttpRouter.ts)
   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.](https://github.com/joelhooks/rat-stack/blob/65e9465f38092e24486392f22c08f45d61230c20/apps/web/src/dev/backend.ts)
   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.](https://github.com/joelhooks/rat-stack/blob/4019cffbcbdb282d6f006854ff5b1f26d3b407e0/packages/capability/test/runtime-boundary.test.ts)
   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.](https://github.com/kitlangton/effect-solutions/blob/09f82e6c5c928e7232cd32daf04d7c6a830b63f7/packages/website/docs/10-incremental-adoption.md)
   Kit Langton. The proposed outline frames integration questions. It is not a completed recipe or API reference. Accessed 2026-10-06.

## Lore on this page

- [Structure an Effect codebase by domain](/lore/structure-effect-by-domain)
- [Handle expected failures without hiding defects](/lore/error-model)

## Linked from

- Agent guide → An Effect stack so pure (aspirational) Kit Langton will blush. → [Read page](https://ratstack.sh/llms.txt)

- Change log → What changed in the files served here, newest first. → [Read page](https://ratstack.sh/log)

- Effect basics → An Effect describes work, expected failures, and required services before a runtime executes it. → [Read page](https://ratstack.sh/lore/effect-basics)

- Full agent guide → An Effect stack so pure (aspirational) Kit Langton will blush. → [Read page](https://ratstack.sh/llms-full.txt)

- Lint and type escape ledger (debt.md) → A source-linked count of repo-owned lint and type escapes. → [Read page](https://ratstack.sh/debt.md)

- Rat Stack lore | rat-stack → Short, source-grounded notes on the ideas and decisions behind rat-stack. → [Read page](https://ratstack.sh/lore)

- Source change history (log.md) → The generated change log as Markdown. → [Read page](https://ratstack.sh/log.md)

- Trace operations where they mean something → Name useful operations, add safe context, and distinguish creating a span from delivering it to a backend. → [Read page](https://ratstack.sh/lore/trace-meaningful-operations)
