NousResearch/hermes-agent · error · SubagentLifecycleError
role must be 'leaf' or 'orchestrator'.
Error message
role must be 'leaf' or 'orchestrator'.
What it means
Validation error from SubagentLifecycleManager._validate_request(): the role field of SubagentLaunchRequest must be exactly 'leaf' or 'orchestrator'. Any other value (including None, '', 'worker', 'sub-agent', or differently-cased strings) is rejected.
Source
Thrown at agent/subagent_lifecycle.py:505
def _validate_request(request: SubagentLaunchRequest, parent: Any) -> None:
if (
not isinstance(request, SubagentLaunchRequest)
or not isinstance(request.goal, str)
or not request.goal.strip()
or len(request.goal) > _MAX_GOAL_CHARS
):
raise SubagentLifecycleError(
"goal must be a non-empty string of at most 16000 characters."
)
if request.context is not None and (
not isinstance(request.context, str)
or len(request.context) > _MAX_CONTEXT_CHARS
):
raise SubagentLifecycleError(
"context must be a string of at most 32000 characters."
)
if request.role not in {"leaf", "orchestrator"}:
raise SubagentLifecycleError("role must be 'leaf' or 'orchestrator'.")
if request.timeout_seconds is not None:
raise SubagentLifecycleError(
"Per-launch timeout is not supported; configure delegation timeout explicitly."
)
if request.working_directory is not None:
raise SubagentLifecycleError(
"working_directory is not supported because Hermes delegates use isolated task environments."
)
if request.blocked_tools:
raise SubagentLifecycleError(
"Per-tool blocking is not supported; use allowed_toolsets. Hermes always blocks unsafe child tools."
)
try:
metadata_bytes = len(
json.dumps(dict(request.metadata), sort_keys=True).encode()
)
except (TypeError, ValueError) as exc:
raise SubagentLifecycleError("metadata must be JSON-serializable.") from excView on GitHub (pinned to c896c09c42)
Solutions
- Use exactly 'leaf' (focused worker, default) or 'orchestrator' (can spawn its own children) — lower-case, no variants.
- Normalize user-supplied role strings to the two allowed values at your boundary, mapping unknowns to 'leaf'.
- Remember orchestrator is additionally gated by delegation.orchestrator_enabled and delegation.max_spawn_depth in config.yaml.
Example fix
# before request = SubagentLaunchRequest(goal=g, role="worker") # after role = "orchestrator" if user_role == "orchestrator" else "leaf" request = SubagentLaunchRequest(goal=g, role=role)
Defensive patterns
Strategy: type-guard
Validate before calling
ALLOWED_ROLES = {"leaf", "orchestrator"}
role = role if role in ALLOWED_ROLES else "leaf"
request = SubagentLaunchRequest(goal=goal, role=role) Type guard
def is_valid_role(value: object) -> bool:
return value in ("leaf", "orchestrator") Prevention
- Use only the literal strings 'leaf' and 'orchestrator' (case-sensitive).
- Normalize external role vocabularies to these two values at your boundary.
- Default to 'leaf'; orchestrator is gated by delegation config anyway.
When it happens
Trigger: Calling launch() with role="worker", role="root", role=None (if the dataclass default is not one of the two), or role="LEAF" (case-sensitive set membership check).
Common situations: Porting code from another framework whose role vocabulary differs; typos and casing mistakes; UI dropdowns offering role names that do not match the contract.
Related errors
- parent_session_id does not match the active session.
- goal must be a non-empty string of at most 16000 characters.
- context must be a string of at most 32000 characters.
- Per-launch timeout is not supported; configure delegation ti
- working_directory is not supported because Hermes delegates
AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14).
Data as JSON: /api/errors/649ce5715ec6befd.
Report an issue: GitHub.