OpenHuman DomainSet Runtime Composition Pattern: How Gate Families Control Domain Availability

The DomainSet runtime composition pattern in OpenHuman provides a boolean flag-based system where each gate family (agent, memory, flows, etc.) controls whether entire domain groups register their controllers, stores, and tools at runtime.

The tinyhumansai/openhuman repository implements a sophisticated runtime composition model that allows embedders to precisely control which functional domains are active. Through the DomainSet struct defined in src/core/runtime/builder.rs, developers can toggle entire families of functionality via simple boolean flags, creating custom runtime configurations ranging from full desktop shells to minimal kernel floors.

The Three-Axis Runtime Composition Model

OpenHuman organizes its initialization into three independent composition axes that work orthogonally:

  • ServiceSet – Controls background services and transport layers (HTTP server, Socket.IO, cron jobs)
  • DomainSet – Determines which gate families (domain groups) are available at runtime
  • ToolGroups – Configures how tools are exposed to the model (advertised, withheld, or disabled)

This separation allows you to combine any service configuration with any domain configuration. For example, you can run a headless HTTP API (ServiceSet::headless_api()) while enabling only the memory domain (DomainSet with memory: true), completely bypassing the agent, voice, or web3 families.

DomainSet Struct and Gate Families

Defining Gate Families in builder.rs

The DomainSet struct in src/core/runtime/builder.rs (lines 52-72) defines a boolean flag for each functional family:

/// Selects which domain *families* exist at runtime on a [`CoreRuntime`] (#4796).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DomainSet {
    pub agent: bool,
    pub memory: bool,
    pub threads: bool,
    pub config: bool,
    pub security: bool,
    pub flows: bool,
    pub skills: bool,
    pub mcp: bool,
    pub channels: bool,
    pub web3: bool,
    pub voice: bool,
    pub media: bool,
    pub medulla: bool,
    pub inference: bool,
    pub integrations: bool,
    pub automation: bool,
    pub runtimes: bool,
    pub desktop: bool,
    pub hosted: bool,
    pub modules: bool,
    pub platform: bool,
}

Each field represents a gate family—a logical grouping of controllers, stores, subscribers, and tools. When a flag is false, the entire family remains unregistered, preventing any RPC methods, event handlers, or tools from that domain from entering the runtime.

DomainGroup Enum and Permission Logic

The DomainGroup enum (defined in src/core/all.rs) provides the typed interface for these families. The DomainSet::allows() method in src/core/runtime/builder.rs (lines 87-111) performs the runtime check:

pub fn allows(&self, group: DomainGroup) -> bool {
    match group {
        DomainGroup::Agent => self.agent,
        DomainGroup::Memory => self.memory,
        // … all other groups …
        DomainGroup::Platform => self.platform,
    }
}

During CoreContext::init_with_config, registration code throughout the codebase calls ctx.domains().allows(group) before wiring any components. For instance, the flows controller only registers if self.domains.flows returns true, ensuring disabled families consume no runtime resources.

Runtime Composition Presets

OpenHuman provides five preset configurations in src/core/runtime/builder.rs (lines 122-176) covering common host shapes:

  • DomainSet::full() – Enables every gate family; default for desktop shells
  • DomainSet::harness() – Minimal agent-memory stack (agent, memory, threads, config, security); disables platform and heavy subsystems. Used by examples/embed_headless.rs
  • DomainSet::embedded() – Long-lived embedder configuration adding Medulla, automation, integrations, and platform to the harness, while keeping web3, voice, and media disabled
  • DomainSet::kernel() – Minimal kernel floor with only threads, config, and security; all large subsystems disabled
  • DomainSet::none() – Disables every family, leaving only always-on core infrastructure

These presets return a DomainSet struct with specific boolean configurations, allowing you to start with a template and override individual flags as needed.

Compile-Time vs Runtime Gating

Gate families operate at two levels. Compile-time gating occurs through Cargo feature flags (e.g., voice, web3, media), which control whether crates are included in the binary. The DomainSet pattern adds a runtime gate on top: even if a feature is compiled in, the corresponding domain remains inactive unless its DomainSet flag is true.

Conversely, if a feature is disabled at compile time, the associated flag cannot enable it—DomainSet only controls availability among compiled-in families. This dual-layer approach ensures binaries remain small while offering runtime flexibility for embedded scenarios.

Practical Implementation Examples

Headless API with Selective Domains

Create a headless server that only exposes the memory domain over HTTP:

use openhuman_core::core::runtime::{CoreBuilder, ServiceSet, DomainSet};

let core = CoreBuilder::new(HostKind::Desktop)
    .services(ServiceSet::headless_api())   // RPC HTTP only
    .domains(DomainSet {
        memory: true,                       // keep the memory domain
        ..DomainSet::none()                 // everything else off
    })
    .build()
    .await?;

CLI Tool with Custom Skill Catalog

Embed the core in a CLI tool requiring agent capabilities and custom skills:

let core = CoreBuilder::new(HostKind::Cli)
    .services(ServiceSet::none())            // no background services
    .domains(DomainSet {
        agent: true,
        memory: true,
        threads: true,
        config: true,
        security: true,
        skills: true,        // enable skill runtime
        ..DomainSet::none()
    })
    .tool_groups(
        ToolGroups::default()
            .with("skills", GroupMode::Advertised)   // expose skill tools
    )
    .build()
    .await?;

Minimal Kernel Floor

Use the kernel preset for resource-constrained embedded environments:

let core = CoreBuilder::new(HostKind::Embedded)
    .domains(DomainSet::kernel())   // only threads, config, security
    .services(ServiceSet::none())
    .build()
    .await?;

Summary

  • DomainSet in src/core/runtime/builder.rs uses boolean flags to control twenty distinct gate families
  • The allows() method checks DomainGroup values against these flags during CoreContext initialization
  • Presets like harness() and kernel() provide common configurations for desktop, headless, and embedded hosts
  • Runtime gating operates orthogonally to compile-time Cargo features, ensuring only enabled and compiled-in families register
  • Registration logic in src/core/all.rs verifies ctx.domains().allows(group) before wiring controllers, stores, or tools

Frequently Asked Questions

What is a gate family in OpenHuman?

A gate family is a logical grouping of related runtime components—controllers, stores, event-bus subscribers, and tools—identified by a single boolean flag in the DomainSet struct. Each family (like agent, memory, or web3) can be toggled independently to include or exclude entire functional domains from the core runtime without modifying source code.

How does DomainSet interact with compile-time features?

DomainSet provides runtime gating while Cargo features provide compile-time gating. If a feature (e.g., voice) is disabled at compile time, its code is excluded from the binary and the corresponding DomainSet flag has no effect. If compiled in, the flag controls whether the family registers during CoreContext::init_with_config. This dual-layer system keeps binaries small while allowing runtime flexibility.

What is the difference between ServiceSet and DomainSet?

ServiceSet controls background infrastructure—transports like HTTP servers, Socket.IO, cron schedulers, and notification channels—while DomainSet controls business logic domains (the actual features and capabilities). You can combine any ServiceSet preset with any DomainSet preset, such as running a headless API server (ServiceSet::headless_api()) with only the memory domain (DomainSet with memory: true).

When should I use DomainSet::harness() versus DomainSet::embedded()?

Use DomainSet::harness() for minimal agent-memory testing environments where you need the core agent stack but no platform services or heavy subsystems. Use DomainSet::embedded() for long-lived production embedders that require additional capabilities like Medulla, automation, and integrations while keeping resource-intensive families (voice, media, web3) disabled to conserve memory and CPU.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →