# Extending OpenHuman with Loadable Native Modules: The tinydocs Implementation Guide

> Extend OpenHuman's capabilities with loadable native modules. Discover how tinydocs integrates seamlessly, adding powerful features without rebuilding the core binary.

- Repository: [Tiny Humans/openhuman](https://github.com/tinyhumansai/openhuman)
- Tags: how-to-guide
- Published: 2026-08-29

---

**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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/modules/registry.rs). Before loading, the core downloads the module’s [`checksum.toml`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/tools/registry.rs) forwards tool calls to loaded modules through the contract types.

```rust
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.

```rust
// 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.

```rust
// 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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/modules/registry.rs) in the `MODULES` constant.

```rust
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`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/modules/registry.rs) and distributed via [`checksum.toml`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/checksum.toml) from the release URL specified in [`src/openhuman/modules/registry.rs`](https://github.com/tinyhumansai/openhuman/blob/main/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.