# How OpenHuman Integrates with TinyAgents: Rust Harness Architecture Explained

> Discover how OpenHuman integrates with TinyAgents. Learn about the Rust harness architecture that separates LLM tool-call parsing from secure tool execution and runtime orchestration.

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

---

**OpenHuman delegates LLM tool-call parsing to the TinyAgents library while retaining full control over tool execution, security policies, and runtime orchestration through a three-layer integration architecture.**

OpenHuman is an AI-driven conversation harness built in Rust by the tinyhumansai organization. Understanding how OpenHuman integrates with TinyAgents reveals a clean separation of concerns where syntax parsing is outsourced to the `tinyagents` crate while execution logic and sandboxing remain native to the OpenHuman codebase.

## Three-Layer Integration Architecture

The integration between OpenHuman and TinyAgents operates at three distinct architectural layers that handle everything from parsing raw LLM output to enforcing security policies.

### Tool Dialect Layer

The **tool dialect** layer handles parsing and rendering tool calls from the LLM. TinyAgents defines the dialects—`XmlDialect`, `PFormatDialect`, and `NativeDialect`—that translate raw tool-call syntax into a typed `ParsedToolCall`. OpenHuman forwards the LLM's output to this layer, receives a structured `ParsedToolCall`, and then looks up the corresponding tool schema registered in its internal registry.

In [`src/openhuman/agent/dispatcher.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/agent/dispatcher.rs), the dispatcher receives the LLM turn, passes it to TinyAgents' dialect parser, and converts the result into OpenHuman's internal `ToolExecutionResult`. The TinyAgents module responsible for this is `tinyagents::harness::tool_calling::dialect`, which implements the three dialects and conversion helpers.

### Tool Registry and Execution Layer

OpenHuman registers its agent tools—such as `generate_document`, `web_search`, and `memory_query`—in a **tool registry** defined in [`src/openhuman/tools/registry.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/tools/registry.rs). Each entry implements the `tinytools::Tool` trait, which is re-exported from [`src/openhuman/tools/traits.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/tools/traits.rs) to ensure all tools share the same ABI.

When TinyAgents returns a parsed call, OpenHuman looks up the matching tool by name, creates the appropriate `ToolRunContext`, and invokes `Tool::run`. Because the `tinytools::Tool` trait is shared between the libraries, any tool compiled into the binary automatically satisfies the dispatcher's contract without requiring type-specific knowledge from the TinyAgents crate.

### Runtime Orchestration Layer

After a tool runs, OpenHuman applies its **security policy**, **approval gate**, **sandbox mode**, and **timeout** logic defined in [`src/openhuman/tools/policy.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/tools/policy.rs) and `src/openhuman/tools/timeout/`. The result (or error) is transformed back into an LLM-compatible response format and fed to the next turn of the harness.

TinyAgents contains no policy code; it simply returns the tool result to OpenHuman, which then wraps it in the JSON-RPC protocol defined in `src/openhuman/api/*`. This design allows OpenHuman to maintain strict control over execution security while TinyAgents handles the complexity of multi-format LLM tool-call syntax.

## Key Integration Points

### Dispatcher to TinyAgents Dialect

The dispatcher in [`src/openhuman/agent/dispatcher.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/agent/dispatcher.rs) serves as the primary bridge between the LLM and TinyAgents. It receives the LLM's raw output and calls `tinyagents::harness::tool_calling::dialect::parse` to obtain a `ParsedToolCall`. This isolation prevents LLM-specific syntax details from leaking into OpenHuman's core business logic.

### Tool Lookup via Registry

The parsed tool name is used to query the registry built in [`src/openhuman/tools/registry.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/tools/registry.rs). The registry holds a vector of `ToolSchema` objects that each contain the `tinytools::Tool` implementation. This lookup decouples the parsing of tool calls from their execution, allowing OpenHuman to dynamically register tools without modifying the TinyAgents library.

### Execution and Policy Enforcement

Once a concrete tool is found, the dispatcher creates a `ToolRunContext`—providing workspace paths, turn budget, and execution metadata—and calls `tool.run(context)`. The result is filtered through OpenHuman's security and approval logic defined in [`policy.rs`](https://github.com/tinyhumansai/openhuman/blob/main/policy.rs), then serialized back into the LLM-compatible JSON structure for the next turn.

### Round-Trip Through the Harness

The `Harness` in [`src/openhuman/harness.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/harness.rs) drives the entire turn lifecycle. After the tool result is processed and inserted into the conversation context, the LLM is prompted again via the configured provider, and the cycle repeats. TinyAgents participates only in the tool-call parsing segment of this loop, while OpenHuman manages the stateful orchestration.

## Implementation Examples

### Parsing a Turn and Dispatching a Tool Call

The following pattern from [`src/openhuman/agent/dispatcher.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/agent/dispatcher.rs) demonstrates the complete flow from raw LLM output to tool execution:

```rust
// Inside src/openhuman/agent/dispatcher.rs
let raw_output = llm.generate(&prompt).await?;
let parsed = tinyagents::harness::tool_calling::dialect::parse(&raw_output)?;
let tool = tools::registry::lookup(&parsed.name)?;
let ctx = tools::run_context::new(...);
let result = tool.run(&ctx, parsed.args).await?;
let filtered = tools::policy::apply(&result, &ctx)?;
let json = serde_json::to_string(&filtered)?;
harness.send_back(json).await?;

```

### Registering a New Tool

Tools are registered in [`src/openhuman/tools/registry.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/tools/registry.rs) by building a vector of `ToolSchema` objects:

```rust
// In src/openhuman/tools/registry.rs
pub fn build_registry() -> Vec<ToolSchema> {
    vec![
        ToolSchema::new("generate_document", generate_document::ToolImpl::default()),
        // …other tools
    ]
}

```

### Tool Implementation Conforming to TinyTools

All tools must implement the `Tool` trait from the TinyTools crate to ensure compatibility with the TinyAgents integration:

```rust
// src/openhuman/tools/impl/document.rs
use tinytools::Tool;

#[derive(Default)]
pub struct ToolImpl;

impl Tool for ToolImpl {
    fn name(&self) -> &'static str { "generate_document" }
    fn run(&self, ctx: &dyn ToolRunContext, args: serde_json::Value) -> ToolResult {
        // actual document generation logic …
    }
}

```

## Core Source Files

The integration relies on specific files that bridge the two libraries:

- **[`src/openhuman/agent/dispatcher.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/agent/dispatcher.rs)** — Dispatches LLM turns, invokes the TinyAgents dialect parser, and connects parsed calls to the OpenHuman tool registry.
- **[`src/openhuman/tools/registry.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/tools/registry.rs)** — Builds and queries the list of available agent tools, serving as the bridge between OpenHuman and TinyTools.
- **[`src/openhuman/tools/traits.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/tools/traits.rs)** — Re-exports the `tinytools::Tool` trait used by all tool implementations.
- **[`src/openhuman/tools/policy.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/tools/policy.rs)** — Enforces security and permission policies on tool executions.
- **[`src/openhuman/harness.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/harness.rs)** — Core high-level API that runs a turn, feeds results back to the LLM, and loops.
- **[`vendor/tinyagents/vendor/tinytools/crates/tinytools/src/tool.rs`](https://github.com/tinyhumansai/openhuman/blob/main/vendor/tinyagents/vendor/tinytools/crates/tinytools/src/tool.rs)** — Definition of the `Tool` trait that TinyAgents expects.
- **[`docs/tinyagents-port-plan.md`](https://github.com/tinyhumansai/openhuman/blob/main/docs/tinyagents-port-plan.md)** — Design document describing the migration of OpenHuman's tool system onto TinyAgents.
- **[`src/openhuman/agent/tinyagents/tinyagents_tests.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/agent/tinyagents/tinyagents_tests.rs)** — Test suite confirming that the TinyAgents integration correctly parses and executes tool calls.

## Summary

- OpenHuman uses TinyAgents exclusively for **parsing** LLM tool-call syntax across multiple dialects (XML, PFormat, Native), delegating the complexity of format detection to the `tinyagents` crate.
- Tool **execution** and **registration** remain under OpenHuman's control through the `tinytools::Tool` trait implementation in [`src/openhuman/tools/registry.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/tools/registry.rs) and [`traits.rs`](https://github.com/tinyhumansai/openhuman/blob/main/traits.rs).
- **Security policies**, **timeouts**, and **sandboxing** are applied by OpenHuman after TinyAgents returns the parsed call, ensuring sensitive operations remain within the host application's governance layer.
- The **dispatcher** in [`src/openhuman/agent/dispatcher.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/agent/dispatcher.rs) orchestrates the handoff between TinyAgents' parsing layer and OpenHuman's execution layer, creating a clean separation between syntax handling and business logic.

## Frequently Asked Questions

### What is the role of the dispatcher in OpenHuman's TinyAgents integration?

The dispatcher in [`src/openhuman/agent/dispatcher.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/agent/dispatcher.rs) acts as the central router between the LLM and the tool execution layer. It receives raw LLM output, forwards it to TinyAgents' dialect parser to obtain a `ParsedToolCall`, looks up the corresponding tool in the OpenHuman registry, and manages the execution context. This component ensures that TinyAgents handles only the syntactic parsing while OpenHuman retains control over the semantic execution.

### How does OpenHuman handle security when executing TinyAgents-parsed tool calls?

OpenHuman applies security policies **after** TinyAgents parses the tool call but **before** the result returns to the LLM. The [`src/openhuman/tools/policy.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/tools/policy.rs) module enforces read/write/network permissions per tool, while the `src/openhuman/tools/timeout/` directory handles execution limits. TinyAgents has no visibility into these security layers; it simply returns the raw tool result to OpenHuman, which then filters the output according to its configured policies.

### Can custom tools be added to OpenHuman's TinyAgents integration?

Yes. Custom tools are added by implementing the `tinytools::Tool` trait (re-exported in [`src/openhuman/tools/traits.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/tools/traits.rs)) and registering the implementation in [`src/openhuman/tools/registry.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/tools/registry.rs) via `ToolSchema::new()`. Because the integration relies on the shared `Tool` trait from the TinyTools crate, any conforming implementation automatically becomes available to the TinyAgents parsing layer without requiring changes to the TinyAgents library itself.

### What Rust trait must all OpenHuman tools implement to work with TinyAgents?

All tools must implement the **`Tool` trait** defined in `tinytools::Tool` (located at [`vendor/tinyagents/vendor/tinytools/crates/tinytools/src/tool.rs`](https://github.com/tinyhumansai/openhuman/blob/main/vendor/tinyagents/vendor/tinytools/crates/tinytools/src/tool.rs) and re-exported in [`src/openhuman/tools/traits.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/tools/traits.rs)). This trait requires implementations to provide a `name()` method returning a static string and a `run()` method accepting a `ToolRunContext` and JSON arguments. This standardized interface allows TinyAgents to invoke tools without knowing their concrete types, enabling the dynamic dispatch pattern used in [`src/openhuman/agent/dispatcher.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/agent/dispatcher.rs).