How the OpenHuman TinyAgents Adapter Seam Integrates Providers and Tools into the Harness

The OpenHuman tinyagents adapter seam is a thin translation layer in openhuman/agent/tinyagents that implements tinyagents harness traits for OpenHuman's native Provider and Tool types, enabling the external harness to execute turns using OpenHuman's existing model clients and tool registry without duplicating policy or execution logic.

OpenHuman by tinyhumansai/openhuman is an open-source AI agent framework that leverages the tinyagents harness for its core execution loop. To avoid rebuilding policy enforcement, tool execution, and LLM request handling from scratch, the project uses a dedicated tinyagents adapter seam that makes OpenHuman's Provider and Tool abstractions appear as native tinyagents components. This integration preserves OpenHuman's rich approval gates, logging, and tool schemas while delegating turn management to the battle-tested harness.

Three Components of the TinyAgents Adapter Seam

The seam lives in the openhuman/agent/tinyagents package and consists of three distinct integration points:

  • Provider adapter (src/openhuman/agent/tinyagents/provider.rs): Converts an OpenHuman Provider into a tinyagents::harness::model::ModelProvider, translating ModelRequest and ModelResponse between the two frameworks.

  • Tool adapter (src/openhuman/tools/traits.rs): Wraps concrete OpenHuman tool implementations from src/openhuman/tools/impl/* as tinyagents::harness::tool::Tool instances, mapping ToolSchema to the harness format.

  • Middleware bridge (src/openhuman/agent/tinyagents/middleware.rs): Stacks tinyagents middleware such as RepeatedToolFailureMiddleware with OpenHuman's ToolPolicy and ApprovalGate to produce a unified RunPolicy.

Provider Adapter: Bridging Model Requests in provider.rs

The provider adapter converts an OpenHuman Provider into a tinyagents::harness::model::ModelProvider that the harness can invoke during each turn. In src/openhuman/agent/tinyagents/provider.rs, the OpenHumanProvider struct implements the request method, which receives a tinyagents::harness::model::ModelRequest and returns a tinyagents::harness::model::ModelResponse.

Inside the method, the adapter translates the tinyagents request into an OpenHuman ProviderRequest using ProviderRequest::from(req), forwards it through the existing HTTP client, and maps the raw JSON response back with ModelResponse::from(resp). This approach reuses OpenHuman's connection pooling, authentication, and retry logic rather than spawning a second HTTP stack.

// src/openhuman/agent/tinyagents/provider.rs
impl tinyagents::harness::model::ModelProvider for OpenHumanProvider {
    async fn request(
        &self,
        req: tinyagents::harness::model::ModelRequest,
    ) -> tinyagents::error::Result<tinyagents::harness::model::ModelResponse> {
        // Translate tinyagents request → OpenHuman request
        let oh_req = ProviderRequest::from(req);
        let resp = self.http_client.send(oh_req).await?;
        // Translate back
        Ok(ModelResponse::from(resp))
    }
}

Tool Adapter: Wrapping OpenHuman Tools for the Harness

Every OpenHuman tool implementation under src/openhuman/tools/impl/* is exposed to the harness via the adapter in src/openhuman/tools/traits.rs. The generic OpenHumanTool<T> struct implements the harness's Tool trait, exposing name, schema, and an async run method. The file also re-exports tinyagents helper functions such as humanize_tool_name, context_detail_from_args, and run_tool to streamline adapter development.

The schema method performs a direct translation from OpenHuman's native ToolSchema to tinyagents::harness::tool::ToolSchema, including parameter definitions. When the harness invokes run, the adapter extracts arguments from the ToolExecutionContext, delegates to the inner OpenHumanToolImpl, and lifts the result into a tinyagents::harness::tool::ToolResult so the harness can append it to the conversation history.

// src/openhuman/tools/traits.rs – tool adapter skeleton
pub struct OpenHumanTool<T> {
    inner: T,
}
impl<T> tinyagents::harness::tool::Tool for OpenHumanTool<T>
where
    T: OpenHumanToolImpl,
{
    fn name(&self) -> &str { self.inner.name() }
    fn schema(&self) -> tinyagents::harness::tool::ToolSchema {
        // Map OpenHuman's schema → tinyagents schema
        tinyagents::harness::tool::ToolSchema {
            description: self.inner.description().into(),
            parameters: tinyagents::harness::tool::ToolParams::from(
                self.inner.parameters(),
            ),
            ..Default::default()
        }
    }
    async fn run(
        &self,
        ctx: tinyagents::harness::tool::ToolExecutionContext,
    ) -> tinyagents::error::Result<tinyagents::harness::tool::ToolResult> {
        // Forward arguments to the real OpenHuman tool
        let args = ctx.args();
        let result = self.inner.run(args).await?;
        Ok(ToolResult::from(result))
    }
}

Middleware Bridge: Unifying Policies in middleware.rs

OpenHuman maintains its own ToolPolicy and ApprovalGate hooks, while the tinyagents harness expects its own middleware types such as RepeatedToolFailureMiddleware. The bridge in src/openhuman/agent/tinyagents/middleware.rs resolves this by constructing a unified RunPolicy.

It layers OpenHuman's budget and timeout constraints with tinyagents-specific middleware into a single policy object. This combined policy is then passed to tinyagents::harness::run::run_harness, ensuring that OpenHuman's approval gates and logging remain active even as the harness applies its native failure-handling rules.

Harness Orchestration: Wiring Adapters into RunConfig

The final assembly happens in src/openhuman/agent/tinyagents/harness.rs, where the orchestration code loads the workspace descriptor, instantiates the provider adapter, populates the tool registry with OpenHumanTool<T> wrappers, and bundles the stacked middleware.

The resulting RunConfig is handed to tinyagents::harness::run::run_harness, which kicks off the turn loop. Because every component speaks the harness's native trait language, the core execution loop requires no knowledge of OpenHuman's internal ProviderConfig or concrete tool structs.

// src/openhuman/agent/tinyagents/harness.rs – building the final harness
let provider = OpenHumanProvider::new(config.clone());
let tool_registry = OpenHumanToolRegistry::new(); // registers all OpenHumanTool<T>
let middleware = tinyagents::middleware::RepeatedToolFailureMiddleware::default();

let run_cfg = tinyagents::harness::run::RunConfig {
    provider: Box::new(provider),
    tools: Box::new(tool_registry),
    policy: tinyagents::harness::run::RunPolicy::default()
        .with_middleware(middleware),
    ..Default::default()
};

tinyagents::harness::run::run_harness(run_cfg).await?;

Turn Execution Flow: How Data Moves Through the Seam

During a single turn, the tinyagents harness delegates to the adapter seam at three critical points:

  1. The harness queries the provider adapter for a ModelRequest. The adapter builds the payload from OpenHuman's ProviderConfig—including model name, temperature, and system prompt—and issues the request through its HTTP client.

  2. When the LLM response contains a tool call, the tinyagents core parses the call and resolves the tool name against the registry.

  3. The matching tool adapter receives a ToolExecutionContext, extracts the arguments via ctx.args(), and invokes the underlying implementation from crate::openhuman::tools::impl::....

  4. The tool result is wrapped as a tinyagents::harness::tool::ToolResult and returned to the model, while OpenHuman's observation logging and approval-gate handling remain intact.

For a complete end-to-end example, src/openhuman/web_chat/run_task.rs demonstrates a full turn passing through the tinyagents harness with all adapter layers active.

Summary

Frequently Asked Questions

What is the tinyagents adapter seam in OpenHuman?

The tinyagents adapter seam is a thin integration layer in the openhuman/agent/tinyagents package that lets OpenHuman's native Provider and Tool abstractions plug into the external tinyagents harness. It preserves OpenHuman's existing logging, approval gates, and tool schemas while allowing the harness to manage turn execution.

How does the provider adapter translate LLM requests?

In src/openhuman/agent/tinyagents/provider.rs, the OpenHumanProvider implements the ModelProvider trait's request method. It converts the incoming ModelRequest into an OpenHuman ProviderRequest, forwards it via the existing HTTP client, and maps the response back into a ModelResponse that the harness understands.

Where does the tool schema mapping occur?

Schema mapping occurs in src/openhuman/tools/traits.rs, where the OpenHumanTool<T> wrapper translates OpenHuman's ToolSchema into tinyagents::harness::tool::ToolSchema. This ensures parameter definitions and descriptions remain compatible with the harness's tool-calling parser.

How does middleware from both systems coexist?

The middleware.rs bridge stacks tinyagents::middleware::RepeatedToolFailureMiddleware alongside OpenHuman's ToolPolicy and ApprovalGate inside a single RunPolicy. The unified policy is passed to run_harness, so both policy enforcement layers execute during every turn.

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 →