Effect Concurrency And Lifecycle
Use this skill for concurrent work, background processes, and resource lifecycles.
Fiber Ownership
Pick the lifecycle intentionally.
const fiber = yield* Effect.forkDetach({ startImmediately: true })(work)
yield* Fiber.interrupt(fiber)
Rules:
- Use
Effect.forkScoped for work that must stop when the current scope closes.
- Use
Effect.forkIn(effect, scope) when scope ownership is explicit.
- Use
Effect.forkDetach({ startImmediately: true })(effect) only when work must outlive the parent fiber.
- Use
Deferred for cross-fiber result delivery, especially from detached work.
- Use
Effect.onExit or Effect.ensuring for cleanup that must run on success, failure, or interrupt.
- Use
Effect.acquireUseRelease or scoped layers for real resources.
- Background work must have an owner: scope, registry, runtime service, or explicit detached lifecycle.
- Queues, streams, subscriptions, and detached fibers must have a shutdown story. If it cannot be interrupted or drained, it is not ready for runtime code.
Bounded Work
- Limit parallelism with
Effect.all(..., { concurrency: n }) or Effect.forEach(..., { concurrency: n }).
- Use
"unbounded" only when the collection and cost are bounded by design.
Effect.race interrupts the losing effect. Make sure both sides are interruption-safe.
- Prefer deterministic shutdown signals over sleeps.
Queues, PubSub, Streams
Queues encode pressure policy. Choose it deliberately:
Queue.bounded<A>(n) - backpressure; producer waits when full.
Queue.sliding<A>(n) - keep latest, drop oldest.
Queue.dropping<A>(n) - keep current, drop new when full.
Queue.unbounded<A>() - only when memory growth is impossible or bounded elsewhere.
Rules:
- Use explicit type parameters for queues.
- Use
PubSub when one event must be broadcast to multiple subscribers.
- Bridge queues to streams with
Stream.fromQueue(queue).
- End queue-backed streams by offering a terminal event plus
Stream.takeUntil(...), or by Queue.shutdown(queue) when shutdown semantics are correct.
- Do not leave background consumers without interruption or shutdown.
Checks
- Who owns this fiber?
- What interrupts it?
- Where is shutdown/drain handled?
- Can producers outrun consumers?
- What happens when the consumer fails?
- Are failures logged, surfaced, or intentionally ignored?
1---2name: effect-concurrency-lifecycle3description: Use when working with Effect Fiber, Deferred, Scope, Queue, PubSub, Stream, interruption, background loops, graceful shutdown, backpressure, races, or bounded concurrency in Theseus.4---56# Effect Concurrency And Lifecycle78Use this skill for concurrent work, background processes, and resource lifecycles.910## Fiber Ownership1112Pick the lifecycle intentionally.1314```typescript15const fiber = yield* Effect.forkDetach({ startImmediately: true })(work)16yield* Fiber.interrupt(fiber)17```1819Rules:2021- Use `Effect.forkScoped` for work that must stop when the current scope closes.22- Use `Effect.forkIn(effect, scope)` when scope ownership is explicit.23- Use `Effect.forkDetach({ startImmediately: true })(effect)` only when work must outlive the parent fiber.24- Use `Deferred` for cross-fiber result delivery, especially from detached work.25- Use `Effect.onExit` or `Effect.ensuring` for cleanup that must run on success, failure, or interrupt.26- Use `Effect.acquireUseRelease` or scoped layers for real resources.27- Background work must have an owner: scope, registry, runtime service, or explicit detached lifecycle.28- Queues, streams, subscriptions, and detached fibers must have a shutdown story. If it cannot be interrupted or drained, it is not ready for runtime code.2930## Bounded Work3132- Limit parallelism with `Effect.all(..., { concurrency: n })` or `Effect.forEach(..., { concurrency: n })`.33- Use `"unbounded"` only when the collection and cost are bounded by design.34- `Effect.race` interrupts the losing effect. Make sure both sides are interruption-safe.35- Prefer deterministic shutdown signals over sleeps.3637## Queues, PubSub, Streams3839Queues encode pressure policy. Choose it deliberately:4041- `Queue.bounded<A>(n)` - backpressure; producer waits when full.42- `Queue.sliding<A>(n)` - keep latest, drop oldest.43- `Queue.dropping<A>(n)` - keep current, drop new when full.44- `Queue.unbounded<A>()` - only when memory growth is impossible or bounded elsewhere.4546Rules:4748- Use explicit type parameters for queues.49- Use `PubSub` when one event must be broadcast to multiple subscribers.50- Bridge queues to streams with `Stream.fromQueue(queue)`.51- End queue-backed streams by offering a terminal event plus `Stream.takeUntil(...)`, or by `Queue.shutdown(queue)` when shutdown semantics are correct.52- Do not leave background consumers without interruption or shutdown.5354## Checks5556- Who owns this fiber?57- What interrupts it?58- Where is shutdown/drain handled?59- Can producers outrun consumers?60- What happens when the consumer fails?61- Are failures logged, surfaced, or intentionally ignored?