# Embedding OpenHuman: Harness vs CoreBuilder and Tokio Runtime Stack Requirements

> Compare Harness and CoreBuilder for embedding OpenHuman. Understand Tokio runtime stack requirements and choose the best approach for your project.

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

---

**Harness provides a high-level, one-call API for library-style embeddings that automatically manages the `CoreRuntime` lifecycle and workspace ownership, while CoreBuilder offers a low-level composable surface for hosts that already own a runtime, and both require configuring Tokio with `AGENT_WORKER_STACK_BYTES` (larger than the default 2 MiB) to prevent stack overflow during agent turns.**

The OpenHuman framework from `tinyhumansai/openhuman` supports two distinct embedding strategies for integrating AI agents into Rust applications. Whether you are building a standalone script or embedding agents into an existing application like the desktop Tauri shell, choosing between the convenience wrapper and the composable builder interface determines how you manage the asynchronous runtime, workspace persistence, and background services.

## Architectural Differences Between Harness and CoreBuilder

### Purpose and Abstraction Level

**Harness** acts as a *one-call front door* designed for **library-style embeddings**. It constructs a `CoreRuntime` internally, applies default configurations for the provider and access tier, and exposes a simple `run` / `turn` API. According to the source in [`src/embed/harness/mod.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/embed/harness/mod.rs), the harness is intended for hosts that do not already manage a `CoreRuntime` and need a managed workspace lifecycle.

**CoreBuilder**, defined in [`src/core/runtime/builder.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/runtime/builder.rs), is the **lower-level composable surface**. It is used when the host application already owns a `CoreRuntime`—such as the desktop Tauri shell or a custom API server—and requires fine-grained control over services, domains, and tool groups. The host manipulates the runtime directly via the `embed::Core` facade.

### Runtime Ownership and Creation

When you invoke `Harness::builder()`, the harness internally instantiates a `CoreBuilder`, configures it with defaults, calls `.build().await`, and stores the resulting `Core` inside the harness instance. The harness then supplies the high-level `run` method.

With **CoreBuilder**, you invoke `CoreBuilder::new(host_kind)` directly, chain configuration methods like `.services()`, `.domains()`, and `.workspace()`, and call `.build().await` to obtain a `CoreRuntime` that the host fully controls.

### Workspace Lifecycle Management

The **Harness** owns a **resolved workspace** (`ResolvedWorkspace`) that is created during the build phase. If configured with `Workspace::Ephemeral`, the workspace is automatically removed when the harness is dropped, guaranteeing the workspace lives exactly as long as the harness session.

In contrast, **CoreBuilder** allows the caller to set an explicit workspace via `.workspace(dir)` or `.action_dir(dir)`. The host manages the lifetime, enabling multiple `CoreRuntime` instances to share the same workspace directory if desired.

### Process-Wide Constraints

The harness enforces a **one-harness-per-process rule** using a process-wide `AtomicBool` (`HARNESS_LIVE`). Attempting to create a second harness returns `HarnessError::AlreadyRunning`. This restriction protects global state such as the keyring master key, RPC bearer, and event bus.

`CoreBuilder` imposes no such restriction; multiple `CoreRuntime` instances can coexist, though they will share process-scoped resources unless the host manually isolates them.

### Background Services and Transport

The harness is a *harness-only* embedder that **does not start any transport**; it uses `ServiceSet::none()` by default, meaning no HTTP or Socket.IO services are spawned.

`CoreBuilder` allows the host to enable a full set of services via `ServiceSet::desktop()`, a headless API via `ServiceSet::headless_api()`, or custom combinations. Transports and background loops start only when the host explicitly calls `CoreRuntime::serve`.

## Embedding with Harness

Use the harness when you need a standalone library or script that runs a few turns without managing transports or background services. The builder is located in [`src/embed/harness/builder.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/embed/harness/builder.rs).

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

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Build the harness – it creates its own CoreRuntime internally.
    let harness = Harness::builder()
        .provider(Provider::openai_compatible("https://api.example/v1", "sk-…")
            .model("gpt-5"))
        .workspace(Workspace::Ephemeral)           // temporary workspace
        .access(Access::readonly())                // read‑only access tier
        .session(Session::local("my‑host"))        // local session satisfies provider gate
        .backend_url("https://my-backend.example") // optional non‑default backend
        .build()
        .await?;

    // First turn.
    let first = harness.run("Summarize what you can see.").await?;
    println!("Reply: {}", first.reply);

    // Continue the same conversation.
    let second = harness
        .turn("Now list the risks.")
        .session(&first.session_id) // keep the same session
        .send()
        .await?;
    println!("Reply: {}", second.reply);

    Ok(())
}

```

## Embedding with CoreBuilder

Use `CoreBuilder` when building host applications that require explicit control over service selection and runtime sharing, such as the desktop UI or a headless API server.

```rust
use openhuman_core::{
    core::runtime::{CoreBuilder, ServiceSet, DomainSet},
    embed::Core,
    HostKind,
};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Create a CoreBuilder for a CLI host.
    let builder = CoreBuilder::new(HostKind::Cli)
        .services(ServiceSet::none())            // no transport, only background services
        .domains(DomainSet::harness())           // minimal agent core
        .workspace("/tmp/openhuman-workspace");  // explicit workspace path

    // Build the runtime.
    let runtime = builder.build().await?;

    // Use the typed Core facade to run a turn.
    let turn = runtime.core().agent().turn("Explain OpenHuman's architecture.");
    let outcome = turn.send().await?;
    println!("Reply: {}", outcome.reply);

    Ok(())
}

```

## Tokio Runtime Stack Requirements

Both embedding approaches rely on the host’s Tokio runtime. An OpenHuman agent turn spawns a large asynchronous state machine; the default Tokio worker stack (2 MiB) is insufficient for sub-agent nesting and will cause a stack overflow. The library exposes two constants in [`src/core/runtime/builder.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/runtime/builder.rs) that the host must use when constructing its runtime:

- **`AGENT_WORKER_STACK_BYTES`** – recommended stack size for each Tokio worker thread.
- **`MAX_BLOCKING_THREADS`** – number of blocking threads Tokio should allocate for CPU-bound work.

As documented in [`src/embed/harness/mod.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/embed/harness/mod.rs), you must configure the runtime with these constants whenever you embed OpenHuman:

```rust
use openhuman_core::core::runtime::{AGENT_WORKER_STACK_BYTES, MAX_BLOCKING_THREADS};

let runtime = tokio::runtime::Builder::new_multi_thread()
    .enable_all()
    .thread_stack_size(AGENT_WORKER_STACK_BYTES)   // ↑ larger than default
    .max_blocking_threads(MAX_BLOCKING_THREADS)   // ↑ enough for blocking tasks
    .build()
    .expect("tokio runtime");

```

Without this configuration, the process will abort during any turn that delegates to a sub-agent.

## Summary

- **Harness** is a high-level convenience wrapper in [`src/embed/harness/mod.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/embed/harness/mod.rs) that builds and owns a `CoreRuntime`, enforces one instance per process, and manages ephemeral workspaces automatically.
- **CoreBuilder** in [`src/core/runtime/builder.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/runtime/builder.rs) provides the low-level API for hosts that need to share runtimes, customize service sets, or manage workspace lifetimes manually.
- Both approaches require the host to configure a Tokio runtime with `AGENT_WORKER_STACK_BYTES` and `MAX_BLOCKING_THREADS` to prevent stack overflow during agent execution.
- Harness uses `ServiceSet::none()` and does not start transports, while `CoreBuilder` allows enabling `ServiceSet::desktop()` or `ServiceSet::headless_api()` for full networking capabilities.

## Frequently Asked Questions

### Can I create multiple Harness instances in the same process?

No. The harness enforces a one-harness-per-process rule using the `HARNESS_LIVE` atomic boolean. Attempting to build a second harness will return `HarnessError::AlreadyRunning` to protect global state such as the keyring master key and event bus. If you need multiple runtimes, use `CoreBuilder` instead.

### Which embedding method should I use for a Tauri desktop application?

Use **CoreBuilder**. The Tauri shell already owns the `CoreRuntime` and requires fine-grained control over which services, domains, and tool groups are active. The builder allows you to configure `ServiceSet::desktop()` and manage the workspace independently of the UI lifecycle.

### What happens if I don't increase the Tokio thread stack size?

The process will abort with a stack overflow. The default Tokio worker stack of 2 MiB is insufficient for the asynchronous state machine spawned during an agent turn, particularly when delegating to sub-agents. You must use `AGENT_WORKER_STACK_BYTES` when building the Tokio runtime as shown in [`src/core/runtime/builder.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/runtime/builder.rs).

### Can multiple CoreRuntime instances share the same workspace?

Yes. When using `CoreBuilder`, you can specify the same directory path via `.workspace(dir)` for multiple runtime instances. The host manages the workspace lifetime, allowing persistence across runtime restarts or sharing between concurrent instances, unlike the ephemeral workspace owned by a Harness instance.