# OpenHuman Controller Schema Pattern: How RPC Handlers Are Wired

> Explore OpenHuman's controller schema pattern. Learn how API metadata separates from business logic and RPC handlers connect via a global registry for efficient JSON-RPC dispatch.

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

---

**The controller-schema pattern in OpenHuman separates API metadata from business logic by defining `ControllerSchema` structs in domain-specific [`schemas.rs`](https://github.com/tinyhumansai/openhuman/blob/main/schemas.rs) files, pairing them with handler functions in `RegisteredController` entries, and dispatching JSON-RPC requests through a global registry keyed by `"${namespace}.${function}"`.**

OpenHuman’s Rust backend implements a **controller-schema** architecture that cleanly decouples API contract definitions from their implementations. This design ensures every JSON-RPC method is self-documenting via structured schemas while maintaining strict separation between the HTTP transport layer and domain business logic.

## Schema Definition in Domain Modules

Each domain—such as *webhooks*, *wallet*, or *voice*—resides under `src/openhuman/<domain>/` and contains a [`schemas.rs`](https://github.com/tinyhumansai/openhuman/blob/main/schemas.rs) file that declares the API contract for every public RPC function.

### The ControllerSchema Structure

The `pub fn schemas(function: &str) -> ControllerSchema` match block returns metadata describing the RPC interface. The **ControllerSchema** struct captures:

- **namespace** – The domain name (e.g., `"webhooks"`)
- **function** – The method name (e.g., `"list_logs"`)
- **description** – Human-readable documentation for UI and auto-generated API listings
- **inputs** – A `Vec<FieldSchema>` describing request parameters, including type, requirement status, and comments
- **outputs** – A `Vec<FieldSchema>` describing the JSON response structure

Example from [`src/openhuman/webhooks/schemas.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/webhooks/schemas.rs):

```rust
pub fn schemas(function: &str) -> ControllerSchema {
    match function {
        "list_logs" => ControllerSchema {
            namespace: "webhooks",
            function: "list_logs",
            description: "List captured webhook request and response debug logs.",
            inputs: vec![FieldSchema {
                name: "limit",
                ty: TypeSchema::Option(Box::new(TypeSchema::U64)),
                comment: "Maximum number of log entries to return.",
                required: false,
            }],
            outputs: vec![json_output("result", "Webhook debug log list.")],
        },
        // ... other functions ...
        _ => ControllerSchema { ... }
    }
}

```

### Handler Registration

The same [`schemas.rs`](https://github.com/tinyhumansai/openhuman/blob/main/schemas.rs) file exposes `pub fn all_registered_controllers() -> Vec<RegisteredController>`, which pairs each schema with its concrete implementation. Each handler follows the signature:

```rust
fn handle_<name>(params: Map<String, Value>) -> ControllerFuture

```

Handlers extract typed arguments using `deserialize_params::<YourParams>(params)?` before delegating to core business logic, typically found in `src/openhuman/<domain>/ops.rs`.

A registration entry looks like:

```rust
RegisteredController {
    schema: schemas("list_logs"),
    handler: handle_list_logs,
}

```

## Connecting RPC Handlers to the Global Registry

### Global Aggregation at Startup

The core server aggregates all domain controllers during initialization. In [`src/core/all.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/all.rs), the `pub fn register_all_controllers()` function iterates over every domain’s `all_registered_controllers()` and inserts each into a global `HashMap<String, RegisteredController>` keyed by the composite string `"${namespace}.${function}"`.

```rust
// src/core/all.rs
pub fn register_all_controllers() {
    let mut map = CONTROLLER_REGISTRY.write().unwrap();
    for ctrl in openhuman::webhooks::all_registered_controllers() {
        map.insert(format!("{}.{}", ctrl.schema.namespace, ctrl.schema.function), ctrl);
    }
    // Repeat for wallet, voice, and other domains...
}

```

### JSON-RPC Dispatch

When requests arrive at the HTTP server ([`src/core_server/http.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core_server/http.rs)), the dispatcher in [`src/core/dispatch.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/dispatch.rs) handles method resolution. The `pub async fn dispatch(method: &str, params: Map<String, Value>) -> RpcOutcome` function looks up the method in the global registry and executes the stored handler.

```rust
// src/core/dispatch.rs
pub async fn dispatch(method: &str, params: Map<String, Value>) -> RpcOutcome {
    let registry = CONTROLLER_REGISTRY.read().unwrap();
    if let Some(rc) = registry.get(method) {
        (rc.handler)(params).await
    } else {
        RpcOutcome::error("method_not_found")
    }
}

```

## Implementing a New RPC Method

To add an endpoint to any domain:

1. Define the request struct and add a new match arm in [`schemas.rs`](https://github.com/tinyhumansai/openhuman/blob/main/schemas.rs) returning a `ControllerSchema`
2. Implement the handler function with signature `fn handle_<name>(params: Map<String, Value>) -> ControllerFuture`
3. Add a `RegisteredController` entry to the domain’s `all_registered_controllers()` vector
4. Place complex business logic in `src/openhuman/<domain>/ops.rs`

The server automatically exposes the new method via `GET /schema` without additional configuration or registry updates.

## Summary

- **ControllerSchema** structs in `src/openhuman/<domain>/schemas.rs` define RPC contracts including inputs, outputs, and documentation.
- **RegisteredController** entries pair each schema with a handler function in domain-specific `all_registered_controllers()` vectors.
- [`src/core/all.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/all.rs) aggregates all controllers into a global `HashMap` keyed by `"namespace.function"` during server startup.
- [`src/core/dispatch.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/dispatch.rs) routes incoming JSON-RPC requests by looking up methods in the registry and invoking the associated handler.

## Frequently Asked Questions

### What is the controller schema pattern in OpenHuman?

The controller-schema pattern is an architectural approach that separates API metadata from implementation. Each RPC method has a `ControllerSchema` describing its inputs, outputs, and documentation, which is registered alongside a handler function that contains the actual business logic. This allows the system to auto-generate API documentation while keeping the transport layer agnostic of domain details.

### How are RPC handlers connected to the HTTP server?

Handlers are collected into a global registry at startup by `register_all_controllers()` in [`src/core/all.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/all.rs). When a JSON-RPC request arrives, the `dispatch` function in [`src/core/dispatch.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/dispatch.rs) looks up the method string (e.g., `"webhooks.list_logs"`) in this registry and invokes the paired handler function, returning the result as an `RpcOutcome`.

### Where is the business logic implemented for RPC methods?

Handler functions typically delegate to domain-specific operations files such as `src/openhuman/<domain>/ops.rs` (for example, [`src/openhuman/webhooks/ops.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/webhooks/ops.rs)) after deserializing parameters using `deserialize_params()`. This keeps the handlers thin and focused on parameter extraction while the ops files contain the core domain logic.

### Do I need to manually update the registry when adding a new RPC method?

No manual registry updates are required. You only need to add the schema definition and handler to your domain’s [`schemas.rs`](https://github.com/tinyhumansai/openhuman/blob/main/schemas.rs) file and include it in that domain’s `all_registered_controllers()` function. The [`src/core/all.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/all.rs) module automatically aggregates all domain controllers during server initialization, making the new method immediately available.