Subagents: restore announce chain + fix nested retry/drop regressions (#22223)

* Subagents: restore announce flow and fix nested delivery retries

* fix: prep subagent announce + docs alignment (#22223) (thanks @tyler6204)
This commit is contained in:
Tyler Yust
2026-02-20 15:39:09 -08:00
committed by GitHub
parent 086af56867
commit fe57bea088
21 changed files with 579 additions and 985 deletions

View File

@@ -166,12 +166,12 @@ Behavior:
- Starts a new `agent:<agentId>:subagent:<uuid>` session with `deliver: false`.
- Sub-agents default to the full tool set **minus session tools** (configurable via `tools.subagents.tools`).
- Sub-agents are not allowed to call `sessions_spawn` (no sub-agent → sub-agent spawning).
- Depth policy is enforced for nested spawns. With the default `maxSpawnDepth = 2`, depth-1 sub-agents can call `sessions_spawn`, depth-2 sub-agents cannot.
- Always non-blocking: returns `{ status: "accepted", runId, childSessionKey }` immediately.
- After completion, OpenClaw runs a sub-agent **announce step** and posts the result to the requester chat channel.
- If the assistant final reply is empty, the latest `toolResult` from sub-agent history is included as `Result`.
- Reply exactly `ANNOUNCE_SKIP` during the announce step to stay silent.
- Announce replies are normalized to `Status`/`Result`/`Notes`; `Status` comes from runtime outcome (not model text).
- After completion, OpenClaw builds a sub-agent announce system message from the child session's latest assistant reply and injects it to the requester session.
- Delivery stays internal (`deliver=false`) when the requester is a sub-agent, and is user-facing (`deliver=true`) when the requester is main.
- Recipient agents can return the internal silent token to suppress duplicate outward delivery in the same turn.
- Announce replies are normalized to runtime-derived status plus result context.
- Sub-agent sessions are auto-archived after `agents.defaults.subagents.archiveAfterMinutes` (default: 60).
- Announce replies include a stats line (runtime, tokens, sessionKey/sessionId, transcript path, and optional cost).

View File

@@ -35,7 +35,7 @@ Use `/subagents` to inspect or control sub-agent runs for the **current session*
- If direct delivery fails, it falls back to queue routing.
- If queue routing is still not available, the announce is retried with a short exponential backoff before final give-up.
- The completion message is a system message and includes:
- `Result` (`assistant` reply text, or latest `toolResult` if the assistant reply is empty)
- `Result` (latest assistant reply text from the child session, after a short settle retry)
- `Status` (`completed successfully` / `failed` / `timed out`)
- compact runtime/token stats
- `--model` and `--thinking` override defaults for that specific run.
@@ -90,7 +90,7 @@ Auto-archive:
## Nested Sub-Agents
By default, sub-agents cannot spawn their own sub-agents (`maxSpawnDepth: 1`). You can enable one level of nesting by setting `maxSpawnDepth: 2`, which allows the **orchestrator pattern**: main → orchestrator sub-agent → worker sub-sub-agents.
By default, sub-agents can spawn one additional level (`maxSpawnDepth: 2`), enabling the **orchestrator pattern**: main → orchestrator sub-agent → worker sub-sub-agents. Set `maxSpawnDepth: 1` to disable nested spawning.
### How to enable
@@ -99,7 +99,7 @@ By default, sub-agents cannot spawn their own sub-agents (`maxSpawnDepth: 1`). Y
agents: {
defaults: {
subagents: {
maxSpawnDepth: 2, // allow sub-agents to spawn children (default: 1)
maxSpawnDepth: 2, // allow sub-agents to spawn children (default: 2)
maxChildrenPerAgent: 5, // max active children per agent session (default: 5)
maxConcurrent: 8, // global concurrency lane cap (default: 8)
},
@@ -110,11 +110,11 @@ By default, sub-agents cannot spawn their own sub-agents (`maxSpawnDepth: 1`). Y
### Depth levels
| Depth | Session key shape | Role | Can spawn? |
| ----- | -------------------------------------------- | --------------------------------------------- | ---------------------------- |
| 0 | `agent:<id>:main` | Main agent | Always |
| 1 | `agent:<id>:subagent:<uuid>` | Sub-agent (orchestrator when depth 2 allowed) | Only if `maxSpawnDepth >= 2` |
| 2 | `agent:<id>:subagent:<uuid>:subagent:<uuid>` | Sub-sub-agent (leaf worker) | Never |
| Depth | Session key shape | Role | Can spawn? |
| ----- | -------------------------------------------- | ----------------------------------- | ------------------------------ |
| 0 | `agent:<id>:main` | Main agent | Always |
| 1 | `agent:<id>:subagent:<uuid>` | Sub-agent (orchestrator by default) | Yes, when `maxSpawnDepth >= 2` |
| 2 | `agent:<id>:subagent:<uuid>:subagent:<uuid>` | Sub-sub-agent (leaf worker) | No, when `maxSpawnDepth = 2` |
### Announce chain
@@ -128,9 +128,9 @@ Each level only sees announces from its direct children.
### Tool policy by depth
- **Depth 1 (orchestrator, when `maxSpawnDepth >= 2`)**: Gets `sessions_spawn`, `subagents`, `sessions_list`, `sessions_history` so it can manage its children. Other session/system tools remain denied.
- **Depth 1 (leaf, when `maxSpawnDepth == 1`)**: No session tools (current default behavior).
- **Depth 2 (leaf worker)**: No session tools `sessions_spawn` is always denied at depth 2. Cannot spawn further children.
- **Depth 1 (orchestrator, default with `maxSpawnDepth = 2`)**: Gets `sessions_spawn`, `subagents`, `sessions_list`, `sessions_history` so it can manage its children. Other session/system tools remain denied.
- **Depth 1 (leaf, when `maxSpawnDepth = 1`)**: No session tools.
- **Depth 2 (leaf worker, default `maxSpawnDepth = 2`)**: No session tools, `sessions_spawn` is denied at depth 2, cannot spawn further children.
### Per-agent spawn limit
@@ -156,17 +156,16 @@ Note: the merge is additive, so main profiles are always available as fallbacks.
## Announce
Sub-agents report back via an announce step:
Sub-agents report back via an announce injection step:
- The announce step runs inside the sub-agent session (not the requester session).
- If the sub-agent replies exactly `ANNOUNCE_SKIP`, nothing is posted.
- Otherwise the announce reply is posted to the requester chat channel via a follow-up `agent` call (`deliver=true`).
- Announce replies preserve thread/topic routing when available (Slack threads, Telegram topics, Matrix threads).
- Announce messages are normalized to a stable template:
- `Status:` derived from the run outcome (`success`, `error`, `timeout`, or `unknown`).
- `Result:` the summary content from the announce step (or `(not available)` if missing).
- `Notes:` error details and other useful context.
- `Status` is not inferred from model output; it comes from runtime outcome signals.
- OpenClaw reads the child session's latest assistant reply after completion, with a short settle retry.
- It builds a system message with `Status`, `Result`, compact stats, and reply guidance.
- The message is injected with a follow-up `agent` call:
- `deliver=false` when the requester is another sub-agent, this keeps orchestration internal.
- `deliver=true` when the requester is main, this produces the user-facing update.
- Delivery context prefers captured requester origin, but non-deliverable channels (for example `webchat`) are ignored in favor of persisted deliverable routes.
- Recipient agents can return the internal silent token to suppress duplicate outward delivery in the same turn.
- `Status` is derived from runtime outcome signals, not inferred from model output.
Announce payloads include a stats line at the end (even when wrapped):
@@ -184,7 +183,7 @@ By default, sub-agents get **all tools except session tools** and system tools:
- `sessions_send`
- `sessions_spawn`
When `maxSpawnDepth >= 2`, depth-1 orchestrator sub-agents additionally receive `sessions_spawn`, `subagents`, `sessions_list`, and `sessions_history` so they can manage their children.
With the default `maxSpawnDepth = 2`, depth-1 orchestrator sub-agents receive `sessions_spawn`, `subagents`, `sessions_list`, and `sessions_history` so they can manage their children. If you set `maxSpawnDepth = 1`, those session tools stay denied.
Override via config: