Concurrent work needs an owner and a bound
Choose how many operations may run at once. Give child work an owner that can stop it. A completed caller must not silently leave its work running.¶
Bound collection processing before running it
A traversal processes each input in a collection. An Effect describes work before execution.¶
Effect.forEach describes one Effect per input and collects results in input order.
Its concurrency option bounds active operations.
The input collection can be larger than that bound.
The checked fixture measures active work with a Ref, an Effect value that stores mutable state.¶
const activity = yield* Ref.make({ active: 0, maximum: 0 });
const input = Array.from({ length: options.count }, (_, index) => index);
const results = yield* Effect.forEach(
input,
(index) =>
Effect.acquireUseRelease(
Ref.update(activity, (state) => ({
active: state.active + 1,
maximum: Math.max(state.maximum, state.active + 1),
})),
() => Effect.yieldNow.pipe(Effect.andThen(Effect.succeed(index))),
() =>
Ref.update(activity, (state) => ({
...state,
active: state.active - 1,
}))
),
{ concurrency: options.concurrency }
);
return { ...(yield* Ref.get(activity)), results };
}The generated test varies both the input count and the bound. It requires completed work, no remaining activity, input-order results, and a peak that does not exceed the supplied bound.¶
const Traversal = Schema.Struct({
concurrency: Schema.Literals([1, 2, 3, 4]),
count: Schema.Literals([1, 2, 3, 4, 5, 6, 7, 8]),
});
it.effect.prop(
"traversal never exceeds its supplied bound and preserves input order",
{ scenario: Arbitrary.schema(Traversal) },
({ scenario }) =>
Effect.gen(function* traversalBound() {
const observed = yield* boundedTraversal(scenario);
expect(observed.maximum).toBeGreaterThan(0);
expect(observed.maximum).toBeLessThanOrEqual(scenario.concurrency);
expect(observed.active).toBe(0);
expect(observed.results).toEqual(
Array.from({ length: scenario.count }, (_, index) => index)
);
})
);
The expectation uses the supplied bound. It is independent of the traversal’s scheduling algorithm.¶
A planted violation replaced the bound with unbounded.
The property failed with seven active operations against a bound of one. The restored implementation passes.
Measure production limits separately from these generated test counts.¶
Stop child work with its Scope
The sandbox writes queued input to a child process through a scoped fiber: child work that stops when its owning Scope34 closes. A fiber is an independently running Effect. A Scope owns cleanup.¶
const outbox = yield* Queue.unbounded<Uint8Array, Cause.Done>();
yield* Stream.fromQueue(outbox).pipe(
Stream.run(handle.stdin),
Effect.ignore,
Effect.forkScoped
);The checked ownership test starts a child that waits indefinitely. When the owner closes, the child must observe interruption before the enclosing Effect returns.¶
Effect.gen(function* childOwnership() {
const started = yield* Deferred.make<boolean>();
const interrupted = yield* Ref.make(0);
yield* Effect.scoped(
Effect.gen(function* ownChild() {
yield* Deferred.succeed(started, true).pipe(
Effect.andThen(Effect.never),
Effect.onInterrupt(() =>
Ref.update(interrupted, (count) => count + 1)
),
Effect.forkScoped
);
yield* Deferred.await(started);
})
);
expect(yield* Ref.get(interrupted)).toBe(1);
})A Deferred is an Effect value completed once to signal that work started. The test does not depend on an arbitrary sleep.¶
Use scopes34 to decide the lifetime before choosing a fork operation. Different fork operations carry different ownership rules; do not treat their names as interchangeable.¶
Consume a stream without promising a bounded queue
The sandbox decodes stdout, splits it into lines, and interprets each protocol message with an Effect. Its consumer stops after the first completed outcome.¶
const outcome = yield* Stream.decodeText(handle.stdout).pipe(
Stream.splitLines,
Stream.filter((line) => line.trim() !== ""),
Stream.mapEffect(
(line) =>
decodeChildMessage(line).pipe(
Effect.mapError(
(error) =>
new SandboxError({
logs: [],
message: `Unreadable sandbox message: ${error.message}`,
reason: "protocol",
})
),
),
{ concurrency: 8 }
),
Stream.filter(Option.isSome),
Stream.map((option) => option.value),
Stream.runHead,
Effect.mapError((error) =>
isSandboxError(error)
? error
: new SandboxError({Stream.mapEffect keeps decoding and dispatch in the stream description. The consumer does not launch eager Promises for each line.¶
Backpressure10 lets the consumer limit how quickly the producer can continue. An unbounded queue can let the producer keep adding data.¶
The producer uses Queue.unbounded. This code does not prove a bounded producer queue or bounded total memory.
A bounded consumer is not enough when producers can accumulate data faster than it is consumed.¶
For a growing input, choose a stream and inspect each buffer’s policy. For a known finite collection, a bounded traversal can be enough. Measure the service’s limits before selecting an operational bound.¶
Common mistakes
- Create eager Promises before building the owned Effect traversal.¶
- Detach work that should stop with the caller.¶
- Bound worker concurrency and claim every upstream buffer is bounded too.¶
- Copy test counts into production without measuring the provider and host.¶
Effect idiom and house rule
- Fibers, traversal bounds and streams compose owned work without replacing the typed Effect description.¶
- In rat-stack, measure operational limits instead of inventing caps.¶
- In rat-stack, machines own finite domain retries and cancellation26. Effect’s native fibers and schedules still exist.¶
Next, validate HTTP responses9 before treating concurrent provider work as successful.¶
For work that must survive a crash, read every task needs an owner9 and the durability index34.¶
Return to the Effect reading order29.¶
Sources
- Effect contributors. 2026. Effect. Effect 4.0.0.. Effect-TS. forEach limits traversal concurrency and retains input order; forkScoped attaches child interruption to the current Scope. Accessed 2026-10-06.
- Effect contributors. 2026. Stream.. Effect-TS. mapEffect performs effectful transformation; splitLines and runHead consume line-oriented output. Accessed 2026-10-06.
- Effect contributors. 2026. Consuming and transforming streams.. Effect-TS. Effectful stream transformation and consumption with explicit concurrency choices. Accessed 2026-10-06.
- rat-stack contributors. 2026. Subprocess sandbox ownership.. rat-stack. A scoped stdin writer and decoded stdout stream; its producer queue is explicitly unbounded. Accessed 2026-10-06.
- rat-stack contributors. 2026. Traversal and child-ownership fixtures.. rat-stack. Generated traversals check peak active work, completion and order. A separate seam proves scoped child interruption. The bound property catches a planted unbounded traversal. Accessed 2026-10-06.
- Langton, Kit. 2026. Testing. Effect Solutions.. Kit Langton. Fiber examples frame the ownership question; the snapshot has no complete concurrency guide. Independent fixtures establish this page's claims. Accessed 2026-10-06.
Last changed 2026-10-23 in 58dc623. Source on GitHub. Change log.
Linked from
- Admission control protects recovery. Refuse or defer work that cannot be served usefully under current load.
Connect this to the existing rat-stack guidance.¶
- Agent guide. An Effect stack so pure (aspirational) Kit Langton will blush.
Concurrent work needs an owner and a bound — Limit active work, retain result order, and stop child work when its owning lifetime ends.
- Backpressure reaches the producer. Bounded consumption helps only when upstream respects waiting or refusal.
Connect this to the existing rat-stack guidance.¶
- Change log. What changed in the files served here, newest first.
Concurrent work needs an owner and a bound — edited: docs(lore): land the durability wiki pages and bibliography
- Checkpoints follow the promised commit. Persist state and progress together when one store can transact both.
Connect this to the existing rat-stack guidance.¶
- Dead letters need a return path. Exhausted work needs durable evidence, an owner, and deliberate redrive.
Connect this to the existing rat-stack guidance.¶
- durability. Keep a rat-stack app standing when a provider, pool, or dependency slows down or a worker dies. Use at any I/O boundary, connection pool, provider call, shared waiter, fan-out, flag, gate, or work that can outlive a request, or when asked "what happens when this is slow", "add a timeout", "make this durable", or "what survives a crash".
Effect runs each fiber as a concurrent computation in one JavaScript runtime, not as an isolated process.
- Durability starts with a recovery record. A reading order for storage, replay, worker ownership, overload, and recovery tests.
Concurrent work needs an owner.¶
- Durable work needs storage. A restarted worker cannot recover intent that existed only in memory.
Connect this to the existing rat-stack guidance.¶
- Effect basics. An Effect describes work, expected failures, and required services before a runtime executes it.
Concurrency: bound work and stop owned children.¶
- Effect is not BEAM. Fibers share a runtime; scope ownership does not supply isolated heaps or OTP restart strategies.
Connect this to the existing rat-stack guidance.¶
- Every task needs an owner. Give child work a lifetime, a failure observer, and a recovery decision.
forkDetach detaches the fiber from its parent.
- Full agent guide. An Effect stack so pure (aspirational) Kit Langton will blush.
- Golden signals expose overload. Measure latency, traffic, errors, and saturation, including failed work.
Connect this to the existing rat-stack guidance.¶
- Queries need result bounds. Limit result cardinality before the driver materializes an oversized response.
Connect this to the existing rat-stack guidance.¶
- Rat Stack lore | rat-stack. Short, source-grounded notes on the ideas and decisions behind rat-stack.
Concurrent work needs an owner and a bound — Limit active work, retain result order, and stop child work when its owning lifetime ends.
- Request lifetime is not durability. Worker waitUntil has a shared post-response allowance; required work needs a durable marker and redrive.
Connect this to the existing rat-stack guidance.¶
- Restart must reconstruct known state. Execution recovery and state recovery need separate contracts.
A new fiber does not restore old local variables or machine memory.¶
- Retry load needs a budget. One retry owner must control attempts, delay, and aggregate load.
Connect this to the existing rat-stack guidance.¶
- Retry needs idempotent effects. Repeating an operation must preserve its intended observable result.
Connect this to the existing rat-stack guidance.¶
- Scopes release resources when work ends. Acquire resources lazily, keep their use inside the owner, and release them after success, failure, or interruption.
Then read concurrent work before starting child work.¶
- Source change history (log.md). The generated change log as Markdown.
Concurrent work needs an owner and a bound — edited: docs(lore): land the durability wiki pages and bibliography
- Timeouts bound waiting. A local timeout stops waiting; it does not prove the remote action never happened.
Connect this to the existing rat-stack guidance.¶
Unlinked mentions (6)
- durability → Its structured fiber ownership is useful, but it does not rebuild lost state.¶
- Effect 4 study: September 2026 → Shape: createEffectActor(machine) returns Effect<EffectActor, never, R | Scope> and drives XState 6’s pure transition through xstate/durable on an Effect fiber.
- Effect Days 2025: Building Effect 4 → A fiber is independently running work.
- Hosted agent interfaces → Pending reads must not use a shared promise, fiber, latch, or response body.
- profile-a-worker → In an Effect program, the fiber runtime (runLoop, makePrimitiveProto, succeedWith) is the floor every request pays.
- Scopes release resources when work ends → A fiber is an independently running Effect.
Content updated .