Understanding the TinyBus ABI for Loadable Native Modules in OpenHuman

The TinyBus ABI is a lightweight, versioned interface that allows OpenHuman to load native extensions as compiled cdylib modules, exposing D-Bus-style object paths and method signatures with mandatory manifest verification and a strict no-unload guarantee.

OpenHuman extends its capabilities through native modules that communicate via TinyBus, an internal message bus system. The TinyBus ABI for loadable native modules defines the contract between the core runtime and these external libraries, specifying how functions are exported, how errors are handled, and how modules are attested before execution.

Core Concepts of the TinyBus Module ABI

A TinyBus module is not a simple DLL; it is a compiled cdylib that implements a specific protocol for service discovery, method invocation, and lifecycle management.

Module Definition and Lifetime

According to src/openhuman/modules/mod.rs, a module is defined as "a compiled cdylib that speaks the tinybus module ABI"【src/openhuman/modules/mod.rs†L3-L5】. Once loaded, this library remains mapped in memory for the entire process lifetime. The source explicitly states that "tinybus never unloads a library"【src/openhuman/modules/mod.rs†L24-L25】, meaning a faulted or compromised module is permanently disabled rather than removed from the address space.

Object Path Conventions

Modules expose services under unique bus names (e.g., ai.tinyhumans.tinymemory.ChatHost). TinyBus derives the object path by transforming the bus name: replacing dot separators with forward slashes. As documented in src/openhuman/modules/registry.rs, "tinybus derives a module's object path from its bus name by replacing" dots with slashes【src/openhuman/modules/registry.rs†L787-L788】. For example, the bus name ai.tinyhumans.tinymemory.ChatHost becomes the object path /ai/tinyhumans/tinymemory/ChatHost.

Interface Declaration

Developers export functions using the #[tinybus::interface(name = "...")] attribute macro. This attribute expands to register D-Bus-compatible method signatures on the module's object path. In src/openhuman/modules/tokenjuice_host.rs, the interface is declared as:

#[tinybus::interface(name = "ai.tinyhumans.tinyjuice.MlHost")]
pub trait MlHost {
    // method definitions
}

【src/openhuman/modules/tokenjuice_host.rs†L15-L21】

Error Handling Contract

All exported methods must return tinybus::Result<T>. Errors are wrapped in tinybus::Error::MethodFailed with sanitized, non-secret messages to prevent information leakage. The helper function method_error is used throughout implementations to convert internal errors into the required format【src/openhuman/modules/tokenjuice_host.rs†L29-L30】.

Module Lifecycle and Security

The TinyBus ABI enforces strict security boundaries during the loading process, ensuring only authenticated code executes within the core process.

Manifest Verification

Before admission, TinyBus fetches the release's checksum.toml and compares it against a pinned digest stored in the registry. As noted in src/openhuman/modules/registry.rs, "tinybus fetches the release's own checksum.toml, compares it with the digest" to verify integrity【src/openhuman/modules/registry.rs†L14-L15】. Only modules with matching digests are loaded via dlopen.

Proxy Acquisition

Host code interacts with loaded modules through a tinybus::Proxy object. Once attestation completes successfully, the registry creates and returns this proxy. In src/openhuman/modules/wallet.rs, the attested_proxy method returns a tinybus::Proxy that core components use to invoke module methods【src/openhuman/modules/wallet.rs†L247-L252】.

No-Unload Guarantee

The ABI deliberately omits an unload operation. As stated in src/openhuman/modules/schemas.rs: "There is no unload, and there cannot be: tinybus never unloads a library"【src/openhuman/modules/schemas.rs†L7-L8】. This design prevents use-after-free vulnerabilities and ensures that a faulted module leaves the process in a consistent, predictable state.

Implementing a TinyBus Module

To create a compliant native module, you must implement the trait defined by your interface attribute and handle the TinyBus result types.

Defining the interface:

#[tinybus::interface(name = "ai.tinyhumans.tinymemory.ChatHost")]
pub trait ChatHost {
    /// Generate a model response for a chat message.
    async fn generate(
        &self,
        prompt: String,
        temperature: f32,
    ) -> tinybus::Result<ModelResponse>;
}

Implementing the trait:

pub struct ChatHostImpl;

#[tinybus::impl_interface]
impl ChatHost for ChatHostImpl {
    async fn generate(
        &self,
        prompt: String,
        temperature: f32,
    ) -> tinybus::Result<ModelResponse> {
        // Implementation logic here
        Ok(ModelResponse { text: response })
    }
}

Loading from the core:

use tinybus::{Connection, Proxy};

async fn load_chat_module(conn: &Connection) -> Result<Proxy, tinybus::Error> {
    // Query registry for the bus name implementing this interface
    let bus_name = conn.call("modules", "list", ("ai.tinyhumans.tinymemory.ChatHost",))
        .await?
        .0;

    // Create proxy using the derived object path
    let object_path = format!("/{}", bus_name.replace('.', "/"));
    let proxy = tinybus::Proxy::new(conn, &bus_name, &object_path).await?;
    
    Ok(proxy)
}

Invoking methods:

async fn generate_chat(
    proxy: &Proxy,
    prompt: &str,
) -> Result<ModelResponse, tinybus::Error> {
    proxy.call("generate", (prompt.to_string(), 0.7_f32)).await
}

Summary

  • The TinyBus ABI requires modules to be compiled as cdylib libraries that implement D-Bus-style interfaces using the #[tinybus::interface] attribute.
  • Object paths are automatically derived from bus names by replacing dots with slashes, as implemented in src/openhuman/modules/registry.rs.
  • Security depends on manifest verification against pinned digests in checksum.toml before any library code executes.
  • Error handling mandates tinybus::Result<T> return types and sanitized error messages via method_error.
  • Lifetime management follows a strict no-unload policy: once loaded in src/openhuman/modules/mod.rs, a library remains mapped until process termination, preventing state corruption.

Frequently Asked Questions

What is the TinyBus ABI?

The TinyBus ABI is the application binary interface that defines how OpenHuman loads and communicates with native extension modules. It specifies that modules must be compiled as cdylib libraries, export methods using the #[tinybus::interface] attribute, return tinybus::Result<T> types, and identify themselves via unique bus names that map to D-Bus-style object paths.

How does TinyBus handle module verification?

Before loading any native code, TinyBus performs attestation by downloading the module's checksum.toml manifest and comparing its digest against a pinned value in the registry, as defined in src/openhuman/modules/registry.rs. Only modules with matching cryptographic digests are loaded via dlopen, ensuring code integrity.

Why doesn't TinyBus support unloading modules?

TinyBus intentionally omits unload functionality because unloading a shared library that has potentially faulted or been compromised could leave the host process in an inconsistent state. As documented in src/openhuman/modules/schemas.rs, this guarantees that once a module is refused or fails, it remains unavailable for the lifetime of the process, preventing use-after-free vulnerabilities.

How are object paths determined in TinyBus?

Object paths are derived automatically from the module's bus name by replacing period separators with forward slashes. For example, a module registering under the bus name ai.tinyhumans.tinymemory.ChatHost receives the object path /ai/tinyhumans/tinymemory/ChatHost, as implemented in the registry's path derivation logic.

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 →