fix(subagents): make completion handoff review-first

This commit is contained in:
OpenClaw Contributor
2026-05-16 15:57:45 -04:00
committed by Peter Steinberger
parent 25090f64b3
commit 8040f28bc5
4 changed files with 17 additions and 6 deletions

View File

@@ -29,6 +29,7 @@ Docs: https://docs.openclaw.ai
- CLI/docs: call the canonical lowercase docs MCP search tool and surface MCP errors instead of returning empty search results. Fixes #82702. (#82704) Thanks @hclsys.
- QA-Lab: ignore heartbeat-only operational transcripts when capturing runtime parity cells so background checks cannot replace the scenario reply. (#80323) Thanks @100yenadmin.
- Gateway/exec approvals: wait for accepted async approval follow-up runs instead of direct-fallback sending duplicate completions when retries use different nonce keys. Fixes #82711. (#82717) Thanks @udaymanish6.
- Agents/subagents: mark completed subagent handoffs as ready for parent review so requester agents verify results and continue required follow-up work before reporting done.
- CLI/config: add `--dry-run` support to `openclaw config unset`, with `--json` output and allow-exec validation parity with `config set`/`config patch` dry-run handling. (#81895) Thanks @giodl73-repo.
- Memory-core: retry disabled dreaming cron cleanup until cron is available after startup, so persisted managed dreaming jobs are removed after restart. Fixes #82383. (#82389) Thanks @neeravmakwana.
- Providers/xAI: keep retired Grok 3, Grok 4 Fast, Grok 4.1 Fast, and Grok Code slugs out of model pickers while preserving compatibility resolution for existing configs.

View File

@@ -99,7 +99,9 @@ requester chat when the run finishes.
- `Result` — latest visible `assistant` reply text, otherwise sanitized latest tool/toolResult text. Terminal failed runs do not reuse captured reply text.
- `Status``completed successfully` / `failed` / `timed out` / `unknown`.
- Compact runtime/token stats.
- A delivery instruction telling the requester agent to rewrite in normal assistant voice (not forward raw internal metadata).
- A review instruction telling the requester agent to verify the result before deciding whether the original task is done.
- Follow-up guidance telling the requester agent to continue the task or record a follow-up when the child result leaves more action.
- A final-update instruction for the no-more-action path, written in normal assistant voice without forwarding raw internal metadata.
</Accordion>
<Accordion title="Modes and ACP runtime">

View File

@@ -511,8 +511,13 @@ describe("subagent announce formatting", () => {
expect(msg).toContain("</prompt-data>");
expect(msg).toContain("raw subagent reply");
expect(msg).toContain("Stats:");
expect(msg).toContain("A completed subagent task is ready for user delivery.");
expect(msg).toContain("Convert the result above into your normal assistant voice");
expect(msg).toContain("A completed subagent task is ready for parent review.");
expect(msg).toContain(
"Review/verify the result above before deciding whether the original task is done.",
);
expect(msg).toContain(
"If additional action is required, continue the task or record a follow-up; otherwise send a truthful user-facing update.",
);
expect(msg).toContain("Keep this internal context private");
expect(call?.params?.internalEvents?.[0]?.type).toBe("task_completion");
expect(call?.params?.internalEvents?.[0]?.taskLabel).toBe("do thing");
@@ -697,7 +702,10 @@ describe("subagent announce formatting", () => {
expect(msg).toContain("tokens 1.0k (in 12 / out 1.0k)");
expect(msg).toContain("prompt/cache 197.0k");
expect(msg).toContain("session_id: child-session-usage");
expect(msg).toContain("A completed subagent task is ready for user delivery.");
expect(msg).toContain("A completed subagent task is ready for parent review.");
expect(msg).toContain(
"If additional action is required, continue the task or record a follow-up; otherwise send a truthful user-facing update.",
);
expect(msg).toContain(
`Reply ONLY: ${SILENT_REPLY_TOKEN} if this exact result was already delivered to the user in this same turn.`,
);

View File

@@ -88,9 +88,9 @@ function buildAnnounceReplyInstruction(params: {
return `Convert this completion into a concise internal orchestration update for your parent agent in your own words. Keep this internal context private (don't mention system/log/stats/session details or announce type). If this result is duplicate or no update is needed, reply ONLY: ${SILENT_REPLY_TOKEN}.`;
}
if (params.expectsCompletionMessage) {
return `A completed ${params.announceType} is ready for user delivery. Convert the result above into your normal assistant voice and send that user-facing update now. Keep this internal context private (don't mention system/log/stats/session details or announce type).`;
return `A completed ${params.announceType} is ready for parent review. Review/verify the result above before deciding whether the original task is done. If additional action is required, continue the task or record a follow-up; otherwise send a truthful user-facing update. Keep this internal context private (don't mention system/log/stats/session details or announce type).`;
}
return `A completed ${params.announceType} is ready for user delivery. Convert the result above into your normal assistant voice and send that user-facing update now. Keep this internal context private (don't mention system/log/stats/session details or announce type), and do not copy the internal event text verbatim. Reply ONLY: ${SILENT_REPLY_TOKEN} if this exact result was already delivered to the user in this same turn.`;
return `A completed ${params.announceType} is ready for parent review. Review/verify the result above before deciding whether the original task is done. If additional action is required, continue the task or record a follow-up; otherwise send a truthful user-facing update. Keep this internal context private (don't mention system/log/stats/session details or announce type), and do not copy the internal event text verbatim. Reply ONLY: ${SILENT_REPLY_TOKEN} if this exact result was already delivered to the user in this same turn.`;
}
function buildAnnounceSteerMessage(events: AgentInternalEvent[]): string {