Safety
Algebraic, with no panics. Failures are Result values the caller must handle, matching is exhaustive, integer arithmetic follows a stated Arith policy, and effects make outside work visible and replaceable.
One language for apps, backends, ML and AI, and mathematics, and good at all of them, from mobile apps to Transformers models on native GPU kernels.
effect Files { write: fn(string, string) -> Result<int, Error> }
effect Log { info: fn(string) -> Unit }
// Deep in the app: no disk or logger is passed in.
fn writeLock(lock) = match perform Files.write("osprey.lock", lock) {
Success _ => "installed"
Error { message } => "rolled back: ${message}"
}
fn install(name) = {
perform Log.info("resolving ${name}")
writeLock("${name} = 2")
}
// The app's edge picks the real disk and logger, once.
let shipped = {
handle Files { write path text => writeFile(path, text) }
handle Log { info text => print("log: ${text}") }
install("serde")
}
print(shipped)
// A test swaps in a full disk. The app code does not change.
test("a full disk rolls back", fn() => {
handle Files { write _ _ => Error { message: "disk full" } }
handle Log { info text => expect(text, "resolving serde") }
expect(install("serde"), "rolled back: disk full")
})
effect Files
write : (string, string) => Result<int, Error>
effect Log
info : string => Unit
// Deep in the app: no disk or logger is passed in.
writeLock lock = match perform Files.write "osprey.lock" lock
Success _ => "installed"
Error message => "rolled back: ${message}"
install name =
perform Log.info "resolving ${name}"
writeLock "${name} = 2"
// The app's edge picks the real disk and logger, once.
shipped =
handle Files
write path text => writeFile path text
handle Log
info text => print "log: ${text}"
install "serde"
print shipped
// A test swaps in a full disk. The app code does not change.
fullDiskRollsBack () =
handle Files
write _ _ => Error(message = "disk full")
handle Log
info text => expect text "resolving serde"
expect (install "serde") "rolled back: disk full"
test "a full disk rolls back" fullDiskRollsBack
# osprey: flavor=python
@effect
class Files:
def write(path: str, text: str) -> Result[int, Error]: ...
@effect
class Log:
def info(text: str) -> None: ...
# Deep in the app: no disk or logger is passed in.
def write_lock(lock):
match perform(Files.write("osprey.lock", lock)):
case Success():
return "installed"
case Error(message=message):
return f"rolled back: {message}"
def install(name):
perform(Log.info(f"resolving {name}"))
return write_lock(f"{name} = 2")
@handler(Files)
class disk:
def write(path, text):
return writeFile(path, text)
@handler(Log)
class terminal:
def info(text):
print(f"log: {text}")
@handler(Files)
class full_disk:
def write(_path, _text):
return Error(message="disk full")
@handler(Log)
class expect_resolving:
def info(text):
expect(text, "resolving serde")
# The app's edge picks the real disk and logger, once.
with disk:
with terminal:
print(install("serde"))
# A test swaps in a full disk. The app code does not change.
def full_disk_test():
with full_disk:
with expect_resolving:
return expect(install("serde"), "rolled back: disk full")
test("a full disk rolls back", full_disk_test)
The app asks for a disk and a logger; the edge of the program and each test decide which ones it gets.
Apps
With @osprey/native, one Osprey app compiles ahead of time to native code, and each host draws it with the platform's own native widgets (SwiftUI, WinUI, Android) or on a GPU canvas. Flutter and Compose Multiplatform draw their own widgets everywhere, and React Native runs JavaScript in an engine; Osprey Native does neither.
macOS · Windows · Linux · WebAssembly · iOS · Android
Real footage: Talon Bank on web, Android and iPhone, Issue Inbox, Data Studio, Osprey Storage, Vigil, Metal scenes, Flock, Prism Synth, Casting and the Proof Workbench; the Windows shots are screen captures of the signal-flow editor, Storage and Casting in the WinUI host. The phones are Issue Inbox in the iPhone simulator and Android emulator. WebAssembly and mobile reject unsupported dynamic control effects. Music: “Raving Energy” by Kevin MacLeod (incompetech.com), CC BY 4.0.
brew install nimblesite/tap/osprey
Getting started guide
Windows uses Scoop. Native compilation requires clang (LLVM 15+). Or try Osprey online ↗
Three pillars
Osprey exists to minimise bugs and failures, to express more program with less text, and to run natively. Every feature on this page serves one of the three.
Algebraic, with no panics. Failures are Result values the caller must handle, matching is exhaustive, integer arithmetic follows a stated Arith policy, and effects make outside work visible and replaceable.
Inferred types, effects instead of plumbing, and no separate async form. Less text means fewer tokens: programs are cheaper for AI agents to read and write, and easier for people to hold in their heads.
Compare the syntax flavors ↗Native binaries through LLVM with no VM or JIT warm-up. Static handler selection removes runtime effect dispatch, fibers are lightweight, and the memory backend is a per-build choice.
See the benchmarks ↗Effects and types
effect declares a need, perform requests it, and a handler supplies it. Swap the handler in a test and the logic stays the same; a missing handler is a compile error.
No annotations; exhaustive matching and Result for failures. Types ↗
Default, ML or Python-style syntax, one language. Flavors ↗
Concurrency in ordinary functions, on GC or ARC. Fibers ↗
An Arith handler sets the overflow policy. Arithmetic ↗
Resumption is single-shot; scoped effect rows are in development. Current support →
What you can build
GPT-2 runs on Metal and OpenCL, and a Qwen3.8-27B model on our Transformers port is in progress. Local LLMs ↗
Machine-checked proofs, with Lean's proof checking being brought to Osprey. Proofs ↗ Mathlib port ↗
Much Python already compiles as Osprey, NumPy and PyTorch are ported, and interop calls CPython both ways. Python interop ↗

Community
Ask questions, share what you are building, and talk language design with the people making Osprey.