TinyBus Module ABI Explained: How OpenHuman Loads Native Extensions
The TinyBus module ABI is a strict contract that compiled cdylib libraries must satisfy before OpenHuman's core loads them, covering descriptor validation, platform matching, and SHA‑256 digest verification.
The TinyBus module ABI defines exactly how native, dynamically-linked libraries integrate with the OpenHuman runtime. In tinyhumansai/openhuman, modules are self-contained cdylib artifacts that extend core capabilities—such as ML inference, document processing, or memory management—while remaining sandboxed in their own in-process TinyBus broker. This article breaks down the ABI's validation gates, shows how to author compliant modules, and walks through the loading pipeline implemented in the source code.
What Defines a TinyBus Module
A module in OpenHuman is not merely a shared library. It is a cdylib downloaded from a pinned release, verified against a known SHA‑256 digest, and attached to a dedicated TinyBus broker that mediates all RPC traffic.
According to src/openhuman/modules/mod.rs, modules expose callable objects through the #[tinybus::interface] macro. This macro registers an interface name (the bus name) and an object path that callers use to locate methods. The host installs its side of the contract via connection.serve_at before requesting the module's bus name.
Modules live for the entire process lifetime. TinyBus never unloads a library, and a failed load is cached for the remainder of the run—this behavior is enforced in src/openhuman/modules/ops.rs.
TinyBus Module ABI Validation Checks
Before dlopen executes, TinyBus inspects the module descriptor against seven strict criteria. These checks form the ABI gate that protects the host process from incompatible or tampered code.
| Check | Purpose |
|---|---|
| Magic number | Confirms the file is a TinyBus module, not an arbitrary shared library |
| ABI revision | Ensures the binary layout matches the core's expected version |
| Descriptor layout | Validates object names, paths, and structural metadata |
| Target triple | Matches the compiled target (e.g., x86_64-unknown-linux-gnu) against the host |
| Pointer width / endianness | Rejects 32-bit modules on 64-bit hosts or mismatched endianness |
| Feature bits | Flags such as panic = abort cause rejection—modules must use unwind-safe panics |
| Digest verification | Compares the SHA‑256 hash from src/openhuman/modules/registry.rs against the downloaded artifact |
If any check fails, TinyBus returns a sanitized error—no file paths or URLs are exposed to the caller. This hardening prevents information leakage about the module resolution pipeline.
Module Loading Pipeline in ops.rs
The ops::ensure_loaded function in src/openhuman/modules/ops.rs implements the complete loading workflow. This is the entry point for all module interactions.
use crate::openhuman::config::Config;
use crate::openhuman::modules::ops;
async fn use_documents_module(config: &Config) -> Result<(), String> {
ops::ensure_loaded(config, "tinydocs").await?;
let runtime = modules::host::runtime().await?;
let proxy = runtime
.connection()
.proxy("ai.tinyhumans.tinydocs", "/ai.tinyhumans.tinydocs.DocumentWriter")?;
// proxy.call(...).await to invoke RPC methods
Ok(())
}
ensure_loaded executes this resolution sequence:
- Cache check — Skip if the module is already loaded or marked failed for this process
- Local resolution — Search for a developer override, installed artifact, or TinyBus search path match
- Remote fetch — Download the pinned release if enabled and local sources exhaust
- Verification gate — Validate the SHA‑256 digest against
registry.rs, then run the full ABI descriptor checks - Broker attachment —
dlopenthe module and connect it to its dedicated TinyBus broker inhost.rs
Declaring a TinyBus Interface in a Module
Modules expose functionality through the #[tinybus::interface] macro. The contract-generated names ensure synchronization between host and module.
use tinybus::ObjectPath;
use tinyjuice_bus::names::{ML_HOST_NAME as NAME, ML_HOST_PATH as PATH};
#[derive(Clone)]
struct MlHost;
#[tinybus::interface(name = "ai.tinyhumans.tinyjuice.MlHost")]
impl MlHost {
async fn compress(
&self,
text: String,
options: serde_json::Value,
) -> tinybus::Result<Option<String>> {
let options = serde_json::from_value(options).map_err(method_error)?;
crate::openhuman::inference::tokenjuice::ml::compress(&text, &options)
.await
.map_err(method_error)
}
}
fn method_error(error: impl std::fmt::Display) -> tinybus::Error {
tinybus::Error::MethodFailed {
name: "ai.tinyhumans.tinyjuice.Error.Host".to_string(),
message: error.to_string(),
}
}
pub async fn install(connection: &tinybus::Connection) -> tinybus::Result<()> {
connection
.serve_at(ObjectPath::new(PATH)?, MlHost)
.await?;
connection.request_name(NAME).await
}
Key implementation details from src/openhuman/modules/tokenjuice_host.rs:
- The interface name
ai.tinyhumans.tinyjuice.MlHostis a globally unique bus identifier - The object path comes from the generated
tinyjuice_buscrate, preventing drift between contract and implementation - Host-side objects use
connection.serve_atto install callbacks that modules can invoke
Host-Side Broker Architecture
src/openhuman/modules/host.rs creates a separate TinyBus broker for every loaded module. This isolation prevents modules from directly accessing the host's main bus or other modules' interfaces.
The ModuleHost::new constructor attaches the module loader to this dedicated broker. As noted in comments at lines 169–172, the following descriptor fields are verified automatically:
- ABI revision and descriptor layout
- Target triple, pointer width, and endianness
- Feature bits, with explicit rejection of
panic = abortmodules
Once admitted, the module communicates exclusively through its broker. The host obtains proxies to module objects via runtime.connection().proxy(bus_name, object_path).
Registry and Security Model
src/openhuman/modules/registry.rs serves as the source of truth for the ABI gate. It contains:
- The compile-time list of available modules
- Pinned release versions for each module
- SHA‑256 digests used during
ops.rsverification
This registry-enforced trust model means:
- Only modules with pre-registered digests can load
- Version pinning prevents supply-chain drift
- Digest mismatches abort loading before any code executes
Summary
- The TinyBus module ABI is a multi-layer contract spanning descriptor validation, platform matching, and cryptographic verification
- Seven checks run before
dlopen: magic number, ABI revision, descriptor layout, target triple, pointer width/endianness, feature bits, and SHA‑256 digest - Modules are process-immortal—once loaded or failed, they remain cached for the process lifetime
ops::ensure_loadedinsrc/openhuman/modules/ops.rsimplements the full resolution, download, and verification pipelinehost.rsprovides broker isolation so each module operates in its own TinyBus instance- The
#[tinybus::interface]macro binds interface names and object paths to concrete Rust implementations
Frequently Asked Questions
What happens if a module's SHA‑256 digest doesn't match the registry?
Loading aborts immediately with a sanitized error. The mismatch is detected in src/openhuman/modules/ops.rs before any descriptor checks or dlopen calls occur. No file paths or URLs are leaked to the caller.
Can TinyBus unload a module to free memory?
No. As implemented in src/openhuman/modules/ops.rs, modules are never unloaded once admitted. A failed load is also cached for the process lifetime to prevent repeated expensive resolution attempts.
Why does the ABI reject modules compiled with panic = abort?
The panic = abort strategy terminates the entire process on panic, violating TinyBus's isolation guarantees. The feature bit check in src/openhuman/modules/host.rs explicitly rejects such modules to preserve host stability.
How do host and module stay synchronized on method signatures?
Both sides import names from a generated -bus crate (e.g., tinyjuice_bus). This crate contains the interface name and object path constants, ensuring the host's serve_at call and the module's bus registration refer to identical identifiers.
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 →