Skip to content

Runtime

An Effect is just a description of a computation — building one runs nothing. Nothing happens until you hand that description to a runtime, which spawns a fiber to actually carry it out. This is the same principle as a lazy () => Promise: you compose effects freely, then execute the whole program in one place.

The golden rule is to run effects at the edge of your application. Keep the vast majority of your logic as composed effects, and call a run* function (or a platform runMain) once, as close to the “outside world” as possible — your main.ts, an HTTP handler, a test. Pushing execution to the edge keeps your code composable, testable, and free of half-run side effects scattered through the middle of your program.

import { Effect } from "effect"
// A description. Nothing has happened yet.
const program = Effect.gen(function*() {
yield* Effect.log("Hello from a fiber")
return 42
})
// Execution happens here, once, at the edge.
Effect.runPromise(program).then((n) => console.log(n))
// Output:
// ... level=INFO message="Hello from a fiber"
// 42
  • Running effects — the run* family (runSync, runPromise, runFork, and their Exit variants), and how to choose between them.
  • Running as an entrypointNodeRuntime.runMain / BunRuntime.runMain, the production-grade way to make an Effect your process root, with signal handling and graceful shutdown.
  • Launching applicationsLayer.launch, for long-running apps (servers, workers) expressed entirely as layers.
  • Managed runtime — build a single runtime from your application Layer and run many effects against it, so services and resources are shared instead of constructed per run. The way to bridge Effect into a non-Effect host (a web framework, a UI event loop).
You want…Use
A process entrypoint (CLI, server, worker)NodeRuntime.runMain / BunRuntime.runMain
A long-running app built from layersLayer.launch + runMain
To share services/resources across runsManagedRuntime.make(layer)
A Promise for interopEffect.runPromise
A background fiber to observe or interruptEffect.runFork
An immediate, purely synchronous resultEffect.runSync

For most applications you will reach for runMain rather than calling the low-level run* functions directly — it adds error reporting and graceful shutdown on top of runFork. The Effect.run* functions are the right tool when embedding Effect inside an existing host (a React event handler, an Express route, an existing async function).

When that host fires events repeatedly and your effects need shared services (database pools, config, caches), don’t build a fresh layer on every event. Build a ManagedRuntime once from your application Layer and call runtime.runPromise(effect) per event — the layer is constructed a single time and torn down with runtime.dispose() on shutdown.

Effects must have their requirements satisfied before they can run. A run*/runMain function only accepts an Effect<A, E, never> — the R channel must be empty. You eliminate requirements by providing services and layers before reaching the edge.

For writing your own process entrypoint or platform adapter, the core Runtime module exposes the building blocks the runMain adapters are made from (makeRunMain, Teardown, defaultTeardown, and the errorExitCode / errorReported markers). Those are documented alongside the practical recipe on the Running as an entrypoint page.

For repeated execution against a shared set of services, a one-shot run* is the wrong shape — it builds and discards everything each call. Build a ManagedRuntime from your application Layer once, then run effects against it as many times as you like. The layer (and its scoped resources) is constructed lazily on first use and released when you dispose.

import { Effect, Layer, ManagedRuntime } from "effect"
// Your application's services, expressed as a Layer.
const AppLayer = Layer.empty
const runtime = ManagedRuntime.make(AppLayer)
// Run many effects; services are shared, not rebuilt each time.
await runtime.runPromise(Effect.log("request 1"))
await runtime.runPromise(Effect.log("request 2"))
// Release layer resources on shutdown.
await runtime.dispose()

A ManagedRuntime exposes runPromise, runPromiseExit, runFork, runSync, runSyncExit, plus dispose / disposeEffect. See Managed runtime for the full reference and framework-integration examples.