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
- 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.
- Remove the offending id from the parent's `subagents` list if the delegation is not actually needed.
- 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
- Run agent-TOML tier validation in CI so a bad subagents pair fails the build instead of boot
- Keep worker-tier agents leaf — never add subagents to them
- Remember upward hops like reasoning→chat are intentional and legal; only worker-parent and same-tier chat/reasoning pairs are rejected
- Change the rule in validate_tier_transition itself — the runtime spawn gate in run_subagent enforces the same function, so bypassing the loader gains nothing
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)