Skip to content

Commit f80c651

Browse files
Bynthamclaude
andcommitted
fix(server): delegated tasks no longer wait on background work the child left running
Co-Authored-By: Claude Opus 5.5 <[email protected]>
1 parent 305a90d commit f80c651

4 files changed

Lines changed: 32 additions & 15 deletions

File tree

‎apps/server/src/orchestration-v2/DelegatedCompletionDelivery.test.ts‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3134,9 +3134,12 @@ it.layer(layerTest)("delegated tasks across a server restart", (it) => {
31343134
occurredAt: now,
31353135
payload: {
31363136
...providerThread,
3137-
pendingBackgroundTasks: open
3138-
? [{ taskId: "sleep-900", kind: "command" as const }]
3139-
: [],
3137+
pendingBackgroundTasks: [
3138+
...(open
3139+
? [{ taskId: "sleep-900", kind: "command" as const, awaited: true as const }]
3140+
: []),
3141+
{ taskId: "log-watcher", kind: "monitor" as const },
3142+
],
31403143
},
31413144
}
31423145
: {

‎apps/server/src/orchestration-v2/Orchestrator.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ import {
7474
import { modelSelectionsEqual } from "@t3tools/shared/model";
7575
import { recordThreadOpen } from "@t3tools/shared/paletteSearch";
7676
import {
77+
backgroundWorkHoldsCompletion,
7778
derivePendingBackgroundWork,
7879
pendingBackgroundTurnItems,
7980
} from "@t3tools/shared/orchestrationV2PendingBackgroundWork";
@@ -13342,14 +13343,14 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
1334213343

1334313344
// A child whose run ends while its background tasks or its own subagents still run has no
1334413345
// result yet. When that work settles there is no run update to finalize it, so these events
13345-
// do it. A background roster counts once it empties after it was seen non-empty.
13346+
// do it. A background roster counts once it stops holding after it was seen holding.
1334613347
const providerThreadsWithBackgroundWork = new Set<string>();
1334713348
const settlesChildWork = (stored: OrchestrationV2StoredEvent) => {
1334813349
const { event } = stored;
1334913350
if (event.type === "subagent.updated")
1335013351
return !isOrchestrationV2WorkActive(event.payload.status);
1335113352
if (event.type !== "provider-thread.updated") return false;
13352-
if ((event.payload.pendingBackgroundTasks?.length ?? 0) > 0) {
13353+
if (backgroundWorkHoldsCompletion(event.payload.pendingBackgroundTasks ?? [])) {
1335313354
providerThreadsWithBackgroundWork.add(event.payload.id);
1335413355
return false;
1335513356
}

‎apps/server/src/orchestration-v2/SubagentProjection.test.ts‎

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -262,15 +262,25 @@ it("waits for nested work and retains the report across monitor acknowledgements
262262
"result_available",
263263
);
264264
}
265-
assert.equal(
266-
delegatedTaskProgress({
267-
...projection,
268-
providerThreads: [
269-
{ pendingBackgroundTasks: [{ taskId: "background-audit", kind: "command" }] },
270-
],
271-
}).state,
272-
"waiting_for_children",
273-
);
265+
for (const [awaited, state] of [
266+
[true, "waiting_for_children"],
267+
[false, "result_available"],
268+
] as const) {
269+
assert.equal(
270+
delegatedTaskProgress({
271+
...projection,
272+
providerThreads: [
273+
{
274+
pendingBackgroundTasks: [
275+
{ taskId: "background-audit", kind: "command", ...(awaited ? { awaited } : {}) },
276+
{ taskId: "log-watcher", kind: "monitor" },
277+
],
278+
},
279+
],
280+
}).state,
281+
state,
282+
);
283+
}
274284
const pending = {
275285
...run,
276286
id: RunId.make("followup"),

‎packages/provider-core/src/server/subagentProjection.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import type {
1818
ThreadId,
1919
TurnItemId,
2020
} from "@t3tools/contracts";
21+
import { backgroundWorkHoldsCompletion } from "@t3tools/shared/orchestrationV2PendingBackgroundWork";
2122
import { runRanAfter } from "@t3tools/shared/orchestrationV2ThreadError";
2223
import * as DateTime from "effect/DateTime";
2324
import * as Effect from "effect/Effect";
@@ -256,7 +257,9 @@ export function delegatedTaskProgress(projection: {
256257
(task.completionDelivery?.state === "pending" ||
257258
task.completionDelivery?.state === "claimed")),
258259
) ||
259-
projection.providerThreads.some((thread) => (thread.pendingBackgroundTasks?.length ?? 0) > 0);
260+
projection.providerThreads.some((thread) =>
261+
backgroundWorkHoldsCompletion(thread.pendingBackgroundTasks ?? []),
262+
);
260263
const resultRun = workRuns
261264
.filter((run) => terminal(run.status) && (run.startedAt !== null || run.ordinal === 1))
262265
.toSorted((a, b) => (runRanAfter(a, b) ? -1 : runRanAfter(b, a) ? 1 : 0))[0];

0 commit comments

Comments
 (0)