# How TinyBus Admits Loadable Native Modules in OpenHuman: The tinydocs and tinyvoice Admission Pipeline

> Discover how TinyBus admits loadable native modules via a five-stage pipeline including registry declaration, checksum verification, and interface binding. Learn about the tinydocs and tinyvoice admission process.

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

---

**TinyBus admits loadable native modules through a five-stage admission pipeline that includes registry declaration, SHA-256 checksum verification, dlopen-based attachment, and contract-based interface binding, ensuring only authenticated artifacts from the static registry are loaded into the process.**

OpenHuman's Rust core extends its capabilities at runtime by loading native `cdylib` modules such as **tinydocs** (document generation) and **tinyvoice** (speech-to-text) through the **TinyBus module system**. Unlike traditional linked libraries, these modules are discovered, verified, and attached dynamically via a strictly controlled admission pipeline. The TinyBus module system ensures that only cryptographically verified artifacts matching the host's target triple are admitted into the process address space.

## The TinyBus Module Admission Pipeline

The admission of loadable modules follows a rigorous five-stage pipeline defined in [`src/openhuman/modules/registry.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/modules/registry.rs) and [`src/openhuman/modules/ops.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/modules/ops.rs). Each stage implements specific security and binding guarantees.

### Stage 1: Registry Declaration

TinyBus maintains a **static registry table** in [`src/openhuman/modules/registry.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/modules/registry.rs) (lines 10-30) that explicitly lists every module the system may load. Each registry entry contains three critical fields:

- **Bus name**: The unique identifier (e.g., `"ai.tinyhumans.tinydocs"` or `"ai.tinyhumans.tinyvoice"`)
- **Object path**: The logical path used to locate the module artifact
- **SHA-256 digest**: The cryptographic hash of the release artifact for integrity verification

This registry acts as a whitelist. If a module is not declared in this static table, TinyBus will refuse to load it regardless of the file's presence on the filesystem.

### Stage 2: Artifact Selection and Verification

When the core requests a module (e.g., via the `modules.list` RPC), TinyBus performs cryptographic verification before loading:

1. **Asset selection**: TinyBus fetches the release's [`checksum.toml`](https://github.com/tinyhumansai/openhuman/blob/main/checksum.toml) and selects a matching asset
2. **Tag validation**: The system verifies that the URL points to a **tagged** artifact; branch URLs are explicitly rejected
3. **Digest validation**: The downloaded file's SHA-256 hash must match the digest stored in the registry entry. If the digest differs, TinyBus rejects the download immediately

This verification ensures that only the exact artifact published by Tiny Humans can be loaded, preventing substitution attacks.

### Stage 3: Process-Wide Bus Attachment

After passing verification, the module is attached to the process through [`src/openhuman/modules/ops.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/modules/ops.rs):

- **Broker creation**: TinyBus creates a `tinybus::broker::Broker` instance that will own the module's bus
- **Dynamic loading**: The library is loaded into the process using `dlopen`
- **Address space sharing**: The module inherits the same address space, privileges, and crash domain as the core Rust process

Critically, as noted in [`ops.rs`](https://github.com/tinyhumansai/openhuman/blob/main/ops.rs) (lines 7-9), **TinyBus never unloads a library**. Once loaded, the module remains attached for the process lifetime. Any failure—whether refusal, fault, or missing artifacts—is cached permanently, eliminating the "unload-and-reuse-after-exploit" attack surface.

### Stage 4: Interface Binding

Each loadable module ships with a companion **contract crate** (e.g., `tinydocs-bus` or `tinyvoice-bus`) located in the `vendor/` directory. These contracts define:

- The TinyBus **object name**
- **Method signatures** for RPC calls
- **Version** compatibility information

After loading, the core uses the contract's `tinybus::Connection` to obtain a **proxy object** (`tinybus::Proxy`) that implements the module's RPC interface. For example, calls to `GenerateDocument` or `TranscribeAudio` are routed through this proxy, which marshals arguments across the bus boundary while maintaining type safety.

### Stage 5: Error Classification

Errors originating from loadable modules are wrapped in `tinybus::Error` types. The core maps these to domain-specific error types—such as `VoiceCallError` for tinyvoice or equivalent document errors for tinydocs—so the rest of OpenHuman can handle failures uniformly. The `classify` function in [`src/openhuman/modules/voice.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/modules/voice.rs) (lines 491-503) demonstrates this mapping for the voice module.

## How tinydocs and tinyvoice Navigate the Pipeline

Both modules follow the identical admission machinery but bind to different bus names and expose distinct capabilities.

### tinydocs Module Flow

- **Bus name**: `"ai.tinyhumans.tinydocs"`
- **Contract crate**: `tinydocs-bus` located in `vendor/tinydocs`
- **Key method**: `GenerateDocx`
- **Admission path**: When the core needs to generate a document, it calls `modules::documents::attested_proxy`, which coordinates the registry lookup, verification, and `dlopen` sequence, returning a `tinybus::Proxy` ready to invoke `GenerateDocx`

### tinyvoice Module Flow

- **Bus name**: `"ai.tinyhumans.tinyvoice"`
- **Contract crate**: `tinyvoice-bus`
- **Key methods**: `TranscribeAudio` and `GenerateSpeech`
- **Admission path**: The core obtains a proxy via `modules::voice::attested_proxy`. The proxy creation logic in [`src/openhuman/modules/voice.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/modules/voice.rs) (lines 491-505) handles the broker connection and proxy instantiation

Because TinyBus creates the **broker once per process**, subsequent calls to either module reuse the same in-process bus, eliminating repeated load overhead while maintaining isolation boundaries.

## Security Guarantees of the TinyBus Module System

The admission pipeline implements three critical security controls:

### Digest-Based Authenticity

The SHA-256 digest stored in [`src/openhuman/modules/registry.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/modules/registry.rs) guarantees that only the exact artifact published by Tiny Humans can be loaded. Any modification to the binary—whether malicious or accidental—results in a digest mismatch and immediate rejection.

### Target-Triple Validation

Before loading, [`src/openhuman/modules/platform.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/modules/platform.rs) validates that the binary matches the host's target triple (architecture and platform). This prevents accidental loading of incompatible builds (e.g., macOS binaries on Linux hosts) that could cause undefined behavior.

### Never-Unload Policy

Once a module passes verification and attaches via `dlopen`, it remains mapped for the process lifetime. This design eliminates the classic attack surface where an exploit might trigger an unload, modify the library on disk, and force a reload of compromised code.

## Implementing Module Calls in Practice

The following Rust example demonstrates loading the tinydocs module and invoking document generation:

```rust
use openhuman::modules::{self, Config};
use tinybus::Proxy;

async fn generate_document(input_markdown: &str, output_path: &str) -> Result<(), modules::Error> {
    // Load configuration containing the modules section
    let config = Config::load().await?;
    
    // Obtain an attested proxy for the documents module
    // This performs registry lookup, checksum verification, and dlopen if needed
    let docx_proxy: Proxy = modules::documents::attested_proxy(&config).await?;
    
    // Invoke the method defined in the tinydocs-bus contract
    let result = docx_proxy
        .call("GenerateDocx", (input_markdown, output_path))
        .await?;
    
    // Convert TinyBus errors into OpenHuman-specific error types
    tinybus::Result::from(result)
        .map_err(modules::documents::classify)
}

```

To use **tinyvoice** instead, substitute `modules::documents` with `modules::voice` and call methods like `TranscribeAudio` using the same proxy pattern.

## Summary

- TinyBus uses a **static registry** in [`src/openhuman/modules/registry.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/modules/registry.rs) to whitelist allowable modules and their SHA-256 digests
- **Cryptographic verification** against [`checksum.toml`](https://github.com/tinyhumansai/openhuman/blob/main/checksum.toml) and target-triple checks in [`platform.rs`](https://github.com/tinyhumansai/openhuman/blob/main/platform.rs) ensure artifact integrity before `dlopen`
- **Contract crates** (`tinydocs-bus`, `tinyvoice-bus`) define type-safe RPC interfaces via `tinybus::Proxy` objects
- The **never-unload policy** prevents runtime exploitation by caching failures and prohibiting library unmapping
- Modules like **tinydocs** and **tinyvoice** share identical admission machinery but bind to unique bus names (`ai.tinyhumans.tinydocs`, `ai.tinyhumans.tinyvoice`)

## Frequently Asked Questions

### How does TinyBus verify module authenticity before loading?

TinyBus verifies authenticity through a two-step process defined in [`src/openhuman/modules/registry.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/modules/registry.rs). First, it checks that the module exists in the static registry table. Second, it downloads the artifact from the tagged release URL and validates that its SHA-256 digest matches the hash stored in the registry. If either check fails, the module is rejected before `dlopen` is ever called.

### Can loadable native modules be unloaded dynamically in OpenHuman?

No. According to the source code in [`src/openhuman/modules/ops.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/modules/ops.rs), TinyBus implements a **never-unload policy**. Once a module is loaded via `dlopen`, it remains attached to the process for its entire lifetime. This design decision eliminates security vulnerabilities related to runtime library substitution and ensures consistent behavior after initial load.

### What happens if a module's SHA-256 checksum doesn't match the registry entry?

If the downloaded artifact's SHA-256 digest differs from the value stored in [`src/openhuman/modules/registry.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/modules/registry.rs), TinyBus immediately rejects the download and refuses to load the module. The error is cached for the process lifetime, preventing retry loops. The system will not fall back to untrusted sources or proceed with an unverified binary.

### How do I add a new loadable module to the TinyBus system?

Adding a module requires three steps: First, create the native `cdylib` library and a corresponding **contract crate** defining the TinyBus interface (object name and methods). Second, add an entry to the static registry in [`src/openhuman/modules/registry.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/modules/registry.rs) containing the bus name, object path, and expected SHA-256 digest. Third, implement an `attested_proxy` function in a new file under `src/openhuman/modules/` (following the pattern in [`voice.rs`](https://github.com/tinyhumansai/openhuman/blob/main/voice.rs) or [`documents.rs`](https://github.com/tinyhumansai/openhuman/blob/main/documents.rs)) that handles the proxy creation and error classification for your specific domain.