Extending OpenHuman with Loadable Native Modules: The tinydocs Implementation Guide

OpenHuman supports runtime-extensible capabilities through loadable native modules—compiled as cdylib libraries that implement the TinyBus ABI—allowing heavyweight features like document generation to be added without bloating the core binary or triggering a rebuild.

OpenHuman’s Rust-based core is architected for runtime extensibility via dynamically loaded native modules. The tinydocs module serves as the canonical reference implementation, demonstrating how to isolate heavy dependencies like PDF and DOCX parsers while maintaining strict version control and memory safety through the TinyBus inter-process communication layer.

Architectural Benefits of Loadable Modules

Moving capabilities like document generation into native modules provides four critical architectural advantages over static linking.

Dependency Boundary Isolation

Heavy codec crates such as pdf-extract, docx-rs, and zstd remain outside the core binary. In src/openhuman/modules/tinydocs/Cargo.toml, these dependencies are declared within the module’s own manifest rather than the core’s. When tinydocs is disabled, the core binary shrinks by approximately 80 kB and the compile-time dependency count drops from 505 to 448, as documented in scripts/kernel-floor.limits.

Runtime Isolation via TinyBus

Each module operates in-process but behind the TinyBus broker, which enforces deadlines, queue limits, and panic safety. The broker initializes via tinybus::OnceBus::init_in_process in src/openhuman/modules/registry.rs, ensuring that a panic within the tinydocs codec stack crashes only the module, not the host application.

Versioned Admission Control

OpenHuman pins a SHA-256 digest of every released module artifact in the static MODULES constant within src/openhuman/modules/registry.rs. Before loading, the core downloads the module’s checksum.toml and verifies the cryptographic hash. If the digest mismatches, the load aborts immediately, preventing supply-chain attacks.

Contract-Only Surface Area

The core communicates with tinydocs solely through the tinydocs-bus contract crate located at src/openhuman/modules/tinydocs-bus/src/lib.rs. This crate contains only wire types such as GenerateDocumentInput and DocumentSpec, meaning implementation changes inside the module never force a core rebuild.

The Module Loading Lifecycle

OpenHuman discovers and activates modules through a five-phase pipeline implemented in src/openhuman/modules/loader.rs.

  1. Discovery – At startup, the core parses the [modules] section of the configuration file defined in src/openhuman/config/schema/modules.rs. Each entry specifies a release URL and the expected SHA-256 digest.

  2. Verification – The core downloads the release’s checksum.toml and compares it against the pinned digest in the registry. Mismatches trigger immediate rejection.

  3. Download and Extraction – The artifact (a .so, .dylib, or .dll depending on target) is fetched, unpacked, and opened using libloading::Library.

  4. Broker Creation – The module registers its TinyBus interfaces via tinybus::Broker::new. The core instantiates a per-module broker via tinybus::OnceBus::init_in_process, preventing the module from publishing global events.

  5. Interface Binding – The core obtains a typed handle to the module’s exported object (tinydocs::DocumentEngine) through the contract crate. All invocations route over the broker, respecting TinyBus deadline and cancellation semantics.

The tinydocs Contract Interface

The tinydocs-bus crate defines the immutable contract between core and module. Located at src/openhuman/modules/tinydocs-bus/src/lib.rs, it exposes only the data structures required for serialization across the TinyBus wire.

Key types include:

  • GenerateDocumentInput – Encapsulates document generation parameters
  • DocumentSpec – Enum discriminating between PDF, DOCX, and PPTX formats
  • DocumentResult – Return type indicating success, error, or unavailability

This contract-only approach ensures that updating the PDF extraction library inside tinydocs requires no changes to the core codebase.

Calling tinydocs from the Core

The ToolRegistry in src/openhuman/tools/registry.rs forwards tool calls to loaded modules through the contract types.

use openhuman::modules::tinydocs_bus::{
    GenerateDocumentInput, DocumentSpec, DocumentResult,
};
use openhuman::tools::registry::ToolRegistry;

let doc_input = GenerateDocumentInput {
    spec: DocumentSpec::Pdf { path: PathBuf::from("example.pdf") },
    // additional parameters...
};

match tool_registry.call_tool("tinydocs_generate", doc_input).await {
    Ok(DocumentResult::Generated { url }) => {
        println!("Document generated at {}", url);
    }
    Ok(DocumentResult::Error(err)) => {
        eprintln!("Generation failed: {}", err);
    }
    Err(e) => {
        eprintln!("Tool unavailable: {}", e);
    }
}

The call_tool method resolves at compile-time to the contract types in tinydocs-bus, then dispatches the request at runtime over the TinyBus broker to the loaded native module.

Creating Your Own OpenHuman Module

Follow the tinydocs pattern to extend OpenHuman with custom capabilities such as machine-learning inference or video transcoding.

Step 1: Define the Contract Crate

Create a new crate myfeature-bus containing only request and response types.

// myfeature-bus/src/lib.rs
pub struct MyRequest { pub payload: String }
pub enum MyResponse { Ok(String), Err(String) }

Step 2: Implement the Module

Create a cdylib crate that depends on your contract crate and implements the service logic.

// myfeature-module/src/lib.rs
#[tokio::main]
pub async fn main() -> tinybus::Result<()> {
    let broker = tinybus::Broker::new();
    broker.register_interface::<MyRequest, MyResponse>(my_handler);
    broker.run().await
}

Step 3: Publish a Release

Tag a GitHub release and include a checksum.toml file containing SHA-256 digests for each target artifact (Linux, macOS, Windows).

Step 4: Pin in the Core Registry

Add a static entry to src/openhuman/modules/registry.rs in the MODULES constant.

static MODULES: &[ModuleEntry] = &[
    ModuleEntry {
        name: "myfeature",
        url: "https://github.com/yourorg/myfeature/releases/download/v1.0.0/myfeature-{{target}}.tar.gz",
        digest: "sha256:abcd…",
    },
];

The core automatically fetches, verifies, and loads your module at the next startup.

Summary

  • Loadable native modules are cdylib libraries implementing the TinyBus ABI that extend OpenHuman without core rebuilds.
  • Dependency isolation keeps heavy crates like pdf-extract out of the core binary, reducing size by ~80 kB in the tinydocs case.
  • Verification relies on SHA-256 digests pinned in src/openhuman/modules/registry.rs and distributed via checksum.toml.
  • Runtime safety is enforced by per-module TinyBus brokers created via tinybus::OnceBus::init_in_process.
  • Contract crates like tinydocs-bus provide the only interface between core and module, enabling independent release cycles.

Frequently Asked Questions

What file format must OpenHuman native modules use?

OpenHuman expects native modules as platform-specific dynamic libraries—.so on Linux, .dylib on macOS, and .dll on Windows—compiled with the cdylib crate type in Cargo. These are packaged as .tar.gz archives containing the library and a checksum.toml for verification.

How does OpenHuman verify module integrity before loading?

Before opening a module with libloading::Library, the core downloads the checksum.toml from the release URL specified in src/openhuman/modules/registry.rs and compares the SHA-256 digest against the static digest field in the MODULES constant. If the hashes differ, the module load aborts immediately.

What happens if a loadable module fails to load at runtime?

If network failure, digest mismatch, or library corruption occurs, the core gracefully omits the module’s tools from the ToolRegistry. Subsequent calls to that module return ToolResult::Unavailable rather than panicking, allowing the application to continue with reduced functionality.

What is the TinyBus ABI and why does OpenHuman use it?

The TinyBus ABI is an in-process message broker protocol that enforces structured communication between the core and modules. OpenHuman uses it to provide deadline-aware request handling, panic isolation, and queue-limit enforcement, ensuring that a failure in a document processing module cannot destabilize the host application.

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 →