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:

  1. seed: build and test with cache upload enabled.
  2. reader: build and test from a separate sandbox with cache upload disabled.
  3. 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.