From 75c87747ef82c93d4be517cea15b8f6aa416361d Mon Sep 17 00:00:00 2001 From: Fluffy-Bunny-23 <193831219+Fluffy-Bunny-23@users.noreply.github.com> Date: Sat, 24 Oct 2026 13:44:31 -0700 Subject: [PATCH 01/12] fix(server): let native thread resume without handoff history When a resumed native conversation has no room for the recovery wrapper, start the requested turn against its existing transcript and leave the handoff pending for a later turn with more context. Implemented With: Codex via GPT-6 Luna on OpenAI through T3 Code --- .../src/orchestration-v2/ContextHandoffDelivery.ts | 8 ++++++++ .../src/orchestration-v2/ProviderTurnStartService.ts | 12 +++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/apps/server/src/orchestration-v2/ContextHandoffDelivery.ts b/apps/server/src/orchestration-v2/ContextHandoffDelivery.ts index 799defe965cb8..f40fee97a3702 100644 --- a/apps/server/src/orchestration-v2/ContextHandoffDelivery.ts +++ b/apps/server/src/orchestration-v2/ContextHandoffDelivery.ts @@ -23,6 +23,7 @@ export const deliverContextHandoffs = Effect.fn("orchestrationV2.deliverContextH readonly providerThread: OrchestrationV2ProviderThread; readonly budget: number | Effect.Effect; readonly deferInline?: boolean; + readonly deferIfOverBudget?: boolean; readonly alreadyDeliveredItemIds: ReadonlySet; readonly inject?: ( history: ProviderAdapter.ProviderAdapterV2HistoricalContext, @@ -76,6 +77,13 @@ export const deliverContextHandoffs = Effect.fn("orchestrationV2.deliverContextH }); if (historyCost(selected.messages, selected.context) > budget) { if (input.deferInline) return { context: "", delivered: Effect.void, unsent: Effect.void }; + // The native transcript already contains this thread's conversation. If + // its remaining context is too small for the recovery wrapper, let the + // provider handle the current request against that transcript instead of + // failing before the provider sees it. Leave the handoff pending so a + // later turn with more room can still recover the omitted app history. + if (input.deferIfOverBudget) + return { context: "", delivered: Effect.void, unsent: Effect.void }; return yield* new ContextHandoffBudgetError(); } const omittedItemIds = new Set(selected.omittedItemIds); diff --git a/apps/server/src/orchestration-v2/ProviderTurnStartService.ts b/apps/server/src/orchestration-v2/ProviderTurnStartService.ts index 4eb988ec43089..b248eb94dc179 100644 --- a/apps/server/src/orchestration-v2/ProviderTurnStartService.ts +++ b/apps/server/src/orchestration-v2/ProviderTurnStartService.ts @@ -1154,6 +1154,11 @@ export const layer: Layer.Layer< ...runningProviderThread, contextUsage: handoffUsage, }; + const nativeThreadHasTurns = + nativeInputRunIds.size > 0 || + (legacyInputRunIds.size > 0 && + sameNativeThread && + !acceptedAttempts.some((source) => source.nativeThreadId !== undefined)); const missedRuns = projection.runs.filter( (source) => source.ordinal < run.ordinal && @@ -1199,6 +1204,7 @@ export const layer: Layer.Layer< const delivery = yield* deliverContextHandoffs({ handoffs: [...effectiveHandoffs, ...retryHandoff], deferInline: compact, + deferIfOverBudget: sameNativeThread && nativeThreadHasTurns, providerThread: runningProviderThread, budget: Effect.gen(function* () { return handoffBudget({ @@ -1324,11 +1330,7 @@ export const layer: Layer.Layer< .filter((turn) => turn.providerThreadId === providerThread.id) .map((turn) => turn.ordinal), ) + 1, - nativeThreadHasTurns: - nativeInputRunIds.size > 0 || - (legacyInputRunIds.size > 0 && - sameNativeThread && - !acceptedAttempts.some((source) => source.nativeThreadId !== undefined)), + nativeThreadHasTurns, shouldStartProviderTurn: runControls.shouldStartProviderTurn, shouldFinalizeRun: runControls.shouldFinalizeRun, hasUnpairedRunInterruptRequest: runControls.hasUnpairedRunInterruptRequest, From a2975add1ad51079b5436dc046af9c5648489553 Mon Sep 17 00:00:00 2001 From: Fluffy-Bunny-23 <193831219+Fluffy-Bunny-23@users.noreply.github.com> Date: Sat, 24 Oct 2026 13:52:16 -0700 Subject: [PATCH 02/12] fix(server): keep deferred context handoffs available Only defer a handoff when its items are proven present in the same native transcript. Keep handoffs without a delivery record eligible after their originating run completes. Implemented With: Codex via GPT-6 Luna on OpenAI through T3 Code --- .../ProviderTurnStartService.ts | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/apps/server/src/orchestration-v2/ProviderTurnStartService.ts b/apps/server/src/orchestration-v2/ProviderTurnStartService.ts index b248eb94dc179..47c60e8ad7eff 100644 --- a/apps/server/src/orchestration-v2/ProviderTurnStartService.ts +++ b/apps/server/src/orchestration-v2/ProviderTurnStartService.ts @@ -5,6 +5,7 @@ import { latestProviderTurnForAttempt, type OrchestrationV2DomainEvent, type OrchestrationV2ExecutionNode, + type OrchestrationV2ContextHandoff, type OrchestrationV2ProviderThread, type OrchestrationV2Run, type OrchestrationV2RunAttempt, @@ -283,14 +284,7 @@ export const layer: Layer.Layer< source.id === handoff.targetRunId && (source.status === "failed" || source.status === "interrupted" || - (source.status === "completed" && - handoff.delivery === undefined && - projection.messages.some( - (message) => - message.id === source.userMessageId && - message.attachments.length === 0 && - message.text.trim().toLowerCase() === "/compact", - ))), + (source.status === "completed" && handoff.delivery === undefined)), ))), ); const nativeForkTransfer = projection.contextTransfers.find( @@ -1159,6 +1153,21 @@ export const layer: Layer.Layer< (legacyInputRunIds.size > 0 && sameNativeThread && !acceptedAttempts.some((source) => source.nativeThreadId !== undefined)); + // A turn in this native thread is not enough: another provider's handoff + // may contain work the native transcript has never seen. + const handoffIsAlreadyInNativeThread = (handoff: OrchestrationV2ContextHandoff) => + handoff.fromProviderThreadIds.includes(providerThread.id) && + handoff.history !== undefined && + handoff.history.messages.length > 0 && + handoff.history.omittedItems === 0 && + (handoff.history.omittedItemIds?.length ?? 0) === 0 && + handoff.history.messages.every( + (historical) => + historical.threadId === projection.thread.id && + historical.providerThreadId === providerThread.id && + historical.runId !== null && + nativeInputRunIds.has(historical.runId), + ); const missedRuns = projection.runs.filter( (source) => source.ordinal < run.ordinal && @@ -1201,10 +1210,16 @@ export const layer: Layer.Layer< createdAt: yield* DateTime.now, }), ]; + const handoffsToDeliver = [...effectiveHandoffs, ...retryHandoff]; const delivery = yield* deliverContextHandoffs({ - handoffs: [...effectiveHandoffs, ...retryHandoff], + handoffs: handoffsToDeliver, deferInline: compact, - deferIfOverBudget: sameNativeThread && nativeThreadHasTurns, + deferIfOverBudget: + sameNativeThread && + nativeThreadHasTurns && + missedItems.length === 0 && + handoffsToDeliver.length > 0 && + handoffsToDeliver.every(handoffIsAlreadyInNativeThread), providerThread: runningProviderThread, budget: Effect.gen(function* () { return handoffBudget({ From 7502fd96b1cf12f1e49e5d661c3e924d7c3a1ade Mon Sep 17 00:00:00 2001 From: Fluffy-Bunny-23 <193831219+Fluffy-Bunny-23@users.noreply.github.com> Date: Sat, 24 Oct 2026 13:54:56 -0700 Subject: [PATCH 03/12] fix(server): verify omitted handoff items before deferring Resolve capped handoff item IDs against source history and defer only when each item belongs to an accepted run on the current native thread. Implemented With: Codex via GPT-6 Luna on OpenAI through T3 Code --- .../ProviderTurnStartService.ts | 67 ++++++++++++++----- 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/apps/server/src/orchestration-v2/ProviderTurnStartService.ts b/apps/server/src/orchestration-v2/ProviderTurnStartService.ts index 47c60e8ad7eff..5b9d4c1bfa6ce 100644 --- a/apps/server/src/orchestration-v2/ProviderTurnStartService.ts +++ b/apps/server/src/orchestration-v2/ProviderTurnStartService.ts @@ -1109,11 +1109,16 @@ export const layer: Layer.Layer< ) .map((source) => source.id), ); + let turnStartHistory: ReadonlyArray | undefined; + const loadTurnStartHistory = Effect.gen(function* () { + turnStartHistory ??= yield* projectionStore.getTurnStartHistory(input.threadId); + return turnStartHistory; + }); // Legacy attempts lack native identity; exclude their explicitly recovered history, whose // attachments were not replayed into the replacement thread. const nativeContextEstimate = Effect.gen(function* () { return sameNativeThread - ? (yield* projectionStore.getTurnStartHistory(input.threadId)).reduce((sum, item) => { + ? (yield* loadTurnStartHistory).reduce((sum, item) => { if ( item.runId === run.id || (item.runId !== null && @@ -1155,19 +1160,36 @@ export const layer: Layer.Layer< !acceptedAttempts.some((source) => source.nativeThreadId !== undefined)); // A turn in this native thread is not enough: another provider's handoff // may contain work the native transcript has never seen. - const handoffIsAlreadyInNativeThread = (handoff: OrchestrationV2ContextHandoff) => - handoff.fromProviderThreadIds.includes(providerThread.id) && - handoff.history !== undefined && - handoff.history.messages.length > 0 && - handoff.history.omittedItems === 0 && - (handoff.history.omittedItemIds?.length ?? 0) === 0 && - handoff.history.messages.every( - (historical) => - historical.threadId === projection.thread.id && - historical.providerThreadId === providerThread.id && - historical.runId !== null && - nativeInputRunIds.has(historical.runId), + const itemIsInNativeThread = (item: { + readonly threadId: string; + readonly providerThreadId: string | null; + readonly runId: string | null; + }) => + item.threadId === projection.thread.id && + item.providerThreadId === providerThread.id && + item.runId !== null && + nativeInputRunIds.has(item.runId); + const handoffIsAlreadyInNativeThread = ( + handoff: OrchestrationV2ContextHandoff, + sourceItemsById: ReadonlyMap, + ) => { + const history = handoff.history; + if ( + !handoff.fromProviderThreadIds.includes(providerThread.id) || + history === undefined || + history.messages.length === 0 + ) + return false; + const omittedItemIds = history.omittedItemIds ?? []; + return ( + history.omittedItems === omittedItemIds.length && + history.messages.every(itemIsInNativeThread) && + omittedItemIds.every((itemId) => { + const item = sourceItemsById.get(itemId); + return item !== undefined && itemIsInNativeThread(item); + }) ); + }; const missedRuns = projection.runs.filter( (source) => source.ordinal < run.ordinal && @@ -1211,15 +1233,24 @@ export const layer: Layer.Layer< }), ]; const handoffsToDeliver = [...effectiveHandoffs, ...retryHandoff]; + const canDeferHandoff = + sameNativeThread && + nativeThreadHasTurns && + missedItems.length === 0 && + handoffsToDeliver.length > 0; + const sourceItemsById = + canDeferHandoff && + handoffsToDeliver.some((handoff) => (handoff.history?.omittedItems ?? 0) > 0) + ? new Map((yield* loadTurnStartHistory).map((item) => [item.id, item] as const)) + : new Map(); const delivery = yield* deliverContextHandoffs({ handoffs: handoffsToDeliver, deferInline: compact, deferIfOverBudget: - sameNativeThread && - nativeThreadHasTurns && - missedItems.length === 0 && - handoffsToDeliver.length > 0 && - handoffsToDeliver.every(handoffIsAlreadyInNativeThread), + canDeferHandoff && + handoffsToDeliver.every((handoff) => + handoffIsAlreadyInNativeThread(handoff, sourceItemsById), + ), providerThread: runningProviderThread, budget: Effect.gen(function* () { return handoffBudget({ From 31bfffb01f40509c9c177d9b115cb6369f612cf1 Mon Sep 17 00:00:00 2001 From: Fluffy-Bunny-23 <193831219+Fluffy-Bunny-23@users.noreply.github.com> Date: Sat, 24 Oct 2026 13:56:31 -0700 Subject: [PATCH 04/12] fix(server): allow verified fully capped handoffs to defer A handoff with no selected messages can still be proven to belong to the current native transcript when every omitted item ID resolves to an accepted native run. Implemented With: Codex via GPT-6 Luna on OpenAI through T3 Code --- .../src/orchestration-v2/ProviderTurnStartService.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/apps/server/src/orchestration-v2/ProviderTurnStartService.ts b/apps/server/src/orchestration-v2/ProviderTurnStartService.ts index 5b9d4c1bfa6ce..3a221becf3129 100644 --- a/apps/server/src/orchestration-v2/ProviderTurnStartService.ts +++ b/apps/server/src/orchestration-v2/ProviderTurnStartService.ts @@ -1174,14 +1174,11 @@ export const layer: Layer.Layer< sourceItemsById: ReadonlyMap, ) => { const history = handoff.history; - if ( - !handoff.fromProviderThreadIds.includes(providerThread.id) || - history === undefined || - history.messages.length === 0 - ) + if (!handoff.fromProviderThreadIds.includes(providerThread.id) || history === undefined) return false; const omittedItemIds = history.omittedItemIds ?? []; return ( + (history.messages.length > 0 || omittedItemIds.length > 0) && history.omittedItems === omittedItemIds.length && history.messages.every(itemIsInNativeThread) && omittedItemIds.every((itemId) => { From dfdd41d9ae8a29a8699be9e6ed627e9d6d001ba1 Mon Sep 17 00:00:00 2001 From: Fluffy-Bunny-23 <193831219+Fluffy-Bunny-23@users.noreply.github.com> Date: Sat, 24 Oct 2026 20:17:12 -0700 Subject: [PATCH 05/12] fix(server): resume long native threads without handoff budget Continuing a long native conversation could fail before the provider received the new request when its recovery handoff no longer fit. Defer only verified same-thread history, keep the handoff available for later delivery, and exclude rolled-back or app-generated items from the transcript check. Implemented With: Codex via GPT-6 Luna on OpenAI through T3 Code --- .../ContextHandoffBudget.test.ts | 28 +++++++ .../ProviderTurnStartService.test.ts | 30 ++++++++ .../ProviderTurnStartService.ts | 75 +++++++++++++++---- 3 files changed, 118 insertions(+), 15 deletions(-) diff --git a/apps/server/src/orchestration-v2/ContextHandoffBudget.test.ts b/apps/server/src/orchestration-v2/ContextHandoffBudget.test.ts index c4068702e3a57..a998ee4455119 100644 --- a/apps/server/src/orchestration-v2/ContextHandoffBudget.test.ts +++ b/apps/server/src/orchestration-v2/ContextHandoffBudget.test.ts @@ -640,4 +640,32 @@ describe("handoff delivery", () => { assert.equal(calls, 0); }), ); + + it.effect("leaves a verified native-thread handoff pending when its wrapper cannot fit", () => + Effect.gen(function* () { + let injections = 0; + let persists = 0; + const result = yield* deliverContextHandoffs({ + handoffs: [handoff], + providerThread, + budget: 0, + deferIfOverBudget: true, + alreadyDeliveredItemIds: new Set(), + inject: () => + Effect.sync(() => { + injections++; + return true; + }), + persist: () => + Effect.sync(() => { + persists++; + }), + }); + + yield* result.delivered; + assert.equal(result.context, ""); + assert.equal(injections, 0); + assert.equal(persists, 0); + }), + ); }); diff --git a/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts b/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts index 8fca16a101725..7bc82808bf900 100644 --- a/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts +++ b/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts @@ -49,6 +49,36 @@ import * as RuntimePolicy from "./RuntimePolicy.ts"; const isDomainEvent = Schema.is(OrchestrationV2DomainEvent); +it("excludes rolled-back runs from native-thread history", () => { + const providerThreadId = ProviderThreadId.make("provider_thread_native_history"); + const keptRunId = RunId.make("run_native_history_kept"); + const rolledBackRunId = RunId.make("run_native_history_rolled_back"); + const acceptedAttempts = [keptRunId, rolledBackRunId].map((runId, index) => ({ + id: RunAttemptId.make(`attempt_native_history_${index}`), + runId, + providerThreadId, + nativeThreadId: "native-thread", + })); + const nativeInputRunIds = ProviderTurnStart.nativeInputRunIdsForThread({ + acceptedAttempts, + runs: [ + { id: keptRunId, status: "completed" }, + { id: rolledBackRunId, status: "rolled_back" }, + ] as const, + providerThreadId, + nativeThreadId: "native-thread", + }); + + expect(nativeInputRunIds).toEqual(new Set([keptRunId])); +}); + +it("only treats provider transcript items as native history", () => { + expect(ProviderTurnStart.isNativeTranscriptHistoryKind("user_message")).toBe(true); + expect(ProviderTurnStart.isNativeTranscriptHistoryKind("command_execution")).toBe(true); + expect(ProviderTurnStart.isNativeTranscriptHistoryKind("run_interrupt_result")).toBe(false); + expect(ProviderTurnStart.isNativeTranscriptHistoryKind("file_change")).toBe(false); +}); + const managedWorktreesDir = "/tmp/provider-turn-start-worktrees"; // fs.realpath applies `..` and `.` before following symlinks. const normalize = (target: string) => { diff --git a/apps/server/src/orchestration-v2/ProviderTurnStartService.ts b/apps/server/src/orchestration-v2/ProviderTurnStartService.ts index 3a221becf3129..53c00d60180a1 100644 --- a/apps/server/src/orchestration-v2/ProviderTurnStartService.ts +++ b/apps/server/src/orchestration-v2/ProviderTurnStartService.ts @@ -70,6 +70,33 @@ export class ProviderTurnStartError extends Schema.TaggedError + >; + readonly runs: ReadonlyArray>; + readonly providerThreadId: OrchestrationV2ProviderThread["id"]; + readonly nativeThreadId: string | undefined; +}) => { + const rolledBackRunIds = new Set( + input.runs.filter((run) => run.status === "rolled_back").map((run) => run.id), + ); + return new Set( + input.acceptedAttempts + .filter( + (attempt) => + attempt.providerThreadId === input.providerThreadId && + attempt.nativeThreadId !== undefined && + attempt.nativeThreadId === input.nativeThreadId && + !rolledBackRunIds.has(attempt.runId), + ) + .map((attempt) => attempt.runId), + ); +}; + +export const isNativeTranscriptHistoryKind = (kind: string) => + kind === "user_message" || kind === "assistant_message" || kind === "command_execution"; + /** * Claude refuses these before it reads the prompt: replacing a process running background work, * or launch arguments whose appended system prompt cannot be read. @@ -1080,15 +1107,12 @@ export const layer: Layer.Layer< (source) => source.providerThreadId === providerThread.id && deliveredAttemptIds.has(source.id), ); - const nativeInputRunIds = new Set( - acceptedAttempts - .filter( - (source) => - source.nativeThreadId !== undefined && - source.nativeThreadId === runningProviderThread.nativeThreadRef?.nativeId, - ) - .map((source) => source.runId), - ); + const nativeInputRunIds = nativeInputRunIdsForThread({ + acceptedAttempts, + runs: projection.runs, + providerThreadId: providerThread.id, + nativeThreadId: runningProviderThread.nativeThreadRef?.nativeId, + }); const legacyInputRunIds = new Set( acceptedAttempts .filter((source) => source.nativeThreadId === undefined) @@ -1161,13 +1185,16 @@ export const layer: Layer.Layer< // A turn in this native thread is not enough: another provider's handoff // may contain work the native transcript has never seen. const itemIsInNativeThread = (item: { - readonly threadId: string; - readonly providerThreadId: string | null; - readonly runId: string | null; + readonly threadId: ThreadId; + readonly providerThreadId: OrchestrationV2TurnItem["providerThreadId"]; + readonly runId: OrchestrationV2TurnItem["runId"]; + readonly type?: string; + readonly kind?: string; }) => item.threadId === projection.thread.id && item.providerThreadId === providerThread.id && item.runId !== null && + isNativeTranscriptHistoryKind(item.kind ?? item.type ?? "") && nativeInputRunIds.has(item.runId); const handoffIsAlreadyInNativeThread = ( handoff: OrchestrationV2ContextHandoff, @@ -1235,10 +1262,28 @@ export const layer: Layer.Layer< nativeThreadHasTurns && missedItems.length === 0 && handoffsToDeliver.length > 0; + const omittedSourceRunIds = new Set( + handoffsToDeliver.flatMap((handoff) => + (handoff.history?.omittedItems ?? 0) === 0 + ? [] + : projection.runs + .filter( + (source) => + source.providerThreadId !== null && + handoff.fromProviderThreadIds.includes(source.providerThreadId) && + source.ordinal >= handoff.coveredRunOrdinals.from && + source.ordinal <= handoff.coveredRunOrdinals.to, + ) + .map((source) => source.id), + ), + ); const sourceItemsById = - canDeferHandoff && - handoffsToDeliver.some((handoff) => (handoff.history?.omittedItems ?? 0) > 0) - ? new Map((yield* loadTurnStartHistory).map((item) => [item.id, item] as const)) + canDeferHandoff && omittedSourceRunIds.size > 0 + ? new Map( + (yield* projectionStore.getTurnStartHistory(input.threadId, [ + ...omittedSourceRunIds, + ])).map((item) => [item.id, item] as const), + ) : new Map(); const delivery = yield* deliverContextHandoffs({ handoffs: handoffsToDeliver, From 0058b4eef3988d517bc0cc858b32796022332140 Mon Sep 17 00:00:00 2001 From: Fluffy-Bunny-23 <193831219+Fluffy-Bunny-23@users.noreply.github.com> Date: Sat, 24 Oct 2026 20:41:08 -0700 Subject: [PATCH 06/12] fix(server): trim handoff deferral comment Keep the native-transcript constraint clear while satisfying the comment length lint rule. Implemented With: Codex via GPT-6 Luna on OpenAI through T3 Code --- apps/server/src/orchestration-v2/ContextHandoffDelivery.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/apps/server/src/orchestration-v2/ContextHandoffDelivery.ts b/apps/server/src/orchestration-v2/ContextHandoffDelivery.ts index 499d78e2c5b52..b0d328583d0f0 100644 --- a/apps/server/src/orchestration-v2/ContextHandoffDelivery.ts +++ b/apps/server/src/orchestration-v2/ContextHandoffDelivery.ts @@ -73,11 +73,8 @@ export const deliverContextHandoffs = Effect.fn("orchestrationV2.deliverContextH }); if (historyCost(selected.messages, selected.context) > budget) { if (input.deferInline) return { context: "", delivered: Effect.void, unsent: Effect.void }; - // The native transcript already contains this thread's conversation. If - // its remaining context is too small for the recovery wrapper, let the - // provider handle the current request against that transcript instead of - // failing before the provider sees it. Leave the handoff pending so a - // later turn with more room can still recover the omitted app history. + // The target transcript already has this history, so an oversized wrapper + // must not block the turn. Keep the handoff pending for later delivery. if (input.deferIfOverBudget) return { context: "", delivered: Effect.void, unsent: Effect.void }; return yield* new ContextHandoffBudgetError(); From ef58cd9d8acd2a30c21d8c56fcfcf5a0cdd1bd68 Mon Sep 17 00:00:00 2001 From: Fluffy-Bunny-23 <193831219+Fluffy-Bunny-23@users.noreply.github.com> Date: Sat, 24 Oct 2026 20:51:21 -0700 Subject: [PATCH 07/12] fix(server): exclude mixed native-thread runs from handoff history Runs accepted on multiple native threads cannot be assigned to a single transcript using only the run ID. Exclude those runs from handoff history membership checks. Add focused coverage for the mixed-thread case. Implemented With: Codex via GPT-6 Luna on OpenAI through T3 Code --- .../ProviderTurnStartService.test.ts | 15 ++++++++++ .../ProviderTurnStartService.ts | 28 ++++++++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts b/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts index 3fb1864b019c8..87de61289147d 100644 --- a/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts +++ b/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts @@ -72,6 +72,21 @@ it("excludes rolled-back runs from native-thread history", () => { expect(nativeInputRunIds).toEqual(new Set([keptRunId])); }); +it("does not treat runs accepted on multiple native threads as native history", () => { + const providerThreadId = ProviderThreadId.make("provider_thread_mixed_native_history"); + const mixedRunId = RunId.make("run_mixed_native_history"); + const singleThreadRunId = RunId.make("run_single_native_history"); + const attempts = [ + { runId: mixedRunId, providerThreadId, nativeThreadId: "current-native-thread" }, + { runId: mixedRunId, providerThreadId, nativeThreadId: "older-native-thread" }, + { runId: singleThreadRunId, providerThreadId, nativeThreadId: "current-native-thread" }, + ]; + + expect(ProviderTurnStart.mixedNativeThreadRunIdsForThread(attempts, providerThreadId)).toEqual( + new Set([mixedRunId]), + ); +}); + it("only treats provider transcript items as native history", () => { expect(ProviderTurnStart.isNativeTranscriptHistoryKind("user_message")).toBe(true); expect(ProviderTurnStart.isNativeTranscriptHistoryKind("command_execution")).toBe(true); diff --git a/apps/server/src/orchestration-v2/ProviderTurnStartService.ts b/apps/server/src/orchestration-v2/ProviderTurnStartService.ts index b463dff2518ea..bd248debf0660 100644 --- a/apps/server/src/orchestration-v2/ProviderTurnStartService.ts +++ b/apps/server/src/orchestration-v2/ProviderTurnStartService.ts @@ -94,6 +94,27 @@ export const nativeInputRunIdsForThread = (input: { ); }; +export const mixedNativeThreadRunIdsForThread = ( + acceptedAttempts: ReadonlyArray< + Pick + >, + providerThreadId: OrchestrationV2ProviderThread["id"], +) => { + const nativeThreadIdsByRunId = new Map>(); + for (const attempt of acceptedAttempts) { + if (attempt.providerThreadId !== providerThreadId || attempt.nativeThreadId === undefined) + continue; + const nativeThreadIds = nativeThreadIdsByRunId.get(attempt.runId) ?? new Set(); + nativeThreadIds.add(attempt.nativeThreadId); + nativeThreadIdsByRunId.set(attempt.runId, nativeThreadIds); + } + return new Set( + [...nativeThreadIdsByRunId] + .filter(([, nativeThreadIds]) => nativeThreadIds.size > 1) + .map(([runId]) => runId), + ); +}; + export const isNativeTranscriptHistoryKind = (kind: string) => kind === "user_message" || kind === "assistant_message" || kind === "command_execution"; @@ -1105,6 +1126,10 @@ export const layer: Layer.Layer< providerThreadId: providerThread.id, nativeThreadId: runningProviderThread.nativeThreadRef?.nativeId, }); + const mixedNativeThreadRunIds = mixedNativeThreadRunIdsForThread( + acceptedAttempts, + providerThread.id, + ); const legacyInputRunIds = new Set( acceptedAttempts .filter((source) => source.nativeThreadId === undefined) @@ -1181,7 +1206,8 @@ export const layer: Layer.Layer< item.providerThreadId === providerThread.id && item.runId !== null && isNativeTranscriptHistoryKind(item.kind ?? item.type ?? "") && - nativeInputRunIds.has(item.runId); + nativeInputRunIds.has(item.runId) && + !mixedNativeThreadRunIds.has(item.runId); const handoffIsAlreadyInNativeThread = ( handoff: OrchestrationV2ContextHandoff, sourceItemsById: ReadonlyMap, From 70c2749922f583d6c37076ea34a9e0b364e2296e Mon Sep 17 00:00:00 2001 From: Fluffy-Bunny-23 <193831219+Fluffy-Bunny-23@users.noreply.github.com> Date: Sat, 24 Oct 2026 21:20:29 -0700 Subject: [PATCH 08/12] fix(server): reject unknown native handoff history A run with an accepted attempt whose native destination is unknown cannot be verified from run-level history. Reject it alongside mixed native-thread runs, normalize nullable identity values, and cover the over-budget service path. Implemented With: Codex via GPT-6 Luna on OpenAI through T3 Code --- .../ProviderTurnStartService.test.ts | 128 +++++++++++++++++- .../ProviderTurnStartService.ts | 15 +- 2 files changed, 138 insertions(+), 5 deletions(-) diff --git a/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts b/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts index 87de61289147d..3f7a6c6fc5048 100644 --- a/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts +++ b/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts @@ -3,6 +3,7 @@ import { expect, it, vi } from "vite-plus/test"; import { it as effectIt } from "@effect/vitest"; import { CheckpointScopeId, + ContextHandoffId, MessageId, NodeId, ProviderSessionId, @@ -14,6 +15,7 @@ import { RunAttemptId, RunId, ThreadId, + TurnItemId, ProjectId, type OrchestrationV2ThreadProjection, OrchestrationV2DomainEvent, @@ -75,15 +77,18 @@ it("excludes rolled-back runs from native-thread history", () => { it("does not treat runs accepted on multiple native threads as native history", () => { const providerThreadId = ProviderThreadId.make("provider_thread_mixed_native_history"); const mixedRunId = RunId.make("run_mixed_native_history"); + const unknownRunId = RunId.make("run_unknown_native_history"); const singleThreadRunId = RunId.make("run_single_native_history"); const attempts = [ { runId: mixedRunId, providerThreadId, nativeThreadId: "current-native-thread" }, { runId: mixedRunId, providerThreadId, nativeThreadId: "older-native-thread" }, + { runId: unknownRunId, providerThreadId, nativeThreadId: undefined }, + { runId: unknownRunId, providerThreadId, nativeThreadId: "current-native-thread" }, { runId: singleThreadRunId, providerThreadId, nativeThreadId: "current-native-thread" }, ]; expect(ProviderTurnStart.mixedNativeThreadRunIdsForThread(attempts, providerThreadId)).toEqual( - new Set([mixedRunId]), + new Set([mixedRunId, unknownRunId]), ); }); @@ -839,6 +844,127 @@ effectIt.effect("recovers an abandoned failed turn before continuing its native }), ); +effectIt.effect( + "does not defer an over-budget handoff for a run accepted on mixed native threads", + () => + Effect.gen(function* () { + const harness = makeLocalCommandHarness({ + text: "Continue", + previousTurn: { runStatus: "failed", turnStatus: "completed" }, + }); + const projection = harness.projection(); + const providerThreadId = projection.providerThreads[0]!.id; + const currentNativeThreadId = "existing-native-conversation"; + const previousRunId = RunId.make("previous-failed-run"); + const currentRunId = projection.runs.at(-1)!.id; + const olderAttemptId = RunAttemptId.make("older-native-thread-attempt"); + const currentAttemptId = RunAttemptId.make("current-native-thread-attempt"); + const historicalItemId = TurnItemId.make("mixed-native-thread-history-item"); + const previousTurn = projection.providerTurns[0]!; + harness.setProjection({ + ...projection, + providerThreads: projection.providerThreads.map((thread) => + thread.id === providerThreadId + ? { + ...thread, + contextUsage: { + usedTokens: 39_999, + maxTokens: 40_000, + autoCompactThreshold: 40_000, + }, + } + : thread, + ), + attempts: [ + ...projection.attempts, + { + ...projection.attempts[0]!, + id: olderAttemptId, + runId: previousRunId, + providerThreadId, + nativeThreadId: "older-native-conversation", + }, + { + ...projection.attempts[0]!, + id: currentAttemptId, + runId: previousRunId, + providerThreadId, + nativeThreadId: currentNativeThreadId, + }, + ], + providerTurns: [ + previousTurn, + { + ...previousTurn, + id: ProviderTurnId.make("older-native-turn"), + runAttemptId: olderAttemptId, + }, + { + ...previousTurn, + id: ProviderTurnId.make("current-native-turn"), + runAttemptId: currentAttemptId, + }, + ], + contextHandoffs: [ + { + id: ContextHandoffId.make("mixed-native-thread-handoff"), + threadId: projection.thread.id, + targetRunId: currentRunId, + fromProviderThreadIds: [providerThreadId], + toProviderThreadId: providerThreadId, + coveredRunOrdinals: { from: 1, to: 1 }, + strategy: "delta_since_target_last_seen", + status: "ready", + summaryMessageId: null, + summaryText: "", + history: { + messages: [ + { + role: "user", + text: "Earlier request", + threadId: projection.thread.id, + runId: previousRunId, + itemId: historicalItemId, + providerThreadId, + status: "completed", + kind: "user_message", + }, + ], + coverage: "Earlier request", + omittedItems: 0, + }, + createdByProviderInstanceId: null, + createdAt: DateTime.makeUnsafe("2026-09-04T12:00:00Z"), + updatedAt: DateTime.makeUnsafe("2026-09-04T12:00:00Z"), + }, + ], + }); + + yield* harness.start; + + const startInput = harness.startRootRun.mock.calls[0]?.[0]; + expect(startInput).toBeDefined(); + const delivery = yield* Effect.result( + startInput!.session.startTurn({ + appThread: startInput!.appThread, + threadId: startInput!.run.threadId, + runId: startInput!.run.id, + runOrdinal: startInput!.run.ordinal, + providerTurnOrdinal: startInput!.providerTurnOrdinal, + nativeThreadHasTurns: startInput!.nativeThreadHasTurns, + attemptId: startInput!.attemptId, + rootNodeId: startInput!.rootNode.id, + providerThread: startInput!.providerThread, + message: startInput!.message, + modelSelection: startInput!.modelSelection, + runtimePolicy: startInput!.runtimePolicy, + }), + ); + + expect(delivery._tag).toBe("Failure"); + }), +); + effectIt.effect.each([ { runStatus: "failed", turnStatus: "completed" }, { runStatus: "completed", turnStatus: "completed" }, diff --git a/apps/server/src/orchestration-v2/ProviderTurnStartService.ts b/apps/server/src/orchestration-v2/ProviderTurnStartService.ts index bd248debf0660..294e79793cbe1 100644 --- a/apps/server/src/orchestration-v2/ProviderTurnStartService.ts +++ b/apps/server/src/orchestration-v2/ProviderTurnStartService.ts @@ -101,16 +101,23 @@ export const mixedNativeThreadRunIdsForThread = ( providerThreadId: OrchestrationV2ProviderThread["id"], ) => { const nativeThreadIdsByRunId = new Map>(); + const runsWithUnknownNativeThread = new Set(); for (const attempt of acceptedAttempts) { - if (attempt.providerThreadId !== providerThreadId || attempt.nativeThreadId === undefined) + if (attempt.providerThreadId !== providerThreadId) continue; + if (attempt.nativeThreadId === undefined) { + runsWithUnknownNativeThread.add(attempt.runId); continue; + } const nativeThreadIds = nativeThreadIdsByRunId.get(attempt.runId) ?? new Set(); nativeThreadIds.add(attempt.nativeThreadId); nativeThreadIdsByRunId.set(attempt.runId, nativeThreadIds); } return new Set( [...nativeThreadIdsByRunId] - .filter(([, nativeThreadIds]) => nativeThreadIds.size > 1) + .filter( + ([runId, nativeThreadIds]) => + nativeThreadIds.size > 1 || runsWithUnknownNativeThread.has(runId), + ) .map(([runId]) => runId), ); }; @@ -1124,7 +1131,7 @@ export const layer: Layer.Layer< acceptedAttempts, runs: projection.runs, providerThreadId: providerThread.id, - nativeThreadId: runningProviderThread.nativeThreadRef?.nativeId, + nativeThreadId: runningProviderThread.nativeThreadRef?.nativeId ?? undefined, }); const mixedNativeThreadRunIds = mixedNativeThreadRunIdsForThread( acceptedAttempts, @@ -1197,7 +1204,7 @@ export const layer: Layer.Layer< // may contain work the native transcript has never seen. const itemIsInNativeThread = (item: { readonly threadId: ThreadId; - readonly providerThreadId: OrchestrationV2TurnItem["providerThreadId"]; + readonly providerThreadId?: OrchestrationV2TurnItem["providerThreadId"]; readonly runId: OrchestrationV2TurnItem["runId"]; readonly type?: string; readonly kind?: string; From 4134e657c8af52b89b678b38399eb2434bff37da Mon Sep 17 00:00:00 2001 From: Fluffy-Bunny-23 <193831219+Fluffy-Bunny-23@users.noreply.github.com> Date: Sat, 24 Oct 2026 21:27:40 -0700 Subject: [PATCH 09/12] fix(server): omit absent native thread history flag The provider turn input requires nativeThreadHasTurns to be omitted when its value is unknown under exact optional property types. Implemented With: Codex via GPT-6 Luna on OpenAI through T3 Code --- .../src/orchestration-v2/ProviderTurnStartService.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts b/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts index 3f7a6c6fc5048..ffb755f10131a 100644 --- a/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts +++ b/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts @@ -951,7 +951,9 @@ effectIt.effect( runId: startInput!.run.id, runOrdinal: startInput!.run.ordinal, providerTurnOrdinal: startInput!.providerTurnOrdinal, - nativeThreadHasTurns: startInput!.nativeThreadHasTurns, + ...(startInput!.nativeThreadHasTurns === undefined + ? {} + : { nativeThreadHasTurns: startInput!.nativeThreadHasTurns }), attemptId: startInput!.attemptId, rootNodeId: startInput!.rootNode.id, providerThread: startInput!.providerThread, From 1c573208def158c5a3ef458ba527aa226e42d272 Mon Sep 17 00:00:00 2001 From: Fluffy-Bunny-23 <193831219+Fluffy-Bunny-23@users.noreply.github.com> Date: Sat, 24 Oct 2026 21:30:58 -0700 Subject: [PATCH 10/12] fix(server): pass definite native history flag in test The provider start-turn input requires nativeThreadHasTurns. Default the test call to false when the captured startup value is undefined. Implemented With: Codex via GPT-6 Luna on OpenAI through T3 Code --- .../src/orchestration-v2/ProviderTurnStartService.test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts b/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts index ffb755f10131a..d5826521baf9e 100644 --- a/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts +++ b/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts @@ -951,9 +951,7 @@ effectIt.effect( runId: startInput!.run.id, runOrdinal: startInput!.run.ordinal, providerTurnOrdinal: startInput!.providerTurnOrdinal, - ...(startInput!.nativeThreadHasTurns === undefined - ? {} - : { nativeThreadHasTurns: startInput!.nativeThreadHasTurns }), + nativeThreadHasTurns: startInput!.nativeThreadHasTurns ?? false, attemptId: startInput!.attemptId, rootNodeId: startInput!.rootNode.id, providerThread: startInput!.providerThread, From 5ecab7c274e962883d0e03e32cebbc4d9e790a45 Mon Sep 17 00:00:00 2001 From: Fluffy-Bunny-23 <193831219+Fluffy-Bunny-23@users.noreply.github.com> Date: Tue, 27 Oct 2026 15:31:31 -0700 Subject: [PATCH 11/12] fix(server): preserve mixed-thread handoff recovery Implemented With: Codex via GPT-6 Luna on OpenAI through T3 Code --- .../ProviderTurnStartService.test.ts | 56 +++++++++++++++---- .../ProviderTurnStartService.ts | 3 +- 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts b/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts index 2891d1aa95278..ef92392bf39f2 100644 --- a/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts +++ b/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts @@ -324,6 +324,7 @@ function makeLocalCommandHarness(input: { readonly openFailure?: unknown; readonly ensureThreadFailure?: unknown; readonly historyReadFailureAfterFallback?: unknown; + readonly allowRecoveryHandoff?: boolean; readonly interruptOpen?: boolean; readonly interruptRunBeforeOpenFailure?: boolean; readonly writeFailure?: unknown; @@ -508,6 +509,7 @@ function makeLocalCommandHarness(input: { checkpoints: [], updatedAt: now, }; + let turnStartHistory: OrchestrationV2ThreadProjection["turnItems"] | undefined; if ("historyReadFailureAfterFallback" in input) { const nativeThreadRef = { driver: providerThread.driver, @@ -615,6 +617,7 @@ function makeLocalCommandHarness(input: { ), ), ); + const startTurn = vi.fn(() => Effect.void); const resumeFallbackSession = { driver: providerThread.driver, resumeThread: () => @@ -665,6 +668,7 @@ function makeLocalCommandHarness(input: { }, ensureThread: () => Effect.succeed(providerThread), resumeThread: () => Effect.succeed(projection.providerThreads[0]), + startTurn, } as never) : Effect.die("A local command must not open a native session."), ); @@ -744,9 +748,31 @@ function makeLocalCommandHarness(input: { Layer.provide( Layer.mergeAll( Layer.mock(ContextHandoffService.ContextHandoffServiceV2)({ - prepareProviderHandoff: () => Effect.die("history read must fail first"), + prepareProviderHandoff: (handoff) => + input.allowRecoveryHandoff + ? Effect.succeed({ + ...handoff, + id: ContextHandoffId.make("provider-turn-start-recovery-handoff"), + status: "ready", + summaryMessageId: null, + summaryText: "", + history: { + messages: [], + coverage: "", + omittedItems: 0, + omittedItemIds: [], + }, + createdByProviderInstanceId: null, + updatedAt: handoff.createdAt, + } as never) + : Effect.die("history read must fail first"), + }), + Layer.mock(EventSink.EventSinkV2)({ + writeIfRunCurrent, + ...(input.allowRecoveryHandoff + ? { write: () => Effect.succeed({ storedEvents: [] } as never) } + : {}), }), - Layer.mock(EventSink.EventSinkV2)({ writeIfRunCurrent }), IdAllocator.layer, layerWorktreeLocation(input.worktreesDir), input.fileSystem === undefined @@ -778,12 +804,14 @@ function makeLocalCommandHarness(input: { ), }), getTurnStartHistory: () => - Effect.fail( - new ProjectionStore.ProjectionStoreReadError({ - threadId, - cause: input.historyReadFailureAfterFallback, - }), - ), + turnStartHistory === undefined + ? Effect.fail( + new ProjectionStore.ProjectionStoreReadError({ + threadId, + cause: input.historyReadFailureAfterFallback, + }), + ) + : Effect.succeed(turnStartHistory), }), Layer.mock(ProviderSessionManager.ProviderSessionManagerV2)({ open, @@ -815,6 +843,7 @@ function makeLocalCommandHarness(input: { resolveRuntimePolicy, createWorktree, open, + startTurn, writeIfRunCurrent, startRootRun, interruptTurn, @@ -824,6 +853,9 @@ function makeLocalCommandHarness(input: { newInstanceId, attemptId, projection: () => projection, + setTurnStartHistory: (items: OrchestrationV2ThreadProjection["turnItems"]) => { + turnStartHistory = items; + }, start: Effect.gen(function* () { yield* (yield* ProviderTurnStart.ProviderTurnStartServiceV2).start({ threadId, runId }); }).pipe(Effect.provide(layer)), @@ -1021,13 +1053,15 @@ effectIt.effect("recovers an abandoned failed turn before continuing its native ); effectIt.effect( - "does not defer an over-budget handoff for a run accepted on mixed native threads", + "recovers an over-budget handoff from mixed native threads before starting the turn", () => Effect.gen(function* () { const harness = makeLocalCommandHarness({ text: "Continue", previousTurn: { runStatus: "failed", turnStatus: "completed" }, + allowRecoveryHandoff: true, }); + harness.setTurnStartHistory([]); const projection = harness.projection(); const providerThreadId = projection.providerThreads[0]!.id; const currentNativeThreadId = "existing-native-conversation"; @@ -1137,7 +1171,9 @@ effectIt.effect( }), ); - expect(delivery._tag).toBe("Failure"); + expect(delivery._tag).toBe("Success"); + expect(harness.startTurn).toHaveBeenCalledOnce(); + expect(harness.startTurn.mock.calls[0]?.[0].message.text).toContain("Earlier request"); }), ); diff --git a/apps/server/src/orchestration-v2/ProviderTurnStartService.ts b/apps/server/src/orchestration-v2/ProviderTurnStartService.ts index 7c856fe1a4888..d62af3e946df1 100644 --- a/apps/server/src/orchestration-v2/ProviderTurnStartService.ts +++ b/apps/server/src/orchestration-v2/ProviderTurnStartService.ts @@ -1304,9 +1304,8 @@ export const layer: Layer.Layer< sameNativeThread, handoffUsage, deliveredItemIds, - acceptedAttempts, nativeInputRunIds, - legacyInputRunIds, + nativeThreadHasTurns, budget, } = handoffContext; const retryHandoff = resumedRetryHandoff ?? (yield* prepareRetryHandoff(handoffContext)); From 76e4e342740a48f5f16fd1cab573dab3782ba732 Mon Sep 17 00:00:00 2001 From: Fluffy-Bunny-23 <193831219+Fluffy-Bunny-23@users.noreply.github.com> Date: Tue, 27 Oct 2026 15:38:27 -0700 Subject: [PATCH 12/12] fix(server): resolve provider handoff typecheck errors Implemented With: Codex via GPT-6 Luna on OpenAI through T3 Code --- .../orchestration-v2/ProviderTurnStartService.test.ts | 9 +++++++-- .../src/orchestration-v2/ProviderTurnStartService.ts | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts b/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts index ef92392bf39f2..3c3a4beb44e7e 100644 --- a/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts +++ b/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts @@ -617,7 +617,11 @@ function makeLocalCommandHarness(input: { ), ), ); - const startTurn = vi.fn(() => Effect.void); + const startedTurns: Array = []; + const startTurn = vi.fn((turnInput: ProviderAdapter.ProviderAdapterV2TurnInput) => { + startedTurns.push(turnInput); + return Effect.void; + }); const resumeFallbackSession = { driver: providerThread.driver, resumeThread: () => @@ -844,6 +848,7 @@ function makeLocalCommandHarness(input: { createWorktree, open, startTurn, + startedTurns, writeIfRunCurrent, startRootRun, interruptTurn, @@ -1173,7 +1178,7 @@ effectIt.effect( expect(delivery._tag).toBe("Success"); expect(harness.startTurn).toHaveBeenCalledOnce(); - expect(harness.startTurn.mock.calls[0]?.[0].message.text).toContain("Earlier request"); + expect(harness.startedTurns[0]?.message.text).toContain("Earlier request"); }), ); diff --git a/apps/server/src/orchestration-v2/ProviderTurnStartService.ts b/apps/server/src/orchestration-v2/ProviderTurnStartService.ts index d62af3e946df1..1931788a1a708 100644 --- a/apps/server/src/orchestration-v2/ProviderTurnStartService.ts +++ b/apps/server/src/orchestration-v2/ProviderTurnStartService.ts @@ -1305,6 +1305,7 @@ export const layer: Layer.Layer< handoffUsage, deliveredItemIds, nativeInputRunIds, + mixedNativeThreadRunIds, nativeThreadHasTurns, budget, } = handoffContext;