Skip to main content
Meerkat is a large Rust workspace. The featured install paths are prebuilt: the Homebrew tap, the GitHub Release archives, and the SDKs’ automatic rkat-rpc download. This page is for everyone else: people who run cargo install rkat, embed the crates in their own binary, build inside a container, or need to know where the prebuilt binaries will and will not run. Every number below was measured against this repository; the memory and time figures come from one cold build described in Build Cost.

What A Build Compiles

The workspace has ~50 member crates. Its lockfile resolves ~670 package entries (~600 distinct crates) for the whole workspace with every feature, dev-dependency, and optional backend enabled. A default-feature cargo install rkat compiles ~380 crates. Most of those are ordinary. The cost is concentrated in a few very large first-party crates, each of which is one rustc and LLVM invocation: These four, plus the final rkat crate that pulls the whole surface into one binary, dominate both wall time and peak memory. The optional consumer profiles below therefore focus on job limits and per-package opt-level pins rather than only the dependency count; they are not the current Windows release recipe. Toolchain and native requirements:
  • The repository pins Rust 1.94.1 in rust-toolchain.toml; make install-build-deps installs exactly that toolchain into a checkout. The published crates declare rust-version = "1.94.0", so cargo install rkat needs Rust 1.94 or newer.
  • The default feature set needs a C compiler and libc headers (bundled SQLite and a few other C sources compile through the cc crate) and nothing else. TLS is rustls; no OpenSSL development package is required and the release gate rejects binaries that link one.
  • cmake is needed only by the optional live-webrtc feature of the meerkat facade and meerkat-rpc crates (meerkat-live webrtc, bundled Opus through audiopus_sys). rkat has no such feature, and no shipped binary enables it by default, so a cargo install of any of them never compiles it.

Build Cost

Two cold builds, measured for this page:
Both ran from an empty target directory with default features and Rust 1.94.1 on an 18-core Apple M5 Max with 128 GiB of RAM, so nothing was memory-constrained and Cargo ran 18 jobs in parallel. The first used Cargo’s default release profile; the second used the low-memory profile below (opt-level = 1 globally, opt-level = 0 for meerkat-machine-schema), with the same job count so the per-crate peaks are comparable. Peaks were sampled once per second from ps. How to translate that to your host:
  • The largest single crate is the floor, and only opt-level = 0 moves it. Even with CARGO_BUILD_JOBS=1, the build needs as much memory as its biggest rustc process. In both runs above that was meerkat-mob at ~11 GiB: going from opt-level = 3 to 1 halved the aggregate peak, the rkat crate, and meerkat-machine-schema, but left meerkat-mob where it was. A third run (the same rkat build with the low-memory profile plus meerkat-mob pinned to opt-level = 0) brought meerkat-mob down to ~6.3 GiB, after which the largest process is meerkat-runtime at ~7.4 GiB. Below that, the levers are the feature set (use --no-default-features and omit mob, rpc-surface, and the CLI openai-live feature to avoid their mob dependencies in a reduced production build), swap, and prebuilt binaries. See Features for scope and limits.
  • Aggregate memory scales with the job count. Cargo runs up to one rustc per core. The aggregate peaks above happened while four or five large first-party crates were compiling at once; with N jobs, budget for the N largest crates that can be in flight together. meerkat-mob, meerkat-runtime, and meerkat-machine-schema did overlap in the default build, and that overlap produced its peak.
  • Wall time scales with cores only up to the serial tail. The hundreds of small dependencies compile in parallel in the first few minutes, but the large first-party crates depend on each other, and the final rkat crate compiled alone for roughly the last third of the wall time. A 4-core cloud VM should expect a few times the wall time above, and the tail does not shrink with more cores. cargo install also has no warm cache to reuse, so every install is a cold build.
  • Disk is transient for cargo install. The temporary target directory is deleted after the binary is installed, but it must fit while the build runs. A checkout that keeps its target/ needs the full figure per profile.
The current Windows release recipe is separate from these local measurements. In .github/workflows/release.yml, build_binaries_windows_cross runs on ubuntu-latest and invokes .github/actions/build-windows-cross/action.yml. That action uses cargo-xwin with clang-cl, lld-link, and the Windows SDK to cross-compile the four surface binaries and package their ZIP archives. It adds an 8 GiB swap file when total swap is below 8 GiB; it does not set CARGO_BUILD_JOBS=2 or inject the per-package optimization pins shown below. The subsequent verify_windows_binaries job runs on windows-latest, extracts the archives, executes each binary’s --version, and checks rkat --help; it does not compile them natively. These workflow settings are not new wall-time or memory measurements. Continuous integration (.github/workflows/ci.yml) uses BuildBuddy for its broad lane and hosted Linux for the required dense Mob topology stress; release binaries are produced by the separate release workflow. Rough sizing, derived from the peaks above:

Lowering Memory

Where profile settings come from

Cargo reads [profile.*] tables only from the root manifest of the build it is running. Two consequences matter here:
  • The Meerkat workspace ships no [profile.release], so its own release builds run at Cargo’s defaults (opt-level = 3, codegen-units = 16, lto = false, debug = false) except where a lane injects pins.
  • Even if it did, that would not reach you. cargo install rkat unpacks the published rkat crate as the root of its own temporary workspace; the repository’s root Cargo.toml is not part of that crate. A downstream binary that depends on meerkat = "=0.8.32" is likewise governed by its own root manifest, never by Meerkat’s.
So the consumer sets the profile. cargo install honors Cargo configuration from a .cargo/config.toml in the directory you run it from (or ~/.cargo/config.toml), from CARGO_PROFILE_RELEASE_* environment variables, and from --config on the command line. This was verified for this page with cargo install -v: with CARGO_PROFILE_RELEASE_OPT_LEVEL=1 set, rustc receives -C opt-level=1. Library embedders put the same tables in their own root Cargo.toml.

Low-memory profile

For hosts under 16 GiB, add the small-host pin. It is the only setting that moved meerkat-mob (~11.5 GiB at opt-level = 1, ~6.3 GiB at 0); it trades CPU on the orchestration path for memory, so measure your workload before shipping a binary built this way:
As an optional per-package alternative, keep everything else at full optimization by dropping the global opt-level line and keeping these pins. This is a consumer profile choice, not the current Windows release recipe:
The same settings on one command line, without a config file:
Per-package profile keys have no environment-variable form, which is why the package pin goes through --config; repeat the flag with profile.release.package.meerkat-mob.opt-level=0 for the small-host pin.

What each lever does

  • CARGO_BUILD_JOBS (or -j) caps the number of concurrent rustc processes. Aggregate memory is the sum of the compilers running at once, so this is the first lever on any memory-limited host. Wall time rises roughly in proportion.
  • opt-level: going from 3 to 1 removes most of LLVM’s expensive passes. Measured on the crates whose peak is in LLVM, it halved the rkat crate (~10.1 to ~4.8 GiB) and, together with the opt-level = 0 pin, meerkat-machine-schema (~9.4 to ~4.8 GiB), and it cut the whole build’s wall time from ~25 min 29 s to ~19 min 6 s. It did not move meerkat-mob; only opt-level = 0 did (~11.5 to ~6.3 GiB). The runtime cost of 1 is modest for an agent host that spends its time waiting on network I/O. opt-level = 0 is free for meerkat-machine-schema because it is a generated catalog, not hot code; on meerkat-mob it is a real trade.
  • codegen-units: LLVM optimizes a crate’s codegen units in parallel, bounded by your core count, so a crate split into 16 units can hold 16 LLVM contexts at once. Lowering it reduces that per-crate parallelism at the cost of a longer compile for that crate, and codegen-units = 1 also widens inlining scope, which can raise memory for the largest crates. Measure on your host before changing it; the release lane leaves it at 16.
  • lto: leave it at the default false. "thin" or "fat" LTO pulls the whole dependency graph into one optimization pass at link time and multiplies link-stage memory.
  • Features: crates/meerkat-cli/Cargo.toml lists the subsystems behind rkat’s default features (mob, rpc-surface, openai-realtime, comms, schedule, workgraph, mcp, skills, storage backends, providers). A narrower --no-default-features --features ... production build can remove the mob graph only if it omits mob and all transitive enablers, including rpc-surface (which selects meerkat-rpc/mob) and the CLI openai-live feature (which selects mob). Omitting just the direct mob feature while keeping rpc-surface still builds meerkat-mob and meerkat-mob-mcp. The repository’s surface feature matrix (scripts/run-surface-feature-matrix, behind make test-feature-matrix-surface) keeps three reduced rkat combinations compiling: --no-default-features --features session-store, session-store,mcp, and session-store,comms,mcp,workgraph. Other subsets are not exercised, so check that yours builds before relying on it. Reduced builds are source builds only; no reduced binaries are published (see the note under Homebrew Tap). This reduction is for the selected production package: workspace/test feature unification and dev-dependencies can bring mob back, and removing it is not a guarantee that every host below 8 GiB can compile the remaining graph.

Worker Stack Budget

Every host binary (rkat, rkat-rpc, rkat-rest, rkat-mcp) runs its Tokio workers, blocking threads, and its own main future on one documented stack budget: 8 MiB per thread, the constant meerkat_runtime::host_stack::HOST_WORKER_STACK_BUDGET. The main future runs on a budgeted thread as well, so the platform main thread (1 MiB on Windows) never bounds a host. The number is measured, not guessed. On the RPC router harness (session create, turn start, session archive through the real method router) a release build fits 1 MiB and overflows at 512 KiB; a debug build, which the e2e lanes run, fits 4 MiB and overflows at 3 MiB because opt-level 0 gives every branch of a function its own stack slots. 8 MiB is twice the debug figure and eight times the release one. It is virtual address space per thread; pages are committed only when touched. Two gates hold the figures: a debug canary in the meerkat-rpc unit lane at 4 MiB and the nightly make stack-budget-release at 1 MiB in release. RKAT_WORKER_STACK_BYTES=<bytes> overrides the budget for stack-overflow diagnosis. Sweeping it distinguishes a bounded large frame (fails below one value, passes above) from unbounded recursion (fails at every value). Values below 65536 or that are not a byte count are refused with exit code 2 rather than guessed at. rkat-rpc still honours its older RKAT_RPC_WORKER_STACK_BYTES when the shared variable is unset.

Containerizing The Binaries

A Rust binary built for a *-unknown-linux-gnu target links the builder’s glibc dynamically and records the newest glibc symbol version it used. The runtime image must provide a glibc at least that new, or the loader refuses to start the binary with version 'GLIBC_2.xx' not found. The reliable way to get that right in a two-stage image is to pin one Debian release for both stages, so the builder’s glibc is the runtime’s glibc:
Notes on that file:
  • ARG DEBIAN_RELEASE is declared before the first FROM and used in both FROM lines, so the two stages cannot drift apart when someone bumps one of them. rust:1.94-bookworm matches the pinned toolchain family.
  • --locked builds with the Cargo.lock shipped inside the rkat crate, so the container compiles the dependency set the release was tested with.
  • To build from a checkout instead of crates.io (unreleased commits, local patches), replace the RUN cargo install line with WORKDIR /src, COPY . ., and cargo build --locked -p rkat --release, and copy /src/target/release/rkat (the build stage above sets no WORKDIR, so without that line the checkout lands in /). The repository’s own examples/035-mdm-tux-rs/Dockerfile does exactly that for the example’s binaries, with the same rust:1.94-bookworm build stage and debian:bookworm-slim runtime stage.
  • The companion surfaces are separate crates with the same shape: cargo install meerkat-rpc (binary rkat-rpc), meerkat-rest (rkat-rest), and meerkat-mcp-server (rkat-mcp).
  • rkat resolves project realm state under .rkat/ in its working directory and the global realm’s config under ~/.rkat/ (see Configuration); mount a volume at /data, and point HOME at persisted storage if the global realm matters, so state outlives the container.
If you do not need to compile at all, the runtime stage alone can unpack a prebuilt Linux release archive. debian:bookworm-slim ships glibc 2.36, above the 2.31 floor the release binaries are built against (next section):
Verify the download against the release’s checksums.sha256 (and, if you use the GitHub CLI, its build provenance attestation; see below) before trusting it in production. Not covered by the prebuilt binaries: Alpine and other musl-based images. No musl target is published, and the glibc binaries are not tested under gcompat. Build from source on such a base or use a Debian-family image.

Where The Prebuilt Binaries Come From

.github/workflows/release.yml builds and publishes the release assets when a v* tag is pushed. Each of the four binaries (rkat, rkat-rpc, rkat-rest, rkat-mcp) is packaged per target as <binary>-<version>-<target>.tar.gz (.zip on Windows), alongside checksums.sha256 and index.json. The Homebrew tap and the SDK bootstrap consume these same assets. Targets:
  • x86_64-unknown-linux-gnu
  • aarch64-unknown-linux-gnu
  • aarch64-apple-darwin
  • x86_64-apple-darwin
  • x86_64-pc-windows-msvc
Every archive carries a GitHub build provenance attestation (actions/attest-build-provenance), which binds the file’s digest to the workflow run and commit that produced it:

Linux glibc floor

The Linux GNU binaries are built inside the buildpack-deps:bullseye image (Debian 11, glibc 2.31) on both release lanes: the BuildBuddy lane pins that image as the execution container in tools/bazel/platforms/BUILD.bazel, and the GitHub-hosted lane runs its Linux jobs under the same container:, because building directly on the Ubuntu 24.04 runner (glibc 2.39) would silently raise the floor. Packaging then runs scripts/check-linux-release-binary-portability.sh on every Linux binary and fails the release if either check trips:
  1. Any versioned glibc symbol reference is newer than the declared floor (MEERKAT_GLIBC_FLOOR=2.31).
  2. The binary has a dynamic NEEDED entry on libssl or libcrypto. All first-party TLS is rustls, so an OpenSSL dependency means a crate regressed into a native-TLS stack.
The gate exists because v0.8.21 shipped binaries referencing GLIBC_2.34 that Debian 11 could not load; the floor is enforced on the produced file, not assumed from the build image. What that guarantees: a published Linux binary starts on any x86_64 or aarch64 distribution whose glibc is 2.31 or newer (Debian 11 and later, Ubuntu 20.04 and later, Debian-based container images from bullseye onward) and needs no OpenSSL runtime package. What it does not guarantee: musl-based systems (see above), or distributions older than glibc 2.31.

Other platforms

The macOS binaries carry no notarization and the release workflow asserts no minimum macOS version (there is no deployment-target pin or gate); the GitHub-hosted lane ad-hoc codesigns them. The Windows binary is x86_64 only. The packaging step checks that every binary embeds the release version, so rkat --version on an installed binary tells you exactly which release it is.

MobKit

MobKit’s gateway binaries are built and released by the meerkat-mobkit repository’s own lane; the glibc floor and portability gate described on this page do not apply to them. Start from the MobKit quickstart for its install and build paths.

See Also

  • CD and distribution for the release pipeline, Homebrew tap, SDK bootstrap, and credentials.
  • Build And CI for the contributor build, test, and BuildBuddy lanes.
  • Quickstart for the featured install paths.
  • Upgrading for per-release upgrade steps, including shutdown deadlines and termination grace periods.