# How the OpenHuman Agent Dispatcher Delegates Tool Execution via the TinyAgents Harness

> Discover how the OpenHuman agent dispatcher delegates tool execution through the TinyAgents harness. Learn about the ToolDispatcher trait and its role as a lossless adapter for efficient conversation management.

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

---

**OpenHuman delegates tool execution parsing and formatting to the TinyAgents harness through the `ToolDispatcher` trait in [`src/openhuman/agent/dispatcher.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/agent/dispatcher.rs), which acts as a lossless adapter between OpenHuman's durable conversation structs and TinyAgents' lightweight dialect layer.**

The OpenHuman agent loop requires a mechanism to translate raw LLM responses into executable tool calls and to format tool results back into messages the model can process. This translation layer is implemented in the **TinyAgents harness** integration, specifically within the dispatcher module of the `tinyhumansai/openhuman` repository. By abstracting the conversion logic behind the `ToolDispatcher` trait, OpenHuman supports multiple tool-calling protocols—XML, P-Format, and Native—without duplicating parsing code or coupling the core agent logic to a specific dialect.

## The ToolDispatcher Trait: Core Abstraction

The **ToolDispatcher** trait defined in [`src/openhuman/agent/dispatcher.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/agent/dispatcher.rs) specifies the contract that each dialect implementation must fulfill. This abstraction allows the agent loop to remain agnostic to whether the model outputs XML tags, compact syntax, or structured JSON.

The trait exposes five essential operations:

- **`parse_response`** – Converts a `ChatResponse` (raw LLM output) into a tuple of plain text and a vector of `ParsedToolCall` objects.
- **`format_results`** – Transforms a slice of `ToolExecutionResult` structs into a `ConversationMessage` for the next turn.
- **`prompt_instructions`** – Renders the system-prompt block that instructs the model on how to format tool calls.
- **`to_provider_messages`** – Serializes the internal conversation history into provider-specific request formats (e.g., OpenAI message arrays).
- **`should_send_tool_specs`** and **`tool_call_format`** – Helper methods consulted by the harness to determine protocol capabilities.

The dispatcher implements **no security, approval-gating, sandboxing, or timeout logic**—those responsibilities reside in separate core modules. Its sole purpose is performing lossless data conversion between OpenHuman's durable structs and the lightweight `DialectMessage`, `ToolOutcome`, and `TranscriptEntry` types used by TinyAgents.

## Bridging Two Vocabularies: The Conversion Seam

The dispatcher acts as a bidirectional adapter, handling three critical translation paths through pure data-mapping functions.

**Incoming Response Parsing**

When the LLM returns a response, the flow moves from `ChatResponse` (OpenHuman core) to `DialectResponse` (TinyAgents) to extract tool calls. This logic is encapsulated in `dispatch_parse` (lines 82‑86):

```rust
fn dispatch_parse<D: ToolDialect>(
    dialect: &D,
    response: &ChatResponse,
) -> (String, Vec<ParsedToolCall>) {
    // Converts ChatResponse -> DialectResponse -> ParsedToolCall
}

```

**Outgoing Result Formatting**

After tools execute, `ToolExecutionResult` objects must become `ConversationMessage` instances. The `dispatch_format_results` helper (lines 89‑94) handles this by mapping through `ToolOutcome` and `TranscriptEntry` intermediates:

```rust
fn dispatch_format_results<D: ToolDialect>(
    dialect: &D,
    results: &[ToolExecutionResult],
) -> ConversationMessage {
    // Converts ToolExecutionResult -> ToolOutcome -> TranscriptEntry -> ConversationMessage
}

```

**History Serialization**

For provider requests, the dispatcher converts a vector of `ConversationMessage` into `TranscriptEntry` objects, then into provider-specific `ChatMessage` formats. This occurs in `dispatch_provider_messages` (lines 96‑101).

All conversion decisions (e.g., handling unknown roles) are delegated to the TinyAgents dialect implementation, keeping OpenHuman's conversion logic centralized and minimal.

## Concrete Dialect Implementations

OpenHuman provides three concrete dispatchers, each forwarding to a corresponding TinyAgents dialect via the shared helper functions.

### XmlToolDispatcher

The **XmlToolDispatcher** wraps `XmlDialect` to parse `<tool_call>` tags containing JSON bodies. It renders the full XML protocol within the system prompt, embedding the complete tool catalogue using `XmlDialect::instructions`.

### PFormatToolDispatcher

The **PFormatToolDispatcher** utilizes `PFormatDialect` for the compact "`tool_name[arg1|arg2]`" syntax. Unlike XML, the catalogue rendering is handled separately by the `ToolsSection` struct, while the dispatcher manages the parsing and formatting of individual calls.

### NativeToolDispatcher

The **NativeToolDispatcher** leverages `NativeDialect` to interface with provider-specific structured tool-calling APIs (OpenAI, Anthropic, etc.). It includes fallback text parsing for robustness when native APIs return unexpected formats. The implementation is typically a one-liner forwarding to `dispatch_parse`:

```rust
fn parse_response(&self, response: &ChatResponse) -> (String, Vec<ParsedToolCall>) {
    dispatch_parse(&NativeDialect, response)
}

```

## Configuring and Instantiating the Dispatcher

Dispatcher selection occurs during harness construction in **[`src/openhuman/agent/harness/session/builder/factory.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/agent/harness/session/builder/factory.rs)**. The builder matches on the `DispatcherKind` enum to instantiate the appropriate boxed trait object:

```rust
let tool_dispatcher: Box<dyn ToolDispatcher> = match cfg.dispatcher {
    DispatcherKind::Native => Box::new(NativeToolDispatcher),
    DispatcherKind::Xml => Box::new(XmlToolDispatcher),
    DispatcherKind::Pformat => {
        Box::new(PFormatToolDispatcher::new(PFormatRegistry::new()))
    },
};

```

This boxed dispatcher is stored in the session and injected into the agent loop, ensuring that all subsequent tool-related operations use the selected protocol.

## Integration with the Agent Loop

Once instantiated, the dispatcher serves four critical functions within the core execution pipeline:

1. **Parsing LLM turns** – `agent::task_dispatcher::dispatch_card` invokes `dispatcher.parse_response` to extract text and tool calls from model outputs.
2. **Formatting results** – The same dispatch card calls `dispatcher.format_results` to package tool outputs into the conversation transcript.
3. **Generating system prompts** – `agent::harness::subagent_runner::ops::runner` calls `dispatcher.prompt_instructions` to embed tool-calling guidelines in the system message.
4. **Serializing history** – The runner calls `dispatcher.to_provider_messages` to convert internal state into provider-compatible request payloads.

This delegation ensures that OpenHuman's core remains focused on orchestration and security while TinyAgents handles the syntactic details of tool dialects.

## Summary

- The **ToolDispatcher** trait in [`src/openhuman/agent/dispatcher.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/agent/dispatcher.rs) abstracts tool-calling protocols, enabling OpenHuman to support XML, P-Format, and Native dialects through a uniform interface.
- **Pure conversion helpers** (`dispatch_parse`, `dispatch_format_results`, `dispatch_provider_messages`) map between OpenHuman's durable structs and TinyAgents' lightweight types without business logic.
- **Three concrete implementations** (`XmlToolDispatcher`, `PFormatToolDispatcher`, `NativeToolDispatcher`) forward to TinyAgents dialects, with selection logic residing in [`src/openhuman/agent/harness/session/builder/factory.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/agent/harness/session/builder/factory.rs).
- The dispatcher performs **no security or sandboxing**—it exclusively handles lossless data translation between the LLM interface and the agent core.
- Using TinyAgents provides a **stable, reusable dialect layer** that improves maintainability and allows protocol improvements to propagate instantly across the codebase.

## Frequently Asked Questions

### What is the ToolDispatcher trait in OpenHuman?

The **ToolDispatcher** trait is a protocol abstraction defined in [`src/openhuman/agent/dispatcher.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/agent/dispatcher.rs) that standardizes how the agent loop parses LLM responses, formats tool results, generates system prompts, and serializes conversation history. It allows OpenHuman to support multiple tool-calling syntaxes (XML, P-Format, Native) without modifying the core agent logic, delegating the actual parsing to the TinyAgents harness.

### How does OpenHuman convert between its internal structs and TinyAgents dialects?

OpenHuman uses three pure helper functions—`dispatch_parse`, `dispatch_format_results`, and `dispatch_provider_messages`—located in [`src/openhuman/agent/dispatcher.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/agent/dispatcher.rs). These functions perform lossless bidirectional translation between OpenHuman's durable types (`ChatResponse`, `ToolExecutionResult`, `ConversationMessage`) and TinyAgents' lightweight intermediates (`DialectResponse`, `ToolOutcome`, `TranscriptEntry`), delegating dialect-specific decisions to the TinyAgents implementation.

### Where is the dispatcher instance created in the OpenHuman codebase?

The dispatcher instance is instantiated in **[`src/openhuman/agent/harness/session/builder/factory.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/agent/harness/session/builder/factory.rs)** during harness construction. The builder matches on the `DispatcherKind` configuration variant (Native, Xml, or Pformat) to create a boxed `dyn ToolDispatcher` that is stored in the session and subsequently used by the agent loop for all tool-related operations.

### Why does OpenHuman use TinyAgents for tool calling instead of implementing parsing directly?

OpenHuman uses TinyAgents to provide a **stable, language-agnostic dialect layer** that can be reused across multiple hosts and projects. By delegating to TinyAgents, OpenHuman avoids duplicating parsing and formatting code, ensures that improvements to dialect robustness (such as more tolerant XML parsers) reflect immediately in the agent, and keeps the core codebase focused on orchestration rather than syntactic protocol details.