How to Embed OpenHuman Core as a Rust Library Using Harness

You can embed OpenHuman's AI agent core in any Rust application by using the Harness builder API to configure the LLM provider, workspace, and access tier, then calling run() or turn() to execute agent turns.

The openhuman crate from the tinyhumansai/openhuman repository exposes its core engine as a reusable Rust library. The primary entry point for programmatic integration is the Harness type, which provides a fluent builder interface to assemble a fully functional AI agent that matches the behavior of the desktop product.

Understanding the Harness Architecture

Harness encapsulates everything required to run an AI agent turn, including the LLM provider configuration, persistence layer, security gates, and optional cloud services. According to the source code in src/embed/harness/mod.rs, this design ensures that the core is instantiated once per process and can be reused across any number of conversational turns.

The builder pattern is implemented in src/embed/harness/builder.rs and enforces compile-time safety for required configuration components. Once constructed via Harness::builder().build().await, the instance manages its own async runtime and security boundaries, making it safe to embed into existing applications without disrupting their execution model.

Configuring the Harness Builder

Setting Up the LLM Provider

The Provider struct (defined in src/embed/harness/provider.rs) supports OpenAI-compatible endpoints, Claude, and other LLM backends. You must specify the base URL, API key, and model name:

use openhuman::embed::harness::{Harness, Provider, Workspace, Access};

let harness = Harness::builder()
    .provider(
        Provider::openai_compatible(
            "https://api.openai.com/v1",
            std::env::var("OPENAI_API_KEY")?,
        )
        .model("gpt-4o"),
    )
    .workspace(Workspace::Ephemeral)
    .access(Access::full())
    .build()
    .await?;

Choosing a Workspace Strategy

Workspace configuration (located in src/embed/harness/workspace.rs) determines how the agent handles persistent state and temporary files:

  • Workspace::Ephemeral – In-memory sandbox with no disk persistence
  • Workspace::Dir(path) – Dedicated directory for agent files and tool workspaces
  • Workspace::Inherit – Reuses the parent process environment

Defining Access Permissions

The access tier (defined in src/embed/harness/access.rs) controls which tools and policies the embedded agent may invoke:

  • Access::full() – Unrestricted tool access
  • Access::supervised() – Requires approval for sensitive operations
  • Access::readonly() – Blocks all mutation operations

Optional Cloud Backend and Services

You can optionally connect to TinyHumans cloud services by specifying a backend URL in src/embed/harness/mod.rs. For extended functionality, src/embed/harness/mcp.rs exposes methods like mcp(), services(), and tool_groups() to enable micro-container protocols, cron jobs, and channel communications that mirror the full desktop product capabilities.

Running Agent Turns

Once built, the Harness exposes two primary methods for execution:

  • run(prompt) – Initiates a new conversation session
  • turn(prompt) – Continues an existing session using a session ID

Both methods return a TurnResult containing output_text, generated tool calls, and the session_id for stateful multi-turn conversations. All I/O operations pass through the same security and approval gates used by the official UI.

// First turn creates a session
let first = harness.run("Summarise the repository.").await?;
println!("{}", first.output_text);

// Continue the same session
let second = harness
    .turn("Now detail the build process.")
    .session(&first.session_id)
    .send()
    .await?;

Complete Integration Examples

Single-Turn Execution

For one-off queries without persistence, use an ephemeral workspace and the run method:

use openhuman::embed::harness::{Harness, Provider, Workspace, 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")?,
            )
            .model("gpt-4o"),
        )
        .workspace(Workspace::Ephemeral)
        .access(Access::full())
        .build()
        .await?;

    let result = harness.run("Explain how OpenHuman's architecture works.").await?;
    println!("Reply:\n{}", result.output_text);
    Ok(())
}

Multi-Turn Session Management

To maintain context across multiple prompts, capture the session_id from the initial turn and pass it to subsequent turn calls:

#[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")?,
            )
            .model("gpt-4o-mini"),
        )
        .workspace(Workspace::Dir("my_workspace".into()))
        .access(Access::full())
        .build()
        .await?;

    let first = harness.run("List the main modules in this repo.").await?;
    println!("First reply: {}", first.output_text);

    let second = harness
        .turn("Give a brief description of each module.")
        .session(&first.session_id)
        .send()
        .await?;
    println!("Second reply: {}", second.output_text);
    Ok(())
}

Embedding in a Third-Party Crate

Add the dependency to your Cargo.toml:

[dependencies]
openhuman = { path = "../openhuman", features = ["full"] }
tokio = { version = "1", features = ["full"] }

Then expose the functionality through your library:

pub async fn embed_core() -> anyhow::Result<String> {
    let harness = openhuman::embed::harness::Harness::builder()
        .provider(
            openhuman::embed::harness::Provider::openai_compatible(
                "https://api.openai.com/v1",
                std::env::var("OPENAI_API_KEY")?,
            )
            .model("gpt-4o"),
        )
        .workspace(openhuman::embed::harness::Workspace::Ephemeral)
        .access(openhuman::embed::harness::Access::full())
        .build()
        .await?;

    let resp = harness.run("What is the purpose of the src/openhuman/web3 package?").await?;
    Ok(resp.output_text)
}

Summary

  • Single entry point: The Harness type in src/embed/harness/mod.rs provides the primary API for embedding OpenHuman core
  • Builder pattern: Use Harness::builder() to configure Provider, Workspace, and Access tiers before calling build().await
  • Session management: run() creates new conversations while turn() continues existing ones using session IDs
  • Security parity: Embedded instances use identical security gates and tool policies as the desktop application
  • File locations: Key implementations reside in src/embed/harness/builder.rs, provider.rs, workspace.rs, and access.rs

Frequently Asked Questions

What is the difference between Harness::run() and Harness::turn()?

Harness::run() initiates a fresh conversation session and returns a TurnResult containing a new session_id. Harness::turn() requires chaining .session(&existing_session_id) to continue a previous conversation, maintaining context and accumulated state from prior turns.

How do I configure the Harness to use Claude instead of OpenAI?

According to src/embed/harness/provider.rs, replace Provider::openai_compatible() with the Claude-specific constructor (or compatible endpoint URL) and adjust the model identifier accordingly. The provider system abstracts the underlying LLM implementation while maintaining a consistent interface for the Harness.

Can I use a custom Tokio runtime instead of the one spawned by Harness?

Yes. While Harness::builder().build().await spawns an internal runtime by default, you can supply a custom Tokio runtime configuration through the runtime() method on the builder, as indicated in src/embed/harness/mod.rs. This allows integration with existing async application architectures.

What permissions does the Access::full() tier grant to the embedded agent?

Access::full() enables all available tools and removes policy restrictions, allowing the agent to execute file operations, network calls, and system commands without additional approval gates. For restricted environments, use Access::supervised() or Access::readonly() as defined in src/embed/harness/access.rs.

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 →