# Tool Calling Dialect System in TinyAgents: How Dispatcher.rs Bridges LLM Syntax and Core Execution

> Explore the tool calling dialect system in TinyAgents and how dispatcher.rs bridges LLM syntax with core execution. Learn how OpenHuman supports multiple dialects seamlessly.

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

---

**The OpenHuman framework implements a dialect-driven tool-calling architecture that decouples LLM interaction syntax from core execution logic through the [`dispatcher.rs`](https://github.com/tinyhumansai/openhuman/blob/main/dispatcher.rs) seam, enabling support for multiple dialects like XML, PFormat, and Native without modifying tool implementations or security policies.**

The `tinyhumansai/openhuman` repository provides a sophisticated **tool calling dialect system** designed to standardize how large language models (LLMs) invoke capabilities while maintaining strict security boundaries. This architecture isolates textual representations of tool calls from their execution, allowing the system to swap dialects—from XML to Pretty-Format—without altering the underlying `Tool` trait implementations or policy enforcement code.

## Architecture of the Tool Calling Dialect System

The system separates three distinct concerns to maintain loose coupling between the LLM interface and the execution core:

- **Tool Vocabulary & Dialects**: Defined in `tinyagents::harness::tool_calling::dialect` (e.g., `XmlDialect`, `PFormatDialect`, `NativeDialect`). These modules determine how the LLM perceives available tools and how tool call arguments are parsed from raw text into structured `ParsedToolCall` objects.

- **Tool Execution & Policy**: Located in `src/openhuman/tools/*`, this layer handles security policy validation, approval-gate checks, sandbox enforcement, and per-call timeouts. All security-sensitive logic remains centralized here, independent of dialect syntax.

- **Seam & Conversion**: The [`src/openhuman/agent/dispatcher.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/agent/dispatcher.rs) file serves as the single integration point between dialects and core tools. It maps `ParsedToolCall` instances to `ToolExecutionResult` objects and translates the persistent `ConversationMessage` format (stored as JSONL) to the dialect-specific `TranscriptEntry` representation.

## The Five-Stage Dialect Execution Pipeline

According to the source code in [`dispatcher.rs`](https://github.com/tinyhumansai/openhuman/blob/main/dispatcher.rs), every tool invocation follows a strict pipeline that preserves dialect flexibility while enforcing security:

1. **Prompt Generation**: The core constructs a prompt containing a **tool catalogue** (`ToolsSection`). The active dialect renders this catalogue via methods like `render_pformat_catalogue`, emitting signatures such as `Call as: my_tool(arg1="value")`.

2. **LLM Response**: The model returns text matching the chosen dialect's syntax, whether XML tags, pretty-format function calls, or native structured output.

3. **Parsing**: The dialect parses the response into a `ParsedToolCall` struct, extracting the tool name and argument map using dialect-specific parsers found in `vendor/tinyagents/src/harness/tool_calling/dialect/`.

4. **Dispatch**: The [`dispatcher.rs`](https://github.com/tinyhumansai/openhuman/blob/main/dispatcher.rs) seam receives the `ParsedToolCall` and lookups the corresponding `Tool` implementation from the registry. It constructs a `ToolExecutionContext` containing workspace handles, thread IDs, and budget constraints before invoking the tool.

5. **Result Handling**: The tool returns a `ToolResult`, which [`dispatcher.rs`](https://github.com/tinyhumansai/openhuman/blob/main/dispatcher.rs) converts into a `ConversationMessage` for persistent storage in the transcript, completing the cycle.

## Inside Dispatcher.rs: The Conversion Seam

The [`src/openhuman/agent/dispatcher.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/agent/dispatcher.rs) file is explicitly designed as the **only location** where LLM-level dialect representations meet core-level tool abstractions. This design ensures that dialect changes never penetrate security-critical code.

When dispatching, the seam performs three critical conversions:

- **Input Translation**: Transforms the dialect's `ParsedToolCall` into the core's `ToolExecutionContext`, injecting runtime dependencies like sandbox configurations.

- **Policy Enforcement**: Applies approval gates and timeout policies defined in [`src/openhuman/tools/ops.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/tools/ops.rs) before execution begins, regardless of which dialect initiated the call.

- **Output Translation**: Converts raw `ToolResult` data into `ConversationMessage` objects that maintain the persistent conversation state, while optionally formatting user-facing output through the dialect's transcript renderer.

This architecture means you can switch from `XmlDialect` to `NativeDialect` simply by changing the dialect configuration, without touching [`dispatcher.rs`](https://github.com/tinyhumansai/openhuman/blob/main/dispatcher.rs) logic or any individual tool implementation.

## Practical Implementation: Registering and Dispatching a Tool

Below is a complete workflow demonstrating how to define a tool, register it for dialect access, and process calls through the dispatcher seam.

```rust
// 1. Define a tool implementation (src/openhuman/tools/impl/my_tool.rs)
pub struct MyTool;

#[async_trait::async_trait]
impl tinytools::Tool for MyTool {
    fn name(&self) -> &'static str { "my_tool" }

    async fn run(
        &self,
        ctx: &dyn tinytools::ToolRunContext,
        args: tinytools::ToolArgs,
    ) -> tinytools::ToolResult {
        // Core execution logic respects sandbox, timeouts, and policies
        Ok(tinytools::ToolResult::content("Tool executed successfully"))
    }
}

// 2. Register the tool (src/openhuman/tools/ops.rs)
pub fn register_my_tool(reg: &mut tinytools::ToolRegistry) {
    reg.register(Box::new(MyTool));
}

```

When the LLM generates a response using the PFormat dialect, the pipeline processes it as follows:

```rust
// 3. LLM response in PFormat dialect
let llm_response = r#"Call as: my_tool(message="hello world")"#;

// 4. Parse using the dialect (provided by tinyagents)
let parsed = tinyagents::harness::tool_calling::dialect::PFormatDialect::parse(
    llm_response,
)?; // Returns ParsedToolCall { name: "my_tool", args: { "message": "hello world" } }

// 5. Dispatch through the seam (src/openhuman/agent/dispatcher.rs)
let result = dispatcher::dispatch(parsed, &core_context).await?;
// Result becomes a ConversationMessage stored in the persistent transcript

```

## Summary

- **Loose Coupling**: Dialects can evolve to support new LLM output formats without requiring changes to tool implementations in `src/openhuman/tools/`.
- **Centralized Security**: All policy enforcement, including approval gates and sandboxing, occurs in the core tool layer before execution, independent of the dialect used to parse the request.
- **Single Integration Point**: The [`dispatcher.rs`](https://github.com/tinyhumansai/openhuman/blob/main/dispatcher.rs) seam is the exclusive bridge between `ParsedToolCall` dialect objects and core `Tool` trait executions, ensuring architectural integrity.
- **Extensibility**: Adding tools requires only registration in [`src/openhuman/tools/ops.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/tools/ops.rs); the dispatcher automatically handles conversion for all supported dialects including XML, PFormat, and Native.

## Frequently Asked Questions

### What is a tool calling dialect?

A tool calling dialect is a standardized syntax for how an LLM expresses its intention to invoke a tool. In the OpenHuman framework, dialects like `XmlDialect`, `PFormatDialect`, and `NativeDialect` live in `tinyagents::harness::tool_calling::dialect` and handle both the rendering of tool catalogues for prompts and the parsing of LLM responses into structured `ParsedToolCall` objects.

### How does dispatcher.rs maintain security across different dialects?

The [`src/openhuman/agent/dispatcher.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/agent/dispatcher.rs) file acts as a sanitization boundary. While dialects handle raw text parsing, [`dispatcher.rs`](https://github.com/tinyhumansai/openhuman/blob/main/dispatcher.rs) validates the resulting `ParsedToolCall` against the `ToolRegistry`, constructs a `ToolExecutionContext` with appropriate sandbox and budget constraints, and enforces approval policies defined in the core tool layer before any code executes.

### Can I add a custom dialect without modifying existing tools?

Yes. New dialects implement the dialect trait (parsing text into `ParsedToolCall` and rendering catalogues) and reside in the `tinyagents` crate. Because [`dispatcher.rs`](https://github.com/tinyhumansai/openhuman/blob/main/dispatcher.rs) only operates on the standardized `ParsedToolCall` struct, existing tools in `src/openhuman/tools/` require no changes to support new dialect syntax.

### What distinguishes the Native dialect from XML or PFormat?

According to the source in [`vendor/tinyagents/src/harness/tool_calling/dialect/native.rs`](https://github.com/tinyhumansai/openhuman/blob/main/vendor/tinyagents/src/harness/tool_calling/dialect/native.rs), the **Native** dialect uses representations closest to Rust's type system, often leveraging structured serialization that maps directly to internal structs. In contrast, **XML** uses tag-based markup and **PFormat** (Pretty-Format) uses human-readable function call syntax like `tool_name(arg="value")`, making the latter more suitable for frontier LLMs trained on code-like outputs.