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 aToolResultschema()– Returns aToolSchemadescribing the tool's JSON-RPC interfacename()– 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:
-
Schema Exposure – The harness in
vendor/tinyagents/src/...queries theToolRegistryto build a JSON-RPC catalogue describing all registered tools' parameters and descriptions. -
LLM Invocation – When the model generates a tool call, the payload matches one of the schemas defined by the
schema()methods. -
Dispatch Resolution –
tinyagents::harness::tool_callingparses the request into aParsedToolCall, resolves the tool name against the registry, and invokes the concrete implementation through the sharedTooltrait. -
Execution – The tool's
run()method executes within the OpenHuman runtime, processing arguments and returning aToolResult. -
Result Integration – The harness serializes the
ToolResultintoToolContentand 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
Tooltrait and result types from the vendoredtinytoolscrate viasrc/openhuman/tools/traits.rs, ensuring version consistency. - Tool registration occurs through
src/openhuman/tools/ops.rs, which aggregates implementations fromsrc/openhuman/tools/toolpacks/registry.rsinto a globalToolRegistry. - Execution flow moves from LLM schema generation through
tinyagents::harness::tool_callingdispatch to concreterun()implementations, with results serialized back throughToolContent. - 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
Tooltrait lives in the externaltinytoolscrate, 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →