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
cdyliblibraries 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.tomlbefore any library code executes. - Error handling mandates
tinybus::Result<T>return types and sanitized error messages viamethod_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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →