robot3: a finite state machine small enough to read in one sitting
🤖 A functional, immutable Finite State Machine library
At a glance
- What is it?
- A 2.2k star JavaScript state machine library whose whole pitch is that the definition, the interpreter and the transition table are the same object, plus a React binding published from the same repository.
- Who is it for?
- robot3 is at its best when a component has two or three states and a handful of events, because that is the case where the declarative form is shorter than the equivalent boolean flags and the machine object can be passed around and logged.
- Can I use it commercially?
- Yes. BSD-2-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 7 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 11, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The entire introduction is one machine definition
The README shows a complete state machine in about a dozen lines, and there is no second example needed to understand the shape of the library. States are values, transitions are named events, and the definition is a plain object:
import { createMachine, interpret, state, transition } from 'robot3';
let machine = createMachine({
off: state(
transition('toggle', 'on')
),
on: state(
transition('toggle', 'off')
)
});
const service = interpret(machine, () => {
render();
});Four imports, two states, one event. What `createMachine` gives you back is a value that can be defined at module scope and imported by whatever renders it, which is the part that separates this from a switch statement buried in a component. What `interpret` gives you is a service, and the callback is the subscription that fires on every transition.
The scale of the thing matters as much as the API. GitHub reports 2,200 stars, 96 forks and 19 open issues for the repository, and the language is JavaScript with a BSD-2-Clause license. This is a library you read once and then stop thinking about, not a framework you configure.
Immutability is the reason the definition and the running state differ
GitHub's summary of the project calls it a functional, immutable finite state machine library, and the README repeats that phrasing. The practical consequence shows up in the two-function split. `createMachine` builds an inert description, and nothing about it changes while your program runs. The running state lives in the service returned by `interpret`, and sending an event to that service produces a new state value rather than mutating the machine.
That distinction is what makes a machine a thing you can pass around. A component can import the same machine object another component already uses, and the two will not interfere, because neither one holds mutable state inside the definition. It also means a machine can be constructed in a test, stepped through events, and compared against a literal, with no rendering involved at all.
The design has a cost worth naming. Because the definition is inert and the interpreter owns the state, there is no single global place to look at what your application is currently doing. If you want that, you subscribe to the service or reach for the logging helper. The 1.2.0 release of `robot3` added a `/logging` subpath so state changes can be printed during development, which tells you the author considers observability a separate concern from the core.
Framework bindings live beside the core, not inside it
The core library knows nothing about rendering. React, Preact and Haunted support are described in the README as integrations with their own pages on the documentation site, which keeps `robot3` usable in a plain Node script or a worker where no component system exists. The documentation site, thisrobot.life, has an integrations page that lists what is available.
One binding ships from this repository rather than a separate one. The release history contains `[email protected]` published on 2025-11-28, alongside `[email protected]` from 2025-09-20 and `[email protected]` from 2025-02-05. Two different package names, two independent version lines, one repository.
There is also a standalone visualisation tool, robot3-viz, maintained in its own repository and linked from the README. It renders a machine as a diagram, which is the fastest way to understand a machine with more than a handful of states than reading the transition table does.
Per package releases explain the version numbers you will see
Release tags in this repository carry the package name and an at sign, such as `[email protected]` and `[email protected]`, rather than a bare version. That is the signature of a workspace repository: the `packages/` directory at the root holds the published packages, and `.changeset/` holds the pending changelog entries that decide which package gets bumped and by how much. Each release body describes changes for one package only, which is why the React binding and the core can be days apart without either release note mentioning the other.
The repository root also holds `demos/` and `docs/`, so usage examples live next to the code rather than only on the documentation site. `contributing.md` and a `CODE_OF_CONDUCT.md` are present at the root as well.
Here is a detail worth planning around. The most recent release of any package is dated 2025-11-28, while the repository was last pushed on 2026-09-07. Work has landed on the default branch that is not in a published version yet. If you need to know whether a fix you care about has shipped, the release list is the place to look, not the commit dates.
The README's test instructions point at a folder the root no longer has
The Testing section says tests are located in the `test/` folder and tells you to load `test/test.html` in a browser through any HTTP server, noting that the tests are written in QUnit. The repository root does not list a `test/` directory. What it lists instead is `packages/`, `demos/`, `docs/`, `.changeset/`, `.github/`, a `package.json`, a `package-lock.json`, a `LICENSE`, `CODE_OF_CONDUCT.md`, `contributing.md`, `readme.md` and a `.gitignore`.
Both statements can be true at once. A workspace repository that has moved its core package into `packages/` almost certainly moved that package's tests with it, which would put the browser test page at `packages/robot3/test/test.html` rather than at the root. That is an inference from the layout, not something the README states, and it is the kind of gap where guessing wastes an afternoon.
The actionable part is that the root listing does not contain `test/`, so the README path will not resolve as written. If you want to run the suite, open the `packages/` directory, find the package you depend on, and look for its test folder there. Contributors have a better route: the README asks for a pull request rather than direct edits, and the repository carries a contributing guide.
Two different homes, and which one answers your question
The repository metadata points at regularhuman.dev as the homepage, while the README sends every documentation link to thisrobot.life. They are not the same destination, and the split tells you something about where to look. The documentation site is the manual: API reference, the integrations page, and guides for each binding. The homepage field is the author's own site.
For a library this small, the README is not trying to be a manual, and it does not pretend otherwise. It gives you the definition example, points at the documentation site, names the integrations, and links the visualiser. Everything you need to decide whether to adopt it is on those two pages. What is not on any of them is the compatibility story between versions, and the release notes are where that lives.
A reader who wants the API should start at thisrobot.life. A reader who wants to know what the project is actually made of should read `packages/` and the release list, because that is where the real boundaries between the core and the React binding are visible.
Editorial conclusion
robot3 is at its best when a component has two or three states and a handful of events, because that is the case where the declarative form is shorter than the equivalent boolean flags and the machine object can be passed around and logged. The pieces to know before adopting it are all in the repository rather than the README: the package is published per package rather than per repository, so `robot3` and `react-robot` move on separate version lines, and the README's browser test instructions reference a `test/` folder that the repository root no longer contains. Start at thisrobot.life for the API and integrations list, then read `packages/` in the repository to see which package you are actually installing.
Frequently asked questions
Is robot3 still maintained?
The repository was last pushed on 2026-09-07, while the most recent package release, react-robot 1.2.1, is dated 2025-11-28. That gap means commits are landing on the default branch without a matching published version, so check the release list rather than the commit dates when you need to know whether a change has shipped.
Does robot3 work with React?
Yes, through a separate package. The README lists React among the supported integrations alongside Preact and Haunted, and a react-robot package is published from this same repository on its own version line, currently 1.2.1. The documentation site has an integrations page covering each binding.
What is the difference between createMachine and interpret?
createMachine returns an inert description of your states and transitions, and nothing in it changes while your program runs. interpret takes that description and returns a service holding the current state, which sends events and notifies subscribers through the callback you pass in. Because the two are separate, the same machine object can be shared and never carries mutable state.
How do I run the tests?
The README describes loading test/test.html in a browser through an HTTP server and says the suite is written in QUnit, but the repository root no longer lists a test folder. The core package now lives under packages/, so look for the test page inside the package you depend on instead of at the root path the README gives.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/matthewp-robot)