# Why tinyagents Vendors tinytools: Type Safety and Cross-Crate Compatibility in OpenHuman

> Discover why tinyagents vendors tinytools in OpenHuman. Ensure type safety and prevent cross-crate compatibility errors with identical Rust type definitions for seamless integration.

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

---

**The tinyagents crate vendors a local copy of tinytools to guarantee that both the core OpenHuman library and the agent harness share identical type definitions, preventing Rust compiler errors caused by trait implementation mismatches across crate boundaries.**

The `tinyhumansai/openhuman` repository employs a specific dependency strategy where the `tinyagents` module contains a vendored checkout of the `tinytools` crate rather than declaring it as a conventional crates.io dependency. This architectural decision ensures that critical tool-related types—specifically the `Tool` trait, `ToolResult`, `ToolContent`, and `ToolRunContext`—maintain strict type identity across the entire codebase.

## The Core Problem: Type Identity Across Crate Boundaries

In Rust, the **orphan rule** prevents implementing a foreign trait for a foreign type. When `openhuman` (the core library) and `tinyagents` (the agent harness) both need to work with tool definitions, they must reference the exact same type instances. If each crate pulled `tinytools` independently from crates.io, Cargo could resolve them to different versions or distinct compiled instances.

This creates a **type mismatch** where `tinytools::Tool` from one crate is treated as a completely different type from `tinytools::Tool` in the other. Consequently, trait implementations made by `tinyagents` would not satisfy the interface expectations of `openhuman`, causing compilation failures when attempting to register tools or execute them through the harness.

## Why Vendoring Solves the Diamond Dependency Problem

Vendoring `tinytools` inside `tinyagents` eliminates the risk of version skew and duplicate type definitions. The repository places the vendored copy at `vendor/tinyagents/vendor/tinytools/`, ensuring both crates reference the same source tree.

### Preventing Duplicate Crate Instances

If `tinyagents` declared a regular dependency on `tinytools` while `openhuman` also depended on it, Cargo's resolution algorithm might select different versions for each consumer. Because Rust treats different crate instances as distinct types—even when they originate from the same source—a tool implementing `tinytools::Tool` from one instance would not satisfy the trait bound expecting `tinytools::Tool` from the other. Vendoring forces both crates to use the **single compiled instance** located in the `vendor/` directory.

### Guaranteed Version Alignment

The vendored checkout pins `tinytools` to a specific commit that is reviewed and synchronized with the core library's expectations. This guarantees that every build uses the same source tree for tool definitions. When `openhuman` updates its tool interfaces, the vendored copy in `vendor/tinyagents/vendor/tinytools/` is updated simultaneously, eliminating subtle runtime mismatches caused by stray version bumps.

### Simplified Auditing and CI

The repository maintains a dedicated CI script at `scripts/ci/check-vendored.mjs` that verifies the vendored copy matches the upstream source. This enforcement ensures that any changes to `tinytools` must be deliberately reviewed and synchronized across both crates. The explicit path dependency in [`vendor/tinyagents/Cargo.toml`](https://github.com/tinyhumansai/openhuman/blob/main/vendor/tinyagents/Cargo.toml) makes the relationship transparent to auditors and prevents accidental drift.

### Build Isolation Without External Network Access

By including the source code directly in the repository, the build process does not require network access to crates.io to fetch `tinytools`. This aligns with the project's security posture where all third-party code is audited and locked in the repository, enabling reproducible builds in air-gapped environments.

## Implementation Details and Source Evidence

The architectural decision is documented directly in the source code. In [`src/openhuman/tools/traits.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/tools/traits.rs), the module comments explicitly state that definitions live in `tinytools` and that the re-export is required so that `dyn tinytools::Tool` represents the same type across the entire codebase.

The [`Cargo.toml`](https://github.com/tinyhumansai/openhuman/blob/main/Cargo.toml) manifest at [`vendor/tinyagents/Cargo.toml`](https://github.com/tinyhumansai/openhuman/blob/main/vendor/tinyagents/Cargo.toml) declares the dependency using a **path reference** rather than a version constraint:

```toml
[dependencies]
tinytools = { path = "vendor/tinytools" }

```

This configuration instructs Cargo to compile the vendored source as part of the `tinyagents` build graph, ensuring that when `openhuman` imports tool types through [`src/openhuman/tools/traits.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/tools/traits.rs), it references the identical types used by the agent harness.

## Practical Code Examples

The following examples demonstrate how vendoring enables seamless interoperability between the core library and the agent harness.

### Using Tools Across Crate Boundaries

Because both crates import from the same vendored source, trait objects remain compatible:

```rust
use openhuman::tools::Tool;           // Re-export from vendored tinytools
use tinyagents::tools::ToolRunContext;

fn execute_tool(context: &dyn ToolRunContext) -> tinytools::ToolResult {
    // ToolResult from tinyagents matches the core's expectations
    let tool = MyCustomTool::new();
    tool.run(context)
}

```

### Implementing a Tool for Both Systems

When implementing the `Tool` trait, the single type definition satisfies both `openhuman` and `tinyagents`:

```rust
use tinytools::{Tool, ToolRunContext, ToolResult};

struct DataProcessor;

impl Tool for DataProcessor {
    fn name(&self) -> &'static str { "data_processor" }
    
    fn run(&self, ctx: &dyn ToolRunContext) -> ToolResult {
        // Implementation uses the unified ToolRunContext type
        ToolResult::success(ctx.input())
    }
}

// Registration works because openhuman::tools::Tool is the same type
openhuman::tools::registry::register(Box::new(DataProcessor));

```

The key file [`src/openhuman/agent/tinyagents/tools.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/agent/tinyagents/tools.rs) contains the actual integration code that relies on these unified type definitions to bridge the core runtime with the agent harness.

## Summary

- **Vendoring prevents type mismatches** by ensuring `tinytools::Tool` and related types have a single definition shared between `openhuman` and `tinyagents`.
- **Path dependencies** in [`vendor/tinyagents/Cargo.toml`](https://github.com/tinyhumansai/openhuman/blob/main/vendor/tinyagents/Cargo.toml) force compilation from the vendored source rather than fetching potentially divergent versions from crates.io.
- **The orphan rule** in Rust makes this necessary—without identical types, `tinyagents` could not implement traits that `openhuman` expects to use.
- **CI enforcement** via `scripts/ci/check-vendored.mjs` ensures the vendored copy stays synchronized with upstream changes.
- **Build reproducibility** improves because the repository contains all necessary source code, eliminating network dependencies during compilation.

## Frequently Asked Questions

### Why can't tinyagents just use a normal Cargo dependency on tinytools?

If `tinyagents` used a normal crates.io dependency while `openhuman` also depended on `tinytools`, Cargo could resolve the two usages to different versions or different compiled instances. Because Rust treats these as distinct types, `tinyagents` would be unable to implement the `Tool` trait that `openhuman` expects, violating Rust's orphan rules and causing compilation errors.

### Where is the vendored tinytools code located in the repository?

The vendored checkout resides at `vendor/tinyagents/vendor/tinytools/`. The [`vendor/tinyagents/Cargo.toml`](https://github.com/tinyhumansai/openhuman/blob/main/vendor/tinyagents/Cargo.toml) file contains a path dependency pointing to this internal directory, ensuring both the core library and the agent harness reference identical source files.

### How does the project ensure the vendored copy stays up to date?

The repository includes a CI script at `scripts/ci/check-vendored.mjs` that verifies the vendored copy matches the upstream `tinytools` source. This ensures any updates to the tool interfaces are deliberately synchronized across both crates, preventing accidental version drift.

### Does vendoring affect how I implement custom tools for the OpenHuman runtime?

No, vendoring simplifies implementation. You import tool types from `tinytools` (or through `openhuman::tools` re-exports) knowing that the types match exactly what the `tinyagents` harness expects. This guarantees that when you register a tool with the core registry, the agent runner can invoke it without type compatibility issues.