How OpenHuman's TinyAgents-Based Agent Orchestrates Multi-Level Sub-Agent Delegation
OpenHuman uses a typed graph engine to build compiled delegation pipelines that steer detached sub-agent tasks through plan-execute-review loops with durable checkpointing and human-in-the-loop approval gates.
The tinyhumansai/openhuman repository implements multi-level sub-agent delegation through a three-layer orchestration architecture that combines static graph compilation with dynamic runtime steering. This system allows AI agents to recursively delegate tasks to specialized sub-agents while maintaining crash resilience and human oversight through the tinyagents graph engine.
The Three-Layer Orchestration Architecture
The delegation system in OpenHuman consists of tightly-coupled layers that handle graph definition, runtime control, and state persistence.
Graph Definition Layer
The static CompiledGraph in [src/openhuman/agent/tinyagents/delegation.rs](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/agent/tinyagents/delegation.rs) defines nodes for plan, execute, review, optional approval, and finalize stages. The build_delegation_graph function (line 610) constructs this graph once per delegation run, accepting parameters for revision budgets, cancellation tokens, and approval requirements.
Each node body invokes an injected run_stage closure that forwards to the standard agent harness. Conditional routing uses Command::with_goto([...]) to loop back to execute or proceed to finalize based on reviewer approval flags and remaining revision counts.
Runtime Steering and Status Layer
Process-local coordination occurs through the SteeringRegistry declared in [src/openhuman/agent/tinyagents/orchestration.rs](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/agent/tinyagents/orchestration.rs). The shared_steering_registry function (line 41) provides a global registry for SteeringHandle instances, while openhuman_steering_handle creates handles differentiated by SteeringRunClass.
Interactive runs (SteeringRunClass::Interactive) permit only InjectMessage and Pause operations. Background sub-agent runs (SteeringRunClass::Background) additionally allow Resume, Cancel, and Redirect commands, enabling safe control-flow steering without injecting untrusted transcript text.
Durable Checkpointing Layer
The Checkpointer trait (e.g., FileCheckpointer) persists the entire DelegationState at every super-step. Functions run_delegation_durable, run_or_resume_delegation, and resume_delegation (lines 84-106 in delegation.rs) manage crash recovery and human-approval pauses by reconstructing the compiled graph and resuming from stable checkpoints.
Graph Construction and State Management
The orchestration begins with graph compilation that type-checks the delegation pipeline:
let graph = build_delegation_graph(
config.max_revisions, // revision budget
config.cancel.clone(), // cooperative cancellation token
config.require_review_approval,// human‑approval gate?
run_stage, // closure that actually runs a sub‑agent stage
)?;
The resulting CompiledGraph<DelegationState, DelegationUpdate> carries a ClosureStateReducer (lines 20-52 of delegation.rs) that merges DelegationUpdate emissions from each node into a single DelegationState struct. This reducer ensures that partial outputs from the planning, execution, and review stages accumulate deterministically across graph traversals.
Steering Handles and Runtime Control
Every detached sub-agent task registers a SteeringHandle in the process-local registry:
pub(crate) fn shared_steering_registry() -> &'static SteeringRegistry {
STEERING_REGISTRY.get_or_init(SteeringRegistry::new)
}
When initiating a background turn, the system creates and registers the handle:
let handle = openhuman_steering_handle(SteeringRunClass::Background);
registry.register(task_id.clone(), handle);
The registry serves as the lookup seam for the rest of the core, enabling external components to issue steering commands against specific TaskId instances without direct coupling to the graph execution.
Durable Human-Approval Workflows
When DelegationConfig::require_review_approval is enabled, the review node routes to an interrupt node named approval. On first entry with ctx.resume == None, the node returns NodeResult::Interrupt, which the tinyagents runtime persists as a checkpoint in Sync durability mode. The interrupt payload carries the current reviews, executions, and revision count (lines 71-84 of delegation.rs).
The caller receives a DelegationOutcome with pending: Some(PendingApproval { … }), using the thread_id as the resumption key. Resumption rebuilds the identical graph and invokes CompiledGraph::resume with a command carrying the approver's decision:
let command = Command::default().resume = Some(decision);
resume_graph(config, command, run_stage).await?;
The decision_is_approve function (lines 86-104) accepts stable RPC strings ("approve_once", "approve_always_for_tool", "deny"), boolean values, or objects with an approved field. If the checkpoint schema is outdated or incompatible, run_or_resume_delegation automatically prunes the thread and starts fresh.
Safety Mechanisms and Observability
The delegation graph implements multiple safety boundaries to prevent runaway execution.
Cancellation and Bounds. A CancellationToken checked at each node entry triggers an immediate jump to finalize with a Cancelled update if set. The RecursionPolicy enforces max_visits_per_node = max_revisions + 2 and total step limits, ensuring that non-terminating reviewer loops cannot exhaust resources.
Observability. All graph events stream to a GraphTracingSink (line 18 of orchestration.rs), forwarding the entire delegation lifecycle to OpenHuman's tracing system for logging and telemetry analysis.
Implementation Examples
Running a Simple Delegation
For scenarios without human gates or checkpointing:
use openhuman::agent::tinyagents::{
delegation::{run_delegation, DelegationConfig, DelegationStage, DelegationStageOutput},
};
async fn example() {
// A mock stage worker that just returns static text.
let mock_stage = |stage: DelegationStage, _state| async move {
Ok(match stage {
DelegationStage::Plan => DelegationStageOutput::done("PLAN"),
DelegationStage::Execute => DelegationStageOutput::done("EXEC"),
DelegationStage::Review => DelegationStageOutput {
text: "review‑ok".into(),
approved: true,
prompt: None,
},
})
};
let state = run_delegation(DelegationConfig::default(), mock_stage)
.await
.expect("delegation finished");
println!("Final output: {:?}", state.final_output);
}
Durable Delegation with Human Approval
For production workflows requiring persistence and human oversight:
use openhuman::agent::tinyagents::delegation::{
run_delegation_durable, resume_delegation,
DelegationConfig, DelegationStage, DelegationStageOutput,
};
use tinyagents::graph::checkpoint::FileCheckpointer;
use std::sync::Arc;
use serde_json::json;
#[tokio::main]
async fn main() {
// Persistent checkpoint directory
let cp = Arc::new(FileCheckpointer::new("./checkpoints"));
let cfg = DelegationConfig {
require_review_approval: true,
checkpointer: Some(cp.clone()),
thread_id: Some("my-run".to_string()),
..DelegationConfig::default()
};
// Run the graph – it will pause on the `approval` interrupt.
let outcome = run_delegation_durable(cfg.clone(), mock_stage).await.unwrap();
let pending = outcome.pending.expect("should be paused");
println!("Paused on node: {}", pending.node);
// Simulate a human approving the result.
let decision = json!("approve_once");
let resumed = resume_delegation(cfg, decision, mock_stage).await.unwrap();
println!("Final output after approval: {:?}", resumed.state.final_output);
}
// Same mock worker as in the first example.
fn mock_stage(stage: DelegationStage, _state: DelegationState) -> impl Future<Output = Result<DelegationStageOutput, String>> {
async move {
Ok(match stage {
DelegationStage::Plan => DelegationStageOutput::done("PLAN"),
DelegationStage::Execute => DelegationStageOutput::done("EXEC"),
DelegationStage::Review => DelegationStageOutput {
text: "review‑ok".into(),
approved: true,
prompt: None,
},
})
}
}
Registering a Steering Handle
To enable external control of background sub-agents:
use openhuman::agent::tinyagents::orchestration::{
shared_steering_registry, openhuman_steering_handle, SteeringRunClass,
};
use tinyagents::graph::orchestration::TaskId;
// Create a unique task identifier.
let task_id = TaskId::new("researcher-subagent");
// Obtain a background‑allowed steering handle.
let handle = openhuman_steering_handle(SteeringRunClass::Background);
// Register it so other core components can issue `Cancel`, `Resume`, etc.
shared_steering_registry().register(task_id.clone(), handle);
// Later, cancel the sub‑agent:
if let Some(h) = shared_steering_registry().get(&task_id) {
h.cancel();
}
Summary
- OpenHuman implements multi-level sub-agent delegation through a compiled graph in
delegation.rsthat orchestrates plan-execute-review loops with type-safe state management. - The three-layer architecture separates static graph definition (
build_delegation_graph), runtime steering (SteeringRegistry), and durable checkpointing (FileCheckpointer). - Human-in-the-loop workflows pause execution via interrupt nodes, persist
DelegationStateto disk, and resume viaresume_delegationwith structured approval decisions. - Safety mechanisms include cooperative cancellation tokens, recursion bounds per node, and automatic pruning of incompatible checkpoints.
- Observability is achieved through
GraphTracingSink, streaming all graph events to OpenHuman's telemetry systems.
Frequently Asked Questions
What is the difference between Interactive and Background steering classes in OpenHuman?
SteeringRunClass::Interactive allows only InjectMessage and Pause commands, restricting control for user-facing turns where safety is paramount. SteeringRunClass::Background permits additional Resume, Cancel, and Redirect operations, granting the orchestration layer full control over detached sub-agent tasks without requiring direct transcript manipulation.
How does the system handle checkpoint schema migrations?
The run_or_resume_delegation function checks the persisted schema version against CURRENT_SCHEMA_VERSION. If the checkpoint is outdated or contains incompatible structures (such as legacy executions: Vec<String>), the runtime automatically prunes the thread and initiates a fresh delegation run rather than attempting risky deserialization.
Can sub-agent delegation handle recursive planning loops?
Yes. The graph supports conditional routing from the review node back to the execute node based on the approved flag. The RecursionPolicy enforces max_visits_per_node = max_revisions + 2, ensuring that iterative refinement loops terminate deterministically even if the reviewer repeatedly requests changes.
Where is the delegation graph state persisted during a human approval pause?
When the approval interrupt node triggers, the tinyagents runtime persists the full DelegationState using the configured Checkpointer (typically FileCheckpointer writing to disk). The thread_id specified in DelegationConfig serves as the stable key for resumption, allowing the process to crash or restart without losing the delegation context.
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 →