okay-effects

Every effect a program performs: seen, allowed, recorded.

Okay-effects is a family of algebraic-effect systems for Scala, Java, Go, TypeScript, Rust, Python. A program's type says what it may do. A handler decides how it is done. An interceptor puts each of those actions through a point of control. A sensitive effect, such as a payment, cannot run anywhere but under its audit. The compiler checks this. It is not a convention.

A program performs operations as typed data; each passes an audit that allows, changes or refuses it and writes it to a trail; handlers carry it out against the database, the payment API and an AI model; the trail can replay the program.

Business apps built on Okay

Coinpath: follow a payment in a blockchain

A whole application built on these libraries, live: paste an address and see where the money went, step by step, each step with its reason. It runs in your browser, with no server of ours and no account.

Open Coinpath

Coinpath's answer for the Bybit hack, 2025: where the money ended, as a table

What it does

  • Follows the transfers out of an address, hop by hop, from the moment the money arrived.
  • Says why it took each step, and where and why the trail ended: an exchange, a bridge, a limit reached.
  • Names exchanges from their own proof-of-reserves lists; every name links to the file it came from.
  • Ethereum, Bitcoin, Tron (USDT too), Base, Arbitrum, Optimism, Polygon and World Chain; Solana is coming.
  • Try it on public incidents: the Bybit hack (2025), the Ronin bridge (2022), Upbit (2019).

Every step explained

Tools that draw a graph leave the reading to you. Coinpath writes down the rule behind each hop, and the reason it stopped.

Every name checkable

An exchange is named only from that exchange's own published list of its wallets, with the date and a link: nothing to take on trust.

The same answer twice

A trace is pinned at a block, and a recorded trace replays to the same report with no network at all, so a result can be handed on and checked.

How it is built

Scala 3 compiled to JavaScript by Scala.js, on okay-scala and okay-ui. The data model is effects: Chain (the transfers of an address), Names (who owns it), Http, Clock, Ui (a screen). Every implementation is a handler: Blockscout turns Chain into Http, the labels' shards answer Names, the browser's fetch answers Http, a recording answers Chain and Names in the tests and the examples. The page is one line that names its handlers. The trace itself never changes between the browser, the tests and a replay; only the handlers do.

okay-guard: see and control what an AI agent does

An AI agent works through MCP servers: payments, mail, files, code. okay-guard stands between them. You see every call, your policy decides what passes, what is blocked and what waits for you, and you decide on your phone where the call stands. The studio runs in your browser, with no account; when the policy works, run the same guard on your own machine.

Open okay-guard

okay-guard's demo: a refund over 200 € waits for a person's decision, the rules in force beside it

What it does

  • Connects your own agent, such as Claude Code, Claude.ai or Cursor, by one MCP address.
  • Shows each call as it happens: the tool, its arguments, what the policy decided, what came back.
  • Stops a call the policy asks about: Approve, Refuse, or change its arguments first, on any device.
  • Rules in words, made from a real call with one tap; or a policy in a language with state and facts, each decision with its certificate.
  • Tests that replay what the agent did against a changed policy, with no model and no key; headless for CI.

Your agent, your servers

Nothing to install to start: add any remote MCP server in the studio and it is called straight from your browser.

A policy you can check

Every decision is recorded with the rule behind it. Cases and tests check the policy before it meets a real call.

From the browser to your machine

Export the agent and its policy, import them into the local guard (Node or Java), and the same policy decides there.

How it is built

Scala 3 and Scala.js, on okay-simple, okay-ui and okay's MCP. The guard is a program over its own effects: the wire, the journal, the policy, a person, the clock. The browser, a Cloudflare Worker that passes messages, Node and the JVM are only their handlers. A call waiting for you is the program's continuation, kept until you answer.

okay-reliability: what breaks, how often, and why it broke

Paste a system — a docker compose file, Kubernetes manifests, or a few lines of its components — and see what each component's loss takes down, how available every entry point is, and which change saves the most downtime. In an incident, give it the logs, the chat and the metrics, and it finds the causes, their probabilities and the check that settles them. Everything is computed in your browser: nothing you paste leaves it.

Open okay-reliability Source

okay-reliability's architecture of a shop: the database its single point of failure, in red; the cache and the external payments dashed

What it does

  • Architecture: each component's blast radius, single points of failure, cycles, unguarded dependencies.
  • Numbers: availability and latency of every entry point, downtime a month, incidents a year, SLOs and error budgets, where the downtime comes from.
  • What if: choose faults and watch them spread, each effect with its chain, and the remedies that fix them.
  • Incidents: logs in any of the usual formats, a chat, Prometheus metrics; the smallest sets of faults that explain everything, ranked by probability, and the next check.
  • Training: an incident made with its logs and chat; find the cause yourself, then compare.

One model, one simulator

The architecture, the numbers, the diagnosis and the remedies all ask the same simulator how a failure spreads, so they cannot disagree.

Checked, not guessed

The availability is computed by minimal cut sets and checked by simulating thousands of years; every remedy is checked by spreading the faults left after it.

A tool for agents

The same tools as an MCP server: a local model with them named the exact cause of 7 incidents in 8, alone 2 in 8.

How it is built

Scala 3 on the JVM and Scala.js, on okay-scala, okay-ui, okay-datalog and okay-simple's YAML. The system is a value and its failures spread by one function; the screens are an okay-ui program over it; the tools a model calls are a program over the effects Desk, Files, Http and Clock, whose handlers are the machine's on the command line and in memory in the tests.

Okay! Chat: a marketplace you talk to

Say in ordinary words what you offer or what you need, and the chat finds the other side, asks them for you and passes on the contacts once they agree. Ask it for vacancies by a keyword and a city, and it reads employers' own public job boards. In the browser and in Telegram, in four languages, on Cloudflare's free plan.

Open Okay! Chat In Telegram Source

Okay! Chat answering "jobs java, Kraków": Java engineers in Kraków, each with the employer's own page

What it does

  • "I repair washing machines, Wrocław" is kept as an offer; "need a plumber, the tap leaks" is answered with the closest offers, matched by meaning.
  • "Ask 1 2" writes to the people you chose; they answer "take 3" or "decline 3", and only then are the contacts passed on.
  • A need with nobody yet waits: when a matching offer comes, you are told.
  • "Jobs scala, Wrocław", "remote", "anywhere": vacancies from Workable's search and from 206 employers' own boards, each with the employer's page; "more" for the next, "remember" to be told of new ones.
  • Russian, Ukrainian, Polish and English: it answers in the language you write in.

No model on the way

A message is routed by rules and a lookup table in microseconds. A language model answers only what the rules do not understand, under a daily budget, and may promise nothing.

Agreed, then shared

A deal is a small state machine of its own: nobody's contact is given until the other side says yes, and two answers at once cannot both win.

Free to run

Workers, Durable Objects, D1, KV and Workers AI, each within its free allowance; the page is static and costs no requests, its messages come by a hibernating WebSocket.

How it is built

Scala 3 compiled to JavaScript, on okay-serverless and okay-ui. A conversation is a Durable Object, a deal is a Durable Object, the market and the vacancies are in D1, and the cloud's services are effects with Cloudflare's handlers. Telegram, the page and the API are three doors to the same conversations. The page is an okay-ui program: the conversation is its effect Talk, answered over HTTP and the socket. The source, with every step measured: okay-chat-edge.

Games built on Okay

Smaller examples of the same way of building, to play: each game is one program of effects, its screens as data, its rules pure and tested. Go adds play over the network and a neural network on your GPU.

Wordle: the word game, as screens of data

Guess the hidden five-letter word in six tries. Each guess colours its tiles: green, the letter is in the word in that place; yellow, it is in the word elsewhere; grey, it is not. A small application to read, whole: one program, its effects, its handlers, and a page that is one line.

Play Wordle Source

Wordle after three guesses, SLATE, CRONY and DUMPY, the keyboard coloured by what is known, and the hint: 3 words still possible

What it does

  • A new game, or hard mode, where every guess must fit the colours so far.
  • A guess that is not five letters, not a word, or against the colours in hard mode stays in its field with the reason.
  • Hint: how many words are still possible, and the one a solver would try next.
  • The board and the keyboard show each letter's colour; the end shows the rows as emoji.
  • Statistics in your browser: rounds played, the share won, the streak, the rounds by the number of guesses.
  • Model plays: a tiny model guesses on its own; you see what it wanted, and what it played through the mask.

Screens as data

The program never touches the page. It describes each screen, a picture of the board, a form and its buttons, as a value, and okay-faces draws it.

The world as effects

The hidden word is the effect Secret, the rounds played are Results. In the browser they are a random draw and the page's storage; in the tests, a fixed word and a list.

A model kept to the rules

The model is a toy of 130 numbers that never sees the colours. A mask built into a transformer's weights allows only letters that lead to a word that fits, so it cannot invent a word or break a colour.

How it is built

Scala 3 compiled to JavaScript by Scala.js, on okay-ui and okay-faces. The model is one more effect, Model, its handler the toy in the page; a language model would be another handler, the program the same. The tests play the whole application as a person would, by the buttons' names, with the word fixed: no mocks, only other handlers. The page is the program with its handlers named, in one line: Page.mount(OnFaces.program(steps(InLocalStorage("wordle-results")(Secret.random()(Wordle.main[App])))), skin).

Sudoku: a puzzle made for you, hints with their reasons

Fill the grid so that every row, every column and every 3 × 3 box holds the digits 1 to 9, each once. Every puzzle is made in your browser, with exactly one solution, and the hint does not just give a digit: it says why it must be there.

Play Sudoku Source

A Medium Sudoku with R5C9 chosen and the hint: R5C9 can only be 6, its row, column and box holding every other digit

What it does

  • Easy, Medium and Hard; each puzzle new, made in the page in a fraction of a second, with a unique solution.
  • Easy and Medium are solved by logic alone, one forced digit after another; Hard may need a guess.
  • Hint: one step and its reason, such as "R5C9 can only be 6: its row has 3, 4, 5, 8, 9; its column 1, 2, 7; its box 4, 9".
  • A digit repeated in its row, column or box turns red; Check marks the digits that are wrong.

A board of data

The board is 81 blocks, each marked by what it holds; a free cell carries a button. The program says what is there; the stylesheet draws the grid.

The puzzle as an effect

The game asks Puzzles for a puzzle. In the browser the generator answers; in the tests a fixed puzzle does, and the whole game is played by its buttons.

Reasons, not answers

The same rules that make the puzzle find the next forced digit and the units that force it, so every hint can be checked by eye.

How it is built

Scala 3 compiled to JavaScript by Scala.js, on okay-ui and okay-faces. The page is one line: Page.mount(OnFaces.program(steps(Puzzles.random()(Sudoku.main[App]))), skin).

Go: 9 × 9 to 19 × 19, with a friend far away, with watchers, or against a neural network in your browser

The ancient board game, on a 9 × 9, 13 × 13 or the full 19 × 19 board. Put stones in turn, surround the other side's groups to capture them, and fence in more of the board. Play a friend at one screen, or play a small model: it can only ever press a legal point, and the page shows when what it wanted was not allowed.

Play Go Source

A game of Go on the full 19 × 19 board against the model: it played H17, a 60% chance to win after 4512 games played out

What it does

  • Two players at one board, or you against the model, as Black or as White, on 9 × 9, 13 × 13 or 19 × 19.
  • Play a friend far away: send them a link, and anyone can watch the game by another link.
  • The full rules: captures, no suicide, no repeated position (ko), passes, area scoring with komi 6.5.
  • Only legal points can be pressed: a taken point, a suicide or a ko simply has no button.
  • After each of the model's moves: what it played, its chance to win and how many positions its network read; the page stays alive while it thinks.
  • At the end, each side's stones and fenced points, and the winner.

Every board

A quick 9 × 9, 13 × 13, or the full 19 × 19 — at one screen, against the model, or online with a friend. Only legal points can be pressed: a taken point, a suicide or a ko simply has no button.

A neural network on your GPU

The model is KataGo's network (the g170 run, 10 blocks, 3 million weights, CC0) running in your browser on WebGPU, equal to KataGo itself to six digits. A search reads hundreds of positions with it before each move. No server: the GPU is yours.

The model as an effect

The game asks Model for the values of the points. A stronger engine would be another handler: the game would not change.

How it is built

Scala 3 compiled to JavaScript by Scala.js, on okay-ui and okay-faces. The board is a picture with a grid of buttons over it, one at each legal point; the tests play whole games by the points' names. A game far apart lives on a Cloudflare Worker on okay-serverless: each game a Durable Object, its players and watchers WebSockets, every move checked there by the same rules as in your page; the same server runs on a machine of one's own. The page is one line: Page.mount(OnFaces.program(steps(Model.toy()(Go.main[App]))), skin).

Control and audit, at three points

Effects make every action of a program an explicit operation that a handler interprets. That gives one place where each action can be checked. okay uses that place at three points: in the types, at run time, and in the compiled code.

Compile time

The type declares

Receipt ! Db + Payments is a program that may reach a database and payments, and nothing else. A program that performs a sensitive operation outside its audit does not compile.

Run time

The interceptor decides

Each operation passes a point that can record it, change it (normalise, cap, mask), or refuse it. An observer can see every operation of every effect. All of these live in the kernel.

Build time

The bytecode is checked

okay-audit reads the compiled classes. It confirms that business code reaches no network, files, SQL, clock or threads on its own, and it lists every module that does, by provider: the dependency inventory DORA asks for.

A payment audit in Scala, from okay-core's test suite, shortened:

enum Pay[X]:
  case Transfer(to: String, amount: Int) extends Pay[String]
given Sensitive[Pay] = Sensitive()           // from here on, Pay runs only under an audit

def guarded[T, A](limit: Int) = new Interceptor[Pay, T, A, A]:
  def apply[X](op: Pay[X], k: X => A ! T, pass: Pass[Pay, T]) = op match
    case Pay.Transfer(to, amount) if amount > limit =>
      record(s"refused: $amount to ${masked(to)} over the limit $limit") *> raise("over the limit")
    case Pay.Transfer(to, amount) =>
      pass(Pay.Transfer(to.trim.toUpperCase, amount)).flatMap: receipt =>   // changed, then passed on
        record(s"transfer $amount to ${masked(to)} → $receipt") *> k(receipt)

audit(guarded(1000))(payments).handleAudited(bank).handle(trail).run
// transfer 100 to ****1234 → ok:ACCT-1234:100
// refused: 5000 to ****T-77 over the limit 1000

The same programs exist in Rust (okay-rust) and TypeScript (okay-ts), so the guarantee does not depend on Scala. Full account: okay-core, docs/intercept.md.

Why algebraic effects

An effect is an interface of operations. A handler implements it. The program is written once, and the caller picks the meaning.

One program, many meanings

The same code runs against a real database, an in-memory one for tests, or a recording one for replay. Nothing in the program changes, only the handler.

Effects combine predictably

A program lists every effect it uses. There is no stack of monad transformers to order up front and no lift. The order of the handlers is chosen when the program is run.

Exceptions that can go back

A handler gets the operation and the rest of the program. It can stop the program like an exception, resume it once with an answer, or resume it many times to search.

The scientific foundation of what is implemented here: Ohad Kammar, Sam Lindley, Nicolas Oury, Handlers in Action, ICFP 2013 (the 18th ACM SIGPLAN International Conference on Functional Programming, Boston). More: what algebraic effects are, and why, with a reading list.

Start with okay-simple

okay-simple is the smallest way in: one typed interface of algebraic effects for Scala 3 (3.3 LTS on) and Scala 2.13, on the JVM, Scala.js and Scala Native. It is released as 0.1.0, and adding it is two lines in sbt.

1. Add it to the build

resolvers += "okay-effects" at "https://okay-effects.github.io/okay-simple/maven"

libraryDependencies ++= Seq(
  "io.github.okay-effects" %% "okay-effects"         % "0.1.0",  // the interface and the standard effects
  "io.github.okay-effects" %% "okay-effects-machine" % "0.1.0"   // an implementation to run on
)

Use %%% for Scala.js and Scala Native. Other modules use the same form: okay-effects-stream, -async, -zio, -cats and the rest. The full list is in the repository index.

2. Write a program, handle it, run it

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

case class Config(greeting: String, limit: Int)

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

// the row named once; inside, the words need no types
object service extends std.On[Service]:
  def serve(user: String): String ! Service =
    for
      c <- ask[Config]
      n <- get
      reply <- if n >= c.limit then tell(s"refused $user: busy").as("try later")
               else put(n + 1).andThen(tell(s"served $user")).as(s"${c.greeting} $user")
    yield reply

val day = for a <- service.serve("ann"); b <- service.serve("bob"); c <- service.serve("cid") yield List(a, b, c)

// the handlers: a config, a log collected, a counter from 0
run(state(0)(writer[String](reader(Config("hello", 2))(day))))
// (2,(List(served ann, served bob, refused cid: busy),List(hello ann, hello bob, try later)))

run(state(0)(writer[String](reader(Config("hi", 5))(day))))
// (3,(List(served ann, served bob, served cid),List(hi ann, hi bob, hi cid)))

The service reads its configuration (Reader), writes a log (Writer) and counts the requests it served (State). Its type, String ! Service, says exactly that. The row is named once, in std.On[Service], and inside it the words take no types: ask[Config], get, put, tell. The handlers give the meaning: here a config value, a log collected into a list and a counter starting at 0. In production they would read a file, write to a log system and keep the counter in a store. The program would not change, and the second run, with a different config, shows how easily its behaviour is tested. Nothing runs until run.

3. Then

The step-by-step page goes on from here: your own effect and its handler, a handler that resumes twice, errors and streams.

Getting started

The same program on two implementations, in Scala 3 and in Scala 2, and the Scala versions each form needs.

A tour in eleven steps

Your own effect, a handler that resumes twice, choice, errors, generators, interception and delimited control.

With what you have

Interop with ZIO (environment, errors, layers), cats-effect, fs2, kyo and monix. Bring effects into an existing codebase one part at a time.

One design, six languages

There is one kernel. A program is data and the rest of the program is a value. The other languages port the kernel, are checked by its test suites, or bridge to it. okay-kernel is a protocol between them: a program in one language runs under handlers written in another, in one process or over a wire. In the demo, a shop runs in Go while its inventory, payments and mail are Scala handlers.

ProjectLanguageWhat it is
okay-coreScala 3The kernel: one monad, delimited control (shift0), multi-shot continuations as data, no macros, no casts. Interceptors and audit.
okay-simpleScala 3, 2.13A small typed interface for both Scala compilers, released as 0.1.0: three implementations, ten modules, and interop with cats, ZIO, fs2, kyo and monix.
okay-kernelJava APIThe protocol: a program steps to done, yield, failed or cancelled. It comes with a TCK that every implementation passes.
okay-javaJava 25The kernel's design on shift0. A capability that escapes its handler is a compile error.
okay-rustRustThe kernel ported to Rust, plus a separate crate for effects in plain async Rust with capabilities. It is checked against the kernel's suites.
okay-goGoDirect-style effects on Go's coroutines, at 49 ns an operation. Parallel branches run on goroutines.
okay-pythonPythonTyped effects on generators, clean under mypy --strict. asyncio needs no coloured functions.
okay-tsTypeScriptEffects tracked in the type as a union. Programs as data resume many times; generators resume once.

The whole family, with an example in each language: docs/family.md.

A platform for real applications

okay is over a hundred modules on one model of effects. The core has no dependencies, and one source builds for the JVM, Scala.js and Scala Native.

Services

  • HTTP (Jetty, Netty)
  • SQL and JDBC
  • Kafka, Spark, Flink interop

Systems

  • Streams and chunked pipelines
  • STM, a cluster
  • Durable workflows

AI and UI

  • LLMs, RAG, agents
  • MCP servers
  • A UI toolkit

For AI agents in particular, the agent's every action is an effect, so the controls above apply to it.

Libraries, released

Each a Maven repository on GitHub Pages — a resolver, no account. All of them, with how to add each.

Effects

okay-simple, okay-scala

Scala 3 and 2.13 behind one interface; Scala 3 alone on a CEK machine. Java (okay-direct) and Kotlin (okay-kotlin) too.

Interfaces and agents

okay-faces

User interfaces on effects: one program is a window, a terminal, a web page, a REST API and an MCP server for an AI agent; on Cloudflare in one line. On okay-http and okay-mcp.

Data, proofs, the cloud

okay-store, okay-proof, okay-serverless

Versioned records and an ORM on effects; policies whose decisions are theorems; a cloud as effects — Cloudflare and AWS, state without replay.

Measured, not asserted

From okay's benchmarks (JMH, average time, lower is better; cats-effect 3.5.7, ZIO 2.1.14, atnos-eff 7.0.4, fs2 3.10.2). Every lane has its protocol written down, and a competitor is measured in the same shape and at the same granularity. A loss is printed as a loss.

The effect system, µs

ProgramokayZIOcatsatnos-effthe floor
10 000 flatMaps, built and run5.5193163 (IO), 117 (Free)286—
10 000 asks, left-nested116258346 (Kleisli)1 469—
10 000 tells, left-nested217—1 222 (WriterT)3 385—
213 choices, all collected1 645——5 487615 (List)
1 000 bracketed resources, left-nested22.5116225 (IO)——
Fibonacci by a generator, 1 000 elements19.2175 (ZStream)268 (fs2)—11.7 (Iterator)
Fork/join of 100 fibers2446.6121 (IO)—21.8 (raw Loom)

okay's 5.5 is its opt-in eager encoding, with its hazard stated: construction evaluates. The lazy encodings, which keep the laziness contract, read 95 and 112.

Streams, µs

Pipelineokayfs2ZIOthe floor
map / filter / take(1000) / sum, one chunk8.2221.935.815.2 (Iterator)
the same, okay staged (fused)1.70——15.2
two 500-element streams merged by readiness13.394.451.5—

Scaling across cores: a real streaming job, 2.4 M events

Wrocław's public transport timetable, event-time windows and keyed state, one JVM per lane. The table shows events per second at 1, 2, 4 and 8 cores.

Library1 core2481 → 8
okay4.29 M7.62 M12.77 M22.56 M5.3×
java.util.stream4.30 M5.27 M6.10 M7.16 M1.7×
zio-streams4.14 M5.79 M6.76 M6.96 M1.7×
fs24.17 M5.11 M6.40 M6.56 M1.6×

At one core every library is within a few percent of the others, the measurement's own noise. What separates them is the slope. okay fans the job across fibers joined by a merge, and the others hit the fan-in cost of one stream before they run out of cores.

Against the engines, on a real cluster

The same job on a standalone cluster of three processes per engine, at 575 k, 1 255 k and 2 414 k events. A least-squares fit separates each engine's fixed cost, paid before it has seen an event, from its marginal rate.

Engine575 k1 255 k2 414 k eventsfixed costmarginal
okay, 8 partitions over 4 processes101 ms141 ms203 ms70 ms18.1 M ev/s
Flink 1.20, 1 JobManager + 2 TaskManagers742 ms736 ms1 379 ms431 ms2.71 M ev/s
Spark 4.0, 1 Master + 2 Workers + executors3 878 ms4 879 ms7 014 ms2 824 ms0.58 M ev/s

The engines' fixed costs buy what okay's lane does not have: checkpointing, rescaling and recovery, none of which is priced here. Spark's 2.8 s is a SparkContext per submission, which a job running for an hour amortises. Distribution costs okay a constant and not a rate: 19.5 M ev/s in one JVM, 18.1 M across four processes.

okay-simple's implementations, µs, n = 1 000

Programmachine, Scala 3machine, Scala 2Free, Scala 3tagless on the machineokay-core's kernel
1 000 maps6.96.812.010.19.1
1 000 asks under their handler17.020.833.824.615.9
the same, across four other handlers23.517.070.139.119.1
1 000 get/put pairs18.316.120.430.114.0

Sources, with every protocol and the raw history: okay's docs/benchmarks.md, the streaming write-up, okay-simple's README.

Tools

symdex: structural code intelligence for coding agents

Definitions, references, callers, implementations, and which given each call site resolves. It reads them from the compiler's own output (SemanticDB, TASTy, SCIP) and serves them over MCP, so an agent asks instead of grepping. It is built on okay's MCP server.

okay-audit: the effect boundary in the bytecode

Every build gets a report that an auditor can read. It shows which modules may reach the outside world, and that business code does not. It has zero dependencies and is evidence, not a compliance claim.

Author

I am Sergiy Shcherbyna, a senior and lead backend engineer in Wrocław. I have thirty years of software behind me, the recent ones in Scala, Spark and the JVM. okay-effects is my work on making what programs do explicit and controllable: types for what a program may do, handlers for how it is done, and audit for proving it.

I am open to working together as a team member or as contract consultant in Scala or JVM projects. The work I do best is refactoring: making a large, tangled codebase clear, typed and testable while it keeps running, and splitting a monolith into microservices along the boundaries its effects already draw. Beside it: effect systems, data pipelines, and controls and audit for fintech, regulated domains and AI agents.