fix: wake yielded parent after subagents finish (#97090)

* fix: wake yielded subagent parents after descendants settle

* fix: wake yielded subagent parents after descendants settle

---------

Co-authored-by: Galin Iliev <Galin.Iliev@microsoft.com>
This commit is contained in:
Galin Iliev
2026-06-26 16:31:20 -07:00
committed by GitHub
parent 91726e9624
commit 6883c6c070
2 changed files with 58 additions and 1 deletions
+55
View File
@@ -3373,6 +3373,61 @@ describe("subagent registry seam flow", () => {
});
});
it("wakes a sessions_yield-paused parent when pending descendants settle", async () => {
mocks.loadSessionStore.mockReturnValue({
"agent:main:subagent:parent": {
sessionId: "sess-parent",
updatedAt: 1,
},
"agent:main:subagent:child": {
sessionId: "sess-child",
updatedAt: 1,
},
});
mod.addSubagentRunForTests({
runId: "run-yielded-parent",
childSessionKey: "agent:main:subagent:parent",
requesterSessionKey: "agent:main:main",
requesterDisplayKey: "main",
task: "yielded parent waiting on descendants",
cleanup: "keep",
createdAt: Date.parse("2026-06-26T02:17:00Z"),
startedAt: Date.parse("2026-06-26T02:18:00Z"),
endedAt: Date.parse("2026-06-26T02:19:00Z"),
pauseReason: "sessions_yield",
wakeOnDescendantSettle: true,
cleanupHandled: false,
cleanupCompletedAt: undefined,
});
mod.registerSubagentRun({
runId: "run-yielded-child-finished",
childSessionKey: "agent:main:subagent:child",
requesterSessionKey: "agent:main:subagent:parent",
requesterDisplayKey: "parent",
task: "descendant settles after yield",
cleanup: "keep",
});
await waitForFast(() => {
expect(mocks.runSubagentAnnounceFlow).toHaveBeenCalledTimes(2);
});
expectRecordFields(
getMockCallArg(mocks.runSubagentAnnounceFlow, 0, 0, "child finished announce"),
{ childRunId: "run-yielded-child-finished" },
"child finished announce params",
);
expectRecordFields(
getMockCallArg(mocks.runSubagentAnnounceFlow, 1, 0, "yielded parent wake announce"),
{
childRunId: "run-yielded-parent",
wakeOnDescendantSettle: true,
},
"yielded parent wake announce params",
);
});
it("loads runtime plugins before emitting killed subagent ended hooks", async () => {
const endedHookRunner = {
hasHooks: (hookName: string) => hookName === "subagent_ended",
+3 -1
View File
@@ -626,7 +626,9 @@ function resumeSubagentRun(runId: string) {
if (typeof entry.endedAt === "number" && isDeliverySuspended(entry)) {
return;
}
if (entry.pauseReason === "sessions_yield") {
// Yielded runs stay paused until explicitly steered, except orchestrators
// waiting on descendants: their settle retry must reach the wake path.
if (entry.pauseReason === "sessions_yield" && entry.wakeOnDescendantSettle !== true) {
return;
}
// Skip entries that have exhausted their retry budget or expired (#18264).