跳转到内容

Effect.ts for TypeScript services

Related: Cloud agent orchestrator · Effect skill · 2026-09-28 Pi SDK v2 abilities boundary and data model · Bend skill

Pin checked 2026-09-28: effect@3.22.2 (latest); v4 under rc / beta (4.0.0-rc.*) — docs site has a version selector. Default this stack to 3.x stable unless a deliberate v4 spike.

Primary docs: effect.website · docs hub · GitHub Effect-TS/effect


ConcernEffect roleBend / other
Legal run transitions—Bend SM (LAWS/PROOF)
Ask legality → append ledger → I/O → effect receiptExecutor as Effect pipelineSQLite/Drizzle TX still host
Retries on git / gh / flaky settleEffect.retry + Schedule—
Worktree / Pi session / DB handle cleanupacquireRelease + Scope—
Hono / MCP / CLI entryManagedRuntime.runPromiseFramework stays Promise-shaped
SSE / event fan-outStream—
Wire Config / Db / Git / BendBridgeContext.Tag / Effect.Service + LayerTest layers swap fakes
OpenAPI / event payloadseffect/Schema—

Effect does not replace Bend proofs or Drizzle SQL. It structures how TS side effects compose, fail, retry, and clean up.


Lazy description of a workflow: succeeds with A, fails with typed E, needs services R. Creating an effect does nothing until a Runtime runs it (Effect.runPromise, runFork, runSync, or ManagedRuntime).

Compose with Effect.gen(function* () { … }) (generators) or pipe operators. Prefer Data.TaggedError("…") for domain errors.

Docs: The Effect Type

KindIn E?Typical use
Expected (failures)YesDomain: TransitionIllegal, PrOpenFailed, DbConflict
Unexpected (defects)NoBugs, thrown exceptions → Cause; recover with catchAllCause / exit

Handle expected errors with catchTag / catchTags / either / match. Docs: Two Types of Errors

Layer<Out, E, In> constructs services without leaking construction deps into service method signatures (keep ops Effect<…, …, never>). Compose with Layer.provide / merge / provideMerge. App entry: build MainLive, then Effect.provide(program, MainLive) or wrap in ManagedRuntime.

Shorthand: Effect.Service generates tag + .Default layer. Docs: Managing Layers

Effect.acquireRelease(acquire, release) ties cleanup to a Scope; Effect.scoped opens/closes the scope (finalizers run reverse order, including on fail/interrupt). Use for worktrees, Pi dispose, DB clients, temp dirs. Docs: Scope

Immutable recurrence policy for Effect.retry / Effect.repeat (backoff, spaced, forever ∩ capped, etc.). Compose with union/intersect. Docs: Scheduling

Like Effect but 0..n values — backpressured pipelines; stand-in for AsyncIterable / Observable-style flows (SSE chunks, log tails). Docs: Streams

Schema<Type, Encoded, Requirements> for decode/encode/assert (HTTP bodies, event payloads, config). Prefer over ad-hoc Zod-at-boundary-only if the rest of the app is Effect. Docs: Schema introduction

Runtime interprets effects (fibers, interruption, finalizers). Default Effect.run* uses empty context. For servers: ManagedRuntime.make(appLayer) once, then runPromise / runFork per request; dispose on shutdown. Docs: Runtime


Reach for Effect when

  • Failures must appear in types and branch in one place (illegal transition vs I/O fail vs bug).
  • You need retries / timeouts / interruption with shared policy (Schedule).
  • Resources must release on every exit path (worktree, session, TX helpers).
  • Services should swap Live/Test via Layers without rewriting call sites.
  • Concurrency is structured (bounded Effect.forEach, race, fibers) not ad-hoc Promise.all + forgotten abort.

Stay on Promise / sync when

  • Trivial glue at a framework edge already typed by Hono/Drizzle.
  • One-shot scripts with no retries/DI (or call runPromise once at the end).
  • A library API is Promise-only and wrapping every call adds noise without gain — wrap at the service boundary, not every line.

Anti-pattern: “Effect everywhere” including pure helpers, or await Effect.runPromise inside every nested function (destroys composition and scopes).


  • @effect/vitest (peer: effect ^3.22, vitest ^3) — helpers to run Effects in Vitest (npm).
  • Provide Test layers (Layer.succeed / fakes) for Git, BendBridge, clock; assert on Exit / Cause / tagged errors.
  • Prefer testing the program with injected layers over mocking internals of Live layers.

PackageRole
effectCore (Effect, Layer, Schema, Stream, Schedule, Scope, ManagedRuntime, …)
@effect/platform + @effect/platform-nodeFS, HTTP client, Command, Path — Node impls
@effect/sql (+ sqlite drivers)Optional SQL Effect integration (orchestrator may keep Drizzle; optional later)
@effect/vitestTest helpers
@effect/opentelemetryTracing/metrics if/when panels need it

Install note (upstream): TypeScript strict; Node 18+ (some SQL packages want newer Node — check package readme; stack already targets Node 24).


// Pseudocode — not a locked API
const applyTransition = (cmd: TransitionCmd) =>
Effect.gen(function* () {
const bend = yield* BendBridge
const db = yield* Ledger
const legal = yield* bend.check(cmd) // Effect; Bend = policy
if (!legal) return yield* Effect.fail(new TransitionIllegal({ cmd }))
yield* db.transact(cmd) // append transition + projection same TX
const receipt = yield* Effect.acquireRelease(
runSideEffect(cmd), // worktree / Pi / gh
(handle, exit) => cleanup(handle, exit),
).pipe(Effect.scoped)
yield* db.appendEffect(receipt)
}).pipe(
Effect.retry(Schedule.intersect(Schedule.exponential("100 millis"), Schedule.recurs(3))),
)
// Hono edge
const runtime = ManagedRuntime.make(MainLive)
app.post("/runs", async (c) => {
const body = Schema.decodeUnknownSync(CreateRun)(await c.req.json())
const result = await runtime.runPromise(applyTransition(body))
return c.json(result)
})

Bend still decides legality; Effect owns retry, cleanup, and service wiring around that call.


  1. Running effects inside effects via nested runPromise — break scopes and fibers.
  2. Putting construction requirements on service methods (Layer leak) — keep method R = never.
  3. Treating defects as domain errors (or swallowing Cause).
  4. Using Effect retries to paper over illegal Bend transitions — those should fail typed, not retry.
  5. Adopting Effect v4 RC for production without a spike — pin 3.22.x until v4 is latest.
  6. Expecting Effect to sandbox processes — still OS/worktree/spawnHook PATH policy.