Understanding OpenHuman Feature Gates and Cargo Features: A Two-Level Architecture
OpenHuman implements a dual-layer gating system that combines compile-time Cargo features with runtime Domain gates to minimize binary size while supporting optional capabilities like voice, web3, and media generation.
The tinyhumansai/openhuman repository employs a sophisticated feature management strategy that separates what gets compiled from what gets exposed at runtime. Understanding OpenHuman feature gates and Cargo features is essential for developers embedding the core library or contributing to the desktop application.
The Two-Level Feature Model
OpenHuman distinguishes between Contributor and Product feature sets to balance development convenience against shipping constraints.
Contributor vs. Product Features
-
Contributor (Default) Features: Defined in the
[features] defaultsection ofCargo.toml, this set controls what compiles duringcargo checkorcargo test. It includes the kernel surface and inexpensive optional dependencies for local development. -
Product Features: Defined in
scripts/ci/product-features.txtand the rootCargo.toml, this set determines what ships in the desktop application. It includes heavier dependencies such aslibgit2-sys,zstd-sys, and user-facing domain implementations.
The product feature set is forwarded to the Tauri shell in app/src-tauri/Cargo.toml to ensure the desktop build matches the core library exactly.
Runtime Domain Gates
Beyond compile-time flags, the core implements a runtime DomainSet that filters entire domain families at program start.
The DomainSet Builder API
Each domain corresponds to a directory under src/openhuman/ (e.g., voice, web3, flows). The builder API allows embedders to select presets:
let harness = Harness::builder()
.domains(DomainSet::full()) // all domains enabled
.domains(DomainSet::harness()) // only core agent + memory + threads
.build()
.await?;
When a domain is disabled, its RPC controllers are completely omitted (clients receive unknown-method errors) and associated agent tools disappear from the tool list. Unlike simple #[cfg] guards, the code path is absent rather than guarded by a runtime check.
How Features and Domains Interact
Feature gates compile-time-remove code and transitive dependencies, while Domain gates filter already-compiled controllers at runtime.
A domain is usually tied to a feature:
-
voicefeature: Enables thevoicedomain with STT/TTS providers, dictation servers, and podcasts (note: local Whisper was removed; STT now requires hosted providers). -
web3feature: Enables theweb3domain with multi-chain crypto wallets, EIP-712 signing, and Solana RPC endpoints. -
mediafeature: Enables themediadomain for image and video generation tools (agents only, no controllers). -
flowsfeature: Enables theflowsdomain with the workflow engine (tinyflows), Rhai scripting, and 25+ workflow-related agent tools. -
modulesfeature: Enables themodulesdomain for dynamic native modules (tinydocs,tinymemory, etc.) loaded at runtime via the TinyBus ABI insrc/openhuman/modules/registry.rs.
When a feature is disabled, the corresponding domain is automatically excluded from DomainSet presets. Conversely, a domain can be turned off at runtime even if its feature is compiled in.
Required-Features for Compile-Time Exclusion
Some domains use Cargo’s required-features entry to force crate omission when a feature is off. For example, the memory-git domain adds git2 and libz-sys; when the memory-git feature is omitted, tests depending on these crates are automatically skipped. This prevents compilation errors for developers who do not need Git-based memory features.
CI Enforcement and Feature Forwarding
The repository includes a feature-forwarding check in scripts/ci/check-feature-forwarding.mjs that verifies:
- The desktop shell forwards exactly the product feature list to the core.
- Every name in the product list is a real core feature.
- Any default-on feature not meant for the product is either forwarded or explicitly whitelisted.
If forwarding drifts (e.g., a feature disappears from the product build), the CI job fails.
Kernel Floor Ratchet
The scripts/kernel-floor.sh tool measures the minimal dependency footprint for the kernel profile (--no-default-features --features flows). The target is approximately 302 packages and 2 native builds. Adding a feature that drastically increases this count requires a deliberate decision and an update to the floor limits file.
Practical Implementation Examples
Building a Harness with Custom Domains
use openhuman_core::{Harness, DomainSet, ServiceSet};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Enable only core agent, memory, and threads domains
let harness = Harness::builder()
.domains(DomainSet::harness())
// Disable background services (no HTTP server, socket.io, etc.)
.services(ServiceSet::none())
.build()
.await?;
let response = harness.run("Summarize the Open Human architecture.").await?;
println!("{}", response.output);
Ok(())
}
DomainSet::harness() selects a minimal set (Agent, Memory, Threads, Config, Security), omitting voice, web3, and flows controllers as defined in src/core/runtime/builder.rs.
Enabling Features at Build Time
# Build full desktop app with voice and web3
cargo build --release --features "voice web3"
This compiles the corresponding submodules in src/openhuman/voice/ and src/openhuman/web3/, exposing their RPC namespaces and agent tools.
Compiling a Minimal Kernel
# Compile only kernel profile
cargo build --release --no-default-features --features "flows"
This disables default-on contributor features (e.g., rusqlite, ring) while keeping the flows domain, resulting in approximately 300 packages as enforced by the kernel-floor check.
Summary
- Cargo features in
Cargo.tomlcontrol compilation and third-party dependencies. - Domain gates in
src/core/all.rsandsrc/core/runtime/builder.rsfilter runtime exposure of RPC endpoints and agent tools. - CI checks enforce feature forwarding consistency and kernel size constraints.
- Adding domains requires updating
DomainGroupenums, preset builders, and CI validation scripts.
Frequently Asked Questions
What is the difference between Cargo features and Domain gates in OpenHuman?
Cargo features determine what code is compiled and which dependencies are linked, controlled via Cargo.toml and required-features. Domain gates are runtime filters implemented in DomainSet that determine which RPC controllers and agent tools are active after compilation.
How do I build a minimal OpenHuman kernel without heavy dependencies?
Use cargo build --release --no-default-features --features "flows" to compile only the kernel profile. This excludes heavy dependencies like libgit2-sys and rusqlite, keeping the binary near the 302-package floor limit enforced by scripts/kernel-floor.sh.
Why does the CI fail when I add a new feature to Cargo.toml?
The scripts/ci/check-feature-forwarding.mjs script validates that the desktop shell forwards exactly the product feature list. If you add a feature to the core but do not update the forwarding rules or whitelist it as a default-only feature, the CI will fail to prevent accidental omission from shipped builds.
Can I disable a domain at runtime even if its feature is compiled in?
Yes. The DomainSet builder API allows runtime filtering of domains regardless of compile-time features. For example, you can compile with --features "voice web3" but instantiate Harness with DomainSet::harness() to disable voice and web3 endpoints at runtime while keeping the binary size.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →