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 <noreply@anthropic.com>
This commit is contained in:
Jiang Bohan 2026-02-12 17:11:25 +08:00
parent 12fb12b895
commit f46c00e902
2 changed files with 76 additions and 15 deletions

View file

@ -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");
});
});

View file

@ -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();