How OpenHuman Sub-Agents (Planner, Researcher, Critic) Coordinate Task Execution

OpenHuman orchestrates planner, researcher, and critic sub-agents through a hierarchical delegation system where the orchestrator dynamically generates delegation tools, spawns isolated worker threads via spawn_subagent, and aggregates results through JSON-L session transcripts.

The open-source OpenHuman framework implements a multi-agent architecture where specialized sub-agents collaborate under a central orchestrator. Understanding how these components coordinate reveals the engine behind OpenHuman's autonomous task execution pipeline according to the tinyhumansai/openhuman source code.

The Orchestrator-Centric Coordination Model

OpenHuman treats the orchestrator as the primary routing and planning agent. Rather than hard-coding agent interactions, the system uses a declarative configuration combined with runtime tool generation to delegate tasks dynamically.

Sub-Agent Declaration in TOML Configuration

The orchestrator's capabilities are defined statically in its configuration file. In src/openhuman/agent/agents/orchestrator/agent.toml, a subagents array lists all child agents available for delegation:

subagents = ["planner", "researcher", "critic"]

Each entry maps to an AgentDefinition loaded from src/openhuman/agent/agents/<name>/agent.toml. This registry is parsed at startup to determine which delegation tools the orchestrator can invoke.

Dynamic Tool Generation

The function collect_orchestrator_tools in src/openhuman/tools/orchestrator_tools.rs transforms the static TOML declarations into executable tools. For each sub-agent listed, it generates a tool named delegate_<agent_id> (e.g., delegate_planner, delegate_researcher, delegate_critic).

These generated tools are exposed to the orchestrator's LLM, allowing the model to invoke sub-agents using standard tool-calling syntax. The tool implementation routes requests through the unified spawn_subagent interface.

Runtime Execution Flow

When the orchestrator decides to delegate a task, the coordination follows a strict three-phase pattern involving tool invocation, thread isolation, and session management.

Phase 1: LLM Tool Invocation

The orchestrator's LLM constructs a tool call using the dynamically generated delegate tool. For example, to activate the planner, the LLM generates:

{
  "name": "delegate_planner",
  "arguments": {
    "objective": "Create a research plan for quantum computing applications"
  }
}

This invocation is captured by the orchestrator's tool dispatcher and routed to the corresponding handler in src/openhuman/tools/impl/agent/spawn_subagent.rs.

Phase 2: Isolated Worker Thread Spawning

The spawn_subagent implementation creates a new OS worker thread via spawn_worker_thread (located in src/openhuman/tools/impl/agent/spawn_worker_thread.rs). This thread runs independently to prevent prompt contamination and allow agent-specific temperature or model configurations.

The function establishes a fresh session with a hierarchical key format: {parent_key}__{child_key}. If the orchestrator has session key session_001, the planner receives session_001__planner, ensuring deterministic lineage tracking.

Phase 3: Session Hierarchy and Transcript Recording

During execution, the sub-agent writes to a JSON-L transcript file named according to the session hierarchy (e.g., orchestrator__planner.jsonl). The SessionBuilder and SessionTranscript modules in src/openhuman/agent/harness/session/*.rs manage these files, storing:

  • Agent identification metadata
  • Input parameters
  • Generated outputs
  • Completion timestamps

The parent orchestrator monitors the worker thread's return channel. Upon completion, the sub-agent returns a ToolResult containing the final output, which the orchestrator parses to determine subsequent steps, such as invoking the researcher or critic.

Practical Coordination Example

The following Rust pseudo-code illustrates how the orchestrator sequentially coordinates the three sub-agents to process a complex query:

// Orchestrator receives user request
let user_input = "Analyze the latest climate research";

// Step 1: Delegate to planner
let plan = delegate_planner(json!({
    "task": user_input
})).await?;

// Step 2: For each research item in plan, spawn researcher
let findings = vec![];
for item in plan.subtasks {
    let data = delegate_researcher(json!({
        "query": item.topic
    })).await?;
    findings.push(data);
}

// Step 3: Validate findings with critic
let validation = delegate_critic(json!({
    "content": findings,
    "criteria": "accuracy_and_completeness"
})).await?;

// Final synthesis
if validation.score > 0.8 {
    return synthesize_response(findings);
}

Each delegate_* call internally triggers spawn_subagent, creating the hierarchical session chain: orchestrator → planner → researcher → critic.

Summary

  • Declarative Registration: Sub-agents are registered in src/openhuman/agent/agents/orchestrator/agent.toml and loaded as AgentDefinition structs at startup.
  • Dynamic Tool Fabrication: collect_orchestrator_tools in src/openhuman/tools/orchestrator_tools.rs generates delegate_<agent> tools for runtime LLM invocation.
  • Thread Isolation: spawn_subagent in src/openhuman/tools/impl/agent/spawn_subagent.rs launches each sub-agent in a dedicated worker thread to ensure execution isolation.
  • Hierarchical Auditing: Session keys follow {parent}__{child} naming conventions, with JSON-L transcripts stored via src/openhuman/agent/harness/session/*.rs for full traceability.
  • Result Aggregation: The orchestrator receives structured ToolResult objects from each sub-agent, enabling conditional routing between planner, researcher, and critic phases.

Frequently Asked Questions

How does the orchestrator decide which sub-agent to invoke?

The orchestrator relies on its system prompt and the dynamically generated delegate_<agent> tools exposed to its LLM. When processing a task, the LLM selects the appropriate tool based on the current objective—choosing delegate_planner for structuring work, delegate_researcher for information gathering, or delegate_critic for validation—according to the tool definitions generated in src/openhuman/tools/orchestrator_tools.rs.

What prevents sub-agents from interfering with each other's state?

Each sub-agent executes in a separate OS thread spawned by spawn_worker_thread, with independent memory spaces and session contexts. The session hierarchy enforces isolation through unique SessionKey identifiers (formatted as parent__child), ensuring that transcript writes and tool invocations remain namespace-separated across the planner, researcher, and critic agents.

Can custom sub-agents be added to the coordination flow?

Yes. Adding a custom sub-agent requires creating a new directory under src/openhuman/agent/agents/ with an agent.toml configuration and prompt implementation (e.g., prompt.rs), then registering the agent name in the orchestrator's agent.toml subagents array. The collect_orchestrator_tools function automatically generates the corresponding delegate_<custom_agent> tool without requiring modifications to the core orchestration logic.

Where are the conversation transcripts stored for debugging?

Transcripts are stored as JSON-L files in the session storage layer managed by src/openhuman/agent/harness/session/*.rs. Each sub-agent writes to a file named according to its hierarchical session key (e.g., orchestrator__planner__researcher.jsonl), providing a complete, replayable audit trail of the multi-agent coordination process.

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 →