How to Use the OpenHuman Harness API for Agent Turns as Library Calls
The OpenHuman Harness type provides a Rust builder API that lets you embed the agent turn engine directly into your application without the desktop UI, using run() for first turns and turn().session().send() for follow-ups.
The tinyhumansai/openhuman repository ships a Rust core library (openhuman-core) that houses the agent turn engine, persistence layer, and business logic. By leveraging the Harness API, you can configure LLM providers, autonomy tiers, and workspace persistence entirely through code, making it possible to integrate OpenHuman into servers, automation scripts, or other Rust applications without launching the Tauri desktop shell.
Initializing the Harness with the Builder Pattern
The public entry point for embedding the core is the Harness type, located in src/embed/harness/mod.rs. You construct a Harness once using Harness::builder(), which exposes a fluent API for configuring every aspect of the agent environment:
provider– Configures the LLM endpoint (OpenAI, Claude, etc.) and authentication. Typical value:Provider::openai_compatible(base_url, api_key).model("gpt-4o").workspace– Defines file system persistence. UseWorkspace::Ephemeralfor throw-away runs orWorkspace::Dir(PathBuf::from("./my_ws"))for persistent storage.access– Sets the autonomy tier (read-only, supervised, or full). UseAccess::full()to allow tool calls, file writes, and network requests.session– Provides a credential isolation identifier. Example:Session::local("demo-session").backend_url– Base URL for TinyHumans cloud services (accounts, billing). Optional for offline usage.mcp– Optional MCP (Message-Consumer-Process) server configuration for external tool hosting.skills_dir– Path to directory containing SKILL.md bundles the agent can invoke.
The builder implementation resides in src/embed/harness/builder.rs. When you invoke .build().await?, the harness initializes a Tokio runtime, in-process RPC server, and all domain services (memory, tools, security) pre-wired and ready to accept turns.
Running Agent Turns
Once constructed, the Harness exposes two primary methods for driving conversation turns, as implemented in src/embed/harness/mod.rs:
run(prompt) – Creates a new session, sends the initial prompt to the agent, and returns a TurnResult. This method handles session initialization automatically and is ideal for one-off interactions or when you want to start a fresh conversation context.
turn(prompt).session(&session_id).send().await? – Continues an existing session, preserving tool state, memory, and conversation history. You must pass the session_id returned by the previous turn's TurnResult to maintain continuity.
The TurnResult struct contains the assistant's reply text, any tool call results, and the session ID required for subsequent calls.
Practical Implementation Examples
Minimal One-Off Prompt
For ephemeral, single-turn usage without persistence, configure an ephemeral workspace and run a prompt immediately:
use openhuman_core::embed::harness::{Harness, Provider, Workspace, Access, Session};
#[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::Ephemeral)
.access(Access::full())
.session(Session::local("demo-session"))
.build()
.await?;
let result = harness.run("Summarize the OpenHuman architecture.").await?;
println!("Assistant reply:\n{}", result.output);
Ok(())
}
Relevant source: Harness definition – src/embed/harness/mod.rs; Builder API – src/embed/harness/builder.rs.
Persistent Workspace with Multi-Turn Sessions
To maintain state across multiple turns and allow file system operations, use a persistent workspace and explicitly chain turns using the session ID:
use openhuman_core::embed::harness::{
Harness, Provider, Workspace, Access, Session,
};
#[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::Dir("./my_workspace".into()))
.access(Access::full())
.session(Session::local("my-session"))
.build()
.await?;
let turn1 = harness
.run("Create a file called `hello.txt` with the text “Hello, OpenHuman!”.")
.await?;
println!("Turn 1 reply: {}", turn1.output);
let turn2 = harness
.turn("Read the contents of `hello.txt`.")
.session(&turn1.session_id)
.send()
.await?;
println!("Turn 2 reply: {}", turn2.output);
Ok(())
}
Key files: Workspace handling – src/openhuman/config/schema/types.rs; Access tier definition – src/openhuman/security/policy.rs; Session persistence – src/openhuman/agent/session_db/mod.rs.
Concurrent Server Usage Pattern
For embedding the harness in a web server or any concurrent context, wrap the instance in Arc<Mutex<>> to safely share across multiple request handlers:
use std::sync::Arc;
use tokio::sync::Mutex;
use openhuman_core::embed::harness::{
Harness, Provider, Workspace, Access, Session,
};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let harness = Arc::new(Mutex::new(
Harness::builder()
.provider(Provider::openai_compatible(
"https://api.openai.com/v1",
std::env::var("OPENAI_API_KEY")?,
)
.model("gpt-4o"))
.workspace(Workspace::Dir("./server_ws".into()))
.access(Access::full())
.session(Session::local("http-api"))
.build()
.await?,
));
async fn handle_prompt(
harness: Arc<Mutex<Harness>>,
prompt: String,
session_opt: Option<String>
) -> String {
let mut h = harness.lock().await;
let result = if let Some(sess) = session_opt {
h.turn(&prompt).session(&sess).send().await
} else {
h.run(&prompt).await
}
.expect("turn failed");
format!("{}\n---session-id---\n{}", result.output, result.session_id)
}
Ok(())
}
Important files: Core entry point – src/lib.rs; Session handling – src/openhuman/agent/session_db/mod.rs.
Summary
- Use
Harness::builder()(defined insrc/embed/harness/builder.rs) to configure the LLM provider, workspace persistence, autonomy tier, and session isolation before initialization. - Invoke
run(prompt)to execute first-turn operations or start new sessions automatically. - Continue existing conversations with
turn(prompt).session(&id).send(), passing thesession_idfrom the previousTurnResultto preserve context. - Configure
Workspace::Ephemeralfor stateless execution orWorkspace::Dir(path)for persistent file system access and tool state. - Wrap the
HarnessinArc<Mutex<>>when integrating into concurrent servers or multi-threaded applications.
Frequently Asked Questions
How do I configure the LLM provider when using the Harness API?
Pass Provider::openai_compatible(base_url, api_key) to the builder's provider() method, then chain .model("gpt-4o") or your desired model identifier. This configuration is processed in src/embed/harness/builder.rs and supports OpenAI, Claude, or any OpenAI-compatible endpoint by adjusting the base URL and authentication headers.
What is the difference between run() and turn() in the Harness API?
run() initializes a new session automatically and is designed for first-turn execution or stateless interactions. turn() requires you to specify an existing session ID via the .session() builder method and is used for follow-up messages that must retain conversation history and tool state. Both methods are implemented in src/embed/harness/mod.rs and return a TurnResult containing the assistant's output and the active session ID.
Can I use the Harness API without writing files to disk?
Yes. Configure the builder with Workspace::Ephemeral instead of Workspace::Dir. This setting creates a temporary in-memory workspace that is discarded when the Harness is dropped, making it ideal for serverless functions or CI/CD pipelines where persistence is undesirable. The workspace configuration is defined in src/openhuman/config/schema/types.rs.
Is the Harness API thread-safe for high-concurrency web servers?
The Harness type itself is not Clone or inherently thread-safe, but you can safely share it across async tasks by wrapping it in Arc<Mutex<Harness>>. Lock the mutex at the request entry point to serialize access to the agent turn engine, then release it after the TurnResult is obtained. This pattern allows multiple HTTP handler threads to utilize a single initialized harness without race conditions.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →