# How OpenHuman CLI Subcommand Dispatch Works and How to Register New Commands

> Understand OpenHuman CLI subcommand dispatch and how to register new commands. Explore the routing logic in cli.rs and the automatic registration process via schemas.rs and all.rs.

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

---

**OpenHuman routes CLI arguments through a central dispatch function in [`src/core/cli.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/cli.rs) that either matches hard-coded top-level commands or falls back to a generic namespace dispatcher, while new commands are registered automatically by adding controller schemas to the domain's [`schemas.rs`](https://github.com/tinyhumansai/openhuman/blob/main/schemas.rs) and including them in [`src/core/all.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/all.rs).**

The OpenHuman command-line interface implements a **data-driven architecture** where JSON-RPC namespaces surface directly as terminal subcommands. Understanding how `run_from_cli_args` dispatches execution and how the controller schema system auto-registers methods is essential for extending the toolkit without modifying the core CLI logic.

## How the CLI Entry Point Dispatches Commands

The entry point `run_from_cli_args` in [`src/core/cli.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/cli.rs) receives the raw argument vector and executes a five-phase dispatch sequence:

1. **Global launch options** – `parse_launch_options` extracts flags such as `--model` or `--provider` that affect the entire process.
2. **TUI auto-launch** – If the user requests `tui`/`chat` or heuristics in `should_auto_launch_tui` trigger interactive mode, `run_tui_from_cli` takes over.
3. **Help handling** – When no arguments are supplied or the token is `--help`, `print_general_help` displays available domains.
4. **Top-level subcommands** – A `match` statement on `args[0]` handles built-in commands (`run`, `serve`, `mcp`, `tui`, `call`, `tree-summarizer`, `memory`, `agent`, `sentry-test`).
5. **Generic namespace dispatch** – Any unrecognized first argument routes to `run_namespace_command` (lines 1090-1112) for RPC-style execution.

```rust
// src/core/cli.rs – main dispatch excerpt
pub fn run_from_cli_args(args: &[String]) -> Result<()> {
    // ... banner printing omitted ...
    match args[0].as_str() {
        "run" | "serve" => run_server_command(&args[1..]),
        "mcp" | "mcp-server" => crate::openhuman::mcp::server::run_stdio_from_cli(&args[1..]),
        "tui" | "chat" => run_tui_from_cli(&args[1..]),
        "call" => run_call_command(&args[1..]),
        "tree-summarizer" => crate::openhuman::memory::tree::tree_runtime::cli::run_tree_summarizer_command(&args[1..]),
        "memory" => crate::core::memory_cli::run_memory_command(&args[1..]),
        "agent" => crate::core::agent_cli::run_agent_command(&args[1..]),
        "sentry-test" => run_sentry_test_command(&args[1..]),
        // Generic namespace dispatcher
        namespace => run_namespace_command(namespace, &args[1..], &grouped),
    }
}

```

Top-level commands receive special handling because they require custom argument parsing or direct domain integration rather than generic JSON-RPC invocation.

## The Generic Namespace Dispatch Mechanism

When the first argument does not match a built-in command, OpenHuman treats it as an **RPC namespace**. The `run_namespace_command` function looks up the requested namespace in the **grouped controller schemas** and executes the method via `invoke_method`.

The helper `grouped_schemas()` (defined in [`src/core/cli.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/cli.rs)) aggregates all registered schemas, indexes them by namespace, and makes them available to the dispatcher. When a user runs:

```bash
openhuman <namespace> <method> [...args]

```

The dispatcher performs the following:

- Verifies that `<namespace>` exists in the grouped schemas.
- Confirms that `<method>` is a valid entry in that namespace's method table.
- Converts remaining CLI arguments to JSON using `parse_json_params`.
- Invokes `invoke_method` to execute the RPC handler inside the core.

This design ensures that any RPC method automatically becomes a CLI subcommand without additional wiring in [`cli.rs`](https://github.com/tinyhumansai/openhuman/blob/main/cli.rs).

## Registering New Commands via Controller Schemas

OpenHuman does not maintain a hard-coded list of RPC methods in the CLI layer. Instead, each domain declares **controller schemas** that describe available namespaces and their functions.

Registration occurs in two locations:

- **Domain schemas** – Each domain contains a [`schemas.rs`](https://github.com/tinyhumansai/openhuman/blob/main/schemas.rs) file (e.g., [`src/openhuman/agent/registry/schemas.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/agent/registry/schemas.rs), [`src/openhuman/memory/ops/schemas.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/memory/ops/schemas.rs)) that defines `ControllerSchema` structs mapping method names to handlers.
- **Global aggregation** – [`src/core/all.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/all.rs) collects all domain registrations via the `register_controllers!` macro, creating the unified registry that `grouped_schemas()` consumes.

A typical controller schema defines the namespace, method handlers, and metadata:

```rust
// src/openhuman/example/schemas.rs
use crate::core::{ControllerSchema, RpcContext, RpcOutcome};
use serde_json::{json, Value};

pub fn register_example_controller() -> Vec<ControllerSchema> {
    vec![ControllerSchema {
        namespace: "example",
        methods: &[("echo", handle_echo)],
        description: "Example echo commands",
    }]
}

fn handle_echo(_ctx: RpcContext, params: Value) -> RpcOutcome {
    let message = params.get("message")
        .and_then(Value::as_str)
        .unwrap_or("default");
    RpcOutcome::json(json!({ "echo": message }))
}

```

Adding this controller to [`src/core/all.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/all.rs) immediately exposes `openhuman example echo` as a valid CLI invocation.

## Step-by-Step: Adding a Custom CLI Command

To register a new command in the OpenHuman CLI, follow this implementation pattern:

### 1. Define the Controller Schema

Create or modify the domain's [`schemas.rs`](https://github.com/tinyhumansai/openhuman/blob/main/schemas.rs) to include the new namespace and method handler:

```rust
// src/openhuman/greetings/schemas.rs
use crate::core::{ControllerSchema, RpcContext, RpcOutcome};
use serde_json::{json, Value};

pub fn register_greetings_controller() -> Vec<ControllerSchema> {
    vec![ControllerSchema {
        namespace: "greetings",
        methods: &[("greet", handle_greet)],
        description: "Simple greeting commands",
    }]
}

fn handle_greet(_ctx: RpcContext, params: Value) -> RpcOutcome {
    let name = params.get("name")
        .and_then(Value::as_str)
        .unwrap_or("world");
    RpcOutcome::json(json!({ "message": format!("Hello, {}!", name) }))
}

```

### 2. Add to the Global Registry

Include the registration function in [`src/core/all.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/all.rs):

```rust
mod openhuman::greetings;
register_controllers!(openhuman::greetings::register_greetings_controller);

```

### 3. Verify via CLI

The command becomes immediately available:

```bash
openhuman greetings greet --name Alice

# Output: {"message":"Hello, Alice!"}

```

### 4. Run Tests

The CLI test suite in [`src/core/cli_capability.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/cli_capability.rs) verifies that every registered controller appears in the dispatch table, ensuring your new command is reachable.

## Extending Top-Level Subcommands

For commands requiring custom argument parsing or specialized initialization (like `memory` or `agent`), you must add explicit routing in [`src/core/cli.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/cli.rs) rather than using the generic dispatcher.

Edit the `match` arm in `run_from_cli_args` to include your command, then implement a thin wrapper in a dedicated CLI module:

```rust
// src/core/memory_cli.rs
pub fn run_memory_command(args: &[String]) -> Result<()> {
    match args.get(0).map(|s| s.as_str()) {
        Some("status") => run_memory_status(&args[1..]),
        Some("clear") => run_memory_clear(&args[1..]),
        Some("custom") => run_memory_custom(&args[1..]), // Your new command
        other => Err(anyhow!("unknown memory subcommand `{:?}`", other)),
    }
}

```

Reference this wrapper in the main dispatch match using `crate::core::memory_cli::run_memory_command`.

## Summary

- **Central dispatch** in [`src/core/cli.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/cli.rs) routes arguments through `run_from_cli_args`, handling global flags, TUI initialization, and command matching.
- **Generic namespace dispatch** (lines 1090-1112) treats unrecognized first arguments as RPC namespaces, looking up methods in `grouped_schemas()` and invoking them via `invoke_method`.
- **Controller schemas** define CLI commands declaratively; domains implement [`schemas.rs`](https://github.com/tinyhumansai/openhuman/blob/main/schemas.rs) files that map namespace strings to handler functions.
- **Global registration** occurs in [`src/core/all.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/all.rs) through the `register_controllers!` macro, automatically surfacing RPC methods as CLI subcommands.
- **Top-level exceptions** like `memory` or `agent` require explicit match arms in [`cli.rs`](https://github.com/tinyhumansai/openhuman/blob/main/cli.rs) and dedicated CLI helper modules ([`src/core/memory_cli.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/memory_cli.rs), [`src/core/agent_cli.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/agent_cli.rs)).
- **Testing coverage** in [`src/core/cli_capability.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/cli_capability.rs) validates that every registered controller is dispatchable from the command line.

## Frequently Asked Questions

### How does OpenHuman decide between top-level and namespace commands?

The dispatcher checks `args[0]` against a hard-coded list in `run_from_cli_args`. If the argument matches strings like `run`, `serve`, `mcp`, `tui`, `call`, `tree-summarizer`, `memory`, `agent`, or `sentry-test`, it routes to the specialized handler. Any other string falls through to `run_namespace_command`, which treats the value as an RPC namespace and attempts to resolve it against the grouped controller schemas.

### What file do I edit to add a new RPC-based CLI command?

Create or modify the [`schemas.rs`](https://github.com/tinyhumansai/openhuman/blob/main/schemas.rs) file in your domain directory (e.g., [`src/openhuman/yourdomain/schemas.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/yourdomain/schemas.rs)) to define the `ControllerSchema` and handler function. Then register the controller in [`src/core/all.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/all.rs) using `register_controllers!`. You do not need to modify [`src/core/cli.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/cli.rs) for standard RPC methods because the generic dispatcher handles them automatically.

### How are CLI commands tested in OpenHuman?

The test suite in [`src/core/cli_capability.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/cli_capability.rs) validates that every controller registered in the global schema appears as a dispatchable CLI command. This ensures that newly added namespaces and methods are reachable from the terminal without writing separate integration tests for each CLI subcommand.

### Can I create a custom top-level subcommand like `memory` or `agent`?

Yes, but this requires explicit routing. Add a new match arm in `run_from_cli_args` within [`src/core/cli.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/cli.rs) that points to a dedicated CLI helper module (following the pattern of [`src/core/memory_cli.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/memory_cli.rs) or [`src/core/agent_cli.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/agent_cli.rs)). This approach is necessary when your command needs custom argument parsing, special initialization, or bypasses the standard JSON-RPC invocation flow.