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

20. The machine

Effeck
function checkout(cartId: string) {
  using hold = Inventory.reserve(cartId)
  try {
    return Payments.charge(cartId)
  } catch (e) {
    return refund(cartId, e)
  }
}
fiber._stack while Payments.charge runs, bottom to top:

  … main │ Ret(checkout) │ Final(close scope: release hold) │ Catch(PaymentError → refund) │ Ret(charge)
           a call          `using`                            `try`/`catch`                  a call

Every construct in the function is a frame on the fiber's stack. The machine defines what every Effeck program means; syntax, types, and the compiler exist to produce and check its states. It is not a model of Effect's runtime that we hope matches: it is Effect's runtime plus a few frame kinds and four operations, and the kernel prototype runs it.

In the machine's terms, Effect supplies two things. It supplies the representation: frames on _stack and the Context. It also supplies the root handler: the scheduler at the bottom of every fiber, which handles the runtime operations (wait, yield, fork, interrupt) by parking and resuming whole fiber stacks. Every other handler is a prompt above the root. That is the precise sense in which the language is the effect system: every feature is a rule over frames, prompts, and one-shot continuations, and Effect's run loop is one handler in that semantics rather than a library the language calls.

Objects

TermDefinitionRepresentation on Effect 4
ComputationOne run of code. It returns a value, fails with an expected error, dies with a defect, is interrupted, or performs an operation. Every function body is a computation.Primitives evaluated by a fiber
ProgramA cold, reusable description of a computation. Constructing it runs nothing. Each run creates a new execution with fresh frames.An Effect value; in source, a function with no arguments (Chapter 13)
FiberOne execution: a frame stack, a Context, an interruptibility flag, a pending interruption, and child fibers.FiberImpl, internal/effect.ts:487-743 [Effect]
FrameA piece of the rest of the computation: an activation waiting for a callee, or a boundary that acts when control leaves it.An object on fiber._stack with success, failure, and ensure hooks, internal/core.ts:31-61 [Effect]
OperationA request a computation makes of its environment: a service method, a control operation, or a runtime request such as "wait for this callback".A Context key
Tail-resumptive operationOne whose handler always resumes exactly once, as its last step. Every service method is one.Runs in place; nothing is captured (Chapter 4)
EvidenceWhat the Context holds for an operation: an in-place implementation, or a reference to a handler's prompt.A Context entry [Kernel]
PromptA frame that delimits capture for one handler and records the Context and interruptibility to restore when control leaves it.Kernel PromptFrame [Kernel]
Delimited continuationThe frames between a perform site and the matching prompt, plus the Context and interruptibility at the perform site. It is live, one-shot, and owned.Kernel Continuation: a spliced _stack segment [Kernel]
SuspendedA continuation whose ownership was transferred to a Scope region: resumable once, abandoned when its region closes or its capturing fiber exits. Internal to the standard library in v0.Kernel Suspended [Kernel]
Execution boundaryA frame or fiber edge that limits capture, restores state, or changes ownership (Chapter 23).

State and frames

Fiber = ⟨ K, Γ, ι, π, C ⟩                                    Effect 4 field
  K  frames, bottom → top                                   fiber._stack
  Γ  Context: services, references, current Scope,          fiber.context
     tracing span, operation evidence
  ι  interruptible                                          fiber.interruptible
  π  pending interruption cause                             fiber._interruptedCause
  C  child fibers                                           fiber._children

Frame                                                        Effect 4 frame
  Ret(f, pc, locals)  activation of f, waiting at pc          Iterator (generator) or OnSuccess
  Restore(Γ′)         on exit: Γ := Γ′                        OnExit pushed by updateContext
  Mask(ι′)            on exit: ι := ι′                        setInterruptible
  Final(fin)          on exit: run fin(exit) with ι = false   OnExit; Scope close
  Catch(h)            on a cause made only of Fail reasons:   catchCause frame (runtime catchExpected)
                      run h(first failure)
  Prompt(H, Γₒ, ιₒ)   delimiter for handler H;                kernel PromptFrame
                      on exit: Γ, ι := Γₒ, ιₒ
  Owner(k)            on exit: abandon k if it is still       kernel OwnerFrame
                      suspended and still owned here
  Clause(k)           on a returned tail-resume request:      kernel ClauseFrame
                      resume k
  Collector(k)        bottom of an abandoned segment:         kernel AbandonCollector
                      restores the abandoner's state
  FiberExit           bottom of a fiber that transferred:     kernel FiberExitFrame
                      abandons its still-suspended transfers

Every frame kind down to Catch already exists in Effect 4 [Effect]; Catch is Effect's catchCause frame with the handler the runtime's catchExpected gives it. The kernel adds the last five kinds and four operations: capture, resume, abandon, and transfer [Kernel].

Transitions

These rules are the specification. Within one rule, changes to K, Γ, ι, π, and continuation state happen atomically with respect to scheduling and interruption.

RuleWhenThen
callf(v) that may suspendPush Ret(caller, pc); run the body of f.
returnreturn v meets Ret(g, pc)Pop it; continue g at pc with v.
meets Restore(Γ′) or Mask(ι′)Pop it; restore Γ or ι; keep returning v.
meets Final(fin)Pop it; run fin(Success v) with ι = false; keep returning v, or fail with fin's failure.
meets Prompt(H, Γₒ, ιₒ)Pop it; Γ, ι := Γₒ, ιₒ; continue with H.onReturn(v).
meets Clause(k) with v a tail-resume request for kPop it; perform resume(k, x) or resumeWith(k, p) as the request says. Any other v passes through.
meets Owner(k)Pop it; if k is suspended and owned here, abandon k first; keep returning v.
perform, in placeop(v) and Γ[op] is an in-place implementation implCall impl(v) here, under the current Γ. Nothing is captured.
perform, captureop(v), Γ[op] refers to H, and K = K₀ · Prompt(H, Γₒ, ιₒ) · K₁k := ⟨K₁, Γ, ι, H, suspended⟩. An abandonment abort in π that belongs to K₁ moves into k. K := K₀ · Owner(k) · Clause(k); Γ, ι := Γₒ, ιₒ; run the clause H.op(v, k).
perform, unhandledΓ[op] absent; H's prompt is on another fiber; or H's prompt has exitedDefect UnhandledOperation, HandlerNotOnThisFiber, or HandlerPromptExited. The checker rejects these programs first.
resumeresume(k, x), k suspended, same fiberk := resumed. If Owner(k) is on top, or Clause(k) over Owner(k), pop them (a resume in tail position). Push Prompt(H, Γ, ι) then K₁; Γ, ι := k.Γ, k.ι; reinstate k's moved abort in π; deliver x at the perform site. resume returns when the handled computation reaches the reinstalled prompt.
resumeWithresumeWith(k, p)As resume, but run the program p at the perform site. p may fail only with the operation's declared E, and its R is never (Chapter 9).
abandonabandon(k) or abandonNow(k), k suspended, same fiberk := abandoned; push Collector(k), then K₁; Γ, ι := k.Γ, k.ι; add a fresh abort to π (never replacing a real pending interruption); unwind K₁. The abort re-raises whenever K₁ re-enters an interruptible region. Final frames run; Catch frames do not. The collector removes only its own abort from π. abandonNow returns the unwinding's Exit; abandon fails with every cleanup reason plus every real pending interruption, and never reports its own abort (K12a-K12e).
transfertransfer(k) in the clause; standard library only (D16)Owner of k := a fresh token registered with the Scope in Γ (the region around the handler); the capturing fiber's FiberExit frame also holds k. Returns Suspended(k). Closing that Scope abandons k only if the same token still owns it; the fiber's exit abandons it if it is still suspended then (K13a-K13d). A Scope with parallel finalizers is rejected (K13e).
throwthrow eFail with Fail(e).
failfailure meets RetDiscard the frame.
meets Catch(h), every reason of the cause is a Fail, not interruptingContinue with h(e), where e is the first failure. A cause with a Die or an Interrupt reason passes through intact (K14a-K14c).
meets Final(fin)Run fin(Failure c) with ι = false; keep failing with c plus fin's failures.
meets Restore, Mask, Prompt, ClauseRestore state; keep failing.
meets Owner(k)Abandon k first if it is owned here and suspended; keep failing with c plus any cleanup failure.
interruptrequest r from another fiberπ := π ⊕ r. If ι and parked: fail with π now. If ι and running: fail with π at the next step. If not ι: fail when a Mask frame restores ι = true. While π ≠ ∅ and ι, unwinding skips Catch frames.
wait (root)suspend(register)Park the fiber with K intact. register receives a one-shot resumer.
wakeresumer.resume(v)Run the fiber with v: synchronously while the wake depth is under budget, otherwise on the scheduler (Chapter 24).
forkfork(p)New fiber ⟨[], Γ, true, ∅, ∅⟩ runs p. Its owner is the innermost block region (fork), the caller's Scope (Effect.forkScoped), the parent fiber (Effect.forkChild), or nothing (Effect.forkDetach). No prompt crosses.

The interruption rows describe Effect 4 as it is (internal/effect.ts:558-579, internal/core.ts:600-631) [Effect]. The prompt, owner, clause, collector, fiber-exit, capture, resume, abandon, and transfer rows are the kernel (packages/runtime/src/kernel/kernel.ts) [Kernel].

Tail position is structural. A resume is in tail position when Owner(k) (with Clause(k) above it) is the top of the stack at the moment it runs. In a generator clause, return yield* resume(k, v) runs with the clause's own frame above them, so it is not a tail resume, and a producer looping that way keeps every element's clause frames until the loop ends (review probe R10: 3,002 frames at 1,000 elements). The compiler therefore emits return tailResume(k, v): the clause's generator finishes first, and Clause(k) performs the resume (K11a). Inside a try, catch, finally, or using region of the clause, the compiler emits yield* resume(…) instead, because the region's cleanup must run after the resumed computation (K11d).

Choices the machine makes

ChoiceAlternativesDecision and reason
Deep or shallow handlersShallow: resume without reinstalling the promptDeep [Rule]. A loop that performs an operation many times (T9, T17) needs the handler every time. A tail resume drops its inert owner, so iterating a producer 100,000 times keeps the stack at 3 frames (K11a). Shallow handling is a different operation and can be added later.
Handler selectionDynamic: nearest prompt on this fiber. Lexical: handlers passed as capabilities (Effekt)Dynamic [Rule]. Effect's Context is already dynamically scoped, and DI must stay Effect-compatible. Lexical capabilities would need a second, value-passing DI system.
Where a clause runsInside or outside its promptOutside, in Γₒ [Kernel]. A clause that performs its own operation reaches the next handler out instead of recursing.
Context across resumeSnapshot, or delimited dynamic bindingSnapshot [Rule], Chapter 22.
Where a continuation can be consumedAny fiber, or only the capturing fiberCapturing fiber only, for resume, transfer, and abandon [Rule]. Effect frames are bound to the fiber that pushed them (T14b), and children, identity, and interruption authority stay coherent (O04, O06). The capturing fiber's exit releases what it transferred (K13a).
Double resumeIgnore, or defectDefect at the caller for handler continuations [Rule]. Root waits keep Effect's first-wins behavior but report the loss: resume returns false (Chapter 21).
Which causes catch recoversFirst failure of any cause (Effect.catch), or only causes made of expected failuresOnly expected failures [Rule] (D13). A recovered mixed cause would drop a defect (review probe R1).