End-to-end demo
Bazelizing Monty without owning a fork.
Monty is a minimal Python interpreter written in Rust, from the
Pydantic team. The demo keeps upstream source immutable, overlays Bazel
build files, and then uses remote cache and remote execution (RBE) to
show the build graph can move between machines. The first proof is
fully local, using Docker only. The second uses Islo — Incredibuild's
cloud sandbox service — with a real provider-backed cache and RBE
endpoint. Disclosure: this demo was built at Incredibuild. The most
instructive failures — Bazel's embedded JVM distrusting a gateway CA,
and SDKMAN's global VERSION variable — are covered at the
end.
The numbers
The final run used Bazel 9.1.1, Crabbox 0.36.0
(a CLI that delegates runs to remote sandbox providers), and Monty
pinned at 4bcbcf5. The local cache demo used a fresh
bazel-remote instance in Docker and a fresh Bazel output
base for each stage, so nothing could leak from local state. Both cache
stages ran with --remote_download_toplevel.
| Stage | Wall time | Processes | Remote cache hits | Result |
|---|---|---|---|---|
| Local seed (cold cache) | 121.135s |
756 | 0 | 1 test passed |
| Local reader (readonly cache) | 15.706s |
756 | 432 / 432 executable | 1 cached test passed |
Islo sandbox build //:monty_cli |
120.287s |
1302 | — | success |
Islo sandbox test //:core_tests |
1.719s |
— | — | 1 test passed |
| GitHub CI (reference push) | 5m01s |
— | — | success |
| GitHub Pages deploy | 17s |
— | — | success |
The reader run finished in 15.706s against
121.135s cold. The honest reading: the seed run
executed 432 sandboxed actions; on the reader run all 432
came back as remote cache hits and zero actions re-executed — even the
test result was served from cache. The remaining 325 processes in both
runs are Bazel-internal bookkeeping, which is never cacheable, and the
residual 15.7s is analysis plus fetching top-level outputs under
--remote_download_toplevel. So the comparison measures
what a remote cache absorbs on a full rebuild, not compiler speed. The
committed summary is
docs/metrics/e2e-results.json;
the reader run's cache line, as logged by Bazel, was:
INFO: 756 processes: 432 remote cache hit, 325 internal.
The build contract
The repository pins upstream Monty at
4bcbcf5251b3a4a30b22fdfc372e5189adf4b86f. Bazel downloads
that archive, verifies its SHA256, and applies
third_party/monty/patches/add-bazel-build-files.patch.
bazelisk build //:monty_cli
bazelisk test //:core_tests
Why this shape
The overlay keeps the demo reviewable. The root repo owns Bazel policy: Bazel version, remote settings, Islo orchestration, CI, and Pages. The upstream archive owns Monty's Rust source. Updating the demo means changing one commit pin and refreshing one patch.
Fully local remote cache
The local demo needs no private service. It starts
bazel-remote in Docker, seeds the cache from one fresh Bazel
output base, then reads from that cache using a second fresh output base.
That makes the cache behavior visible without relying on local action
cache state.
./scripts/run_local_demo.sh
The script writes local-seed.local.json,
local-reader.local.json, and matching logs under
docs/metrics/. Those files are ignored locally so you can
run the demo repeatedly without dirtying the repo.
The same proof is available as a manual GitHub Actions workflow named Demo. It uploads the generated local metrics as an artifact, which is useful when collecting numbers for the post.
Provider cache and RBE
Islo gives the demo clean machines. Bazel still talks to standard
Remote Execution API (REAPI) endpoints through
--remote_cache and --remote_executor — the
same flags described in the
Bazel remote caching docs.
Copy
.bazelrc.remote.example to .bazelrc.remote,
fill in provider details, then run the three-sandbox flow:
- seed: build and test with cache upload enabled.
- reader: build and test from a separate sandbox with cache upload disabled.
- rbe: execute actions remotely on the configured Linux platform.
./scripts/run_islo_demo.sh
Crabbox with Islo
The latest Crabbox has a built-in provider=islo backend.
It is a delegated-run provider: Crabbox owns repo sync, claims, run
metadata, and normalized status; Islo owns Linux sandbox lifecycle and
streaming command execution. No Crabbox broker is involved.
brew upgrade openclaw/tap/crabbox
export CRABBOX_ISLO_API_KEY=ak_...
./scripts/run_crabbox_islo_demo.sh
The remote command calls scripts/bootstrap_bazelisk.sh.
That bootstrap installs Java through SDKMAN CI mode when the image has
no JDK, then installs Bazelisk and runs the Bazel build/test targets.
Metrics
The demo writes summaries into docs/metrics/. Example files
are committed so the page structure is stable. Local and provider runs
generate richer ignored metrics with cache-log evidence.
What worked and what changed
The core Bazel overlay worked after fixing two rules_rust details:
proc macros belong in proc_macro_deps, and
monty-typeshed needs its Cargo build script to run from the
crate directory. The local remote-cache proof worked once the demo used
unique output bases per invocation instead of deleting Bazel trees with
read-only generated files.
Islo worked as the remote Linux runner, but its gateway presents an
Islo Gateway CA certificate to Bazel Central Registry
traffic. Curl trusted the system store; Bazel's embedded JVM did not.
The final bootstrap installs Java with SDKMAN CI mode, imports
/etc/ssl/certs/islo-ca.pem into the SDKMAN Java trust store,
and runs Bazel with that trust store. SDKMAN also required temporarily
disabling set -u, and its global VERSION
variable forced the bootstrap to rename its Bazel version variable.
Crabbox provider=islo is wired and uses Islo as a delegated
provider. It requires CRABBOX_ISLO_API_KEY or
ISLO_API_KEY; Islo CLI login alone does not authenticate
Crabbox runs.