Riot is a tech-demo of a stack and tooling for building applications in OCaml. It is heavily opinionated, and designed from the ground up to get out of your way and help you ship great software with agents.
It comes with a single tool for all your needs, a modern package registry, a multi-core ready actor-model runtime, a whole new standard library, and first-class support for agentic work.
Riot is also a tool: riot — and it's the only tool you need in this stack.
Riot ships with agent-facing instructions, not just human docs. The riot-ml skill tells agents how to use the stack, which commands to prefer, where workflows live, and how to keep package discovery grounded in pkgs.ml.
Every new Riot project created with riot init includes an .agents folder with the Riot skill already there. Start the project, then use your agent.
The local skill explains how to build, test, benchmark, run, format, fix, fuzz, and maintain Riot projects.
Riot commands prefer structured output with --json, so agents can inspect build results, diagnostics, test runs, package metadata, and registry flows without scraping prose.
The skill links deeper workflow references for testing, fuzzing, benchmarking, snapshots, and package lookup, so agents can run the right loop instead of guessing.
/llms.txt is the public discovery map for agents that find Riot from the web; inside a Riot workspace, the local skill is the starting point.
riot build
Easy, fast builds
Riot builds packages. A package is the unit of the build, and each package is defined by a plain riot.toml file.
Riot implements a new package-aware build system, so package boundaries, dependencies, targets, and generated artifacts all belong to the same model.
riot.toml is ordinary TOML: no S-expressions, no separate configuration language, and no hidden build folklore.
Build one package, the whole workspace, debug builds, release builds, and cross targets from the same command.
Target selection supports exact triples and broad patterns like linux.
riot fmt
One house style
riot fmt is a fast zero-config formatter optimized
for readability and minimizing diffs. There is one house style, and no options. Done.
Formatting is part of the stack, not a project-by-project bikeshed.
Output should be readable first and stable enough that reviews show the actual change.
riot fix
Linting and codemods
riot fix is an extensible linter with automated fixes, codemods, and package-provided rules.
Riot fix should not just tell you something is wrong. When Riot knows the fix, it shows the edit and can apply it.
Packages can register lint rules and fixes, then workspaces or packages can enable those rules in riot.toml.
This is what makes upgrades smoother: the package that changed can ship the rule that helps your code move with it.
riot test
Testing
Unit tests, property tests, snapshot tests, and fuzz cases live under one test runner.
Use unit tests for exact behavior, property tests for broad invariants, and snapshots for generated output that needs review. Fuzz cases declared with the test API replay under riot test, so weird inputs found later become part of the ordinary regression loop.
Package and suite filters keep the local loop tight when you only need one slice of the workspace. riot fuzz
Coverage-guided fuzzing
riot fuzz runs coverage-guided fuzzing campaigns against narrow boundaries such as parsers, codecs, protocol handlers, and CLI parsers.
A fuzz case is an ordinary Std.Test case declared with Test.fuzz, with seeds, corpus files, and mutator hints.
Generated coverage-increasing inputs are stored under .riot/fuzzing/<package>/<suite>/<case>/corpus/ as local state.
Crashes are saved under crashes/, with captured stdout, stderr, and status in crash-artifacts/ for triage.
A real finding should become a small durable regression: an inline seed, a curated fixture, or a minimized tracked crash input.
riot snapshots
Snapshot review
Snapshot testing checks generated output by comparing it against a saved expected file. When the output changes, the test writes a new candidate so you can inspect the diff and decide whether the change is correct.
riot snapshots is the review loop for those candidates: approve the new expected output, reject it, or leave it pending until you understand what changed.
Use snapshots for output where the full shape matters: formatter output, diagnostics, generated docs, parser fixtures, and text reports. Snapshot candidates are written as pending files, so generated changes do not silently replace the expected behavior.
Interactive review lets you approve, reject, ignore, or quit without manually hunting through fixture paths.
The goal is not to bless every diff. The goal is to make expected-output changes visible, reviewable, and boring to manage.
riot bench
Benchmarking
riot bench runs small and large benchmarks, then records and compares runs from the same toolchain.
Benchmark suites run through Riot, so they use the same package, profile, and toolchain model as the rest of the workspace.
--record persists a run under .riot/bench when you want history, and --compare brings previous comparable runs into the current report.
Use release profile benchmarks when the performance question is about optimized code. pkgs.ml is the Riot package registry: package index, publish flow, search surface, generated docs, and
public usage stats. The registry is powered by GitHub and Cloudflare, but the workflow stays in Riot: add, remove, update, publish, yank, search, login, and logout.
Publishing a package can generate documentation and make it available under docs.pkgs.ml.
The registry tracks useful public statistics, like downloads and version usage, so maintainers can see what still needs support.
Package operations stay scriptable for local workflows and agents with --json where automation needs it. riot initriot new
Golden-path scaffolding
riot init and riot new create workspaces and packages from the blessed path instead of assembling boilerplate by hand.
riot init starts a workspace with the files Riot expects, including package manifests and the OCaml toolchain config.
riot new adds a package inside an existing workspace without making you remember the directory shape.
The first generated project should already feel like Riot: a package, a manifest, a test loop, and a clear next command.
riot toolchain
OCaml toolchains
Riot ships vendored, precompiled OCaml toolchains and compilers for supported architectures.
A workspace declares the OCaml version and target set in ocaml-toolchain.toml.
Riot provisions and validates the toolchain under ~/.riot/toolchains, including cross-toolchain components when targets need them.
Builds, docs, tests, benches, fuzzing, and package commands use the same toolchain decision.
riot doc
Documentation
riot doc generates package documentation locally
for your packages and dependencies. Documentation is written in Markdown in source comments and package docs, then generated into static pages.
Generated docs follow Riot's Jolly Roger, so package docs, API reference pages, and registry docs share one readable style.
Publishing to pkgs.ml can publish docs under docs.pkgs.ml as part of the package flow.
riot run
Run executables
riot run runs Riot executables from your workspace, GitHub repos, and URLs.
Run local workspace binaries without manually finding the build output path.
Disambiguate by package when a workspace has more than one runnable target.
Run remote sources for project generators, scaffolding helpers, and one-off tools.
Forward runtime arguments after -- so the command remains scriptable.
riot <pkg>:<cmd>
Package provider commands
Packages can register first-class Riot commands, so dependencies can extend your workflow without becoming separate global tools.
A package can declare commands in riot.toml; Riot discovers them from the workspace and runs them as riot package:command.
Riot builds the package before executing the command, so provider commands stay in sync with the package version you depend on.
That makes package-specific workflows feel native: migrations, code generation, asset compilation, schema checks, and project upgrades can all live behind Riot.
What’s possible in Riot?
Riot is for building OCaml software as one piece: tools, services, databases, packages, experiments, and systems that need to stay understandable while they grow.
Multi-core applications
Build actor-oriented systems on top of the runtime in std/runtime and the actor interface exposed through std/actor. Spawn work, supervise it, and let Riot use the cores you have.
Command line interfaces
Build fast CLIs with structured output, clear errors, and the kind of terminal behavior agents and humans can both drive.
Cloud and networked services
Use actors, supervision, message passing, and a practical standard library to build services that do real IO. The std/IO surface includes ioSlice for zero-copy operations when bytes need to move without extra copying.
Developer tooling
Write formatters, linters, code generators, release tools, migration scripts, and project automation with the same toolchain they plug into.
TUI applications
Build terminal interfaces with Riot packages like minttea and gooey: structured state, keyboard-driven UI, and native terminal ergonomics.
Web applications
Build web surfaces with suri, including LiveView support in suri/liveview, typed domain logic, and agent-friendly APIs without giving up the ergonomics of OCaml.
Database-backed systems
Build typed database flows with sqlx, connect to PostgreSQL through postgres, and use sqlite when the right database is a local file.
Agentic workflows
Create workflows that can be run, inspected, repaired, and repeated by agents: JSON output, stable diagnostics, clear command boundaries, and recoverable failure modes.