Warning: Effeck is v0. The language will change in major, breaking ways. It is not suitable for production use.

Appendices

A. Evidence index

Run everything with:

cd experiments
bun install        # effect@4.0.5 exactly
bun test           # 148 tests in 19 files, about a second
bun test ./scratch-review-codex/                     # the Codex review's probes R1-R10
bun test ./scratch-review-fable/suspend-in-acquire.test.ts ./scratch-review-fable/tail-resume-generator.test.ts
bun bench/perform.ts
bun bench/deep-capture.ts
node scripts/wakeup-chain-depth.ts       # F10 depths on this host (also runs under bun)
node scripts/v3-wakeup-chain.mjs 100000  # F10b: the same chain on effect 3.22.3
cd reference-machine && bun install && bun test   # strategy B, 20 tests, about 18 s

The docs site build (docs/tools/render-user.ts) runs this suite first and refuses to build if any test fails.

How this revision was checked

The first draft had two independent reviews. Codex (GPT-6.1 Sol) wrote 54 conformance tests against the kernel and found five bugs, all fixed. Fable reviewed the draft against the original request; its findings shaped Chapters 9, 13, and 26.

The second draft had two more, on 2026-10-16: Codex (GPT-6 Astra, docs/reviews/2026-10-16-codex-astra.md) and Fable (docs/reviews/2026-10-16-fable.md). Both passed the central test and verified counterexamples with probes. Five were kernel or lowering defects, now fixed with regression tests K11-K15; 12 of those tests fail on a copy of the kernel with the fixes reverted, and K15 pins Effect's own acquisition behavior (experiments/KERNEL-CHANGES.md). The rest were rule gaps, now decided (D9-D21). A feature-coverage audit then mapped every section of Effect's documentation to the language (docs/research/effect-feature-coverage.md, 309 features); its gaps are decided in D22-D28 and D37-D43, and its map is Appendix C. Then a prototype compiler compiled 46 example files written from this document and ran them on the kernel; its findings are decided in D44-D53. Last, a single-pass experiment reached the same lowering with one patched TypeScript check per file, byte for byte; its probes are decided in D56 and D52.

Effect facts

IDFileWhat it shows
F1-F3tests/effect-facts.test.tsJavaScript finally and catch in Effect.gen do not run when a yielded effect fails or the fiber is interrupted
F4sameA JavaScript exception in a generator body is a defect
F5, F6sameEffect.callback ignores every resume after the first; an asynchronous resume runs the fiber on the resumer's stack
F7, F8sameA child snapshots Context at fork, with nothing flowing back; forkChild children are interrupted when the parent completes
F9sameDirect recursion limit: about 39,900 frames on Bun 1.4.1 and about 10,150 on Node 26.10
F10a, F10btests/wakeup-chain.test.ts, scripts/A chain of Deferred wake-ups overflows in one fiber and strands the rest; effect 3.22.3 completes the same chain at 100,000 hops
T5a, T6btests/kernel.test.tsEffect.callback drops a late resume silently; plain JavaScript recursion overflows
D01-D05tests/conformance/source-claims.test.tsCorrections the conformance review made to the first draft's Effect claims: synchronous callback resume, fork-time async context, zip's concurrent flag, catchCause on a self-raised interrupt, sibling provides sharing a supplied MemoMap
A3tests/fiber-behavior.test.tsTwo providers for one key in Layer.merge: the later one wins silently
K15atests/review-fixes.test.tsDefault acquireRelease masks a wait: interruption waits for the value, which is then released

Kernel laws

IDFileWhat it shows
T1tests/trace.test.tsNested suspension and resumption keep Context and Scope; the clause sees the outer Context; the fiber waits at its root while the continuation is held
T2-T4tests/kernel.test.tsDouble resume is a defect; a clause that returns without resuming abandons first; interruption order is clause, continuation, outer
T5b, T5c, K10a, K10btests/kernel.test.ts, tests/wakeup-chain.test.tssuspend reports lost races, delivers once, survives a 100,000-fiber chain, and releases a deferred value that loses to cancellation
T6atests/kernel.test.ts100,001 nested compiled frames captured in one operation and resumed
T7a, T7b, T8sameCapture through TypeScript-authored Effect.forEach; forking combinators and child fibers cannot reach the parent's prompt
T9-T11sameDeep handlers; external iteration with transfer; retry runs fresh continuations
T12-T15tests/kernel-edges.test.tsPending interruption at resume; resume-site Context; fiber-affine abandonment (T14b caught a real bug); HandlerPromptExited instead of rebinding
T16, T17tests/producers.test.tsfor...of lowering: break runs the producer's finalizer first; 100,000 elements in constant stack with a flatMap-shaped clause
K11a-K11dtests/review-fixes.test.tsA generator clause that returns tailResume iterates 100,000 elements at 3 frames, 5 with a per-element using and finally; yielding a tail-resume request is a defect; a resume inside a finally region is not a tail resume
K12a-K12esameAbandonment reports every cleanup failure beside real pending interruptions: explicit abandon, abandonNow, owner exit, and Scope exit
K13a-K13esameA transferred continuation never outlives its capturing fiber; transfer into a parallel Scope is rejected
K14a-K14csamecatchExpected recovers only causes made of expected failures, the first one first; mixed causes pass through intact
K15b, K15csameInterruptible acquisition: cancellation wins, and late or deferred values are released
H1-H4tests/handoff.test.tsHandoff: one delivery; late delivery during asynchronous shutdown released before cancellation completes; deferred delivery released; delivery after quiescence rejected
G6a, G6btests/context-bridges.test.tsSpans are correct across capture; AsyncLocalStorage is not delimited
Probestests/fiber-behavior.test.ts17 combinators keep control on the performing fiber; 7 move it to other fibers
A01-A04tests/conformance/abandonment.test.tsFinalizer failures and swallowed aborts surface at every abandoning boundary
F01-F09tests/conformance/frames.test.tsEffect's own frames (restore closures, masks, nested prompts, iterator frames, finalizers) behave correctly when spliced
I01-I11tests/conformance/interruption.test.tsInterruption at capture, at resume, and during abandonment; real interrupts are never lost or mixed with the internal abort
O01-O08tests/conformance/ownership.test.tsNested owners, transfer, revocation, fiber affinity, and the ownership-cycle check
S01, S02tests/conformance/scopes.test.tsEffect.scoped, acquireRelease, and Layer resources across resume and abandonment
T01, T02tests/conformance/tracing-cache.test.tsEffect.fn and withSpan frames across capture; Context.Reference and fiber caches at every boundary
W01-W05tests/conformance/wake.test.tsWake budget ordering and release on every losing path
X01, X02tests/conformance/async-frames.test.tsEffect's async finalizer frame inside a captured segment
Breference-machine/The independent strategy-B machine (Codex, GPT-6.1 Sol): 20 tests on the same laws

The conformance suite (A, D, F, I, O, S, T, W, X) was written by Codex (GPT-6.1 Sol) as an independent review of the first kernel. Its failing tests found the first five bugs. Tests that encoded the old behavior as counterexamples were rewritten to assert the corrected behavior.

Review probes

Counterexamples from the 2026-10-16 reviews, kept as observational tests. Where a defect was fixed, the probe now asserts the fixed behavior.

IDFileWhat it showed
R1scratch-review-codex/probes.test.tsEffect.catch recovers a cause holding both a failure and a defect, and drops the defect (D13, K14)
R2, R2bsameOwner-driven abandonment keeps a cleanup defect; explicit abandon dropped it while an interruption was pending (D15, K12)
R3sameDefault acquireRelease shields suspend from cancellation (D25, K15)
R4sameInlining an Effect.sync operation removes an interruption point (D10)
R5sameA child that transfers into its inherited Scope and exits stranded the segment (D16, K13)
R6sameA TypeScript function forks its operation without taking a program argument (D20)
R7sameA provider around resumeWith cannot satisfy the injected program's R (D17)
R8sameA block-wide Scope closes acquisitions not marked using (D14)
R9sameStrategies A and B disagree on the next clause's Context after a resume (G16)
R10sameA generator clause written return yield* resume(k, v) defeats constant-stack iteration (D18, K11)
Fable probesscratch-review-fable/The masked adapter's race branches never run; generator clauses grow 200,003 frames over 100,000 elements before K11
Servicesscratch-abstract-services/probe.tsStructurally equal abstract classes alias in R, and the program dies with Service not found (D1)

Prototype compiler

The prototypes predate the rename to Effeck, so their sources keep the old .es extension.

# From the repository root:
bun prototype/esc/examples/run-all.ts       # 46 of 46 example files pass, about 16 s
bun prototype/esc/src/cli.ts build prototype/esc/examples/ch01-first-look.es
bun prototype/esc/src/cli.ts run prototype/esc/examples/ch09-handlers.es
WhatWhereWhat it shows
The compilerprototype/esc/src/Type-directed lowering of .es to TypeScript on typescript@5.9.3, with the experiments kernel as its runtime; about 3,560 lines
26 runnable examplesprototype/esc/examples/*.esEach compiles with no errors and produces its stated result, including the six reference programs (ref1 to ref6)
20 expected failuresprototype/esc/examples/*.fail.esEach reports its diagnostic code at the right span
regions.essameCompletion records through nested try, catch, and finally match plain JavaScript's results and order
stack-depth.essameProducer loops and handler clauses stay under 50 frames over 100,000 elements
scratch/probe2.tsprototype/esc/scratch/The service emission of D45: nominal R, working implements, and a parameter typed with the service meaning its methods
Findingsprototype/esc/FINDINGS.mdThirty findings, settled by D44-D53

Single-pass experiment

cd prototype/single-pass      # from the repository root
bun patch/apply.ts            # copy TypeScript 5.9.3 to typescript/ and patch it
bun harness/compare.ts        # the corpus: 46 of 46 files, about 10 s
bun harness/compare.ts --dir "$PWD/probes" --verbose
bun harness/passes.ts         # the prototype's decision changes per pass
WhatWhereWhat it shows
The patched checkerprototype/single-pass/patch/es-checker.jsEach lambda classified per overload candidate inside chooseOverload, D10 on demand, and narrowing through the call rule: one 668-line block and 15 hooks in TypeScript 5.9.3
The comparisonprototype/single-pass/harness/compare.tsOne check per file reaches the prototype's lowering in 46 of 46 files: 817 call sites, 257 lambdas and methods, 0 disagreements, byte-identical output, 46 checks instead of 124
The probesprototype/single-pass/probes/17 disagreements: 16 from 6 lambdas with two self-consistent lowerings (D56), 1 from mutual recursion (D52)
Resultsprototype/single-pass/RESULTS.mdThe method, the per-file table, and the rules for the Go checker (Chapter 27)

The Go fork

cd typescript                    # from the repository root
effeck/smoke.sh            # builds tsc (about 75 s cold), then compiles and runs every case
effeck/smoke.sh effeck-infer   # one case
CaseWhat it shows
effeck-wrapFunctions lower to Effect.fnUntraced generators, and JavaScript runs them
effeck-lowerCalls run by type: pure helpers stay plain, Effect-returning calls run, operands run left to right, and a Config stays data
effeck-inferUnannotated functions get Effect<A, E, R>: a typed failure flows from lookup into main, and the .d.ts declares it

typescript/EFFECK.md records the base, the 11 commits, the upstream lines it changes, and the differential check against the prototype.

Research and drafts

  • docs/design/DECISIONS.md: the decision log, D1-D60.
  • docs/research/effect4-runtime.md: Effect 4.0.5 runtime facts, with 23 scripts in experiments/scratch-agent-a/scripts/. Scripts written for this design live in experiments/scripts/.
  • docs/research/continuation-prior-art.md: OCaml 5, Kotlin, Swift, Loom, WasmFX, Koka, Effekt, Effection, js_of_ocaml, and the papers.
  • docs/research/vibelang.md: VibeLang main (c60eca97) and the unmerged checkpoint (ebe89a2b).
  • docs/research/typescript-native-compiler.md: the native compiler upstream and effect-tsgo.
  • docs/research/independent-design-codex.md: the independent semantics Codex wrote from the same brief.
  • docs/research/pipe-census.md: how real Effect code uses pipe (D8).
  • docs/research/tc39-syntax.md: the status of every TC39 proposal that could give syntax to an Effect feature, fetched 2026-10-16.
  • docs/research/effect-feature-coverage.md: every Effect docs section mapped to the language (Appendix C).
  • experiments/KERNEL-CHANGES.md: the 2026-10-16 kernel fixes.
  • prototype/esc/FINDINGS.md: what the prototype compiler found.
  • prototype/single-pass/RESULTS.md: the single-pass experiment (D56, D52).
  • typescript/EFFECK.md: the Go fork's base, commits, seams, and gaps.
  • Upstream draft index: nine Effect runtime, tooling, documentation, and API drafts with reproductions; all drafted, not filed (D59).

B. Prior art

Full notes, with quoted sources, are in docs/research/continuation-prior-art.md. Rule numbers below refer to its final section.

QuestionPrior artEffeck
Second resumeA runtime error everywhere it is checked: OCaml Continuation_already_resumed, Kotlin "Already resumed", Swift's checked continuation traps (6.4 rejects it at compile time), Loom "Continuation terminated", WasmFX traps. Effect.callback and Effection drop it silentlyDefect at the caller (T2). Root waits keep first-wins and report the loss (rule 1)
Abandoned continuationsNo system cleans them up from the GC. Deterministic cleanup exists only where a handler or scope owns the continuation: Koka's final yield, Effection's return(), OCaml's discontinue, WasmFX's resume_throwOwner frames, Scope ownership, and the capturing fiber's exit; development-only leak reports (rules 3, 4)
Cancellation versus resumptionOne take-once cell. Only Kotlin hands the losing value to a release hook (onCancellation)Kernel suspend reports the loss and releases deferred values (T5b, K10b); handoff releases late values after producer quiescence (H2-H4) (rule 2)
finally in generated codeEffection loses a halt when a delegated finally performs an effect; Effect 4 never calls return()Finalizers compile to Effect frames, never JavaScript finally (rule 5)
Dynamic state across resumeDDB in the delimited-control literature, Racket, and Koka; snapshot in TC39 AsyncContext (at each await, and a generator's creation-time context), Effect 4, Kotlin, Swift, and tokioSnapshot (Chapter 22). It differs from the research recommendation (rule 6); the reasons and the cost are in Chapter 22
Tail-resumptive operationsRun in place: 10× on Koka's counter benchmark, under the handler's evidenceRun in place under the caller's Context, as Effect services do; call-time lookups must be in the contract (Chapter 4). Differs from rule 7 on whose Context
Handler identityGenerative tags (WasmFX), labels (Koka), capabilities (Effekt)Handler instances, found through Context evidence (rule 8)
Multi-shotExcluded by every fiber or thread runtime; Kyo stays single-shot so it never re-enters a released bracketExcluded (rule 9)
Foreign framesEvery system refuses to capture through them (OCaml Unhandled, js_of_ocaml, JSPI SuspendError, Racket barriers)Rejected at compile time (EK4301, EK4202); unhandled at run time (rule 10)
Which code gets transformedSelective: Koka cut CPS-translated functions by over 80%; Stopify measured generators at about 2× slower than exception-based captureFrames only where capture or parking is reachable (Chapter 15; rule 11)
Cross-fiber resumeKotlin and Swift always scheduleBounded synchronous wake-ups (Chapter 24; rule 12)
Resumable handlers on Effect-TS or ZIONone found by searches of npm, GitHub code (fiber._stack, "~effect/Effect/successCont"), and Effect issues and discussions on 2026-10-15. Effect's maintainer: generators "are only valid to express one-shot effect types"No other implementation is known; this kernel is one-shot by design. The claim fails if a search turns one up

TC39 proposals and Effeck

Stages as fetched on 2026-10-16 (docs/research/tc39-syntax.md).

ProposalStageIn Effeck
Explicit Resource Management (using)4 since 2026-05-19, for ES2027Adopted: using with Effect's guarantees (Chapter 6)
Pipeline operator (Hack |>)2 since 2021-08; topic token undecided; "somewhat stuck" (January 2026)In .effeck files with the topic %; a topic that stands for a program passes the left side by name (Chapter 5, D154, D155; supersedes D8)
AsyncContext2Prior art for the snapshot rule (Chapter 22). Effeck code keeps request-scoped values in Effect's Context
Partial application (f~(x, ?))1Not adopted. A function with no arguments is the program spelling (D7)
Pattern matching (match)1Not adopted. switch and catch narrowing, and Effect's Match module
Throw expressions2, unchanged since 2017Not adopted. throw is a statement and a typed failure
Extractors2Not in v0. A possible future syntax for Schema decoding
Import defer3Not used. Deferring a module's evaluation does not change EK1001: its top level still cannot run a program
Await dictionary (Promise.allKeyed)3Not needed: Effect.all takes a record
Abort Controller, Concurrency control1Not adopted. Effect's interruption and concurrency options
F#-style pipes; Function.pipe and flowFailed Stage 2; withdrawnNot applicable