A test sequencer: it runs step sequences against real equipment, retries the
ones that fail, and reports the outcome. Written in Rust compiled to WASM
(wasm32-wasip2, on wasmtime).
The sequence is data, not code: the engine walks it without knowing what each step does, and each step is invoked over gRPC by name — never with a direct call. That isolates the steps from one another and leaves the door open to writing them in any language.
The product documentation (vision, requirements, architecture, ADRs, domain
design, licensing and roadmap) lives in docs/. Start at
docs/vision.md.
To learn to use Anvil from nothing — install it, write steps in C#, write and run sequences, read the reports — follow The Anvil Book.
Two things, and a command for each. anvil (ADR-0011) hosts wasmtime and
the engine's WASM guest in a sandbox; it carries no step executor of its own
(ADR-0041) and starts none (ADR-0046). An executor is an address: a
sequence says where one is listening, and putting something there is the job of
whoever runs the bench — at boot, by a service manager, or by hand. The package
ships anvil-exec-wasm, the executor that serves .wasm steps, and a demo
bench built with it. Both are statically linked against musl: they need no
Rust, no cargo, no glibc, nothing installed on the system.
curl -LO https://github.com/anlaco/anvil/releases/download/v0.9.0/anvil-v0.9.0-x86_64-linux-musl.tar.gz
tar xzf anvil-v0.9.0-x86_64-linux-musl.tar.gz
cd anvil-v0.9.0-x86_64-linux-musl
# 1. the bench, in one terminal — or in the background, as here
./ejemplos/arrancar-banco.sh &
# 2. the sequence
./anvil ejemplos/subsecuencia.yseq --json ./out.json --csv ./out.csvThe second command is the one that used to be the only one, and the first is what was happening invisibly. Every bench has it; making it visible on the first run is the point. To point a sequence somewhere else without editing it:
./anvil ejemplos/basica.yseq --executor demo=192.168.1.50:9101To ask a bench what it serves, without running anything:
./anvil describe ejemplos/subsecuencia.yseqLinux x86_64, any libc. The release page publishes one SHA256SUMS
for every download; to check the tarball, download it alongside and run
sha256sum -c --ignore-missing SHA256SUMS (it lists the Windows downloads
too, which you did not fetch). The .yaml files ship in the package because
subsecuencia.yseq invokes medir_fuentes.yseq by relative path, and the demo
bench ships in ejemplos/departamento/dist/ because every example expects one
at 127.0.0.1:9101.
Windows (ADR-0036): the release page also publishes
anvil-vX.Y.Z-x86_64-windows.zip — the same engine, anvil.exe and
anvil-exec-wasm.exe, statically linked against the CRT so it needs no
Visual C++ redistributable installed. Same contents and same commands as
above, with .exe.
The engine binary above runs a sequence headless — CI, a bench, scripting.
To write one, download the Sequence Editor instead: an Electron app wrapping
the same editor that also runs standalone in any browser (editor/,
ADR-0031). It carries its own Chromium, so it needs no browser installed and
behaves the same on Linux and Windows (ADR-0037) — the reason it is not the
system's webview is that on Linux that would mean depending on whichever
libwebkit2gtk the machine happens to have. The release page
publishes it as anvil-editor-vX.Y.Z-x86_64-linux.AppImage,
anvil-editor-vX.Y.Z-amd64.deb and
anvil-editor-vX.Y.Z-x86_64-windows-setup.exe. It does not carry the engine:
to run a sequence it uses the anvil installed on the machine — the one on
PATH, or the one you point it at with File ▸ Locate Anvil Engine…, which
it remembers. On Windows, a PATH set in one terminal is not seen by an editor
started from the Start menu, so locate anvil.exe once. To run it from source:
cd editor && npm install && npm run app, which uses the engine built in this
checkout.
Only needed if you are going to touch the code; to use Anvil, download the binary above.
A clone of this repository is all it takes. The gRPC stack,
wasi-grpc, is a git dependency
pinned to a tag, and cargo fetches it (#25). To build against a local
checkout of it while working on both, use a [patch] section rather than
editing the dependency.
git clone https://github.com/anlaco/anvil
cd anvilmake release # WASM guest and example components → bridge → host, in that order
./ejemplos/arrancar-banco.sh &
./packaging/anvil-host/target/release/anvil ejemplos/subsecuencia.yseq --json ./out.json --csv ./out.csvThese are three chained builds (the host's build.rs copies the artifacts,
it does not build them), and the order matters; the Makefile exists so you
do not have to remember it. By hand:
cargo build --release --target wasm32-wasip2 -p motor # guest
cargo build --release --manifest-path executors/wasm/Cargo.toml # bridge (ADR-0015)
cargo build --release --manifest-path packaging/anvil-host/Cargo.toml # host (embedded wasmtime)That leaves a binary linked against your machine's glibc, which is what you
want for development. The binary that gets published in the releases is
another matter: it is built for the x86_64-unknown-linux-musl target so it
runs on any Linux. It requires a C compiler for musl, because wasmtime
drags in zstd-sys; musl-gcc or zig cc -target x86_64-linux-musl work
after rustup target add x86_64-unknown-linux-musl. On Windows the
equivalent target is x86_64-pc-windows-msvc, built natively (no
cross-compiling): a runner or machine with the MSVC Build Tools already
resolves the same zstd-sys dependency, and packaging/package.ps1 is the
Windows sibling of packaging/package.sh (ADR-0036). Both scripts build the
bridge for their target triple, and the host's build.rs finds it there
(#72). Releases are built by .github/workflows/release.yml, which runs each
script on the platform it is for and drafts the Release.
make build does the same in debug. Use it for development, but expect that
binary to start in tens of seconds: wasmtime compiles the guest
unoptimized every time. The release one starts in ~1 s.
To debug the engine guest on its own with the wasmtime CLI (two terminals),
start the demo bench by hand and point the guest at it. Nothing starts an
executor for you (ADR-0046) — here you also choose its port, which is why the
guest is given an --executor overriding the sequence:
make release
# terminal 1
executors/wasm/target/release/anvil-exec-wasm --modules ejemplos/departamento/dist --port 9300
# terminal 2
wasmtime -S cli -S tcp=y -S inherit-network=y --dir=. \
target/wasm32-wasip2/release/anvil-guest.wasm ejemplos/basica.yseq \
--executor demo=127.0.0.1:9300The wasmtime flags are not optional: without -S tcp=y -S inherit-network=y the guest cannot touch the network. More in the
quick-start guide.
crates/
modelo/ data model + paso.proto messages (prost)
cargador/ YAML → model: validates, resolves paths, detects cycles
expr/ expression engine (a Julia-syntax subset)
result_sink/ report sinks: console, JSON, CSV
motor/ gRPC client: walks the sequence (bin `anvil-guest`)
packaging/
anvil-host/ native host: one binary hosting wasmtime + the engine guest
(its own workspace; the core drags no wasmtime)
executors/
python/ the Python executor: a downloadable module (ADR-0012)
rust/ the Rust step SDK: `#[step]` on a function, compiled to
a WASM component (ADR-0024)
wasm/ the WASM executor: the gRPC ↔ user's `.wasm` component
bridge (ADR-0015); its own workspace, shipped as a file
next to `anvil` (ADR-0023)
csharp/ the C# step SDK: `[Step]` on a method, served by the
user's own process (ADR-0038)
editor/ the Sequence Editor: a browser SPA, wrapped by Electron
for download (ADR-0031, ADR-0037)
The gRPC stack lives apart, in
anlaco/wasi-grpc: gRPC over native
WASI sockets, because tonic/tokio do not compile to WASM. anvil is its
first consumer and dogfoods it.
These are the decisions that define the product. Do not touch them without meaning to:
- Execution semantics. Setup → Main (only if Setup passed) → Cleanup. Main stops at the first failure; Cleanup always runs — an instrument left switched on is worse than a sequence that failed.
- Retries per step. Each step declares how many attempts it allows. The attempt number reaches the step, which may use it.
- A closed vocabulary of statuses: an executor returns
pass,fail,errororskipped, and nothing else. In the sequence aggregate anerrorwins over afail; the engine adds two of its own,donefor a step that finished without judging anything (ADR-0040) andinconclusivewhen it could not judge (ADR-0019). Both are neutral. - A step says how it is judged, not what it calls.
typeis required and is one ofaction,pass_fail,numeric_limit,statement,sequence_call;moduleis what its executor serves, andnameis only the label in the report (ADR-0040). Anumeric_limitcarries alimitin TestStand's comparison codes (GELE,GE,EQT, …), which the engine evaluates — the step never learns the threshold (ADR-0008). - The contract lives in
crates/modelo/paso.proto:StepRequest,StepResultandservice StepExecutor { rpc Invoke, rpc Describe }. It is the source of truth; theproststructs ofcrates/modelo/src/proto.rsmirror it by hand (wasi-grpc v0.1 has no codegen).Describereturns the executor's catalog —which steps it serves and with what signature— and is what lets--validate --with-executorscatch a mistyped name without executing anything (ADR-0021).
make test # core, bridge, host, the Python, Rust and C# step SDKs, the editor
make check # fmt + clippy for the Rust workspaces, dotnet format for C#anvil is AGPL-3.0-or-later (see LICENSE). anvil is the product: it is used, not linked. The AGPL prevents anyone from closing it and reselling it, and it does not affect your test sequences — they are data you hand the sequencer, not a derivative work of it. The acceptance limits and product know-how inside a sequence are yours and stay yours.
The libraries it rests on are deliberately Apache-2.0:
| Piece | License | Why |
|---|---|---|
| WIT interfaces | Apache-2.0 | We want them adopted as a reference |
wasi-grpc, wasi-visa |
Apache-2.0 | They get linked in someone else's code |
executors/ |
Apache-2.0 | Their SDK enters your steps' code (its own LICENSE) |
| anvil | AGPL-3.0 | It is the product |
A test step links with the libraries, so copyleft there would infect the code of whoever uses them. In the sequencer it does not happen.