How Harness::builder() Composes a Core for Library Embedding in OpenHuman

The Harness::builder() static method returns a configurable builder object that assembles a fully-initialized core runtime through step-by-step axis configuration, culminating in a single build() call that produces an embeddable Harness instance for library integration.

The tinyhumansai/openhuman repository provides a library-friendly entry point for embedding AI capabilities directly into Rust applications. Understanding how Harness::builder() composes a core for library embedding reveals a three-stage composition pattern that transforms configuration parameters into a running, in-process AI core capable of executing prompts without network overhead.

The Three-Stage Composition Architecture

The composition process implemented in the OpenHuman source code follows a clear pipeline from empty builder to running core. Each stage corresponds to specific source files and responsibilities within the embedding layer.

Stage 1: Builder Creation

In src/embed/harness/builder.rs, the Harness::builder() static method constructs a new Builder struct. This builder holds mutable configuration fields for every runtime axis, including the provider, workspace, access tier, backend URL, MCP servers, skill directories, and tool groups. The builder starts with sensible defaults but remains uninitialized until the caller specifies required dependencies like the AI provider.

Stage 2: Axis Configuration

The caller configures the runtime through a fluent interface of chaining methods. According to src/embed/harness/builder.rs, each method—provider(), workspace(), access(), backend_url(), mcp(), skills_dir(), and tool_groups()—updates the corresponding internal field and returns &mut self to enable further chaining. These methods perform lightweight validation, such as verifying that a Workspace::Dir(path) exists on disk, before returning the builder for the next configuration step.

Stage 3: Core Construction

When the caller finally invokes build(), the builder transfers its collected configuration to CoreBuilder located in src/core/runtime/builder.rs. This internal constructor performs three critical selections:

  • Service set: Configures infrastructure components including HTTP RPC, socket handlers, cron schedulers, channels, and heartbeat monitors.
  • Domain set: Registers business logic domains such as agent, memory, threads, config, and security controllers, plus optional families like flows, skills, and web3.
  • Tool groups: Determines tool visibility—advertised to the LLM, withheld, or turned off entirely.

CoreBuilder then spawns the core's asynchronous task system, registers all domain controllers, installs the tool registry, and wraps the runtime inside an embed::Core handle. This handle is stored within the Harness struct defined in src/embed/harness/mod.rs, exposing high-level methods like run(prompt) and turn(prompt) for library consumers.

Configuring Runtime Axes

The builder pattern provides granular control over the embedding environment through type-safe configuration methods. Each method targets a specific axis of the runtime:

  • provider(): Accepts a Provider enum variant, such as Provider::openai_compatible(base_url, api_key), configuring the LLM backend.
  • workspace(): Defines storage semantics using Workspace::Ephemeral, Workspace::Dir(path), or Workspace::Inherit for persistent or temporary session storage.
  • access(): Sets the access tier and origin permissions, typically via Access::full() for unrestricted library usage.
  • backend_url(): Specifies the TinyHumans API endpoint for authentication and cloud services.
  • mcp(): Registers Model Context Protocol servers via McpServer::stdio(name, command, args) for external tool integration.
  • tool_groups(): Controls tool advertisement using ToolGroups::packed() or ToolGroups::with("media", GroupMode::Off) to hide specific capabilities.

Because the core is embedded in the same process, there is no network hop between the library caller and the runtime—all RPC calls resolve to direct method invocations on the embed::Core handle.

Practical Implementation Examples

The following examples demonstrate common patterns for library embedding found in examples/run_turn.rs and tests/harness_embed.rs.

Basic Single-Turn Usage

This pattern creates a harness, composes the core, and executes a single prompt:

use openhuman::embed::harness::Harness;
use openhuman::provider::Provider;
use openhuman::workspace::Workspace;
use openhuman::access::Access;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let harness = Harness::builder()
        .provider(Provider::openai_compatible(
            "https://api.openai.com/v1",
            std::env::var("OPENAI_API_KEY")?,
        ))
        .workspace(Workspace::Ephemeral)
        .access(Access::full())
        .backend_url("https://api.tinyhumans.ai")
        .build()?;                     // ← core is now created and running

    let response = harness.run("Explain how Harness::builder works.").await?;
    println!("AI says: {}", response.message);
    Ok(())
}

Multi-Turn Session Management

For conversational continuity, reuse the same core across multiple turns:

let harness = Harness::builder()
    .provider(Provider::openai_compatible("https://api.openai.com/v1", "sk-..."))
    .workspace(Workspace::Dir("./my_workspace".into()))
    .access(Access::full())
    .backend_url("https://api.tinyhumans.ai")
    .build()?;                     // core lives for the entire program duration

let mut session = harness.session();   // optional explicit session handle
for prompt in ["First question?", "Second question?"] {
    let reply = session.turn(prompt).send().await?;
    println!("→ {}", reply.message);
}

Customizing Tool Visibility

Control which tool schemas the LLM receives by configuring tool groups before composition:

let harness = Harness::builder()
    .provider(Provider::openai_compatible("...", "..."))
    .tool_groups(ToolGroups::with("media", GroupMode::Off))
    .skills_dir("./skills")
    .build()?;

Summary

  • Harness::builder() in src/embed/harness/builder.rs initiates a three-stage composition process for library embedding.
  • Axis configuration uses fluent methods to set providers, workspaces, access tiers, and tool groups before core instantiation.
  • CoreBuilder in src/core/runtime/builder.rs translates builder state into a running core by selecting service sets, domain sets, and tool visibility rules.
  • The resulting embedded core runs in-process, eliminating network latency while maintaining parity with the full OpenHuman desktop runtime.

Frequently Asked Questions

What source file implements the Harness::builder() method?

The Harness::builder() static method and the associated Builder struct are implemented in src/embed/harness/builder.rs. This file contains the fluent API for configuring providers, workspaces, and access tiers before core construction.

How does workspace configuration affect the embedded core?

The workspace() method accepts Workspace::Ephemeral for temporary storage, Workspace::Dir(path) for persistent state on disk, or Workspace::Inherit to reuse an existing workspace. The builder validates that directory paths exist during the configuration phase, ensuring the core receives valid storage parameters.

What is the difference between service sets and domain sets during core construction?

According to src/core/runtime/builder.rs, service sets configure infrastructure capabilities like HTTP RPC, socket communication, and cron scheduling, while domain sets register business logic controllers for agents, memory, threads, and optional features like skills or web3 integrations. Both sets are selected based on the builder's accumulated configuration before the asynchronous runtime spawns.

Can I run multiple independent prompts on the same composed core?

Yes. After calling build(), the Harness instance owns a long-lived core. You can execute harness.run(prompt) for single-turn interactions or create a persistent session via harness.session() and chain session.turn(prompt).await calls to maintain conversational context across multiple prompts without reconstructing the core.

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 →