From f46c00e902e381969f3002ec02ca151c17e02886 Mon Sep 17 00:00:00 2001 From: Jiang Bohan Date: Thu, 12 Feb 2026 17:11:25 +0800 Subject: [PATCH] feat(subagent): update announce flow for group continuation When all runs in a group complete, deliver combined findings plus the `next` continuation prompt to the parent agent via writeInternal. The parent can then act on the collected data (summarize, write files, etc). Co-Authored-By: Claude Opus 4.6 --- .../core/src/agent/subagent/announce.test.ts | 42 +++++++++++++++- packages/core/src/agent/subagent/announce.ts | 49 +++++++++++++------ 2 files changed, 76 insertions(+), 15 deletions(-) diff --git a/packages/core/src/agent/subagent/announce.test.ts b/packages/core/src/agent/subagent/announce.test.ts index f532dd5c..efba367b 100644 --- a/packages/core/src/agent/subagent/announce.test.ts +++ b/packages/core/src/agent/subagent/announce.test.ts @@ -188,7 +188,7 @@ describe("formatCoalescedAnnouncementMessage", () => { const msg = formatCoalescedAnnouncementMessage(records); - expect(msg).toContain("All 2 background tasks have completed"); + expect(msg).toContain("All 2 background task(s) have completed"); expect(msg).toContain('Task 1: "Task A"'); expect(msg).toContain("Found issue A"); expect(msg).toContain('Task 2: "Task B"'); @@ -251,4 +251,44 @@ describe("formatCoalescedAnnouncementMessage", () => { expect(msg).toContain("上海:多云,9°C"); expect(msg).toContain("MUST include findings from every task item above"); }); + + it("includes continuation prompt when next is provided", () => { + const records = [ + makeRecord({ runId: "run-1", label: "AAPL data", findings: "AAPL revenue: $100B" }), + makeRecord({ runId: "run-2", label: "MSFT data", findings: "MSFT revenue: $200B" }), + ]; + + const msg = formatCoalescedAnnouncementMessage(records, "Summarize all data and write a PDF investment report"); + + expect(msg).toContain("CONTINUATION TASK"); + expect(msg).toContain("Summarize all data and write a PDF investment report"); + expect(msg).toContain("AAPL revenue: $100B"); + expect(msg).toContain("MSFT revenue: $200B"); + // Should NOT contain the default summarize instruction + expect(msg).not.toContain("Summarize these results naturally for the user"); + }); + + it("uses continuation prompt even for single record when next is provided", () => { + const records = [ + makeRecord({ runId: "run-1", label: "Data collection", findings: "All data collected" }), + ]; + + const msg = formatCoalescedAnnouncementMessage(records, "Generate the final report"); + + expect(msg).toContain("CONTINUATION TASK"); + expect(msg).toContain("Generate the final report"); + expect(msg).toContain("All data collected"); + }); + + it("uses default summarize instruction when next is not provided", () => { + const records = [ + makeRecord({ runId: "run-1" }), + makeRecord({ runId: "run-2" }), + ]; + + const msg = formatCoalescedAnnouncementMessage(records); + + expect(msg).not.toContain("CONTINUATION TASK"); + expect(msg).toContain("Summarize these results naturally for the user"); + }); }); diff --git a/packages/core/src/agent/subagent/announce.ts b/packages/core/src/agent/subagent/announce.ts index c1cf3330..de6091f3 100644 --- a/packages/core/src/agent/subagent/announce.ts +++ b/packages/core/src/agent/subagent/announce.ts @@ -193,12 +193,17 @@ export function formatAnnouncementMessage(params: FormatAnnouncementParams): str /** * Format a coalesced announcement message from multiple completed subagent runs. * When only one record is provided, delegates to formatAnnouncementMessage. + * + * @param next — Optional continuation prompt from a SubagentGroup. When present, + * the parent agent is instructed to execute the continuation using the combined + * findings, rather than just summarizing. */ export function formatCoalescedAnnouncementMessage( records: SubagentRunRecord[], + next?: string, ): string { - // Single record: delegate to existing format for backward-compatible behavior - if (records.length === 1) { + // Single record without continuation: delegate to existing format + if (records.length === 1 && !next) { const r = records[0]!; return formatAnnouncementMessage({ runId: r.runId, @@ -214,10 +219,9 @@ export function formatCoalescedAnnouncementMessage( }); } - // Multiple records: build combined message. - // Include a strict raw-findings section so parent can reliably cover every task result. + // Multiple records (or single with continuation): build combined message. const parts: string[] = [ - `All ${records.length} background tasks have completed. Here are the combined results:`, + `All ${records.length} background task(s) have completed. Here are the combined results:`, "", ]; @@ -262,14 +266,30 @@ export function formatCoalescedAnnouncementMessage( ); } - parts.push( - "", - "Summarize these results naturally for the user.", - "You MUST include findings from every task item above, without omission.", - "Keep it concise, but preserve concrete findings from each task.", - "Do not mention technical details like session IDs or that these were background tasks.", - "You can respond with NO_REPLY if no announcement is needed.", - ); + // Continuation vs. summarization + if (next) { + parts.push( + "", + "---", + "", + "CONTINUATION TASK: The user's original request requires further work using the findings above.", + "Execute the following task now, using ALL the collected data:", + "", + next, + "", + "Use the raw findings above as your data source. Call tools as needed to complete this task.", + "Do not mention technical details like session IDs or that these were background tasks.", + ); + } else { + parts.push( + "", + "Summarize these results naturally for the user.", + "You MUST include findings from every task item above, without omission.", + "Keep it concise, but preserve concrete findings from each task.", + "Do not mention technical details like session IDs or that these were background tasks.", + "You can respond with NO_REPLY if no announcement is needed.", + ); + } return parts.join("\n"); } @@ -289,8 +309,9 @@ export function formatCoalescedAnnouncementMessage( export function runCoalescedAnnounceFlow( requesterSessionId: string, records: SubagentRunRecord[], + next?: string, ): boolean { - const message = formatCoalescedAnnouncementMessage(records); + const message = formatCoalescedAnnouncementMessage(records, next); try { const hub = getHub();