Apalache encodes TLA+ for an SMT solver instead of enumerating states
APALACHE: symbolic model checker for TLA+ and Quint
At a glance
- What is it?
- Apalache is a symbolic model checker for TLA+ and Quint that translates a specification into the logic that SMT solvers such as Z3 and CVC5 accept, then checks inductive invariants and bounded safety with constraint solving. It is built for engineers who want a machine to argue about a protocol, not to trace it by hand.
- Who is it for?
- Apalache earns a place on the desk of an engineer writing or reviewing a TLA+ specification, especially when the state space involves integer clocks, Byzantine faults or parameters that make explicit enumeration impractical.
- Can I use it commercially?
- Yes. Apache-2.0 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 11 days ago.
- What is it written in?
- Mainly Scala, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Why encode a specification instead of enumerating it
Model checking answers a question with a counterexample rather than with a proof: does some execution of my system violate this property? The classic way to answer it is to enumerate states, which is what the reference checker TLC does and what the README points at when it says that, in general, Apalache runs under the same assumptions as TLC.
Apalache takes a different route. It translates TLA+ into the logic supported by SMT solvers such as Microsoft Z3 and CVC5, and then lets a constraint solver search instead of a program walking a state graph. The README states the practical consequence plainly: Apalache benefits from constraint solving and can handle potentially larger state-spaces, for example spaces involving integer clocks and Byzantine faults.
Those two examples are the heart of the audience. A clock that increments by an unbounded amount is awkward to enumerate but natural as an arithmetic constraint. A set of faulty processes that may act adversarially produces a combinatorial explosion when you enumerate their choices, and a solver can reason about the disjunction directly.
Two checking modes are documented. Apalache can check inductive invariants, for fixed or bounded parameters, and it can check safety of bounded executions, which is bounded model checking. In both cases the guarantee is tied to the bounds you supply, and that constraint is the thing to keep in mind before quoting a result.
The pipeline visible in the repository directory layout
The top level of the repository reads like a compiler pipeline, and that is not accidental. There is a tla-parser/ directory, a tla-typechecker/ directory, and a tlair/ directory, which by name holds an intermediate representation between the parsed source and the solver encoding. Beside them sit passes/, which is where transformation passes live, and tla-bmcmt/, which is the bounded model checking engine, matching the bounded model checking mode the documentation describes.
Supporting layers are just as visible. tla-io/ handles reading and writing specifications, and its test resources include JSON fixtures produced from Quint sources, such as tictactoe.json and booleans.qnt.json, alongside clockSync3.json. tla-assignments/ deals with assignments of values to variables, which is the part of an inductive invariant check where you commit to a concrete starting configuration.
The packaging side is split into mod-tool/, mod-infra/ and mod-distribution/, which together suggest a CLI front end, infrastructure code and release artifacts. json-rpc/ points at a JSON-RPC surface, and shai/ and prompts/ sit alongside an AI_POLICY.md at the root, so the project carries tooling and policy files related to language model assisted work.
On the build side the project is Scala, with build.sbt at the root, a .scalafmt.conf for formatting, and both flake.nix and a .devcontainer/ directory. The .envrc file and flake.lock point at a Nix-based developer environment, which matches the README note that Apalache is also packaged via Nix.
Running Apalache from the Docker image, the jar or a release tag
The README gives three ways to get a stable release, and the wording is worth quoting because it distinguishes stable from bleeding edge. For a stable release it recommends pulling the latest docker image with `docker pull ghcr.io/apalache-mc/apalache:main`, using the jar from the most recent release, or checking out the source code from the most recent release tag. To try the newest features, the guidance is to check out the head of the main branch. The releases page is the place to find the newest release, and the user manual has an installation section with further options.
For CI, the Docker image is the least friction path since it removes any dependency on the JVM toolchain you happen to have on the host. The jar suits a build you control. Checking out the release tag is the option that lets you read exactly the source that produced the artifact, which matters when a verification result has to be reproduced later.
The main branch carries the work in progress and is where the Quint support and the newest passes are likely to be, but the README separates it from the stable recommendation in plain terms. A result produced from main is a result about a moving target, so record which of the three you used alongside the outcome.
Documentation for first use starts with a beginner's tutorial, then the user manual. Two further guides are listed as work in progress: idioms for writing better TLA+, and a TLA+ language manual written for engineers. Both being marked WIP is a fair signal about how polished they are today.
The Makefile wrapper that keeps SBT out of your workflow
Apalache is a Scala project built with SBT, but the repository ships what its Makefile calls a good old Makefile for the end users, so they do not need to learn SBT commands. The targets wrap the underlying build.
sbt compile
sbt testThose two invocations are what the compile and test targets run. The package target assembles the project for local development use without running tests, while the apalache target tests and assembles the package for development and integration tests. Release packaging is handled separately by the dist target.
Integration testing has its own arrangement. The integration target first packages the project, then runs test/mdx-test.py over the Markdown files collected from test/tla/*.md, and separately over test/tla/tlc-integration-tests.md. The comment in the Makefile calls these Markdown files used for integration tests, which means the specification documents themselves double as test fixtures.
There is also a quint-fixtures target that generates the JSON fixtures the Quint integration needs, and it reaches out to the Quint repository to fetch an example source before compiling it. The recipe uses curl against the raw Quint examples and then the quint compile command with a main module argument, writing the result into the tla-io test resources. Other targets cover docs, docs-view, fmt-check and fmt-fix, and clean.
One target is explicitly marked as no longer used by CI. The test-coverage target runs a scoverage report, and the comment states it is not used in CI anymore because it needs additional devops work to be re-enabled. Worth knowing before you plan on coverage numbers from a local run.
Where the TLA+ feature boundary sits
The real constraint on this tool is not the solver but the front end. The README directs readers to a list of supported TLA+ constructs, and that list is the boundary of the tool. A specification that uses a construct outside it cannot be translated, so the work of using Apalache often includes rewriting parts of a spec into the supported subset rather than only pointing the checker at it.
The second constraint is about what a result proves. An inductive invariant check needs a candidate invariant and a parameter, and the documentation is explicit that this works for fixed or bounded parameters. Bounded model checking, by name, establishes safety only for executions up to a bound. Neither mode is an unbounded proof of liveness, and the README does not present Apalache as a liveness checker.
A third practical limit comes from the Quint path. The integration fixtures show that Quint sources are compiled to JSON and then consumed through tla-io, which means the Quint route depends on the quint tool being available to produce that JSON. The Makefile fetches an upstream example and compiles it with the quint command, so a contributor working on Quint support needs that tool in the environment.
Taken together these are honest limits rather than defects: a supported feature list, a bounded conclusion, and an extra front-end tool for one input language.
Apalache against TLC, and Quint against TLA+
Two comparisons are worth making precisely, because they are the two decisions people actually face.
The first is against TLC, the reference model checker the README links to. Both tools run under the same assumptions, so for a specification that TLC can decide quickly, TLC remains the simpler path with fewer moving parts. The difference is the search strategy. TLC enumerates states explicitly, while Apalache encodes the specification into SMT logic and lets the solver search, which is what lets it take on state spaces involving integer clocks and Byzantine faults. The repository keeps this comparison honest with a dedicated integration file named tlc-integration-tests.md and with a separate benchmark repository at apalache-mc/apalache-tests, where reports on inductive invariant checking and on bounded model checking are linked.
The second is between the two input languages. Apalache's own description covers TLA+ and Quint, and Quint sources reach the checker as compiled JSON through tla-io. Writing the specification in Quint and compiling it to JSON is a different authoring route from writing TLA+ and having the tla-parser and tla-typechecker handle it. If your team already writes Quint, or wants to start from a tool with a more familiar syntax, that route changes the front end while leaving the checking engine the same.
The practical consequence of both comparisons is that switching to Apalache is a decision about where the difficulty lives, in state enumeration or in encoding.
Governance, licence and the cost of an upgrade
The project has moved to the TLA+ Foundation, under the Linux Foundation, and the README notes that Apalache is governed according to a Technical Charter. The copyright line reads Apalache a Series of LF Projects, LLC, and the licence recorded for the repository is Apache-2.0.
Apache-2.0 is a permissive licence with an explicit patent grant and a requirement to keep notices intact, which in practice means you can use the tool, embed it in a build pipeline, and redistribute modified versions with the notice files retained. It does not oblige anyone to contribute changes back, and it says nothing about the correctness of any verification result.
On release rhythm, the recent tags are v0.62.4 on 2026-10-12, v0.62.3 on 2026-10-01 and v0.62.2 on 2026-08-26, and the last push was on 2026-10-12. The repository is not archived. Note the version shape: zero-major releases moving by patch increments, so pinning matters if you want reproducible checks. The presence of an .unreleased/ directory next to CHANGES.md suggests changes are collected before they are tagged.
For an upgrade, the cost is not in the licence but in the results. A new release may support more constructs, and your model may then behave differently, so verification runs should be repeated after each bump rather than assumed to carry over. Funding has come from grants and employment, with the README listing support from Informal Systems from 2020 to 2024, the Vienna Business Agency from 2021 to 2023, the Interchain Foundation from 2019 to 2023, the WWTF from 2016 to 2020, Inria Nancy and LORIA from 2018 to 2019, and TU Wien from 2016 to 2020. Past support of that kind, and the move into the foundation structure, is the relevant context for how the project is funded now.
Editorial conclusion
Apalache earns a place on the desk of an engineer writing or reviewing a TLA+ specification, especially when the state space involves integer clocks, Byzantine faults or parameters that make explicit enumeration impractical. It is the wrong tool when your specification needs a TLA+ construct outside the documented supported features list, when you want a full unbounded proof of liveness, or when your team would rather keep the reference checker TLC as the single source of truth. Verify first that the features page covers the constructs your spec uses, then run the specification through the inductive invariant check with the parameter bounds your system actually needs, since the checker only concludes what those bounds allow.
Frequently asked questions
How do I install Apalache to run TLA+ checks?
For a stable release the README recommends pulling the Docker image with docker pull ghcr.io/apalache-mc/apalache:main, using the jar from the most recent release, or checking out the source from the most recent release tag. The installation section of the user manual lists further options, and Apalache is also packaged via Nix.
What is the difference between Apalache and TLC when checking a specification?
Both run under the same assumptions, but Apalache translates TLA+ into logic for SMT solvers such as Z3 and CVC5 and relies on constraint solving, while TLC enumerates states explicitly. The README states that this lets Apalache handle potentially larger state-spaces, for example ones involving integer clocks and Byzantine faults.
Can Apalache check a specification written in Quint as well as TLA+?
The project is described as a symbolic model checker for TLA+ and Quint, and the integration fixtures in tla-io/src/test/resources include JSON files produced from Quint sources. The Makefile's quint-fixtures target fetches a Quint example and compiles it with the quint compile command.
How do I build and test Apalache from source without learning SBT commands?
The repository ships a Makefile for end users, with a package target that assembles the project without running tests, an apalache target that tests and assembles it, and an integration target that runs test/mdx-test.py over the Markdown specs in test/tla. The compile and test targets run sbt compile and sbt test underneath.
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/apalache-mc-apalache)Was this useful?