How OpenHuman Handles Tool Definitions via tinytools: A Technical Deep Dive

OpenHuman delegates all tool contract definitions to the tinytools crate, re-exporting its core traits through src/openhuman/tools/traits.rs to ensure strict compile-time compatibility between the Rust core and the TinyAgents runtime.

The tinyhumansai/openhuman repository implements a modular AI agent system where tool definitions via tinytools serve as the canonical interface for all executable capabilities. Rather than duplicating interface definitions, the project vendors the tinytools dependency under vendor/tinyagents/vendor/tinytools and re-exports its types, creating a single source of truth for the Tool trait and its associated types.

Core Architecture of OpenHuman Tool Definitions

The tinytools Trait System

At the heart of OpenHuman's tool system lies the Tool trait defined in the vendored tinytools crate and exposed through src/openhuman/tools/traits.rs. This trait mandates three essential methods that every concrete tool must implement:

  • run() – Executes the tool's logic with JSON arguments and returns a ToolResult
  • schema() – Returns a ToolSchema describing the tool's JSON-RPC interface
  • name() – Provides the string identifier used for dispatch

The trait also defines ToolResult and ToolContent wrappers in the same file, standardizing how tool outputs serialize back to the LLM context. Because these definitions live in the external tinytools crate rather than the OpenHuman source tree, any breaking change to the trait signature triggers immediate compile-time errors across both the core library and the TinyAgents harness.

Tool Registration and Discovery

Concrete tool implementations register themselves through the ToolRegistry system located in src/openhuman/tools/ops.rs. During startup, tools::ops::register_all_tools() iterates over compiled tool packs defined in src/openhuman/tools/toolpacks/registry.rs, inserting each tool into a global registry that the agent harness queries when building the LLM's available tool catalogue.

Implementing Tools in OpenHuman

Creating a new tool requires implementing the re-exported Tool trait from tinytools. Below is a minimal implementation pattern used throughout the src/openhuman/tools/impl/ directory:

use tinytools::{Tool, ToolResult, ToolContent, ToolSchema};
use serde_json::json;

pub struct EchoTool;

impl Tool for EchoTool {
    fn name(&self) -> &'static str { "echo" }

    fn schema(&self) -> ToolSchema {
        ToolSchema::new()
            .with_description("Returns the supplied text unchanged")
            .with_parameters(json!({ "text": { "type": "string" } }))
    }

    fn run(&self, args: serde_json::Value) -> ToolResult {
        let text = args["text"].as_str().unwrap_or_default();
        Ok(ToolResult::new(ToolContent::text(text)))
    }
}

Registration typically occurs within domain-specific tool packs:

use crate::tools::registry::ToolRegistry;

pub fn register_echo(reg: &mut ToolRegistry) {
    reg.register(Box::new(EchoTool));
}

The Tool Execution Flow

OpenHuman processes tool definitions via tinytools through a five-stage pipeline that bridges the LLM and Rust implementations:

  1. Schema Exposure – The harness in vendor/tinyagents/src/... queries the ToolRegistry to build a JSON-RPC catalogue describing all registered tools' parameters and descriptions.

  2. LLM Invocation – When the model generates a tool call, the payload matches one of the schemas defined by the schema() methods.

  3. Dispatch Resolution – tinyagents::harness::tool_calling parses the request into a ParsedToolCall, resolves the tool name against the registry, and invokes the concrete implementation through the shared Tool trait.

  4. Execution – The tool's run() method executes within the OpenHuman runtime, processing arguments and returning a ToolResult.

  5. Result Integration – The harness serializes the ToolResult into ToolContent and injects it back into the conversation turn, allowing the LLM to continue reasoning with the tool's output.

Conditional Compilation and Feature Gates

OpenHuman's modular architecture respects Cargo feature flags when exposing tool definitions via tinytools. The registration logic in src/openhuman/tools/ops.rs uses #[cfg(feature = "...")] attributes to conditionally include tool families, while the underlying trait definitions remain available regardless of feature state.

#[cfg(feature = "documents")]
mod document_tools {
    // Document-related tool implementations live here
    // and register themselves only when the feature is enabled
}

When the documents feature is disabled, the module is excluded from compilation and register_all_tools() skips adding those tools to the catalogue. However, the tinytools types themselves (Tool, ToolResult, ToolContent) remain present because they are part of the always-on vendored dependency, ensuring other domains can still compile against the trait interface.

Summary

  • OpenHuman re-exports the Tool trait and result types from the vendored tinytools crate via src/openhuman/tools/traits.rs, ensuring version consistency.
  • Tool registration occurs through src/openhuman/tools/ops.rs, which aggregates implementations from src/openhuman/tools/toolpacks/registry.rs into a global ToolRegistry.
  • Execution flow moves from LLM schema generation through tinyagents::harness::tool_calling dispatch to concrete run() implementations, with results serialized back through ToolContent.
  • Feature gates in the registration layer allow conditional compilation of tool families without breaking the underlying trait contracts.
  • Compile-time safety is enforced because the Tool trait lives in the external tinytools crate, causing build failures if OpenHuman implementations drift from the expected interface.

Frequently Asked Questions

What is the tinytools crate in OpenHuman?

The tinytools crate is the upstream dependency vendored at vendor/tinyagents/vendor/tinytools that defines the core Tool trait, ToolResult, and ToolContent types. OpenHuman re-exports these types through src/openhuman/tools/traits.rs rather than maintaining duplicate definitions, ensuring that both the Rust core and TinyAgents runtime share identical interface contracts.

How do I register a new tool in OpenHuman?

New tools implement the Tool trait from tinytools and call ToolRegistry::register() during initialization. The src/openhuman/tools/ops.rs module orchestrates this through register_all_tools(), which iterates over tool packs defined in src/openhuman/tools/toolpacks/registry.rs. Each tool pack typically contains a registration function that instantiates and boxes the concrete tool implementation.

Can I disable specific tools at compile time?

Yes. OpenHuman uses Cargo feature flags to conditionally compile tool families. Wrapping tool modules in #[cfg(feature = "feature_name")] attributes allows the registration logic in src/openhuman/tools/ops.rs to skip unavailable tools while keeping the underlying tinytools trait definitions accessible to the rest of the codebase.

Where does the JSON schema for tool arguments come from?

Each tool implements the schema() method required by the Tool trait, returning a ToolSchema object that describes the tool's JSON-RPC interface. The TinyAgents harness queries these schemas from the ToolRegistry to construct the tool catalogue presented to the LLM during prompt generation.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →