Ask eight reference implementations the same question: how does one agent delegate to another, and can it still control that work afterward? Compare eight dimensions across all eight systems, explicitly marking absent features. Our own mechanism is described inAgent collaboration: tool surface; the design specification isagent-collaboration.md.
This page contains levels two and three. Level one describes our implemented mechanism on a separate, mutually linked page.
Binary @250599082. Differences between the two sources are identified individually below.
Eight dimensions in one view. Every cell is expanded below with code locations.
| Child spawning | Direct communication | Task tracking | Cascading cancellation | Loop prevention | Tool restriction | Addressing | Size | |
|---|---|---|---|---|---|---|---|---|
| Ours | In-process, depth 1 | Yes, any-to-any | task_id, three tools | Cascades | Eight mechanisms | Remove according to remaining allowance | Branch name / SID:HEAD | 4.5k |
| Claude Code | In-process, depth 3 | Yes, including across machines | agentId, three tools | Cascades | Five mechanisms | Remove by depth, then reject at invocation | name@team | 24k |
| codex-cli | In-process, V1 depth 1, V2 unlimited | Yes, direct sibling communication | ThreadId, four tools | Only partial | Two mechanisms | Remove; V1 additionally rejects | Path-style /root/x | 7k |
| openclaw | In-process or external process, depth 1 | Yes, but requires enabling | runId, list only | Cascades | Eight mechanisms | Remove, then recheck at spawn | session key | 17k |
| opencode | In-process, implicit depth 1 | No | Child session id | Cascades | Three mechanisms | Remove or reject depending on rule shape | Agent type name | 0.4k |
| hermes-agent | In-process thread pool, depth 1 | No within the tree; indirectly through the board | Model cannot obtain ids | Cascades | Five mechanisms | Remove at toolset level | Anonymous, one-shot | 2.9k |
| pi-mono | Child process, unlimited depth | No | None | Cascades one level only | Two mechanisms | Remove using allowlist | Name of an on-disk .md file | 1.3k |
| pi-ai | None | None | None | Not applicable | None | Not applicable | None | 0 |
| weclaw | None | No | None | Not applicable | One mechanism | Not applicable | @name | 1.1k |
Three questions: is spawning supported, in-process or subprocess, and is depth limited?
| Supported? | In-process or subprocess | Depth limit and enforcement point | |
|---|---|---|---|
| Ours | Yes,agent |
In-process thread pool, 4 workers by default | agent.max_spawn_depthDefault 1, with 0 meaning unlimited. Exceeding it returns “do it yourself.” A separate per-turn fan-out limit isagent.max_spawn_fanout8 by default
programs/tools/agent/agent/agent.py; configuration defaults in config_schema.py |
| Claude Code | Yes,Agent(formerlyTask) |
In-process nested query loop, plus tmux/iTerm subprocess teammates and cloud agents throughisolation:"remote" |
Depth 3, configurable through environment variableCLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH; concurrency cap 20.
The leaked source tree has no depth counter; this limit exists only in the binary
Binary @250599082 var WJu=3; check @258438030; concurrency @250622624 yny=20 |
| codex-cli | Yes,spawn_agent |
In-process Tokio tasks reuse the same thread manager without creating processes | V1 depth 1;V2 computes child depth but never checks it, effectively unlimited except for the concurrency cap core/src/config/mod.rs:284; V1 check handlers/multi_agents/spawn.rs:65-71; V2 calculation multi_agents_v2/spawn.rs:62 |
| openclaw | Yes,sessions_spawn |
Two runtimes:subagentIn-process,acpis an external host process, disabled in sandboxes |
Depth 1, configurable up to 5. Count:subagent:segments in the session key
config/agent-limits.ts:8; check agents/subagent-spawn.ts:784-791 |
| opencode | Yes,taskCreate child session |
In-process re-entry into the same prompt loop, without creating a process | No numeric limit. Inject atask: * denyrule into the child session, which removes thetasktool from its schema, making effective depth 1. If the agent itself declarespermission.task=allow, depth is unlimited
tool/task.ts:133-135; agent/subagent-permissions.ts:18,25 |
| hermes-agent | Yes,delegate_task |
In-process thread pool by default. Two subprocess paths: external ACP CLI and one hermes process per board card | MAX_DEPTH=1, clamped to [1,3]
tools/delegate_tool.py:133,136-137; check :1958-1972 |
| pi-mono | Core deliberately omits it; example extension implements it | Subprocess: each child starts a newpi --mode json -p --no-session |
None. Children reload the same extension directory; theworkeragent declares no tool allowlist and therefore receives the spawning tool, creating an unbounded recursion path used by the official implementation workflow itself
README.md:476"No sub-agents"; examples/extensions/subagent/index.ts:329-333; agents/worker.md:1-5 |
| pi-ai | None | Not applicable | Not applicable. The repository is a read-only snapshot of pi-mono's LLM transport layer, neither compiled nor imported README.md:1-19 |
| weclaw | None | It does start subprocesses, but they are user-configured agent CLIs themselves: top-level agents | Not applicable; no nesting agent/cli_agent.go:140,159; interface has only four methods in agent/agent.go:79-95 |
Is there a channel that bypasses the parent? Some systems only support parent delegation and child results; others let siblings address each other.
| Direct communication? | Connection and delivery | |
|---|---|---|
| Ours | Yes | send_message(to=), from any agent to any unarchived branch, using the same path across sessions without ownership checks. Busy targets receive queued session-inbox messages consumed after their current turn
programs/tools/send_message/send_message/send_message.py:82,154; address resolution addressing.py:77-156; inbox agent/inbox.py |
| Claude Code | Yes, including across machines | SendMessage.tomay be a teammate name,main(back to the main conversation),*(broadcast),
uds:/path.sock(another local session),bridge:session-id(another machine), or a bare agentId. Teammates use file mailboxes~/.claude/teams/{team}/inboxes/{name}.jsonwhile in-process children use memory queues. Sending to a finished agent wakes it from its record and resumes execution
tools/SendMessageTool/SendMessageTool.ts; utils/peerAddress.ts:8-21; utils/teammateMailbox.ts:4-8,56-66; wakeup tools/AgentTool/resumeAgent.ts:42-67; address table Binary @258816708 |
| codex-cli | Yes, direct sibling communication | send_messageandfollowup_taskdeliver directly to the target thread without the parent. Addresses are path-based and accept absolute paths, so siblings can address each other. Delivery either queues only or immediately wakes the recipient for one turn
handlers/multi_agents_v2/message_tool.rs:11-24,51-131; absolute paths protocol/src/agent_path.rs:59-71; delivery core/src/agent/control.rs:171 |
| openclaw | Yes, disabled by default | sessions_sendCross-agent communication requires a configuration switch and pairwise allowlisting. Conversation uses a bounded back-and-forth loop.Children cannot use this tooland must return through the parent's announcement queue
agents/tools/sessions-send-tool.ts:193,249-256; back-and-forth loop sessions-send-tool.a2a.ts:94; child restriction agents/pi-tools.policy.ts:49-50 |
| opencode | No | All communication is parent-child. The built-in registry has no messaging tool. A parent can append messages to a running child, still only in the parent-to-child direction tool/registry.ts:217-236 is the complete built-in registry; parent-to-child tool/task.ts:242-257 |
| hermes-agent | No within delegation trees; indirectly through another path | Children report only to parents;send_messageis on the “never available to children” list. The board path differs: each card has a worker process, workers exchange card comments through a shared database, and the root card includes a conventionally formatted shared area
Denylist tools/delegate_tool.py:50; board toolsets.py:252-269; shared area hermes_cli/kanban_swarm.py:11-14,26,226-268 |
| pi-mono | No | Parent-to-child communication supplies one prompt at spawn; child-to-parent communication returns stdout on exit. In sequential mode, string substitution inserts the previous output into the next task description, the only sibling data channel examples/extensions/subagent/index.ts:530,569; parallel tasks are fully isolated :618-639 |
| pi-ai | None | No agents, therefore no communication |
| weclaw | No | Only human-to-multiple-agent broadcast:@a @b messagedistributes the same original message to several agents, each returning a separately prefixed reply to the human. Agents cannot see one another
messaging/handler.go:456-486; command registry has only 7 entries :686-698 |
Four questions: are there task ids, can status be queried, how are results returned, and can tasks be cancelled? A fifth: does stopping a parent stop its descendants?
| Can the model obtain a task id? | Can status be queried? | How results return | Can the model cancel? | Does cancellation cascade? | |
|---|---|---|---|---|---|
| Ours | t_prefix | list_tasks |
Automatically appended to the dispatcher's session for its next turn | task_stop, with ownership checks |
Cascades. Traverseparent_job_idbreadth-first with a visited set for cycle prevention
agent/job/runner.py:336-380; return delivery :1075-1186; ownership programs/tools/agent/_ownership.py:21-58 |
| Claude Code | agentId | Task registry;TaskOutputretrieves results |
Synchronous children return directly as tool results; background agents write to disk and send a notification | TaskStop, with ownership checks; the main session can stop any agent |
Cascades in the binary; the leaked source treedoes not cascade: background agents there deliberately omit the parent's abort signal so user ESC does not kill background work Cascade Binary @265089223, ancestor check @253677780; non-cascade tools/AgentTool/AgentTool.tsx:694-696 |
| codex-cli | ThreadId plus path | list_agents, get_status, with push subscriptions |
A completion monitor synthesizes a message for the parent;wait_agentcan wait synchronously, default 30 seconds and maximum 1 hour |
interrupt_agent; V1 also hasclose_agent |
Only partial: close_agentcascades, whileinterrupt_agentstops only one. V2 lacksclose_agententirely, leaving only non-cascading interruption
Cascade core/src/agent/control/legacy.rs:86-101, descendant enumeration control.rs:732-756; non-cascade control.rs:247-257 |
| openclaw | runId | subagentsThe tool offers only list |
Push rather than polling:sessions_yieldends the current turn; results arrive as the next message |
No model cancellation tool; cancellation uses chat command/subagents killand an HTTP endpoint |
Cascades recursively with a visited set; each child also clears its queue and writes an abort marker agents/subagent-control.ts:205,232,238,256; kill command auto-reply/reply/commands-subagents/action-kill.ts:27 |
| opencode | Child session id, reusable to resume that child session | No query tool; the prompt explicitly prohibits status polling | Foreground returns through the tool; background inserts a synthetic message into the parent session | No model cancellation tool; cancellation is host-side | Cascades through abort signals and breadth-first traversal of background-task metadata until a fixed point; session deletion is also recursive session/run-state.ts:116-148; tool/task.ts:296-306; tests test/tool/task.test.ts:870 |
| hermes-agent | No. Returns only index, status, summary, and usage; internal ids are operations-only | No model tool; status queries are gateway RPC | Fully synchronous blocking; returns all child-task results together | No model control; interruption is gateway RPC | Cascades recursively through active child objects, cooperatively without forcibly killing threads Result fields tools/delegate_tool.py:1684-1720; recursion run_agent.py:1690-1730 |
| pi-mono | No. The invocation id received by the tool is discarded | No. Progress only reaches the UI through callbacks | Read the child stdout event stream and return the final assistant text, truncated to 50KB per task | No handle, only the environment abort signal | One level only: abort sends SIGTERM, then SIGKILL after 5 seconds. Children are not in the shared cleanup registry, leaving orphans if the parent is forcibly killed Discarded id examples/extensions/subagent/index.ts:466; process termination :393-403; missing registration compared with src/utils/shell.ts:172 |
| pi-ai | None | None | None | None | Not applicable. Only one flat abort signal for an individual HTTP request |
| weclaw | None | None | Synchronous return, associated by WeChat user id | No cancellation command in the registry | Not applicable; no hierarchy. Only global cancellation during process shutdown messaging/handler.go:541-556; command registry :686-698 |
The matrix lists mechanisms each system actually uses. Empty cells indicate absent mechanisms, not missing investigation.
| Key constants and locations | |
|---|---|
| Ours | Depth 1 counts only created generations; a per-chain message budget of 8 counts spawning, messages, delegation, and result return. Per-turn fan-out is 8, concurrency 4, tool calls per turn 50, and inbox size 50 with oldest eviction. Identical sender/content within 60 seconds is duplicate. Self-messages are always rejected regardless of budget. All three budgets allow 0 for unlimited. Each numeric choice is justified beside its constant by comparison with this page MAX_MESSAGES=8 send_message/depth.py; MAX_SPAWN_DEPTH=1 and MAX_SPAWN_FANOUT=8 agent/agent.py; two separate ContextVars in depth.py; 50 per turn agent/agent_loop.py:246; limit 50 and 60 seconds agent/inbox.py; self-message rejection send_message.py:139-144 |
| Claude Code | Depth 3, concurrency 20, a 50-message ring mailbox evicting oldest entries, message-uuid deduplication, a boolean fork-recursion guard, a flat teammate roster prohibiting teammates from creating teammates, and no new agents after budget exhaustion.No message rate limit or message-count cap Binary @250599082 depth 3; @250622624 concurrency 20; @253677687 mailbox 50; @253677581 uuid deduplication; fork guard tools/AgentTool/forkSubagent.ts:78-89; flat teammates AgentTool.tsx:273 |
| codex-cli | Only two limits: depth (V1=1, absent in V2) and concurrency (global 6, per-session V2 4). No deduplication, message count, rate limit, or cycle detection.V2 removes depth limits and permits sibling addressing, so no counter stops two agents repeatedly delegating to one another core/src/config/mod.rs:212-213,284; concurrency check core/src/agent/control/execution.rs:68-76 |
| openclaw | Most layered controls: depth 1 (up to 5), at most 5 children per agent (up to 20), concurrency 8 and 4, cross-agent conversations defaulting to 5 rounds with maximum 20, 2-second steering rate limit, announcement retries capped at 3, child-session-key list deduplication, and a general tool-loop detector disabled by default config/agent-limits.ts:3-8; conversation rounds agents/tools/sessions-send-helpers.ts:15-16; rate limit agents/subagent-control.ts:45,507-516; retries agents/subagent-registry-helpers.ts:35 |
| opencode | Depth is enforced through permission injection without a number. Repeated-call detection prompts for permission when the last 3 calls use the same tool and byte-identical arguments. Step limits default to unlimited DOOM_LOOP_THRESHOLD=3 session/processor.ts:35, check :519-546; steps session/prompt.ts:1281 |
| hermes-agent | Depth 1 (up to 3), 3 concurrent children, at most 50 iterations per child, a 600-second hard timeout plus heartbeat checks per child, and one global spawn-pause switch.No deduplication or message count.Recursion is additionally restricted structurally by removing the delegation toolset from leaves tools/delegate_tool.py:132-133,136-137,512-523; pause switch :149-172; toolset removal :672-680 |
| pi-mono | Only fan-out 8, concurrency 4, and per-task output truncation at 50KB.No depth limit, deduplication, rate limit, or main-loop round cap. An unbounded recursion path actually exists examples/extensions/subagent/index.ts:27-30; main loop packages/agent/src/agent-loop.ts:170 |
| pi-ai | None. There is no tool-call loop and therefore no loop to prevent |
| weclaw | Only two deduplication mechanisms: WeChat message ids for 5 minutes and agent names within one broadcast. No depth, count, or rate limit. Cycles are structurally impossible because agents have no return channel to the handler messaging/handler.go:273,63-64; name deduplication :246-253 |
There are only two approaches: remove tools from the list, or retain them and reject invocation. The difference is whether the model sees the tool.
| Approach | Tools removed and conditions | |
|---|---|---|
| Ours | A by remaining allowance, with B as fallback | The sole condition isdelegation_budget_left(): only when the message budget is exhausted, removeagent, task_output, task_stopfrom the list. Every dispatch consumes one message, so these three tools can do nothing without message allowance. Exhausted generation allowance does not remove tools because work can still be assigned to existing agents.
send_message, list_agents, archive_agent, list_tasksare never removed. The condition is intentionally binary: code explains that provider prompt caches require byte-identical definitions between turns; per-level definitions fragment the cache. Separate rejection messages handle each exceeded limit
send_message/depth.py delegation_budget_left; integration points agent/agent.py, task_output.py:37, task_stop.py:32; list filtering functions/_helpers.py:141-147; rejection messages agent/agent.py and send_message.py |
| Claude Code | Both A and B, with a historical change | The leaked source tree removes unconditionally:Agentis hardcoded in the “never available to children” set. Binary 2.1.226 changes toremoval by depth: its filter retainsAgentonly below the depth limit. Invocation rechecks and rejects to cover stale tool definitions.
The fork path deliberately reverses this: forked children receive the parent's complete toolset, includingAgentitself, preserving identical definitions for cache hits; recursion is rejected only at invocation
Unconditional removal src/constants/tools.ts:36-46, filtering tools/AgentTool/agentToolUtils.ts:94-95; depth filtering Binary @256834811; full fork toolset tools/AgentTool/forkSubagent.ts:44-71; fork rejection AgentTool.tsx:333 and Binary @258441552 |
| codex-cli | V1 uses A+B; V2 only A | Tool-list construction checks a switch and omits collaboration tools at the depth limit. V1 handlers recheck and reject, while V2 only removes. Role differences use overlaid configuration files instead of allowlists core/src/tools/spec_plan.rs:561-572,1099; V1 rejection handlers/multi_agents/spawn.rs:67-71; role configuration core/src/agent/role.rs:37,249 |
| openclaw | A+B | Two denylists: all children lose gateway, agent listing, session status, scheduled tasks, and cross-agent messaging. Leaves additionally lose subagent listing, session listing, session history, and spawning. Spawning rechecks depth even if its tool was removed agents/pi-tools.policy.ts:42-51,57-62,170-175; recheck agents/subagent-spawn.ts:786 |
| opencode | A or B depending on rule shape | When a deny rule's pattern is"*", remove the tool from the request schema; specific-pattern deny rules retain the definition and throw at invocation, returning the exact matched rule to the model
Removal permission/index.ts:215-225 and session/llm/request.ts:198-204; rejection permission/index.ts:86-90, message packages/core/src/v1/permission.ts:84-90 |
| hermes-agent | A only, at toolset granularity | Child toolsets are the intersection of requested and parent-owned sets, minus delegation, user-question, memory, and code-execution sets. No rejection message is needed because the tool is absent from the schema.One gap:
send_messageis on the “never available to children” list, but that list only generates documentation. No matching toolset removal exists, so children inheriting the messaging set still receive it
tools/delegate_tool.py:945-961,672-680; list :45-53; gap compared with toolsets.py:190 |
| pi-mono | A only | Convert the agent's tool allowlist to child-process command-line arguments. The child filters its registry, constructs no executable wrappers for excluded tools, and rebuilds its system prompt from surviving tools core/agent-session.ts:2253-2342; allowlist source core/sdk.ts:280-286; arguments examples/extensions/subagent/index.ts:290 |
| pi-ai | Not applicable | No tool executor; definitions are merely part of the transport format |
| weclaw | Not applicable | Defines no LLM tools itself; all tools are opaque inside the wrapped CLI The repository mentions tool only once, for display: agent/acp_agent.go:139 |
Do agents have names, and how can an existing agent be found? The distinction is between a live entity and merely a definition.
| Named? | Finding existing agents | Address format | |
|---|---|---|---|
| Ours | Yes. Branch creation assigns a 24-character placeholder, then a background LLM improves it at turns 1, 6, 16, and 40 | list_agents, with three views: current session, all sessions, and archived. Each includes a directly usable address, turn count, approximate text length, and tail preview |
SID:HEAD, branch name, or unique name prefix. Addresses resolve to the branch's current tip, so old heads remain valid
list_agents.py:100-179; address resolution send_message/addressing.py:77-156,200-208; renaming agent/dispatcher/titles.py:339-405 |
| Claude Code | Yes. Teammate id isname@team, readable and reproducible without lookup. Child names come from the spawnnameparameter; duplicate names receive registry suffix-2, -3,
mainis reserved |
ListAgents(formerlyListPeers). The list means recipients you can message: your children, other local sessions, cloud sessions, and other machines when remote control is connected |
Name / main / * / uds:path / bridge:session-id / bare agentId. Duplicate names resolve to the newest, with an additional suffix for disambiguation
utils/agentId.ts:36-57; registry Binary @254742111,@254743413; ListAgents prompt Binary @250608059 |
| codex-cli | Yes, two forms. Machine addressing is path-based with root/root; human-facing nicknames come from a list of 101 scientists |
list_agents, filterable by path prefix. Live children are also injected into environment context and visible without a query |
Path-based, accepting absolute paths for sibling addressing; bare ThreadId is also accepted protocol/src/agent_path.rs:15-19,54-71,129-146; names core/src/agent/agent_names.txt; listing core/src/agent/control.rs:383-395,361-381 |
| openclaw | Yes. Agents are defined in configuration files, not Markdown frontmatter | agents_list, but this tool is removed from children |
Session key, such asagent:<id>:…:subagent:…; label plus agentId is also accepted
sessions/session-key-utils.ts:108-126; agents-list-tool.ts:37; child restriction pi-tools.policy.ts:45 |
| opencode | Names identify agent types, not live instances | No independent list tool. Available names are embedded in thetasktool description. A running agent cannot be discovered; previously obtained ids only reconnect to one's own child session |
Bare name string tool/task.ts:46,116; names in description tool/registry.ts:252-265; agent definitions agent/agent.ts:35-55 |
| hermes-agent | Delegated children are anonymous and one-shot, addressed with objective text | No list tool. Internal ids followsa-index-randomand are exposed only through operations interfaces |
None. The board path differs: profile names identify card owners Parameters tools/delegate_tool.py:2675-2751; internal id :920-922; board hermes_cli/kanban_db.py:5674-5679 |
| pi-mono | Yes, but names identify on-disk Markdown definitions, not live processes | Cannot discover existing instances. Every invocation rescans the directory; children start without session retention and leave nothing after completion | Name strings matched by linear scan. No list tool; the model learns available names only through error messages examples/extensions/subagent/agents.ts:52-56,97-116; startup arguments index.ts:288; parsing index.ts:272-286 |
| pi-ai | None | None | None |
| weclaw | Yes: configuration keys, plus built-in and custom aliases | No list tool./inforeports only the current agent and is a human command, not an agent-callable tool |
@nameor/name
config/config.go:16-33; parsing messaging/handler.go:206-265; aliases :169,187 |
Four domains each own a distinct noun. The tool surface uses one primitive: deliver content to a branch, trigger one turn, and automatically return results. The tool surface is detailed onanother page; this section only explains its relationship to the eight systems above.
A manually maintained checklist. Editing it does not run any agent.
One task_id, one status, and a guaranteed result return. Cancellable with cascading cancellation.
An agent is a branch in the session graph. Create it, delegate to existing ones, list, or archive.
Send a message without creating a task record, or read any branch's complete transcript without triggering it.
| Our choice | Source | Rationale |
|---|---|---|
Align tool names with Claude Codeagent list_agents send_message task_output task_stop |
Claude Code | Models familiar with those names already recognize these tools.task_outputEven parameter shapes are copied |
Rename the planning checklist totodo_* | Deliberately differs from Claude Code | ItsTaskListis a planning board, not a view of running tasks. Changing the prefix letslist_tasksretain its literal meaning: running tasks |
Address a branch, not a processSID:HEADor branch name |
Similar to codex-cli path addresses and openclaw session keys | Agent conversations already form branches in the session graph. (Session id, head node) uniquely identifies one without another agent entity table |
| Any agent can message any agent without ownership checks | Less restrictive than openclaw; comparable to codex-cli V2 and Claude Code | Messages create no task record, cannot be cancelled, and do not cascade; the worst outcome is no response. openclaw disables messaging for children; we retain it |
Separate delegation from messagingagent(to=)creates a task;send_messagedoes not |
No other compared system separates them this way | Guaranteed result return, cancellation, and cascading cancellation belong to tasks, requiring ownership checks. Messages provide none of these promises, so anyone may send |
| Prevent loops with a per-chain message budget Depth 1, messages 8, including return flow; separate counters |
Only openclaw and ours use this message-counting mechanism | Depth limits alone cannot stop existing agents from repeatedly delegating to one another; codex-cli V2 lacks precisely this counter while allowing sibling links. The value 8 is informed by openclaw's default 5 alternating replies and maximum 20. Our count also includes spawning and delegation, so it falls between them. Return delivery rebinds the completed task's counter rather than incrementing it: one round trip costs 1, permitting eight round trips. Generation depth is separate, increasing only on creation and restoring the dispatcher's value on return. Reading results is not creation, so coordinators can dispatch another batch afterward |
| Do not copy Claude Code's depth 3 Five systems use 1; so do we |
Comparable to openclaw, codex-cli V1, hermes, and opencode | Claude Code's 3 cannot be copied directly: the leaked source has no depth counter, andAgentis removed from every child's tool pool unlessUSER_TYPE=ant, making effective external-user depth 1. Its asynchronous-tool allowlist also excludesAgent, so background children cannot spawn regardless of the counter. Depth 3 applies only to synchronous nesting where the parent tool call blocks until completion while a human is present. Our unattended path isrun_in_background=True, where they also use 1
Leaked source src/constants/tools.ts:36-46 and :55-71; filtering tools/AgentTool/agentToolUtils.ts:94-113 |
Add a per-turn fan-out limitagent.max_spawn_fanout8 by default |
Only openclaw has a true fan-out limit | Spawning passes counters to children without changing the parent's copy, so neither chain counter counts siblings. Before this limit, a turn could repeatedly invokeagentuntil the 50-iteration cap stopped it. openclaw allows at most 5 live children per parent (configurable 1–20), a reusable unit. Hermes's 3 and pi-mono's 8 validate batch-argument length per invocation and cannot be copied becauseagentcreates only one child per call. Eight is twice our four-worker pool: fill it, queue one more batch, then reject the ninth and direct the agent to its existing eight children |
| No steering rate limit or child hard timeout | openclaw uses 2 seconds; hermes uses 600 seconds | The 2-second rate limit protects openclaw steer, which aborts the child's current turn, clears its queue, and restarts it; closely spaced steer calls can abort each other. Its non-interrupting send path has no rate limit. Oursend_messageis non-interrupting and queues in a busy target's inbox, with nothing to interrupt. Hermes's 600 seconds is caller-sideFuture.result(timeout=…)rather than process termination: it sets a cooperative interrupt flag and abandons the thread, while children blocked in I/O continue. We already provide both parts more strongly:task_output(timeout=)provides the same wait, and
task_stopalso terminates the runtime and forces terminal state after 30 seconds |
| Restrict tools by remaining allowance, deliberately binary | Other systems remove unconditionally or by depth tier | Depth tiers change definitions at every level and invalidate provider prompt caches. Claude Code's fork path chooses full exposure with invocation-time rejection for the same reason |
Three additional toolslist_tasks archive_agent read_conversation |
No Claude Code equivalents | The model must inspect background work itself. Without archiving, agent lists accumulate finished workers. Reading other histories requires a readable transcript rather than raw session files |
agent-collaboration.md §5.6) is absent from return delivery; visibility metadata excluding internal branches from session pickers (§5.9) is absent beyond current archive fields; session cancellation clearing the inbox (§5.3) is connected only to the Web entry, not CLI stop