Subagent Delegation
HooCode delegates focused work to specialized subagents that run in isolated
child processes. Delegation is description-driven: the parent agent decides
when to delegate and which agent to use, based on each agent’s description.
There is no deterministic keyword router and no blocking dispatch gate.
How Delegation Works
The parent agent has a Task tool whose description lists the available agents
(see the registry below). The model chooses an agent via subagent_type and
passes a self-contained prompt. The subagent runs in a fresh context and only
its final answer is returned to the parent.
Guidance baked into the parent prompt:
- Delegate self-contained units where you only need the final result.
- Prefer handling small, quick, or single-file work inline.
- The subagent cannot see the parent conversation — pass all needed context in
prompt.
The DispatchEvaluator no longer routes. It survives only to:
- Enforce the nesting cap — a process may delegate only while its
HOOCODE_SUBAGENT_DEPTHis below the tree-wide capHOOCODE_SUBAGENT_MAX_DEPTH(seeded from themaxSubagentDepthsetting, default1). At the default cap a subagent cannot spawn further subagents. - Record a complexity estimate in
.hoocode/dispatch/<task_id>/dispatch-log.jsonfor diagnostics. This is a cheap heuristic, not a routing decision.
Available Agents
Agents are defined by frontmatter .md files loaded from a registry with
precedence project > user > built-in:
- Built-in:
templates/agents/*.md(explore,plan,general-purpose) — matching Claude Code’s built-in roster.exploreandplanare strictly read-only;general-purposeis the delegating agent (delegate: true). Ship task-specialized agents (edit, review, etc.) yourself under.hoocode/agents/or.claude/agents/. - Project:
.hoocode/agents/, and.claude/agents/(Claude Code compatible) - User:
~/.hoocode/agents/, and~/.claude/agents/
Each definition supplies name, description, and optional tools, model,
maxTurns, background, and delegate. When a definition omits tools, the
subagent inherits all parent tools (Claude Code behavior).
Forcing a Subagent with /subagent
In interactive mode you can dispatch a subagent directly, bypassing the model’s decision (the mode is still validated against the registry):
/subagent explore "How does the auth middleware work?"
/subagent general-purpose "Add a --json flag to the export command and update its tests"
Execution Model
Subagents run as isolated hoocode child processes, managed by SubagentPool.
Each delegation:
- Spawns
hoocode --mode json --session <file> --task-id <id> [--system-prompt <prompt>] [--tools <allowlist>] --max-turns <n> <prompt>(re-running the current runtime/entry, so it works fromdist/, from source via tsx, or as a packaged binary). - Runs under a hard turn cap (
--max-turns, default 50). Near the cap the agent is asked to wrap up; at the cap it stops and returns apartialresult instead of failing. - Emits a periodic
{"ping":true}heartbeat on stdout; the lifeguard SIGKILLs a child that goes silent for 60s and enforces a per-mode hard timeout. - On exit, writes
.hoocode/dispatch/<task_id>/result.json(summary, files_changed, confidence, status, usage), which the parent verifies before accepting the result.
The tool returns only the subagent’s summary to the calling agent.
Background and Resume
- An agent definition can set
background: true. TheTasktool then dispatches it detached and returns atask_idimmediately. TheTaskOutputtool polls bytask_idand collects the final answer once finished. - Subagents persist their session to
.hoocode/dispatch/<task_id>/session.jsonl. Passresume_task_idto theTasktool to continue a previous run with a follow-upprompt(full prior transcript intact). Partial results surface their resume handle.
Concurrency is bounded (default 5). Parallelism happens when the model issues
multiple Task calls; there is no batch-dispatch API.
Guardrails
-
Token budget is advisory. It emits
budget_warning(80%) andbudget_exceeded(100%) for telemetry but never kills or fails a subagent; the turn cap is the guaranteed hard stop. -
Bounded nesting (default: none). Nesting is capped by
maxSubagentDepth(default1). The root seeds the cap intoHOOCODE_SUBAGENT_MAX_DEPTH; each spawned child is stamped with its depth (HOOCODE_SUBAGENT_DEPTH= parent + 1). TheTask/TaskOutputtools are registered only while a process’s depth is below the cap, and theDispatchEvaluatorenforces the same bound as defense in depth — so at the default cap subagents cannot recursively dispatch. When the cap is raised, nested pools (depth ≥ 1) run with a reduced concurrency so the worst-case live process count stays a fixed function of depth (e.g.5 + 5×2 = 15at depth 2), with no shared state to leak on crash. -
Delegation is opt-in per agent. A subagent only receives the
Tasktool when its agent definition setsdelegateand it is spawned below the cap (childDepth < maxSubagentDepth). On spawn, such an agent hasTask/TaskOutputadded to its tool allowlist and--enable-subagentspropagated, so it can dispatch one further level. Every other agent keeps its declared sandbox and cannot delegate, preserving the deliberate “Task is not a normal tool” boundary.delegate: truepermits any subagent type;delegate: explore, planscopes it to those types (forwarded as--delegate-allow; the Task tool rejects out-of-scope dispatches). Seeexamples/agents/orchestrator.mdfor a ready-to-use delegating agent.To exercise depth-2 end to end: drop the orchestrator into a discovered agents dir (e.g.
.hoocode/agents/), then run with--enable-subagents --max-subagent-depth 2and delegate to it. A[DISPATCH] … depth=2 …line confirms a subagent delegated. That line is written to stderr only when no TUI owns the terminal (--print, RPC, CI) — in an interactive session it would corrupt the differential render, so check.hoocode/dispatch/<task_id>/dispatch-log.json(which carries the samedepth) or setHOOCODE_DEBUG_AGENT_LOG=1to tee it tohoocode-debug.log. -
The pool prioritizes
exploreandreviewtasks overdoctasks because they often block downstream work. -
On completion, the subagent writes
.hoocode/dispatch/<task_id>/result.json(verified by the parent) and the pool writes.hoocode/dispatch/<task_id>/output.json(raw process outcome).