Enhance background subagent handling in runAgent function

- Introduced a new function `awaitPendingBg` to manage background subagents more effectively, ensuring that the main agent waits for all unreported subagents to settle before proceeding.
- Updated the logic to handle in-flight background tasks, preventing premature model calls while background work is still ongoing.
- Improved status notifications to inform users about the waiting state for background subagents.
- Refactored existing code to streamline the handling of background tasks and their results.
This commit is contained in:
Pawan Osman
2026-07-15 14:05:32 +03:00
parent 2f04612e8d
commit 3da6b8675b
+48 -18
View File
@@ -228,6 +228,26 @@ export async function runAgent(opts: RunAgentOptions): Promise<void> {
return done.length;
};
const bgPending = () => bgSubagents.length > bgReported;
/** Block until all unreported background subagents settle, then flush into history. */
const awaitPendingBg = async (): Promise<boolean> => {
if (!bgPending()) return false;
flushSettledBg();
if (!bgPending()) return true;
const pending = bgSubagents.slice(bgReported);
const n = pending.length;
emit({ type: "run-status", status: "running" });
emit({
type: "shell-notify",
message: `Waiting for ${n} background subagent${n > 1 ? "s" : ""} to finish — will resume when done…`,
});
await Promise.allSettled(pending);
if (signal.aborted) return true;
flushSettledBg();
return true;
};
// Summarize older steps with the same model (non-streaming aggregate) so
// compaction keeps task intent, decisions, file paths and unfinished work.
const summarizeSteps = async (steps: Step[]): Promise<string> => {
@@ -385,6 +405,17 @@ export async function runAgent(opts: RunAgentOptions): Promise<void> {
});
continue;
}
// In-flight background Task subagents: wait + feed results before any
// "continue" nudge. Otherwise the model gets another turn while workers
// are still running and often spawns a second wave of subagents.
if (bgPending()) {
await awaitPendingBg();
if (signal.aborted) {
emit({ type: "run-status", status: "cancelled" });
return;
}
continue;
}
// Truncated response (hit max output tokens): the model didn't choose to
// stop — never treat this as a final answer. Ask it to continue.
if (isAgentic() && /length|max_tokens|max_output_tokens/i.test(finishReason)) {
@@ -425,24 +456,6 @@ export async function runAgent(opts: RunAgentOptions): Promise<void> {
continue;
}
}
// Before truly finishing, if background subagents are still in flight (or
// completed but not yet reported), wait for them and feed their summaries
// back so the model continues its own loop and synthesizes the results.
if (bgSubagents.length > bgReported) {
// Report whatever already finished without blocking; only wait for the rest.
if (flushSettledBg() > 0) continue;
const pending = bgSubagents.slice(bgReported);
const n = pending.length;
emit({ type: "run-status", status: "running" });
emit({ type: "shell-notify", message: `Waiting for ${n} background subagent${n > 1 ? "s" : ""} to finish — will resume when done…` });
await Promise.allSettled(pending);
if (signal.aborted) {
emit({ type: "run-status", status: "cancelled" });
return;
}
flushSettledBg();
continue;
}
finalText = assistantText;
break;
}
@@ -587,6 +600,23 @@ export async function runAgent(opts: RunAgentOptions): Promise<void> {
});
history.push({ kind: "tool-result", callId: call.id, name: call.name, output: r.output, status: r.status, image: r.image });
}
// After launching background Task(s), wait for that wave before calling the
// model again. Otherwise the next turn (or empty-turn / todo nudge) races
// ahead and the coordinator spawns more subagents while workers still run.
if (bgPending()) {
const launchedBg = parsed.some((p, i) => {
if (p.call.name !== "Task") return false;
const out = results[i]?.output || "";
return /Launched .+ in the background/i.test(out);
});
if (launchedBg) {
await awaitPendingBg();
if (signal.aborted) {
emit({ type: "run-status", status: "cancelled" });
return;
}
}
}
}
// Paused at the step limit with work still in flight → surface a Continue