# Harness Builder Pattern in OpenHuman: Constraints and Requirements for Safe Core Initialization

> Learn OpenHuman's Harness builder pattern constraints: workspace isolation, feature-gated skills, and singleton guarantees for safe, deterministic AI core initialization.

- Repository: [Tiny Humans/openhuman](https://github.com/tinyhumansai/openhuman)
- Tags: deep-dive
- Published: 2026-08-26

---

**The Harness builder pattern in OpenHuman enforces strict constraints—including mandatory workspace isolation, feature-gated skill directories, and process-wide singleton guarantees—to prevent state leakage and ensure deterministic initialization of the AI core.**

The `HarnessBuilder` API in the `tinyhumansai/openhuman` repository serves as the mandatory entry point for constructing a fully initialized OpenHuman core. Every configuration method implements concrete invariants that protect against accidental state leakage, unwanted background services, or misconfigured paths. All parameters must be supplied upfront in the builder chain, as the resulting `Harness` instance is immutable after construction.

## Workspace Isolation and Directory Safety Constraints

### Explicit Workspace Configuration

The builder defaults to an **ephemeral workspace** via `Workspace::default()` rather than inheriting the user's home directory. This guarantees a fresh, isolated directory for memory, credentials, and skills unless the caller intentionally opts into shared state. In [`src/embed/harness/builder.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/embed/harness/builder.rs) at lines 45–50, the builder initializes the workspace field to `Workspace::Ephemeral` unless overridden, ensuring no accidental pollution of the host filesystem.

### Directory Restrictions with Workspace::Inherit

When using `Workspace::Inherit`, which links the harness to the operator's existing data directory, the builder explicitly forbids setting `action_dir` or `skills_dir`. As enforced in [`src/embed/harness/builder.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/embed/harness/builder.rs) lines 73–81 and 95–103, attempting to configure these paths while inheriting the workspace returns a `HarnessError::Invalid`. This prevents agent-generated files or skill bundles from being installed into the user's persistent environment, where they would outlive the process and potentially corrupt the installation.

### Skill Directory Feature Gating

The `skills_dir` method is available only when the `skills` feature is enabled at compile time. Even when enabled, the builder validates that the workspace mode is not `Inherit` before accepting a skill directory path. Skill bundles are copied—not linked—into the workspace to maintain the guarantee that the workspace owns all its artifacts, as implemented in the validation logic at lines 95–103 of [`builder.rs`](https://github.com/tinyhumansai/openhuman/blob/main/builder.rs).

## Build-Time Immutability and Service Control

### Fixed MCP Server Registry

MCP (Model Context Protocol) servers must be registered during the build phase and are immutable afterward. The `mcp()` method on the builder adds servers to an internal vector, but once `build().await` is called, the registry becomes static. As noted in [`src/embed/harness/builder.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/embed/harness/builder.rs) lines 107–115, adding servers later would require constructing a new `Harness` instance entirely, ensuring the core's tool capabilities remain deterministic throughout its lifetime.

### Minimal Background Services by Default

Background services default to the minimal set (`harness_init` only), with cron, heartbeat, and memory-queue services disabled. Enabling these services unintentionally makes the embedder unsafe for concurrent usage, as they write to the workspace on a schedule. The builder enforces this conservative default in [`src/embed/harness/builder.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/embed/harness/builder.rs) lines 117–125, requiring explicit opt-in for long-running background tasks.

### Tool Group Schema Visibility

The `tool_groups` method controls schema visibility through three distinct modes: **`Advertised`** (schemas appear on the wire), **`Withheld`** (reachable through `load_skill`/`use_skill` but not exposed), and **`Off`** (omitted entirely). This configuration, processed at lines 138–151 of [`builder.rs`](https://github.com/tinyhumansai/openhuman/blob/main/builder.rs), determines whether external callers can discover available tools via the protocol interface.

## Provider Authentication and Session Requirements

### Custom Provider Session Gate

When supplying a custom-routed provider via the `provider()` method, the builder requires a valid `Session` instance. The `verify_session_active` check at lines 195–207 of [`src/embed/harness/builder.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/embed/harness/builder.rs) enforces that even embedders supplying their own credentials must provide a session to satisfy the provider-gate. Without this, the core would reject the first turn with an authentication error.

### Optional Backend URL Resolution

The `backend_url` parameter is optional; if omitted, the core resolves the URL from the loaded `Config` object. This allows the harness to point at a stub or self-hosted backend while using the same inference endpoint configuration. The resolution logic in [`src/embed/harness/builder.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/embed/harness/builder.rs) lines 176–193 merges the builder's explicit URL with the configuration file's settings, with builder values taking precedence.

## Process Singleton and Lifecycle Guarantees

### Single Harness Per Process Constraint

OpenHuman enforces a strict **one Harness per process** rule using the `HARNESS_LIVE` atomic flag. The builder checks this flag at lines 221–240 of [`src/embed/harness/builder.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/embed/harness/builder.rs) before proceeding with initialization. If a harness already exists, the builder returns an error. If an error occurs during construction, the flag is cleared automatically to keep the process reusable for subsequent attempts.

### Configuration Precedence

While the `config()` method allows overriding approximately 200 low-level configuration fields, the builder's high-level choices—such as workspace type, action directories, and provider settings—take precedence during the merge. This occurs at lines 209–217 of [`builder.rs`](https://github.com/tinyhumansai/openhuman/blob/main/builder.rs), ensuring that safety-critical builder invariants cannot be accidentally subverted via raw configuration access.

## Practical Implementation Examples

### Minimal Safe Embedder with Ephemeral Workspace

This example demonstrates the safest default configuration: an ephemeral workspace with no tool schemas exposed and a single harness guarantee.

```rust
use openhuman_core::Harness;
use openhuman_core::openhuman::tools::toolpacks::ToolGroups;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let harness = Harness::builder()
        .tool_groups(ToolGroups::none())
        .build()
        .await?;

    let out = harness.run("Summarise this repository.").await?;
    println!("{}", out.message);
    Ok(())
}

```

### Custom Provider with Required Session

When routing to a custom OpenAI-compatible endpoint, you must supply both the provider configuration and a session to satisfy the authentication gate.

```rust
use openhuman_core::Harness;
use openhuman_core::embed::{Provider, Session};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let provider = Provider::openai_compatible("http://localhost:1234", "my-key")
        .model("gpt-4o-mini");

    let session = Session::local("my-session-id");

    let harness = Harness::builder()
        .provider(provider)
        .session(session)
        .build()
        .await?;

    let resp = harness.run("Explain the HarnessBuilder constraints.").await?;
    println!("{}", resp.message);
    Ok(())
}

```

### Skill Directory with Non-Inherited Workspace

To load skill bundles, you must use a dedicated workspace directory, not `Workspace::Inherit`, and enable the `skills` feature.

```rust
use openhuman_core::Harness;
use openhuman_core::openhuman::tools::toolpacks::ToolGroups;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let harness = Harness::builder()
        .workspace(openhuman_core::embed::workspace::Workspace::Dir(
            "/tmp/my-workspace".into(),
        ))
        .skills_dir("./my_skills")
        .tool_groups(ToolGroups::advertised())
        .build()
        .await?;

    let resp = harness.run("Run the skill `my_skill`.").await?;
    println!("{}", resp.message);
    Ok(())
}

```

### Adding MCP Servers at Build Time

MCP servers must be registered before calling `build()`, as the registry becomes immutable afterward.

```rust
use openhuman_core::Harness;
use openhuman_core::embed::mcp::McpServer;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let server = McpServer::new("http://localhost:8080")
        .auth("my-token")
        .name("local-mcp");

    let harness = Harness::builder()
        .mcp(server)
        .domains(openhuman_core::core::runtime::DomainSet::embedded())
        .build()
        .await?;

    let out = harness.run("Call the tool `example_mcp`.").await?;
    println!("{}", out.message);
    Ok(())
}

```

## Summary

- **Workspace isolation is mandatory**: Defaults to ephemeral; using `Workspace::Inherit` disables `action_dir` and `skills_dir` to prevent filesystem pollution.
- **Skills require explicit workspace ownership**: The `skills_dir` method is feature-gated and validates that skills are copied, not linked, into isolated workspaces.
- **Configuration is immutable after build**: MCP servers, tool groups, and background services are fixed at initialization; changes require a new `Harness` instance.
- **Custom providers require sessions**: The `verify_session_active` check enforces that custom-routed providers include a valid session object.
- **Process safety is enforced**: The `HARNESS_LIVE` atomic flag in [`src/embed/harness/builder.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/embed/harness/builder.rs) guarantees only one `Harness` exists per process, with automatic cleanup on construction failure.

## Frequently Asked Questions

### Can I change the workspace directory after initializing the Harness?

No. The workspace path is fixed during the `build()` call and cannot be modified afterward. If you need to relocate the workspace, you must drop the existing `Harness` instance (clearing the `HARNESS_LIVE` flag) and construct a new builder with the desired `Workspace::Dir` configuration.

### Why does the builder require a Session when using a custom provider?

The `verify_session_active` check at lines 195–207 of [`src/embed/harness/builder.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/embed/harness/builder.rs) enforces the provider-gate invariant. Even when you supply custom endpoint credentials via `Provider::openai_compatible()`, the core expects a session object to track authentication state and ensure the provider can satisfy incoming requests.

### What happens if I try to create multiple Harness instances in the same process?

The builder will return a `HarnessError::Invalid` when it detects the `HARNESS_LIVE` flag is already set. This atomic flag, checked at lines 221–240 of [`builder.rs`](https://github.com/tinyhumansai/openhuman/blob/main/builder.rs), prevents multiple cores from sharing global state such as credential files or the event bus. You must drop the existing instance before creating a replacement.

### How do I enable skill loading without risking corruption of the user's environment?

Use `Workspace::Dir` to specify a dedicated temporary directory rather than `Workspace::Inherit`. This satisfies the builder's constraint at lines 95–103 of [`builder.rs`](https://github.com/tinyhumansai/openhuman/blob/main/builder.rs), allowing you to set `skills_dir` safely. The builder copies skill bundles into this isolated workspace, ensuring no files persist in the user's home directory after the process terminates.