In this way of building software, the business logic does not carry out its dealings with the outside world: it states each one as a piece of data, and separate components decide what that means in a given installation. So the logic is written once, and where it runs, on which platform and under which controls is decided in one place, where every such dealing can also be seen, limited, approved and recorded. The price is an unfamiliar discipline, young libraries, and design work that comes before the first benefit.
For those who decide about systems and budgets. One line of code. The page describes the public repositories of okay-effects as of 11 November 2026. Where a benefit has not been measured, it is given as an argument, with what the argument depends on.
A business program does two kinds of work. It computes: it decides whether an order may be paid. And it deals with the world: it charges the card, writes to a database, calls another service, sends a letter, asks a language model. Usually the two are written together, so the code that decides also knows which database and which payment provider it talks to. Here they are kept apart. Three words are enough for the rest of the page.
| Word | What it means |
|---|---|
| Operation | One request to the world, written down as data: "charge this user 20", "reserve this item". The program states it and waits for the answer. It does not perform it. |
| Effect | A named set of operations that belong together: payments, the stock, mail. The effects a part of the system may use are its declared interface. Where the language can express it, the compiler refuses a part that uses an effect it did not declare. |
| Handler | The component that receives an operation and decides what it means here: do it in the same process, send it to another service, record it, ask a person to approve it, refuse it, or answer it from a recording in a test. |
The system is put together in one place: the program, and around it the handlers chosen for this installation. Changing a handler changes where and how the work is done. The program is not edited.
The expensive changes in a system's life are often moves: a monolith into services, a data centre into a cloud, one cloud into another, a blocking call into a queue. They cost so much because the code that matters most has the old arrangement written into it, and is rewritten while it keeps running.
| The usual way | With effects |
|---|---|
| Business code calls the database, the provider's client and the other module directly. | Business code states operations. Handlers make the calls. |
| Moving a module into a service means editing every place that calls it. | The handler that called the module is replaced by one that sends the operation over the network. |
| Retries and timeouts are written into the business code, call by call. | They are handlers around the remote handler. |
From a monolith to services shows this on a shop's checkout in
three steps: local, remote, asynchronous. The last arrangement is this line, and the function
checkout in it is the one written at the first step:
runAsync(handle(remoteAsync(http))(checkout("ann", "book", 20)))
The argument, which is not a measurement: the logic is not rewritten, so the cost of a move is the cost of writing and testing the new handlers. It holds as far as the logic reaches the world only through operations.
A manager meets this as a recurring project: prove who did what, under which rule, with what result. Usually it rests on discipline. Developers are told to log every sensitive action and to call the permission check, and reviewers look for the places where they forgot.
With effects, an operation has one way to the world, through its handler. A control placed there sees every operation of that kind, including those written next year by someone who never heard of the rule. That point serves four kinds of obligation: keeping records, tracing a decision to its rule, limiting access, and reproducing what happened.
| The usual way | With effects |
|---|---|
| The audit record is written by the business code, where the developer remembered. | A handler around the operation writes it: the intent before the operation runs, the result or the failure after. |
| What a module is able to reach is found by reading its code. | The effects a module may use are declared, and the compiler holds it to them. |
| A limit or an approval rule is a check repeated in each function. | The rule stands at the handler. It passes an operation, changes it, refuses it, or holds it for a person. |
An auditor can be shown the declared effects of each component, the record of operations with their results, the rule that decided each one, and a report from the build. Audit by construction sets this against the provisions of specific regulations, with the code. This page gives no legal advice.
In the repositories: in okay-core (Scala) a program that handles an effect marked sensitive outside its audit does not compile, and okay-direct has the same rule for Java 25. okay-audit reads the compiled classes at every build, checks that business modules reach no network, files, SQL or clock on their own, and lists the modules that do.
A test of a business flow usually needs a database and a provider's sandbox, or mocks that drift from the real thing. Here a test is the same program under other handlers: one answers "out of stock", and the test checks that no charge and no letter followed. Failures are scripted the same way. okay-serverless has an in-memory platform with faults (a crash, a conflicting writer, an eviction) and one set of scenarios that every platform passes.
The cost: test handlers are code to maintain, and they prove the logic, not the real provider. Tests against the real systems are still needed, for the handlers.
If every operation and its answer were recorded, the program can be run again from the record, with no database and no network. A changed version stops at the first operation that differs. This holds only if everything that varies is an operation, the clock and random numbers too. The bank example shows the mechanism.
A component that can only state operations can do only what its handlers allow. That matters most for AI agents, whose next action nobody wrote in advance. okay-guard stands between an agent and its tools. A policy decides each call: it passes, it is blocked, or it waits for a person. Each is written to a journal with the rule that decided it, each record chained to the one before, so a later change shows. A changed policy can be tried against every recorded call first. okay-remote applies this to coding agents on a developer's machine: if okay-remote cannot be reached, the action is not done.
The limit: a guard sees the calls routed through it. An agent with another way to the network or the disk is not held by it, and okay-remote adds a sandbox as a second layer for that reason.
An application written against a cloud's own services is tied to that cloud: leaving means rewriting it. If the cloud's services are effects (a key-value store, a queue, a scheduled wake-up), each cloud is a set of handlers. okay-serverless runs one program on Cloudflare Workers, on AWS Lambda, on a plain JVM server and in memory for tests. okay-chat-edge is an application that does it: its home is Cloudflare, and the same code runs as one JVM process with a local database file.
The same model exists in several languages, so teams in Java, Scala and Rust can share one idea. The reference model is one small core written in all three. The three are tested to give the same results on a thousand generated programs, and they share a protocol by which a program in one language is answered by handlers in another; each of the nine pairings is run. It is a model, not a product: its limits are in the table below. More in which is which for Scala and on the languages page.
In a large system the hard questions are about boundaries: who owns what, what one team may assume about another's part, what a change will break. The usual answers are documents and meetings, and both go stale. An effect is a contract that a machine checks: the operations one part asks of another, and the type of each answer.
The same boundaries are where a service can later be cut out. Across languages, okay-go has a shop written in Go whose stock, payments and mail are answered by Scala handlers; its README calls it an experiment.
The costs. Choosing the operations is design work, and a poor choice is felt by every team that uses it. Shared effects need an owner and a rule for changing them. And the compiler checks the contract only where the language can express it: in Go, that README says, a program's effects cannot be in its type.
Nobody should rewrite a working system for an architecture. The approach can be introduced at one boundary of an existing system and stopped there. The business page gives the author's three steps. For a manager they read so:
| Step | What is done | What it costs |
|---|---|---|
| 1. One boundary | Take one integration that hurts, or one audit requirement: payments, customer data, calls to a model. Its calls become operations, and a handler wraps the code that exists today. | A few engineers learn the approach and rewrite the places that call this one integration. |
| 2. One control | Add the record, or a limit, at that handler. Add a test handler and move the flow's tests onto it. | The handlers, and somewhere to keep the record. |
| 3. Decide | Compare what the pilot showed with what it cost. Widen module by module, or stop. | A review. |
Stop if the team cannot explain the code to its neighbours, if the measurements show no gain, or if the only person who understands it is leaving. Stopping after step 2 loses little: one integration sits behind a clear interface with a record on it.
An existing Scala codebase does not have to choose: okay-simple works together with cats-effect, ZIO, fs2, kyo and monix, so effects can enter one part at a time.
Skills. Writing business logic against existing effects is the easier part: it reads as a sequence of steps. Designing effects and writing handlers needs someone at home in typed functional programming. A team needs one such person for as long as it owns the code, and a second, so that the first can leave.
Time to productivity, and hiring. This page has no measured figure and will not invent one. Make the time to a first correct change a result of the pilot. Engineers who have used algebraic effects are fewer than those who know the mainstream frameworks, so plan to train. The public material is a six-step start for Scala and a guide in each repository.
What exists. Released libraries for Scala, Java 25 and Kotlin, and on them libraries for HTTP, data, user interfaces and the cloud; the build-time audit; a module of okay-direct that reports every operation to OpenTelemetry, the common tracing standard; published benchmarks with their method.
What does not. The libraries are at versions 0.1.x and are the work of one author. This site names no production user other than the applications it shows. There is no commercial support contract and no certification. A buyer should weigh that as a dependence on one person, softened by the open licence and the small size of the cores.
Performance. Stating an operation as data and finding its handler takes time: in the reference model's own runs about 6 to 14 nanoseconds for a simple operation, and about 90 to 190 where a handler runs the rest of the program more than once (its README, which calls the table "a record of runs, not a claim"). Next to a database write or a network call this is not visible. In a loop that only computes it is. Comparisons with other libraries, losses included, are under Measured on the front page.
From each repository's own README or documents, as of 11 November 2026. The links go to the place that says it.
| Repository | What it says |
|---|---|
| okay-effects, the reference | Nothing is published as a library. The Rust core runs on one thread. The link between processes is a child process's standard input and output. Agreement with the wider family is checked against a number recorded in the tests; the machine that number comes from is in another repository. |
| okay-core | No release. The audit is in the kernel's interface and not yet in the simpler one above it. The trail holds an operation's name and printed form: "enough for a trail, not a schema". |
| okay-direct, Java 25 | On two of its three implementations a held program can be continued once, not many times. Handlers of different implementations do not nest in one program. |
| okay-serverless | AWS is partly covered. Cloudflare's model service and vector index are written to their interfaces and not run. The managed Postgres gateways of both clouds are tested against a local Postgres. |
| okay-go | "An experiment". A program's effects are not in its type. |