OpenHuman Harness API: Run Agent Turns as Library Calls in Rust

The OpenHuman Harness API provides a typed façade that lets embedding hosts configure a HarnessBuilder, instantiate a single process-wide CoreRuntime, and execute agent turns via the run() or turn() methods.

The OpenHuman framework enables developers to embed autonomous AI agents directly into Rust applications. The OpenHuman Harness API abstracts the complex bootstrap logic of the core runtime, exposing a clean interface for running agent turns as ordinary library calls. This article examines the implementation in src/embed/harness/ and demonstrates how to configure providers, manage workspaces, and execute conversational turns.

Initializing the OpenHuman Harness API with HarnessBuilder

The entry point for embedding hosts is the HarnessBuilder defined in src/embed/harness/builder.rs at line 22. This builder collects all runtime dependencies before instantiating the global core state.

Configuration parameters include:

  • Workspace: Ephemeral or persistent directory for agent file operations
  • Provider: Model endpoint configuration via src/embed/harness/provider.rs
  • Access: Permission tiers defined in src/embed/harness/access.rs
  • Session: Authentication context required for custom provider routing
  • Backend URL: Optional redirection for telemetry and session checks away from the hosted TinyHumans backend

When build().await? is invoked, the builder constructs a Config and DomainSet/ServiceSet pair, then passes them to CoreBuilder in src/core/runtime/builder.rs. The implementation enforces a single-instance constraint per process using the HARNESS_LIVE atomic flag, which is cleared only when the Harness is dropped (see src/embed/harness/mod.rs lines 85-88).

Single Process Constraint and Runtime Requirements

Embedding hosts must supply their own Tokio runtime; the Harness does not create one. Per the constants defined in src/core/runtime/mod.rs, the runtime thread stack size must exceed AGENT_WORKER_STACK_BYTES to prevent overflow during sub-agent spawning. Attempting to instantiate a second Harness in the same process will panic due to the HARNESS_LIVE guard.

Executing Agent Turns via the Harness API

Once initialized, the Harness struct in src/embed/harness/mod.rs exposes two methods for running agent turns:

run(message) – Creates a fresh Turn, applies default provider routes and model settings from the builder, then immediately dispatches the request. This method is ideal for stateless interactions.

turn(message) – Returns a mutable Turn builder from src/embed/agent/turn.rs that allows customization of the model, access origin, or session ID before calling .send().await. Use this method when maintaining conversation state or overriding defaults.

Both methods are implemented at lines 38-63 of src/embed/harness/mod.rs and require an active Session because the core gates custom provider routing behind session validation.

Session and Provider Requirements

The Session parameter (e.g., Session::local("my-host")) is mandatory for embedding hosts using custom LLM providers. Without a valid session, the core rejects provider routing requests. For hosts running without TinyHumans backend accounts, the backend_url builder method redirects session checks and telemetry to an alternative endpoint.

Complete Implementation Example

The following Rust code demonstrates a complete embedding host configuration using the OpenHuman Harness API:

use openhuman_core::{
    Access, Harness, Provider, Session, Workspace,
};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Configure the HarnessBuilder with provider and workspace settings
    let harness = Harness::builder()
        .provider(
            Provider::openai_compatible(
                "https://api.openai.com/v1",
                "sk-my-secret-key",
            )
            .model("gpt-4o")
        )
        .workspace(Workspace::Ephemeral)
        .access(Access::full())
        .session(Session::local("my-embedder"))
        .backend_url("https://my-backend.example")
        .build()
        .await?;

    // Execute a simple turn
    let outcome = harness
        .run("Summarize the OpenHuman architecture.")
        .await?;
    println!("Reply: {}", outcome.reply);

    // Continue with a custom turn maintaining session context
    let follow_up = harness
        .turn("What are the security guarantees?")
        .session(&outcome.session_id)
        .send()
        .await?;
    println!("Reply: {}", follow_up.reply);

    Ok(())
}

Key implementation details:

  • Provider::openai_compatible configures a custom inference endpoint via src/embed/harness/provider.rs
  • Workspace::Ephemeral creates a temporary directory bound to the Harness lifetime, handled in src/embed/harness/workspace.rs
  • Session::local satisfies authentication requirements without backend login
  • The turn() method preserves conversation state via the session_id parameter before calling send().await

Summary

  • The OpenHuman Harness API lives in src/embed/harness/ and provides the only sanctioned interface for embedding agents as library calls.
  • HarnessBuilder at src/embed/harness/builder.rs collects configuration and enforces single-process instantiation via the HARNESS_LIVE flag cleared in the Drop implementation.
  • Agent turns execute via run() for immediate dispatch or turn() for customizable interactions requiring mutable state.
  • Embedding hosts must provide a Tokio runtime with sufficient stack size (AGENT_WORKER_STACK_BYTES) and a valid Session for custom provider routing.
  • Backend calls can be redirected via backend_url for hosts operating without TinyHumans cloud accounts.

Frequently Asked Questions

Can I create multiple Harness instances in the same process?

No. The OpenHuman core runtime prohibits multiple concurrent Harness instances. The HARNESS_LIVE atomic flag in src/embed/harness/mod.rs ensures exactly one global CoreRuntime exists per process. Attempting to call build() twice will trigger a runtime panic. To manage multiple agent configurations, you must either restart the process or reconfigure the existing Harness instance.

What is the difference between run() and turn() methods?

The run() method executes a complete agent turn immediately using default configurations from the HarnessBuilder, returning the outcome directly. The turn() method returns a Turn builder from src/embed/agent/turn.rs that allows pre-send customization of models, access tiers, or session identifiers. Use run() for simple, stateless requests and turn() when you need to override defaults or maintain conversational context across multiple calls.

Why does the Harness require a Session for custom providers?

The core gates all custom provider routing behind session validation as a security measure. The Session type—whether obtained via Session::local() for embedded hosts or through backend authentication—proves the host has permission to route inference requests to non-default endpoints. Without an active session, the send() method will return an authentication error when using custom Provider configurations.

How do I configure the Tokio runtime for OpenHuman embedding?

Embedding hosts must initialize their own Tokio runtime before calling Harness::builder(). According to src/core/runtime/mod.rs, the runtime must use a thread stack size of at least AGENT_WORKER_STACK_BYTES (typically 2MB or larger) to accommodate recursive agent spawning. The Harness does not spawn its own async runtime or threads; it relies entirely on the host application's executor.

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 →