tinyhumansai/openhuman · error

agent ` ` ( ) lists ` ` ( ) in subagents

Error message

agent `{parent}` ({ptier}) lists `{child}` ({ctier}) in subagents — {reason}

What it means

Boot-time validation in the builtin-agent registry loader (`validate_tier_hierarchy`). Every agent definition's `subagents` list is resolved and each pair is checked against `validate_tier_transition` (src/openhuman/agent/harness/definition.rs:381). Forbidden pairs: Worker→anything (workers are leaf executors), Chat→Chat (would defeat the fast tier), and Reasoning→Reasoning (a depth-blowing recursion of slow models). The bail wraps the rule's reason with the offending parent/child ids and tiers for a boot-friendly diagnostic; upward hops like reasoning→chat are intentionally legal.

Solutions

  1. Change the child's `agent_tier` so the pair is legal: chat parents delegate to `reasoning` or `worker` children, reasoning parents compose downward into `worker` (or the intentional `chat`) children, and workers have no subagents at all.
  2. Remove the offending id from the parent's `subagents` list if the delegation is not actually needed.
  3. If the delegation is genuinely required and same-tier is intentional, change the rule itself in `validate_tier_transition` (the single source of truth shared with the runtime spawn gate in `run_subagent`) — bypassing the loader check is pointless because the runtime gate enforces the same function.

Example fix

# before (agent.toml) — chat-tier parent lists a chat-tier child
[id]
agent_tier = "chat"
subagents = ["quick_chat_agent"]

# after — delegate downward instead
[id]
agent_tier = "chat"
subagents = ["research_agent"]   # agent_tier = "reasoning"
Defensive patterns

Strategy: validation

Validate before calling

// Pre-validate every declared subagents pair before shipping agent TOMLs
use openhuman::agent::harness::definition::{validate_tier_transition, AgentTier};

fn hierarchy_is_valid(pairs: &[(String, AgentTier, String, AgentTier)]) -> Result<(), String> {
    for (parent, ptier, child, ctier) in pairs {
        validate_tier_transition(*ptier, *ctier)
            .map_err(|reason| format!("`{parent}` -> `{child}`: {reason}"))?;
    }
    Ok(())
}

Type guard

fn allows_subagent(parent: AgentTier, child: AgentTier) -> bool {
    validate_tier_transition(parent, child).is_ok()
}

Prevention

When it happens

Trigger: Loading agent definitions (core boot or registry reload) where an agent TOML lists a subagent whose `agent_tier` forms a forbidden pair with the parent: any subagent under a `worker`-tier agent, a `chat`-tier agent listing another `chat`-tier agent, or a `reasoning`-tier agent listing another `reasoning`-tier agent. Also fires when a `subagents` id resolves to a different agent than intended (e.g. after a rename or tier change elsewhere).

Common situations: Editing agent.toml (orchestrator or custom agents) to add subagents; duplicating an existing agent entry and forgetting to change `agent_tier`; adding a new builtin chat agent and pointing an existing chat parent at it; renames that make a stale subagents id resolve to an agent with an incompatible tier.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.


AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17). Data as JSON: /api/errors/7216bffc4324da7e. Report an issue: GitHub.

Appendix: source

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

                );
            }

            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`
            // (the single source of truth shared with the runtime spawn gate
            // in `run_subagent`); here we wrap its reason with the offending
            // agent ids + tiers for a boot-time-friendly diagnostic.
            if let Err(reason) = validate_tier_transition(def.agent_tier, child_tier) {
                anyhow::bail!(
                    "agent `{parent}` ({ptier}) lists `{child}` ({ctier}) in subagents — {reason}",
                    parent = def.id,
                    ptier = def.agent_tier.as_str(),
                    child = child_id,
                    ctier = child_tier.as_str(),
                );
            }
        }
    }

    Ok(())
}

/// Parse a single [`BuiltinAgent`] triple into a finished [`AgentDefinition`].
fn parse_builtin(b: &BuiltinAgent) -> Result<AgentDefinition> {
    // The TOML ships without `system_prompt` — serde falls back to
    // `defaults::empty_inline_prompt` — and the loader injects the
    // rendered sibling `prompt.md` immediately below.

View on GitHub (pinned to 7491200858)