{"record":{"id":"7a6c5b81b33e277e","repo":"tinyhumansai/openhuman","slug":"agent-parent-is-a-worker-tier-and-must-not-l","errorCode":null,"errorMessage":"agent `{parent}` is a `worker` tier and must not list `{child}` in its subagents — workers are leaf executors.","messagePattern":"agent `(.+?)` is a `worker` tier and must not list `(.+?)` in its subagents — workers are leaf executors\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/openhuman/agent/registry/agents/loader.rs","lineNumber":410,"sourceCode":"/// agents that violate the contract fail the boot rather than crashing\n/// at spawn time.\npub fn validate_tier_hierarchy(defs: &[AgentDefinition]) -> Result<()> {\n    let tier_by_id: HashMap<&str, AgentTier> =\n        defs.iter().map(|d| (d.id.as_str(), d.agent_tier)).collect();\n\n    for def in defs {\n        for entry in &def.subagents {\n            let child_id = match entry {\n                SubagentEntry::AgentId(id) => id.as_str(),\n                // Workflow wildcards always route to `integrations_agent`\n                // (a Worker) via a single collapsed delegation tool —\n                // not subject to the tier-mismatch rule.\n                SubagentEntry::Skills(_) => continue,\n            };\n\n            // Worker leaves: no open-ended spawn surface.\n            if def.agent_tier == AgentTier::Worker {\n                anyhow::bail!(\n                    \"agent `{parent}` is a `worker` tier and must not list `{child}` in its \\\n                     subagents — workers are leaf executors.\",\n                    parent = def.id,\n                    child = child_id,\n                );\n            }\n\n            let Some(child_tier) = tier_by_id.get(child_id).copied() else {\n                // Unknown id — that's a separate `subagents` integrity\n                // concern (covered by existing tests / runtime spawn\n                // resolution); don't mask it as a tier error.\n                continue;\n            };\n\n            // Same-tier delegation is forbidden for chat and reasoning.\n            // (Chat→Chat would defeat the whole point of the fast tier;\n            // Reasoning→Reasoning produces a depth-blowing recursion of\n            // slow models.) The pair-rule lives in `validate_tier_transition`","sourceCodeStart":392,"sourceCodeEnd":428,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/src/openhuman/agent/registry/agents/loader.rs#L392-L428","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Remove the subagents list (or the AgentId entries) from the worker agent's TOML — workers stay leaf executors","If delegation is intentional, raise the parent's agent_tier to chat/reasoning/orchestrator as appropriate","If the goal was workflow/skill access, use the skills wildcard entry ({ skills = \"*\" }), which is exempt from this rule","Reload the agent registry / restart the core after fixing the TOML"],"exampleFix":"# before\nid = \"indexer\"\nagent_tier = \"worker\"\nsubagents = [\"search_agent\"]\n\n# after — leaf executor, or use the exempt wildcard\nid = \"indexer\"\nagent_tier = \"worker\"\nsubagents = [{ skills = \"*\" }]","handlingStrategy":"validation","validationCode":"// Lint definitions with the same rule the loader enforces, before boot:\nopenhuman::agent::registry::agents::validate_tier_hierarchy(&defs)\n    .context(\"agent TOML rejected before registry load\")?;","typeGuard":"// A worker must be a leaf: no AgentId subagent entries allowed\nfn worker_is_leaf(def: &AgentDefinition) -> bool {\n    def.agent_tier != AgentTier::Worker\n        || def.subagents.iter().all(|s| matches!(s, SubagentEntry::Skills(_)))\n}","tryCatchPattern":"// Around AgentDefinitionRegistry::load — surface the offending file, not a raw bail\nmatch registry.load() {\n    Ok(_) => {},\n    Err(e) if e.to_string().contains(\"worker` tier\") => {\n        eprintln!(\"agent config error: {e:#}\\nRemove subagents from the worker agent or raise its tier.\");\n        std::process::exit(2);\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Treat worker agents as leaf executors by design — never add subagents to them","Use the { skills = \"*\" } wildcard when a worker needs workflow access","Run validate_tier_hierarchy in a config-lint step for custom workspace agents"],"tags":["rust","agents","config","boot","tier-hierarchy"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}