tinyhumansai/openhuman · error

agent `{parent}` is a `worker` tier and must not list `{chil

Error message

agent `{parent}` is a `worker` tier and must not list `{child}` in its subagents — workers are leaf executors.

What it means

Boot-time agent-registry validation (validate_tier_hierarchy, loader.rs:410): an agent declared agent_tier = "worker" lists an AgentId entry in subagents. Workers are leaf executors with no open-ended spawn surface, so the whole registry load fails. Only SubagentEntry::Skills wildcards are exempt (they collapse to one delegation tool on integrations_agent, itself a Worker). The check runs for bundled archetypes and again after workspace-local TOML overrides merge, so violating custom agents fail boot rather than crash at spawn time.

Source

Thrown at src/openhuman/agent/registry/agents/loader.rs:410

/// agents that violate the contract fail the boot rather than crashing
/// at spawn time.
pub fn validate_tier_hierarchy(defs: &[AgentDefinition]) -> Result<()> {
    let tier_by_id: HashMap<&str, AgentTier> =
        defs.iter().map(|d| (d.id.as_str(), d.agent_tier)).collect();

    for def in defs {
        for entry in &def.subagents {
            let child_id = match entry {
                SubagentEntry::AgentId(id) => id.as_str(),
                // Workflow wildcards always route to `integrations_agent`
                // (a Worker) via a single collapsed delegation tool —
                // not subject to the tier-mismatch rule.
                SubagentEntry::Skills(_) => continue,
            };

            // Worker leaves: no open-ended spawn surface.
            if def.agent_tier == AgentTier::Worker {
                anyhow::bail!(
                    "agent `{parent}` is a `worker` tier and must not list `{child}` in its \
                     subagents — workers are leaf executors.",
                    parent = def.id,
                    child = child_id,
                );
            }

            let Some(child_tier) = tier_by_id.get(child_id).copied() else {
                // Unknown id — that's a separate `subagents` integrity
                // concern (covered by existing tests / runtime spawn
                // resolution); don't mask it as a tier error.
                continue;
            };

            // Same-tier delegation is forbidden for chat and reasoning.
            // (Chat→Chat would defeat the whole point of the fast tier;
            // Reasoning→Reasoning produces a depth-blowing recursion of
            // slow models.) The pair-rule lives in `validate_tier_transition`

View on GitHub (pinned to a221052e0d)

Solutions

  1. Remove the subagents list (or the AgentId entries) from the worker agent's TOML — workers stay leaf executors
  2. If delegation is intentional, raise the parent's agent_tier to chat/reasoning/orchestrator as appropriate
  3. If the goal was workflow/skill access, use the skills wildcard entry ({ skills = "*" }), which is exempt from this rule
  4. Reload the agent registry / restart the core after fixing the TOML

Example fix

# before
id = "indexer"
agent_tier = "worker"
subagents = ["search_agent"]

# after — leaf executor, or use the exempt wildcard
id = "indexer"
agent_tier = "worker"
subagents = [{ skills = "*" }]
Defensive patterns

Strategy: validation

Validate before calling

// Lint definitions with the same rule the loader enforces, before boot:
openhuman::agent::registry::agents::validate_tier_hierarchy(&defs)
    .context("agent TOML rejected before registry load")?;

Type guard

// A worker must be a leaf: no AgentId subagent entries allowed
fn worker_is_leaf(def: &AgentDefinition) -> bool {
    def.agent_tier != AgentTier::Worker
        || def.subagents.iter().all(|s| matches!(s, SubagentEntry::Skills(_)))
}

Try / catch

// Around AgentDefinitionRegistry::load — surface the offending file, not a raw bail
match registry.load() {
    Ok(_) => {},
    Err(e) if e.to_string().contains("worker` tier") => {
        eprintln!("agent config error: {e:#}\nRemove subagents from the worker agent or raise its tier.");
        std::process::exit(2);
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: A workspace agent TOML with agent_tier = "worker" plus subagents = ["some_agent"]; copying a chat/orchestrator agent file and changing only the tier field to worker; editing ~/.openhuman agent overrides by hand.

Common situations: Users customizing workspace agent TOMLs; attempting to give a worker access to another agent by listing it as a subagent instead of using the skills wildcard.

Related errors


AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16). Data as JSON: /api/errors/7a6c5b81b33e277e. Report an issue: GitHub.