okay-effects

The Scala implementations: which is which

For a new program in Scala 3, use okay-scala. If the code must also compile on Scala 2.13, or it uses the libraries written over the common interface (okay-http, okay-mcp, okay-faces, okay-store), use okay-simple; its adapter runs the same code on okay-scala's machine. The other repositories are the platform and its kernel, a direct-style library for Java with Scala front ends, a reference model in three languages, and the experiments that came before them.

The state of the master branches on 11 November 2026. Every statement on this page was checked in the repositories; where a file says it, the file is named. Outside Scala: effects in other languages.

The implementations

Eleven repositories hold an implementation of effects in Scala. Seven are public. Three of them are released: okay-scala, okay-simple and okay-direct.

RepositoryWhat it isHow a program runsScala, platformsStatus
okay-scala A library for Scala 3 alone. Package okay.scala3. No dependency. One CEK machine: a program is data (Prog), a step is one case of a @tailrec loop over frames and handlers' levels. Built on Scala 3.3 LTS, tested on 3.9 too. JVM, Scala.js, Scala Native. Released, 0.1.1. Last commit 11 Nov 2026.
okay-simple One interface, Effectful, for Scala 3 and Scala 2.13, and implementations under it. Package okay.effects, artifacts okay-effects-*. Three implementations: a machine with an explicit stack of frames and levels; the Free monad, each handler its interpreter; tagless, a program made on whichever implementation runs it. Adapters to other machines: see the bridges. Built on Scala 3.3 LTS and 2.13. JVM, Scala.js, Scala Native. Released, 0.1.1. Last commit 4 Nov 2026.
okay-direct Direct style for Java 25, with front ends for Scala 3 (okay.direct.scala3) and Scala 2.13 (okay.direct.scala2). No monad: perform is a method call, a handler gives its body a capability. Three implementations: Loom's continuations, virtual threads, Espresso's continuations. Scala 3.9 and 2.13 over the Java API. JVM. Released, 0.1.0. Last commit 23 Oct 2026.
okay-effects The reference model: one small kernel written three times, in Scala 3, Java 25 and Rust. The Scala one is the package okay.effects.scala. A program is data; one loop over a stack of levels, one level a handler. The model is the repository's SPEC.md. Scala 3.3, built by scala-cli. JVM. Public. Nothing is published as an artifact. Last commit 11 Nov 2026.
okay-core The kernel of the okay platform: effects and delimited control. Packages okay.one (the kernel), okay.core (the facade), okay (the words). One monad, Cont[R, I, O, A], on a CEK machine; a handler's k is data. Scala 3.9; modules for Scala 2.13. JVM, Scala.js, Scala Native. Public. No release. Last commit 18 Oct 2026.
okay-kernel A protocol between implementations, its API in plain Java; a reference machine in Scala, Steps; and the family's TCK. A program steps to Done, Yield, Failed or Cancelled. The reference machine is a stepper over an explicit stack of frames, "exact rather than fast". Scala 3.9 and 2.13. JVM, Scala.js, Scala Native. Public. No release. Last commit 18 Oct 2026.
okay The platform: over a hundred modules on effects. On okay-core since 14 Oct 2026: a program A ! R is okay-core's own (docs/contract.md). Scala 3.9; a build for Scala 2.13. JVM, Scala.js, Scala Native. Public. Last commit 23 Oct 2026.
okay-free, okay-eff,
okay-cont, okay-one
The experiments of October 2026 that led to okay-core's one monad. Effects on shift0 and $ in one monad. See how they came about. Scala 3.9. History. Not published.

One name means three things. okay-effects is the organization. It is also the name of okay-simple's artifacts and package (okay-effects, okay-effects-machine, okay.effects). And it is the repository of the reference model, whose Scala package is okay.effects.scala. On this page "okay-effects" is the repository.

A program in each

Copied from each repository's own documentation.

okay-scala (README.md)

import okay.scala3.*       // the rows, programs, handlers, the machine
import okay.scala3.std.*   // the standard effects: State, Reader, Writer, Fail, Async, ...

def greet[R[_]: Has[Reader % String] : Has[State % Int] : Has[Writer % String]]: Int ! R =
  for
    name <- Reader.ask
    n    <- State.get
    _    <- State.put(n + 1)
    _    <- Writer.tell(s"hello, $name")
  yield n

type Greeting[X] = (Reader % String + State % Int + Writer % String)[X]

run(State.run(12)(Reader.run("Ann")(Writer.run(greet[Greeting]))))   // (13, (List(hello, Ann), 12))

okay-simple, Scala 3 (docs/getting-started.md)

import okay.effects.*
import okay.effects.std.*

/** greets, in any row that has a `Reader` of the name and a `State` of the count */
def greet[R[_]: {Has[Reader % String], Has[State % Int]}]: String ! R =
  for
    name <- ask[String, R]
    n    <- get[Int, R]
    _    <- put[Int, R](n + 1)
  yield s"hello, $name (#${n + 1})"

type App = Reader % String + State % Int

val handled: (Int, String) ! Pure = state(0)(reader("Ann")(greet[App]))
val result: (Int, String) = run(handled)

In Scala 2.13 the same program takes its evidence as implicits, and a row is written with names: ((Reader % String) + (State % Int))#L.

okay-effects, the Scala kernel (README.md)

import okay.effects.scala.*, std.*

val p: Int ! State % Int + Choose =
  for x <- Choose.among(1, 2); s <- State.get[Int]; _ <- State.put(s + x) yield s + x

run(Choose.all(State.run(0)(p)))   // List((1, 1), (2, 2))
run(State.run(0)(Choose.all(p)))   // (3, List(1, 3))

okay-direct, the Scala 3 front end (docs/scala.md)

import okay.direct.{Effect, Effects}
import okay.direct.scala3.*

def program(heads: Boolean)(using Effect[Reader[Int]], Effect[State[Int]], Effect[Fail[String]]): Int =
  val base = Reader.ask[Int]
  State.put(State.get[Int] + 1)
  val result = if heads then base else -base
  if result < 0 then Fail.raise(s"negative: $result") else result

given Effects = LoomEffects()
Reader.run(42)(State.run(0)(Fail.attempt(program(true))))     // (1, Right(42))

okay-core (README.md)

import okay.*

def greet: Int ! Reader % String + Writer % String =                  // a program as its type says it
  ask[String].flatMap(name => tell(s"hello, $name").map(_ => name.length))

run(reader("Ann")(writer[String, Int, Reader % String +: Pure](greet)))   // (List("hello, Ann"), 3)

What each one does

Control and audit of effectsA continuation resumed many times Constant stackChecked by
okay-scala intercept, observe, around Yes Yes: the run is one loop; defer and loop for tail calls Its own tests (157 on the JVM, 155 on Scala.js and Scala Native); okay-simple's interface suite, through the adapter
okay-simple intercept, observe Yes Yes Its interface suite (okay-effects-tck), on every implementation
okay-direct Interceptor, Audited, Observer On Espresso only; on Loom and on threads a continuation is resumed once For handlers that answer in place; a general handler nests the stack at each resumption Its own TCK (okay-direct-tck, JUnit)
okay-effects intercept, observe, around (Scala), an audit trail; an effect marked sensitive is handled only under its audit (in Scala, Java and Rust) Yes (in Scala, Java and Rust) Yes Its properties, the same in three languages (Scala 118 tests; Java 64 properties, 27 wire tests, 8 programs refused by javac; Rust 125 tests); the family's TCK digest; the wire's transcripts
okay-core audit, observe, intercept; an effect marked sensitive is handled only under its audit Yes Yes Its reference in CPS (TestReference); the family's TCK digest; okay-simple's interface suite
okay-kernel — Yes: the protocol's k is multi-shot — The family's TCK is here; its reference machine gives the digest

Are they compatible?

In notation, mostly. In classes, no: each library has its own, and a program of one runs on another only through an adapter. In behaviour, as far as two shared test suites reach. On the wire, one of them.

Notation

okay-simple, okay-scala and the Scala kernel of okay-effects write a program the same way. A ! R is a program of the value A over the row R. A row is a type constructor: R + E is [X] =>> Sum[R, E, X], F % S is the effect F with its parameter, Pure is the empty row. In[E, R] and Has[E] say that an effect is in a row, Split[E, R, Rest] is what a handler takes, Sub[R, S] puts a program into a bigger row. All three define these names.

They are three sets of classes. Sum, Void and the program type are defined in each library, in okay.effects, okay.scala3 and okay.effects.scala. A program of one is not a program of another, and a handler of one does not take a program of another.

The words differ. In okay-simple an operation is a function that names its row, get[Int, R], and a handler is state(0). In okay-scala and in okay-effects an operation is a word of its effect's object, State.get, and a handler is State.run(0). okay-scala names two effects Fail and Yield; okay-effects names them Throws and Emit and has the other two names as aliases.

okay-core and okay use the same three symbols, A ! E1 + E2 and F % S, over a different row: a type, E +: T ending in Pure, with a typed path to each effect.

okay-direct has no row and no monad. A program is plain code that takes a capability for each effect, using Effect[State[Int]]; a body with no handler for an effect does not compile.

The bridges

Every adapter starts from okay-simple's interface. None runs a program of okay-scala, okay-direct or okay-effects on another implementation.

FromToWhereWhat it does
okay-simpleokay-scala okay-simple, module cek: okay-effects-cek, released, Scala 3 OnCek runs okay-simple's interface on okay-scala's machine. OnCek.into[S](p) puts such a program into a row of an okay-scala program: one machine, one run. No class is shared.
okay-simpleokay-core okay-core, module effects: okay-effects-core Core: a program of the interface is okay-core's own Cont.
okay-simpleokay-kernel okay-kernel, module effects: okay-effects-kernel, Scala 3 and 2.13 OnKernel.on(effects): the interface on any implementation of the protocol.
okay-simpleokay-direct okay-simple, core/direct: okay-effects-direct, JVM; in the repository, not in the release 0.1.1 OnDirect: the interface's programs run by okay-direct's handlers.
okay-coreokay-kernel okay-core: okay.core.kernel.OnCore The protocol on the one monad.

okay-simple, okay-scala and okay-direct each have interop modules with cats-effect and ZIO; okay-simple also with fs2, monix, kyo and tofu, okay-scala with tofu.

Behaviour

Two test suites are shared between repositories. Neither covers all of them.

The family's TCK is in okay-kernel (tck/Tck.scala): a thousand random programs, their outcomes folded into one number, -8623788747993822161. These come to that number: okay-kernel's reference machine, okay-core through OnCore, okay-java (run from okay-kernel's integration tests), the machine crate of okay-rust, the machine of okay-zig, and each of the three kernels of okay-effects. okay-effects compares with the constant; the reference machine is not in that repository (its README says so). okay-simple, okay-scala and okay-direct do not run this TCK. The okay platform does not run it itself; it runs on okay-core, which does.

okay-simple's interface suite (InterfaceSuite, the artifact okay-effects-tck) runs on the machine, on Free, on tagless over both, on okay-scala's machine through OnCek, on okay-core through Core, and on okay-kernel's reference machine through OnKernel. On okay-direct it runs through OnDirect; on Loom and on threads the tests that resume a continuation twice are refused.

So okay-scala and okay-effects are not held to one test: okay-scala is not checked by the digest, and okay-effects is not checked by the interface suite.

The wire

The okay wire is the protocol of the okay platform: a program runs in one process and its operations are answered in another. Of the other repositories on this page only okay-effects speaks it, in all three languages. It implements a part: its wire/README.md lists what is left out (tables and data frames, TCP and TLS among them). Its hosts and workers are checked with each other and against transcripts; a script, wire/family/check.sh, runs its host with the far sides of okay-zig, okay-lean, okay-ts and okay's Python shim, and okay's own host with its three workers, where those repositories are at hand. okay-simple, okay-scala, okay-direct and okay-core have no implementation of the wire.

okay-scala and the reference kernel

The two closest: the same notation, a machine of frames and levels in both, the same five handler shapes. okay-scala as released in 0.1.1; okay-effects' Scala kernel as on its master.

okay-scalaokay-effects, the Scala kernel
Purpose A library to write applications on. A reference: the model in its smallest form, the same in Java 25 and Rust, with a specification (SPEC.md).
Platforms JVM, Scala.js, Scala Native. Built by sbt. JVM. Built by scala-cli.
Release 0.1.1, a Maven repository on GitHub Pages. Binary compatibility with the release is checked by MiMa. On this site, okay-serverless and okay-proof resolve it. Nothing is published; it is used from source.
Standard effects State, Reader, Writer, Fail, Abort, Choose, Yield, Control, Lifted, Fresh, Random, Store, Clock, Env, Log, Async with fibers, Sync, Scope; and Ref, Deferred, Semaphore, Queue, Stream, Retry, Coeffects. Nine: State, Reader, Writer, Throws, Choose, Fresh, Clock, Emit (streams), Async.
Interop cats-effect, ZIO, tofu. None.
Casts No asInstanceOf in the main sources. 34 matches are marked @unchecked. No asInstanceOf and no @unchecked, in the sources and in the tests.
Words Both: a word such as State.get names no row; it takes the row of the program's declared type, or of an evidence in scope. In okay-effects since 10 Nov 2026; in okay-scala since the 0.1.1 published on 11 Nov 2026.
The row a handler leaves Both: the row as one would write it. State % Int + Choose without the state is Choose.
Handler shapes Both: general, tail, abort, stateful, and control: k as a value, a Continuation, that is resumed or unwound (k.unwind).
ensuring The finalizer is a program of the row, or a thunk, run by the machine. Leaving is an operation: k.unwind runs the finalizers waiting in k. Every standard handler that drops a rest unwinds it: Fail, Abort, Choose at a choice of nothing, Yield.take at its cut, Lift at a None or a Left, Async at a cancel, a race's loser, a timeout. Scope releases by ensuring, and so does a stream's pull that is left. A k neither resumed nor unwound runs none. The finalizer is a program, run by the machine. Leaving is an operation: k.unwind runs the finalizers waiting in k. A k neither resumed nor unwound runs none.
A program of a smaller row in a bigger one Both: it runs under one more level of the machine, which renames its operations on their way out, and embeddings compose: one entered in tail position on the level of another takes no level of its own, so calls in tail position between rows keep one level.
Control and audit intercept, observe, around. No sensitive effects. The same, and k.after; an audit trail, Audit; an effect declared sensitive is handled only under its audit (audit, handleAudited), and the compiler refuses it anywhere else.
The wire No. Yes, a part of it: scala/src/wire.
Tests in common okay-simple's interface suite, through the adapter. The family's TCK digest.

How they came about

Dates are those of the commits and tags in each repository.

WhenWhat
2 Jul 2025The first commit of okay, the platform.
7 Oct 2026okay-core begins: delimited continuations over rows of effects.
10 Oct 2026okay-kernel begins: one API for every language and platform.
12–14 Oct 2026The experiments beside okay-core: okay-free, okay-eff, okay-cont, okay-one. Each tried one way of putting effects and delimited control in one monad. okay-free kept the answer types in the effects' monad. okay-eff took them out and made k data. okay-cont put the rows inside the monad of shift0 and $. okay-one made the boundary between the framework and its kernel data. They are not published; what each found is recorded in okay-core's docs/one.md.
13–14 Oct 2026The decision: okay-core is one monad on one machine, k is data. okay-one's kernel becomes okay-core's package okay.one, and the okay platform moves onto it.
17–18 Oct 2026okay-simple: one interface for Scala 3 and 2.13. Released as 0.1.0 on 18 Oct; 0.1.1 on 4 Nov.
22–23 Oct 2026okay-direct: direct style on Java 25, with the Scala front ends. Released as 0.1.0.
23 Oct 2026okay-scala: Scala 3 alone, one CEK machine. Released as 0.1.0 the same day.
10 Nov 2026okay-effects, the repository: the kernel in Scala 3, Java 25 and Rust. Words that take their row from the declared type, and the row a handler leaves, are committed there, and in okay-scala later the same day.
11 Nov 2026okay-scala 0.1.1 is published with them.

Two lines are developed for Scala today: okay-scala, for applications in Scala 3, and okay-simple, the common interface for Scala 2.13 and 3. okay-effects is where the model is kept small and checked in three languages. okay-core is the kernel under the okay platform.