This part is for reviewing how Effeck runs. It explains how the features in Parts I and II execute on Effect 4's fiber runtime, what Effect already provides, what a small runtime extension (the kernel) adds, and the tests that check each claim. Claims carry tags:
| Tag | Meaning |
|---|---|
| [Effect] | Verified in effect@4.0.5 source (experiments/node_modules/effect/src, cited as file:line) or by an Effect-only test (F1-F10, conformance tests D01-D05, K15a) |
| [Kernel] | Verified by a kernel test: T1-T17, K10-K15, H1-H4, G6, and the conformance suite's A, F, I, O, S, T, W, and X tests. These test the language model, not ordinary Effect programs |
| [Prototype] | Compiled from .effeck by the prototype compiler and run on the kernel: an example in prototype/esc/examples (Chapter 27) |
| [Rule] | A language rule, decided in Part V; the compiler enforces it, and no test can prove a rule a checker has not been built to enforce |
| [Experiment] | A rule whose implementation an experiment in Chapter 30 still has to confirm |
18. Why a continuation runtime
const cell = handle(() => search(grid, 42), Found, {
found(cell, _k) { return cell } // ends search() now; the cursors it opened close first
})
for (const line of lines(path)) { // one value at a time; break closes the file
if (line.startsWith("ERROR")) break
}Both examples need one thing Effect does not expose today: a way for code on a fiber to take "the rest of the computation, up to here", hand it to a handler, and let the handler continue it once, later, or never. That object is a one-shot delimited continuation. Fubhy's advice was to build the language on it: "Start with a one-shot delimited continuation though. That is kind of the core thing that anything else should be based on." This part shows that Effect 4 already provides most of it, and what the rest takes.
Effect already runs on continuations
Effect 4 runs every program as a one-shot continuation. A fiber's _stack is the rest of the computation, reified as an array of frames. Effect.callback parks it and accepts one resume. Interruption unwinds it through finalizer frames. What Effect lacks is a delimiter inside a fiber: the only one is the fiber's root, so the only continuation anyone can take is the whole fiber.
Effeck adds the missing delimiter. A 877-line kernel prototype on effect@4.0.5 pushes prompt frames onto the real fiber stack and captures the segment above a prompt as a one-shot continuation. It restores the Context and interruptibility recorded at each boundary, abandons unresumed continuations through Effect's own finalizers, keeps a continuation from outliving the fiber that captured it, and bounds cross-fiber wake-up depth. 148 tests cover nested suspension with Context and Scope, double resumption, abandonment, interruption while a continuation is held, cancellation races, a 100,001-frame capture, capture through TypeScript-authored Effect.forEach, retry, producers in constant stack, cause-aware catch, and a 54-test conformance suite written by an independent reviewer. A prototype compiler then compiled 46 example files written from this document to that kernel and ran them (Chapter 27).
Every feature in Parts I and II is a rule over that one machine:
- A function call is a frame;
throwunwinds frames. - A service is an operation whose handler runs in place, at the call.
usingandfinallyare finalizer frames.forkstarts a new root that inherits the Context but no handlers.handleis a prompt, and a control operation captures the frames above it.- A function compiles to plain JavaScript only when the compiler proves it cannot reach a pause or a capture.
Why not lower to Effect.gen first
An earlier proposal compiled Effeck through Effect.gen and deferred delimited control to a later experiment. That cannot be the foundation, for four reasons that are facts about the running system:
Effect.genandflatMappush frames onto one fiber stack whose only delimiter is the root. They sequence work. They cannot hand "the rest of the computation up to here" to a handler, because nothing marks "here". When the handler is on another fiber, the operation is simply unhandled (T7b, T8).Effect.gendrops its generator on failure and interruption without running JavaScriptfinally(F1-F3). A language that compiledfinallyto generatorfinallywould leak on every failure.- Wrapping an API in
Effect.callbackparks the whole fiber or nothing. No frame inside the fiber can act as the handler for part of the stack. - A service method always returns to its caller exactly once. It cannot end the caller's remaining work with a value, defer it, or store it.
General delimited control therefore needs new frame kinds (Prompt and Owner above all) and capture, resume, and abandon operations. Those are a runtime extension; no choice of code generation supplies them.
The central test
Does the language make Effect's continuation runtime its foundation, with everything else derived from that model?
Claim [falsifiable]. Every feature is a rule over the machine in Chapter 20, which has three continuation mechanisms: frames, root capture, and prompt capture. Effect provides frames and root capture; the kernel provides prompt capture. The claim is false if any feature needs a mechanism outside the machine: a second scheduler, a second Context, a JavaScript try the machine cannot see, or a calling convention that skips frames where a pause or capture is reachable. Plain JavaScript calls (Chapter 15) skip frames only where the compiler proves no such point is reachable, which is the frame invariant (Chapter 26) applied, not an exception to it.
| Feature | Mechanism in the machine | Captures | Evidence |
|---|---|---|---|
| Program entry | A runner creates a fiber root for a program value | No | Chapters 13, 23 |
| Calls and returns | Ret frames; plain calls only where no pause or capture is reachable | No | T6a, T17; F9; ch02-functions.effeck, ch13-plain.effeck |
throw and catch | Abortive unwinding to a Catch frame that recovers only causes made of expected failures | No: aborts | K14a-K14c; resumeWith test; ch15-failures.effeck |
finally and using | Final frames on every exit, abandonment included | No | T1, T3, T4, T10, K11b; regions.effeck |
| Services | In-place evidence in the Context | No: runs in place | In-place evidence test; ch04-services.effeck |
| Providers and Layers | Installing evidence: Restore frames plus a Scope | No | Effect (Chapter 24) |
| Test overrides | Services: a Restore frame. Control operations: a handler for one region | Services: no. Control operations: yes | Override test; Chapter 9 |
| Async I/O, sleep, waits | Root capture with one-shot resume | Yes, at the root | T1 step ③; F5, F6 |
| Callback adapters | Root capture with a reported race and bounded wake-ups; masked or interruptible acquisition | Yes, at the root | T5b, T5c, K10a, K10b, K15a-K15c |
| Interruption | Asynchronous unwinding of the fiber's continuation, deferred by Mask frames | No | T4, T12 |
| Structured concurrency | New roots with a Context snapshot and no prompts; block-owned forks | No | F7, F8, T8 |
| Control handlers | Prompt capture, resume, abandon, ownership | Yes | T1-T4, T9, T13, T15; O01-O08; A01-A04; I03-I11; K12; ref6-handlers.effeck |
Producers (function*) | One prompt capture per element; abandonment on break; constant stack under a tail resume | Yes | T10, T16, T17, K11a-K11d; stack-depth.effeck |
| Retry, race, all | Fresh runs of a program; nothing reused | No | T11 |
Features checked against the claim and kept out:
- Multi-shot handlers (backtracking, probabilistic choice). A multi-shot feature needs copyable frames, and Effect's frames are not copyable: they hold live generators and closures bound to one fiber.
- Durable execution. It needs serializable continuations, a different machine. VibeLang reversed course on it twice in ten days (
vibelang.md§9).
Verdict. Yes. Two independent reviews (2026-10-16) reached the same verdict and found five defects in how the machine was implemented or lowered, not in what it is; all five are fixed with regression tests (K11-K15). A prototype compiler then lowered 46 example files to the machine mechanically and ran them, with no feature needing a mechanism outside it (Chapter 27). Two qualifications remain, both tracked in Chapter 30. Prompt capture relies on undocumented Effect fields, so the foundation is real but not yet a supported Effect API (Chapter 25, G1). And the checker rules that make it safe to program against (fiber crossing, second-class continuations, control classes) are specified here but not built (G8).