From f2e6515b61d4f5f17dca6337c45cee1c2f9d696e Mon Sep 17 00:00:00 2001 From: Alb11747 Date: Wed, 14 Oct 2026 08:05:05 -0400 Subject: [PATCH 1/6] fix(server): a handoff too large for the context window no longer wedges the thread When a context handoff could not fit the target's context budget, turn start failed with "Insufficient context allowance" on every turn, so the thread was unusable until the user compacted it manually. An oversized handoff is now marked deferred and the turn proceeds without it. Later turns on the same provider thread retry it until it fits, and the flag clears once it is sent. The flag is an additive optional field, so no migration is needed. Fixes #16147 Co-Authored-By: GPT-6.1-Sol at high effort Co-Authored-By: Claude Opus 5.5 Co-Authored-By: GPT-6.1-Sol at high effort, implementation subagent for handoff deferral Co-Authored-By: GPT-6.1-Sol at xhigh effort, review subagent for PR 19414 maintenance rebase --- .../ContextHandoffBudget.test.ts | 92 +++++++++-- .../ContextHandoffDelivery.ts | 15 +- .../ProviderTurnStartService.test.ts | 148 +++++++++++++++++- .../ProviderTurnStartService.ts | 1 + packages/contracts/src/orchestrationV2.ts | 2 + .../provider-core/src/server/failure.test.ts | 5 +- packages/provider-core/src/server/failure.ts | 12 -- 7 files changed, 237 insertions(+), 38 deletions(-) diff --git a/apps/server/src/orchestration-v2/ContextHandoffBudget.test.ts b/apps/server/src/orchestration-v2/ContextHandoffBudget.test.ts index c2024926e378..a15ca474f6e4 100644 --- a/apps/server/src/orchestration-v2/ContextHandoffBudget.test.ts +++ b/apps/server/src/orchestration-v2/ContextHandoffBudget.test.ts @@ -10,6 +10,7 @@ import { ThreadId, TurnItemId, OrchestrationV2ContextHandoff, + OrchestrationV2ContextHandoffJson, type OrchestrationV2HistoricalMessage, type OrchestrationV2ProviderThread, } from "@t3tools/contracts"; @@ -29,6 +30,9 @@ import { deliverContextHandoffs } from "./ContextHandoffDelivery.ts"; const encodeJson = Schema.encodeEffect(Schema.fromJsonString(Schema.Unknown)); const decodeHandoff = Schema.decodeUnknownSync(OrchestrationV2ContextHandoff); +const handoffJson = Schema.fromJsonString(OrchestrationV2ContextHandoffJson); +const encodeHandoff = Schema.encodeSync(handoffJson); +const decodePersistedHandoff = Schema.decodeSync(handoffJson); const now = DateTime.makeUnsafe("2026-09-17T00:00:00Z"); const threadId = ThreadId.make("thread:handoff"); @@ -667,23 +671,85 @@ describe("handoff delivery", () => { }), ); - it.effect("fails before delivery when even the coverage marker cannot fit", () => + it.effect.each([true, false])( + "retries deferred history until it fits, delivering once (native=%s)", + (native) => + Effect.gen(function* () { + let durable = handoff; + let calls = 0; + const persisted: OrchestrationV2ContextHandoff[] = []; + const input = { + providerThread, + alreadyDeliveredItemIds: new Set(), + inject: () => + Effect.sync(() => { + calls++; + return native; + }), + persist: (value: OrchestrationV2ContextHandoff) => + Effect.sync(() => { + durable = decodePersistedHandoff(encodeHandoff(value)); + persisted.push(durable); + }), + }; + const first = yield* deliverContextHandoffs({ ...input, handoffs: [durable], budget: 0 }); + assert.equal(first.context, ""); + yield* first.delivered; + yield* first.unsent; + assert.equal(durable.deferred, true); + assert.isUndefined(persisted[0]!.delivery); + assert.equal(calls, 0); + assert.lengthOf(persisted, 1); + + const stillTooSmall = yield* deliverContextHandoffs({ + ...input, + handoffs: [durable], + budget: 0, + }); + assert.equal(stillTooSmall.context, ""); + assert.equal(durable.deferred, true); + assert.equal(calls, 0); + assert.lengthOf(persisted, 1); + + const fits = yield* deliverContextHandoffs({ + ...input, + handoffs: [durable], + budget: 16_000, + }); + assert.isUndefined(durable.deferred); + assert.equal(fits.context === "", native); + if (!native) assert.include(fits.context, messages[0]!.text); + yield* fits.delivered; + assert.equal(durable.delivery?.status, native ? "injected" : "inline"); + const writes = persisted.length; + const later = yield* deliverContextHandoffs({ + ...input, + handoffs: [durable], + budget: 16_000, + }); + assert.equal(later.context, ""); + yield* later.delivered; + assert.equal(calls, 1); + assert.lengthOf(persisted, writes); + }), + ); + + it.effect("stops retrying deferred history sent inline before the native thread exists", () => Effect.gen(function* () { - let calls = 0; - const result = yield* deliverContextHandoffs({ - handoffs: [handoff], - providerThread, - budget: 0, + let durable: OrchestrationV2ContextHandoff = { ...handoff, deferred: true }; + const delivery = yield* deliverContextHandoffs({ + handoffs: [durable], + providerThread: { ...providerThread, nativeThreadRef: null }, + budget: 16_000, alreadyDeliveredItemIds: new Set(), - inject: () => + persist: (value) => Effect.sync(() => { - calls++; - return true; + durable = decodePersistedHandoff(encodeHandoff(value)); }), - persist: () => Effect.void, - }).pipe(Effect.result); - assert.equal(result._tag, "Failure"); - assert.equal(calls, 0); + }); + assert.include(delivery.context, messages[0]!.text); + yield* delivery.delivered; + assert.isUndefined(durable.deferred); }), ); }); diff --git a/apps/server/src/orchestration-v2/ContextHandoffDelivery.ts b/apps/server/src/orchestration-v2/ContextHandoffDelivery.ts index 799defe965cb..03961f74dd44 100644 --- a/apps/server/src/orchestration-v2/ContextHandoffDelivery.ts +++ b/apps/server/src/orchestration-v2/ContextHandoffDelivery.ts @@ -3,7 +3,6 @@ import type { OrchestrationV2ProviderThread, } from "@t3tools/contracts"; import type * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter"; -import { ContextHandoffBudgetError } from "@t3tools/provider-core/server/failure"; import * as Effect from "effect/Effect"; import * as Schema from "effect/Schema"; import { @@ -76,7 +75,17 @@ 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 }; - return yield* new ContextHandoffBudgetError(); + yield* Effect.forEach( + pending.filter((handoff) => handoff.deferred !== true), + (handoff) => input.persist({ ...handoff, deferred: true }), + { discard: true }, + ); + yield* Effect.logDebug("Deferring context handoffs that exceed the delivery budget", { + providerThreadId: input.providerThread.id, + handoffCount: pending.length, + budget, + }); + return { context: "", delivered: Effect.void, unsent: Effect.void }; } const omittedItemIds = new Set(selected.omittedItemIds); const persist = (status: "pending" | "injected" | "inline") => @@ -85,6 +94,8 @@ export const deliverContextHandoffs = Effect.fn("orchestrationV2.deliverContextH (handoff) => input.persist({ ...handoff, + // Sent (or possibly sent) now, so later turns must not retry it. + deferred: undefined, ...(nativeThreadId === undefined ? {} : { diff --git a/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts b/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts index d8ae628e6f45..8f94197fd2e5 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, @@ -288,6 +289,7 @@ function makeLocalCommandHarness(input: { readonly interruptRunBeforeOpenFailure?: boolean; readonly writeFailure?: unknown; readonly failReadsAfterRunning?: boolean; + readonly nativeTurn?: boolean; }) { const now = DateTime.makeUnsafe("2026-09-04T12:00:00Z"); const threadId = ThreadId.make("thread-native-account-command"); @@ -511,6 +513,7 @@ function makeLocalCommandHarness(input: { ), ensureThread: () => Effect.succeed(providerThread), }; + const startTurn = vi.fn((_input: ProviderAdapter.ProviderAdapterV2TurnInput) => Effect.void); const open = vi.fn(() => input.interruptOpen === true ? Effect.interrupt @@ -532,7 +535,7 @@ function makeLocalCommandHarness(input: { ), ), ) - : input.failReadsAfterRunning === true + : input.failReadsAfterRunning === true || input.nativeTurn === true ? Effect.succeed({ driver: providerThread.driver, providerSession: { @@ -548,15 +551,50 @@ function makeLocalCommandHarness(input: { lastError: null, }, ensureThread: () => Effect.succeed(providerThread), + resumeThread: () => Effect.succeed(projection.providerThreads.at(-1)!), + startTurn, } as never) : Effect.die("A local command must not open a native session."), ); - const startRootRun = vi.fn< - (input: RunExecutionService.RunExecutionServiceV2StartRootRunInput) => Effect.Effect - >(() => - input.failReadsAfterRunning === true - ? Effect.void - : Effect.die("A local command must not start a native turn."), + const startRootRun = vi.fn((turn: RunExecutionService.RunExecutionServiceV2StartRootRunInput) => + input.nativeTurn === true + ? turn.session + .startTurn({ + appThread: turn.appThread, + threadId, + runId: turn.run.id, + runOrdinal: turn.run.ordinal, + providerTurnOrdinal: turn.run.ordinal, + attemptId: turn.attemptId, + rootNodeId: turn.rootNode.id, + providerThread: turn.providerThread, + message: { + messageId, + text: input.text, + attachments: [], + createdBy: "user", + creationSource: "web", + }, + modelSelection: turn.run.modelSelection, + runtimePolicy: { + cwd: "/tmp/native-account-command", + runtimeMode: "full-access", + interactionMode: "default", + }, + }) + .pipe( + Effect.mapError( + (cause) => + new RunExecutionService.RunExecutionStartError({ + commandId: turn.commandId, + runId: turn.run.id, + cause, + }), + ), + ) + : input.failReadsAfterRunning === true + ? Effect.void + : Effect.die("A local command must not start a native turn."), ); const failReadIfRunning = Effect.suspend(() => input.failReadsAfterRunning === true && @@ -625,7 +663,18 @@ function makeLocalCommandHarness(input: { Layer.mock(ContextHandoffService.ContextHandoffServiceV2)({ prepareProviderHandoff: () => Effect.die("history read must fail first"), }), - Layer.mock(EventSink.EventSinkV2)({ writeIfRunCurrent }), + Layer.mock(EventSink.EventSinkV2)({ + writeIfRunCurrent, + write: ({ events: incoming }) => + Effect.sync(() => { + for (const event of incoming) { + expect(isDomainEvent(event)).toBe(true); + events.push(event); + projection = ProjectionStore.applyToProjection(projection, event); + } + return []; + }), + }), IdAllocator.layer, layerWorktreeLocation(input.worktreesDir), input.fileSystem === undefined @@ -685,6 +734,12 @@ function makeLocalCommandHarness(input: { newInstanceId, attemptId, projection: () => projection, + updateProjection: ( + update: (current: OrchestrationV2ThreadProjection) => OrchestrationV2ThreadProjection, + ) => { + projection = update(projection); + }, + startTurn, start: Effect.gen(function* () { yield* (yield* ProviderTurnStart.ProviderTurnStartServiceV2).start({ threadId, runId }); }).pipe(Effect.provide(layer)), @@ -856,6 +911,83 @@ effectIt.effect("starts another provider in the same checkout while a turn is pe }), ); +effectIt.effect.each([ + { deferred: true, sameThread: true, usedTokens: 0, delivers: true }, + { deferred: true, sameThread: true, usedTokens: 32_000, delivers: false }, + { deferred: true, sameThread: false, usedTokens: 0, delivers: false }, + { deferred: undefined, sameThread: true, usedTokens: 0, delivers: false }, +] as const)( + "retries completed ordinary-turn handoffs only when deferred on this provider thread: %j", + (scenario) => + Effect.gen(function* () { + const harness = makeLocalCommandHarness({ text: "Continue", nativeTurn: true }); + harness.updateProjection((projection) => { + const run = projection.runs[0]!; + const providerThread = projection.providerThreads[0]!; + const previousRunId = RunId.make("completed-ordinary-handoff-run"); + return { + ...projection, + runs: [ + { + ...run, + id: previousRunId, + ordinal: 1, + status: "completed", + startedAt: run.requestedAt, + completedAt: run.requestedAt, + }, + run, + ], + providerThreads: [ + { + ...providerThread, + nativeThreadRef: { + driver: providerThread.driver, + nativeId: "native-handoff-target", + strength: "strong", + }, + contextUsage: { usedTokens: scenario.usedTokens, maxTokens: 32_000 }, + }, + ], + contextHandoffs: [ + { + id: ContextHandoffId.make("completed-ordinary-handoff"), + threadId: run.threadId, + targetRunId: previousRunId, + fromProviderThreadIds: [], + toProviderThreadId: scenario.sameThread + ? providerThread.id + : ProviderThreadId.make("another-provider-thread"), + coveredRunOrdinals: { from: 1, to: 1 }, + strategy: "full_thread_summary", + status: "ready", + deferred: scenario.deferred, + summaryMessageId: null, + summaryText: "Remember the original constraints.", + createdByProviderInstanceId: null, + createdAt: run.requestedAt, + updatedAt: run.requestedAt, + }, + ], + }; + }); + + yield* harness.start; + + expect(harness.startTurn).toHaveBeenCalledOnce(); + const text = harness.startTurn.mock.calls[0]![0].message.text; + expect(text.includes("Remember the original constraints.")).toBe(scenario.delivers); + expect(text.endsWith("Continue")).toBe(true); + const handoff = harness.projection().contextHandoffs[0]!; + expect(handoff.delivery?.status).toBe(scenario.delivers ? "inline" : undefined); + expect(handoff.deferred).toBe(scenario.delivers ? undefined : scenario.deferred); + expect(harness.projection().runs.at(-1)?.status).toBe("running"); + expect( + harness.events.filter((event) => event.type === "context-handoff.updated"), + ).toHaveLength(scenario.delivers ? 2 : 0); + }), +); + effectIt.effect("terminalizes a starting run when its provider session cannot open", () => Effect.gen(function* () { const harness = makeLocalCommandHarness({ diff --git a/apps/server/src/orchestration-v2/ProviderTurnStartService.ts b/apps/server/src/orchestration-v2/ProviderTurnStartService.ts index 5c1a27cb4737..925dbeeb762f 100644 --- a/apps/server/src/orchestration-v2/ProviderTurnStartService.ts +++ b/apps/server/src/orchestration-v2/ProviderTurnStartService.ts @@ -244,6 +244,7 @@ export const layer: Layer.Layer< (handoff) => handoff.status === "ready" && (handoff.targetRunId === run.id || + (handoff.deferred === true && handoff.toProviderThreadId === run.providerThreadId) || (handoff.toProviderThreadId === run.providerThreadId && projection.runs.some( (source) => diff --git a/packages/contracts/src/orchestrationV2.ts b/packages/contracts/src/orchestrationV2.ts index 94a9eca331d1..d6a806a2ae6d 100644 --- a/packages/contracts/src/orchestrationV2.ts +++ b/packages/contracts/src/orchestrationV2.ts @@ -925,6 +925,8 @@ export const OrchestrationV2ContextHandoff = Schema.Struct({ "manual_context", ]), status: Schema.Literals(["pending", "ready", "failed", "superseded"]), + // Known unsent because it did not fit; retry on later turns. + deferred: Schema.optional(Schema.Literal(true)), summaryMessageId: Schema.NullOr(MessageId), summaryText: Schema.String, history: Schema.optional( diff --git a/packages/provider-core/src/server/failure.test.ts b/packages/provider-core/src/server/failure.test.ts index 175c3a2526c3..f0e74c9a7cca 100644 --- a/packages/provider-core/src/server/failure.test.ts +++ b/packages/provider-core/src/server/failure.test.ts @@ -16,7 +16,6 @@ import { makeProviderFailureTurnItem, MAX_PROVIDER_FAILURE_CODE_LENGTH, MAX_PROVIDER_FAILURE_MESSAGE_LENGTH, - ContextHandoffBudgetError, } from "./failure.ts"; import * as IdAllocator from "./IdAllocator.ts"; import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter"; @@ -93,11 +92,11 @@ it("preserves actionable handoff errors wrapped by turn startup", () => { threadId: ThreadId.make("thread:handoff-error"), providerThreadId: ProviderThreadId.make("provider-thread:handoff-error"), runId: RunId.make("run:handoff-error"), - cause: new ContextHandoffBudgetError(), + cause: { _tag: "ContextHandoffDeliveryUncertainError" }, }); assert.equal( makeProviderFailure({ cause: Cause.fail(cause) }).message, - new ContextHandoffBudgetError().message, + "T3 could not confirm whether conversation history reached the provider. Retry the turn to recover the session.", ); }); diff --git a/packages/provider-core/src/server/failure.ts b/packages/provider-core/src/server/failure.ts index 94441085a2ad..a0a90511cb0e 100644 --- a/packages/provider-core/src/server/failure.ts +++ b/packages/provider-core/src/server/failure.ts @@ -15,16 +15,6 @@ import * as Cause from "effect/Cause"; import * as IdAllocator from "./IdAllocator.ts"; import type { ProviderAdapterFailureReason } from "./ProviderAdapter.ts"; -import * as Schema from "effect/Schema"; - -export class ContextHandoffBudgetError extends Schema.TaggedError()( - "ContextHandoffBudgetError", - {}, -) { - override get message() { - return "Insufficient context allowance for the provider handoff. Compact the target conversation or use a larger-context model; the current request has not been truncated."; - } -} export const MAX_PROVIDER_FAILURE_MESSAGE_LENGTH = 4_096; export const MAX_PROVIDER_FAILURE_CODE_LENGTH = 128; @@ -71,8 +61,6 @@ function causeMessage(cause: unknown): string | undefined { if (typeof cause !== "object") break; const tag = (cause as Record)._tag; switch (tag) { - case "ContextHandoffBudgetError": - return new ContextHandoffBudgetError().message; case "ClaudeBackgroundWorkBlocksQueryReplacementError": case "CursorModelUnavailableError": return stringField(cause, "message"); From 7de8daff9848574ba05fe9ca3b92aa26994eee4d Mon Sep 17 00:00:00 2001 From: Alb11747 Date: Wed, 14 Oct 2026 08:56:08 -0400 Subject: [PATCH 2/6] fix(server): preserve deferred handoffs through delivery retries Keep the retry marker until delivery settles and reuse deferred missed-run history across low-budget turns. Update the integration expectation so an oversized handoff allows the turn to complete while preserving its context. Co-Authored-By: GPT-6.1-Sol at high effort Co-Authored-By: GPT-6.1-Sol at high effort, review subagent for deferred handoff recovery Co-Authored-By: GPT-6.1-Sol at xhigh effort, review subagent for PR 19414 maintenance rebase --- .../ContextHandoffBudget.test.ts | 53 ++++++++++++++++++- .../ContextHandoffDelivery.ts | 5 +- .../ProviderTurnStartService.ts | 13 +++++ .../ProviderSwitch.integration.test.ts | 48 +++++++++++++---- 4 files changed, 106 insertions(+), 13 deletions(-) diff --git a/apps/server/src/orchestration-v2/ContextHandoffBudget.test.ts b/apps/server/src/orchestration-v2/ContextHandoffBudget.test.ts index a15ca474f6e4..d66071fbf641 100644 --- a/apps/server/src/orchestration-v2/ContextHandoffBudget.test.ts +++ b/apps/server/src/orchestration-v2/ContextHandoffBudget.test.ts @@ -716,10 +716,11 @@ describe("handoff delivery", () => { handoffs: [durable], budget: 16_000, }); - assert.isUndefined(durable.deferred); + assert.equal(durable.deferred, native ? undefined : true); assert.equal(fits.context === "", native); if (!native) assert.include(fits.context, messages[0]!.text); yield* fits.delivered; + assert.isUndefined(durable.deferred); assert.equal(durable.delivery?.status, native ? "injected" : "inline"); const writes = persisted.length; const later = yield* deliverContextHandoffs({ @@ -748,8 +749,58 @@ describe("handoff delivery", () => { }), }); assert.include(delivery.context, messages[0]!.text); + assert.equal(durable.deferred, true); yield* delivery.delivered; assert.isUndefined(durable.deferred); }), ); + + it.effect.each([true, false])( + "retains deferred history through an interrupted send (native=%s)", + (native) => + Effect.gen(function* () { + let durable: OrchestrationV2ContextHandoff = { ...handoff, deferred: true }; + const persist = (value: OrchestrationV2ContextHandoff) => + Effect.sync(() => { + durable = decodePersistedHandoff(encodeHandoff(value)); + }); + const send = deliverContextHandoffs({ + handoffs: [durable], + providerThread: native ? providerThread : { ...providerThread, nativeThreadRef: null }, + budget: 16_000, + alreadyDeliveredItemIds: new Set(), + ...(native ? { inject: () => Effect.fail("lost injection acknowledgement") } : {}), + persist, + }); + if (native) { + assert.equal(yield* send.pipe(Effect.flip), "lost injection acknowledgement"); + assert.equal(durable.delivery?.status, "pending"); + } else { + const delivery = yield* send; + assert.include(delivery.context, messages[0]!.text); + assert.isUndefined(durable.delivery); + } + assert.equal(durable.deferred, true); + + let injectedText = ""; + yield* deliverContextHandoffs({ + handoffs: [durable], + providerThread: { + ...providerThread, + nativeThreadRef: { ...providerThread.nativeThreadRef!, nativeId: "native:replacement" }, + }, + budget: 16_000, + alreadyDeliveredItemIds: new Set(), + inject: (history) => + Effect.sync(() => { + injectedText = history.messages.map((message) => message.text).join("\n"); + return true; + }), + persist, + }); + assert.include(injectedText, messages[0]!.text); + assert.isUndefined(durable.deferred); + assert.equal(durable.delivery?.status, "injected"); + }), + ); }); diff --git a/apps/server/src/orchestration-v2/ContextHandoffDelivery.ts b/apps/server/src/orchestration-v2/ContextHandoffDelivery.ts index 03961f74dd44..9378db766d3f 100644 --- a/apps/server/src/orchestration-v2/ContextHandoffDelivery.ts +++ b/apps/server/src/orchestration-v2/ContextHandoffDelivery.ts @@ -94,8 +94,9 @@ export const deliverContextHandoffs = Effect.fn("orchestrationV2.deliverContextH (handoff) => input.persist({ ...handoff, - // Sent (or possibly sent) now, so later turns must not retry it. - deferred: undefined, + // Keep retries selectable until delivery settles, including after + // an ambiguous send forces replacement of the native thread. + ...(status === "pending" ? {} : { deferred: undefined }), ...(nativeThreadId === undefined ? {} : { diff --git a/apps/server/src/orchestration-v2/ProviderTurnStartService.ts b/apps/server/src/orchestration-v2/ProviderTurnStartService.ts index 925dbeeb762f..15b141dcc00c 100644 --- a/apps/server/src/orchestration-v2/ProviderTurnStartService.ts +++ b/apps/server/src/orchestration-v2/ProviderTurnStartService.ts @@ -1062,6 +1062,18 @@ export const layer: Layer.Layer< !deliveredAttemptIds.has(source.activeAttemptId), ); const missedRunIds = new Set(missedRuns.map((source) => source.id)); + // Deferred history is still delivered below; do not allocate another + // recovery handoff for the same missed input on every low-budget turn. + const deferredItemIds = new Set( + effectiveHandoffs.flatMap((handoff) => + handoff.deferred === true + ? [ + ...(handoff.history?.messages.map((message) => message.itemId) ?? []), + ...(handoff.history?.omittedItemIds ?? []), + ] + : [], + ), + ); const missedItems = missedRunIds.size === 0 ? [] @@ -1070,6 +1082,7 @@ export const layer: Layer.Layer< item.runId !== null && missedRunIds.has(item.runId) && !coveredItemIds.has(item.id) && + !deferredItemIds.has(item.id) && historicalMessage(item) !== null, ); const startWithHandoffs = ( diff --git a/apps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts b/apps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts index e5bde378f5c7..92af1d0ad419 100644 --- a/apps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts +++ b/apps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts @@ -725,19 +725,18 @@ describe("orchestration v2 provider switching", () => { } if (reasoningScenario && replaceNative) { const replaced = yield* orchestrator.getThreadProjection(threadId); - assert.equal( - replaced.runs.at(-1)?.status, - scenario.includes("small") ? "failed" : "completed", - ); + assert.equal(replaced.runs.at(-1)?.status, "completed"); const target = replaced.providerThreads.find( (thread) => thread.providerInstanceId === CLAUDE_MODEL_SELECTION.instanceId, )!; assert.isNull(target.contextUsage); assert.equal(yield* Ref.get(generation), 2); - assert.equal( - (yield* Ref.get(capturedTurns)).at(-1)!.driver, - scenario.includes("small") ? CODEX_DRIVER : CLAUDE_DRIVER, - ); + assert.equal((yield* Ref.get(capturedTurns)).at(-1)!.driver, CLAUDE_DRIVER); + if (scenario.includes("small")) { + const handoff = replaced.contextHandoffs.at(-1)!; + assert.equal(handoff.deferred, true); + assert.isUndefined(handoff.delivery); + } return; } if (replaceNative) { @@ -1191,12 +1190,13 @@ describe("orchestration v2 provider switching", () => { ), ); - it.live("keeps the native session after turns refused before reaching the provider", () => + it.live.each([false, true])("reuses native sessions after refused turns (%s)", (deferred) => Effect.scoped( Effect.gen(function* () { const cwd = yield* checkpointWorkspace("refused-start-keeps-native"); const capturedTurns = yield* Ref.make>([]); const refuseStarts = yield* Ref.make(0); + let modelWindow = 32_000; const generation = yield* Ref.make(0); const registry = ProviderAdapterRegistry.layerFromAdapters([ makeTestAdapter({ @@ -1207,6 +1207,7 @@ describe("orchestration v2 provider switching", () => { responseByRunOrdinal: {}, capturedTurns, refuseStarts, + ...(deferred ? { getModelContextWindow: () => modelWindow } : {}), nativeThreadGeneration: generation, }), ]); @@ -1253,8 +1254,35 @@ describe("orchestration v2 provider switching", () => { worktreePath: null, }); assert.equal(yield* run(1), "completed"); - yield* Ref.set(refuseStarts, 2); + yield* Ref.set(refuseStarts, deferred ? 1 : 2); assert.equal(yield* run(2), "failed"); + if (deferred) { + modelWindow = 16_000; + assert.equal(yield* run(3), "completed"); + const first = (yield* orchestrator.getThreadProjection(threadId)).contextHandoffs; + assert.lengthOf(first, 1); + assert.equal(first[0]!.deferred, true); + assert.equal(yield* run(4), "completed"); + const later = (yield* orchestrator.getThreadProjection(threadId)).contextHandoffs; + assert.lengthOf(later, 1); + assert.equal(later[0]!.id, first[0]!.id); + assert.equal(later[0]!.deferred, true); + + modelWindow = 32_000; + assert.equal(yield* run(5), "completed"); + const delivered = (yield* orchestrator.getThreadProjection(threadId)).contextHandoffs; + assert.lengthOf(delivered, 1); + assert.equal(delivered[0]!.id, first[0]!.id); + assert.isUndefined(delivered[0]!.deferred); + assert.equal(delivered[0]!.delivery?.status, "inline"); + const accepted = (yield* Ref.get(capturedTurns)).at(-1)!; + assert.equal(accepted.text.split("Request 2").length - 1, 1); + assert.equal(yield* run(6), "completed"); + assert.notInclude((yield* Ref.get(capturedTurns)).at(-1)!.text, "Request 2"); + assert.lengthOf((yield* orchestrator.getThreadProjection(threadId)).contextHandoffs, 1); + assert.equal(yield* Ref.get(generation), 1); + return; + } assert.equal(yield* run(3), "failed"); assert.equal(yield* run(4), "completed"); From 401162e84e2e81d45b7d39b64c32e20ec4f3b2bd Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Mon, 19 Oct 2026 04:05:26 +0000 Subject: [PATCH 3/6] test(server): consolidate oversized handoff deferral coverage Co-Authored-By: GPT-6.1-Sol at high effort Co-Authored-By: GPT-6.1-Sol at xhigh effort, review subagent for PR 19414 maintenance rebase --- .../ContextHandoffBudget.test.ts | 123 ++++---------- .../ProviderTurnStartService.test.ts | 148 +--------------- .../ProviderSwitch.integration.test.ts | 158 ++++++++++++++---- 3 files changed, 163 insertions(+), 266 deletions(-) diff --git a/apps/server/src/orchestration-v2/ContextHandoffBudget.test.ts b/apps/server/src/orchestration-v2/ContextHandoffBudget.test.ts index d66071fbf641..01d7c9008055 100644 --- a/apps/server/src/orchestration-v2/ContextHandoffBudget.test.ts +++ b/apps/server/src/orchestration-v2/ContextHandoffBudget.test.ts @@ -672,135 +672,68 @@ describe("handoff delivery", () => { ); it.effect.each([true, false])( - "retries deferred history until it fits, delivering once (native=%s)", + "retains deferred history until an interrupted send is confirmed (native=%s)", (native) => Effect.gen(function* () { let durable = handoff; - let calls = 0; - const persisted: OrchestrationV2ContextHandoff[] = []; const input = { - providerThread, + providerThread: native ? providerThread : { ...providerThread, nativeThreadRef: null }, alreadyDeliveredItemIds: new Set(), - inject: () => - Effect.sync(() => { - calls++; - return native; - }), persist: (value: OrchestrationV2ContextHandoff) => Effect.sync(() => { durable = decodePersistedHandoff(encodeHandoff(value)); - persisted.push(durable); }), }; - const first = yield* deliverContextHandoffs({ ...input, handoffs: [durable], budget: 0 }); - assert.equal(first.context, ""); - yield* first.delivered; - yield* first.unsent; - assert.equal(durable.deferred, true); - assert.isUndefined(persisted[0]!.delivery); - assert.equal(calls, 0); - assert.lengthOf(persisted, 1); - - const stillTooSmall = yield* deliverContextHandoffs({ + const deferred = yield* deliverContextHandoffs({ ...input, handoffs: [durable], budget: 0, }); - assert.equal(stillTooSmall.context, ""); + assert.equal(deferred.context, ""); assert.equal(durable.deferred, true); - assert.equal(calls, 0); - assert.lengthOf(persisted, 1); - - const fits = yield* deliverContextHandoffs({ - ...input, - handoffs: [durable], - budget: 16_000, - }); - assert.equal(durable.deferred, native ? undefined : true); - assert.equal(fits.context === "", native); - if (!native) assert.include(fits.context, messages[0]!.text); - yield* fits.delivered; - assert.isUndefined(durable.deferred); - assert.equal(durable.delivery?.status, native ? "injected" : "inline"); - const writes = persisted.length; - const later = yield* deliverContextHandoffs({ - ...input, - handoffs: [durable], - budget: 16_000, - }); - assert.equal(later.context, ""); - yield* later.delivered; - assert.equal(calls, 1); - assert.lengthOf(persisted, writes); - }), - ); + assert.isUndefined(durable.delivery); - it.effect("stops retrying deferred history sent inline before the native thread exists", () => - Effect.gen(function* () { - let durable: OrchestrationV2ContextHandoff = { ...handoff, deferred: true }; - const delivery = yield* deliverContextHandoffs({ - handoffs: [durable], - providerThread: { ...providerThread, nativeThreadRef: null }, - budget: 16_000, - alreadyDeliveredItemIds: new Set(), - persist: (value) => - Effect.sync(() => { - durable = decodePersistedHandoff(encodeHandoff(value)); - }), - }); - assert.include(delivery.context, messages[0]!.text); - assert.equal(durable.deferred, true); - yield* delivery.delivered; - assert.isUndefined(durable.deferred); - }), - ); - - it.effect.each([true, false])( - "retains deferred history through an interrupted send (native=%s)", - (native) => - Effect.gen(function* () { - let durable: OrchestrationV2ContextHandoff = { ...handoff, deferred: true }; - const persist = (value: OrchestrationV2ContextHandoff) => - Effect.sync(() => { - durable = decodePersistedHandoff(encodeHandoff(value)); - }); const send = deliverContextHandoffs({ + ...input, handoffs: [durable], - providerThread: native ? providerThread : { ...providerThread, nativeThreadRef: null }, budget: 16_000, - alreadyDeliveredItemIds: new Set(), ...(native ? { inject: () => Effect.fail("lost injection acknowledgement") } : {}), - persist, }); if (native) { assert.equal(yield* send.pipe(Effect.flip), "lost injection acknowledgement"); assert.equal(durable.delivery?.status, "pending"); } else { - const delivery = yield* send; - assert.include(delivery.context, messages[0]!.text); + assert.include((yield* send).context, messages[0]!.text); assert.isUndefined(durable.delivery); } assert.equal(durable.deferred, true); let injectedText = ""; - yield* deliverContextHandoffs({ + const recovered = yield* deliverContextHandoffs({ + ...input, handoffs: [durable], - providerThread: { - ...providerThread, - nativeThreadRef: { ...providerThread.nativeThreadRef!, nativeId: "native:replacement" }, - }, budget: 16_000, - alreadyDeliveredItemIds: new Set(), - inject: (history) => - Effect.sync(() => { - injectedText = history.messages.map((message) => message.text).join("\n"); - return true; - }), - persist, + ...(native + ? { + providerThread: { + ...providerThread, + nativeThreadRef: { + ...providerThread.nativeThreadRef!, + nativeId: "native:replacement", + }, + }, + inject: (history: ProviderAdapter.ProviderAdapterV2HistoricalContext) => + Effect.sync(() => { + injectedText = history.messages.map((message) => message.text).join("\n"); + return true; + }), + } + : {}), }); - assert.include(injectedText, messages[0]!.text); + assert.include(native ? injectedText : recovered.context, messages[0]!.text); + yield* recovered.delivered; assert.isUndefined(durable.deferred); - assert.equal(durable.delivery?.status, "injected"); + if (native) assert.equal(durable.delivery?.status, "injected"); }), ); }); diff --git a/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts b/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts index 8f94197fd2e5..d8ae628e6f45 100644 --- a/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts +++ b/apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts @@ -3,7 +3,6 @@ import { expect, it, vi } from "vite-plus/test"; import { it as effectIt } from "@effect/vitest"; import { CheckpointScopeId, - ContextHandoffId, MessageId, NodeId, ProviderSessionId, @@ -289,7 +288,6 @@ function makeLocalCommandHarness(input: { readonly interruptRunBeforeOpenFailure?: boolean; readonly writeFailure?: unknown; readonly failReadsAfterRunning?: boolean; - readonly nativeTurn?: boolean; }) { const now = DateTime.makeUnsafe("2026-09-04T12:00:00Z"); const threadId = ThreadId.make("thread-native-account-command"); @@ -513,7 +511,6 @@ function makeLocalCommandHarness(input: { ), ensureThread: () => Effect.succeed(providerThread), }; - const startTurn = vi.fn((_input: ProviderAdapter.ProviderAdapterV2TurnInput) => Effect.void); const open = vi.fn(() => input.interruptOpen === true ? Effect.interrupt @@ -535,7 +532,7 @@ function makeLocalCommandHarness(input: { ), ), ) - : input.failReadsAfterRunning === true || input.nativeTurn === true + : input.failReadsAfterRunning === true ? Effect.succeed({ driver: providerThread.driver, providerSession: { @@ -551,50 +548,15 @@ function makeLocalCommandHarness(input: { lastError: null, }, ensureThread: () => Effect.succeed(providerThread), - resumeThread: () => Effect.succeed(projection.providerThreads.at(-1)!), - startTurn, } as never) : Effect.die("A local command must not open a native session."), ); - const startRootRun = vi.fn((turn: RunExecutionService.RunExecutionServiceV2StartRootRunInput) => - input.nativeTurn === true - ? turn.session - .startTurn({ - appThread: turn.appThread, - threadId, - runId: turn.run.id, - runOrdinal: turn.run.ordinal, - providerTurnOrdinal: turn.run.ordinal, - attemptId: turn.attemptId, - rootNodeId: turn.rootNode.id, - providerThread: turn.providerThread, - message: { - messageId, - text: input.text, - attachments: [], - createdBy: "user", - creationSource: "web", - }, - modelSelection: turn.run.modelSelection, - runtimePolicy: { - cwd: "/tmp/native-account-command", - runtimeMode: "full-access", - interactionMode: "default", - }, - }) - .pipe( - Effect.mapError( - (cause) => - new RunExecutionService.RunExecutionStartError({ - commandId: turn.commandId, - runId: turn.run.id, - cause, - }), - ), - ) - : input.failReadsAfterRunning === true - ? Effect.void - : Effect.die("A local command must not start a native turn."), + const startRootRun = vi.fn< + (input: RunExecutionService.RunExecutionServiceV2StartRootRunInput) => Effect.Effect + >(() => + input.failReadsAfterRunning === true + ? Effect.void + : Effect.die("A local command must not start a native turn."), ); const failReadIfRunning = Effect.suspend(() => input.failReadsAfterRunning === true && @@ -663,18 +625,7 @@ function makeLocalCommandHarness(input: { Layer.mock(ContextHandoffService.ContextHandoffServiceV2)({ prepareProviderHandoff: () => Effect.die("history read must fail first"), }), - Layer.mock(EventSink.EventSinkV2)({ - writeIfRunCurrent, - write: ({ events: incoming }) => - Effect.sync(() => { - for (const event of incoming) { - expect(isDomainEvent(event)).toBe(true); - events.push(event); - projection = ProjectionStore.applyToProjection(projection, event); - } - return []; - }), - }), + Layer.mock(EventSink.EventSinkV2)({ writeIfRunCurrent }), IdAllocator.layer, layerWorktreeLocation(input.worktreesDir), input.fileSystem === undefined @@ -734,12 +685,6 @@ function makeLocalCommandHarness(input: { newInstanceId, attemptId, projection: () => projection, - updateProjection: ( - update: (current: OrchestrationV2ThreadProjection) => OrchestrationV2ThreadProjection, - ) => { - projection = update(projection); - }, - startTurn, start: Effect.gen(function* () { yield* (yield* ProviderTurnStart.ProviderTurnStartServiceV2).start({ threadId, runId }); }).pipe(Effect.provide(layer)), @@ -911,83 +856,6 @@ effectIt.effect("starts another provider in the same checkout while a turn is pe }), ); -effectIt.effect.each([ - { deferred: true, sameThread: true, usedTokens: 0, delivers: true }, - { deferred: true, sameThread: true, usedTokens: 32_000, delivers: false }, - { deferred: true, sameThread: false, usedTokens: 0, delivers: false }, - { deferred: undefined, sameThread: true, usedTokens: 0, delivers: false }, -] as const)( - "retries completed ordinary-turn handoffs only when deferred on this provider thread: %j", - (scenario) => - Effect.gen(function* () { - const harness = makeLocalCommandHarness({ text: "Continue", nativeTurn: true }); - harness.updateProjection((projection) => { - const run = projection.runs[0]!; - const providerThread = projection.providerThreads[0]!; - const previousRunId = RunId.make("completed-ordinary-handoff-run"); - return { - ...projection, - runs: [ - { - ...run, - id: previousRunId, - ordinal: 1, - status: "completed", - startedAt: run.requestedAt, - completedAt: run.requestedAt, - }, - run, - ], - providerThreads: [ - { - ...providerThread, - nativeThreadRef: { - driver: providerThread.driver, - nativeId: "native-handoff-target", - strength: "strong", - }, - contextUsage: { usedTokens: scenario.usedTokens, maxTokens: 32_000 }, - }, - ], - contextHandoffs: [ - { - id: ContextHandoffId.make("completed-ordinary-handoff"), - threadId: run.threadId, - targetRunId: previousRunId, - fromProviderThreadIds: [], - toProviderThreadId: scenario.sameThread - ? providerThread.id - : ProviderThreadId.make("another-provider-thread"), - coveredRunOrdinals: { from: 1, to: 1 }, - strategy: "full_thread_summary", - status: "ready", - deferred: scenario.deferred, - summaryMessageId: null, - summaryText: "Remember the original constraints.", - createdByProviderInstanceId: null, - createdAt: run.requestedAt, - updatedAt: run.requestedAt, - }, - ], - }; - }); - - yield* harness.start; - - expect(harness.startTurn).toHaveBeenCalledOnce(); - const text = harness.startTurn.mock.calls[0]![0].message.text; - expect(text.includes("Remember the original constraints.")).toBe(scenario.delivers); - expect(text.endsWith("Continue")).toBe(true); - const handoff = harness.projection().contextHandoffs[0]!; - expect(handoff.delivery?.status).toBe(scenario.delivers ? "inline" : undefined); - expect(handoff.deferred).toBe(scenario.delivers ? undefined : scenario.deferred); - expect(harness.projection().runs.at(-1)?.status).toBe("running"); - expect( - harness.events.filter((event) => event.type === "context-handoff.updated"), - ).toHaveLength(scenario.delivers ? 2 : 0); - }), -); - effectIt.effect("terminalizes a starting run when its provider session cannot open", () => Effect.gen(function* () { const harness = makeLocalCommandHarness({ diff --git a/apps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts b/apps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts index 92af1d0ad419..27a649b8bb90 100644 --- a/apps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts +++ b/apps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts @@ -1190,13 +1190,12 @@ describe("orchestration v2 provider switching", () => { ), ); - it.live.each([false, true])("reuses native sessions after refused turns (%s)", (deferred) => + it.live("keeps the native session after turns refused before reaching the provider", () => Effect.scoped( Effect.gen(function* () { const cwd = yield* checkpointWorkspace("refused-start-keeps-native"); const capturedTurns = yield* Ref.make>([]); const refuseStarts = yield* Ref.make(0); - let modelWindow = 32_000; const generation = yield* Ref.make(0); const registry = ProviderAdapterRegistry.layerFromAdapters([ makeTestAdapter({ @@ -1207,7 +1206,6 @@ describe("orchestration v2 provider switching", () => { responseByRunOrdinal: {}, capturedTurns, refuseStarts, - ...(deferred ? { getModelContextWindow: () => modelWindow } : {}), nativeThreadGeneration: generation, }), ]); @@ -1254,35 +1252,8 @@ describe("orchestration v2 provider switching", () => { worktreePath: null, }); assert.equal(yield* run(1), "completed"); - yield* Ref.set(refuseStarts, deferred ? 1 : 2); + yield* Ref.set(refuseStarts, 2); assert.equal(yield* run(2), "failed"); - if (deferred) { - modelWindow = 16_000; - assert.equal(yield* run(3), "completed"); - const first = (yield* orchestrator.getThreadProjection(threadId)).contextHandoffs; - assert.lengthOf(first, 1); - assert.equal(first[0]!.deferred, true); - assert.equal(yield* run(4), "completed"); - const later = (yield* orchestrator.getThreadProjection(threadId)).contextHandoffs; - assert.lengthOf(later, 1); - assert.equal(later[0]!.id, first[0]!.id); - assert.equal(later[0]!.deferred, true); - - modelWindow = 32_000; - assert.equal(yield* run(5), "completed"); - const delivered = (yield* orchestrator.getThreadProjection(threadId)).contextHandoffs; - assert.lengthOf(delivered, 1); - assert.equal(delivered[0]!.id, first[0]!.id); - assert.isUndefined(delivered[0]!.deferred); - assert.equal(delivered[0]!.delivery?.status, "inline"); - const accepted = (yield* Ref.get(capturedTurns)).at(-1)!; - assert.equal(accepted.text.split("Request 2").length - 1, 1); - assert.equal(yield* run(6), "completed"); - assert.notInclude((yield* Ref.get(capturedTurns)).at(-1)!.text, "Request 2"); - assert.lengthOf((yield* orchestrator.getThreadProjection(threadId)).contextHandoffs, 1); - assert.equal(yield* Ref.get(generation), 1); - return; - } assert.equal(yield* run(3), "failed"); assert.equal(yield* run(4), "completed"); @@ -1311,6 +1282,131 @@ describe("orchestration v2 provider switching", () => { ), ); + it.live( + "defers missed-run history until after compaction without blocking or duplicating turns", + () => + Effect.scoped( + Effect.gen(function* () { + const cwd = yield* checkpointWorkspace("deferred-missed-run"); + const capturedTurns = yield* Ref.make>([]); + const refuseStarts = yield* Ref.make(0); + const generation = yield* Ref.make(0); + const registry = ProviderAdapterRegistry.layerFromAdapters([ + makeTestAdapter({ + instanceId: CLAUDE_MODEL_SELECTION.instanceId, + driver: CLAUDE_DRIVER, + capabilities: ClaudeProviderCapabilitiesV2, + modelSelection: CLAUDE_MODEL_SELECTION, + responseByRunOrdinal: {}, + capturedTurns, + refuseStarts, + getModelContextWindow: () => 32_000, + tokenUsageByRunOrdinal: { + 1: { usedTokens: 30_000, maxTokens: 32_000 }, + 5: { usedTokens: 100, maxTokens: 32_000 }, + }, + nativeThreadGeneration: generation, + }), + ]); + yield* Effect.gen(function* () { + const orchestrator = yield* Orchestrator.OrchestratorV2; + const worker = yield* EffectWorker.OrchestrationEffectWorkerV2; + const run = Effect.fn("run")(function* (ordinal: number, text = `Request ${ordinal}`) { + yield* orchestrator.dispatch({ + type: "message.dispatch", + commandId: CommandId.make(`deferred-missed-run:${ordinal}`), + threadId, + messageId: MessageId.make(`deferred-missed-run:${ordinal}`), + createdBy: "user", + creationSource: "web", + text, + attachments: [], + modelSelection: CLAUDE_MODEL_SELECTION, + dispatchMode: { type: "start_immediately" }, + }); + yield* orchestrator.streamStoredEvents.pipe( + Stream.filter( + ({ event }) => + event.type === "run.updated" && + event.payload.ordinal === ordinal && + (event.payload.status === "completed" || event.payload.status === "failed"), + ), + Stream.runHead, + ); + yield* worker.drain(); + return (yield* orchestrator.getThreadProjection(threadId)).runs.at(-1)?.status; + }); + yield* orchestrator.dispatch({ + type: "thread.create", + commandId: CommandId.make("deferred-missed-run:create"), + threadId, + projectId, + createdBy: "user", + creationSource: "web", + title: "Refused start", + modelSelection: CLAUDE_MODEL_SELECTION, + runtimeMode: "full-access", + interactionMode: "default", + branch: null, + worktreePath: null, + }); + assert.equal(yield* run(1), "completed"); + yield* Ref.set(refuseStarts, 1); + assert.equal(yield* run(2), "failed"); + assert.equal(yield* run(3), "completed"); + assert.equal((yield* Ref.get(capturedTurns)).at(-1)!.text, "Request 3"); + const first = (yield* orchestrator.getThreadProjection(threadId)).contextHandoffs; + assert.lengthOf(first, 1); + assert.equal(first[0]!.deferred, true); + assert.isUndefined(first[0]!.delivery); + + assert.equal(yield* run(4), "completed"); + assert.equal((yield* Ref.get(capturedTurns)).at(-1)!.text, "Request 4"); + assert.deepEqual( + (yield* orchestrator.getThreadProjection(threadId)).contextHandoffs, + first, + ); + assert.equal(yield* run(5, "/compact"), "completed"); + const compacted = yield* orchestrator.getThreadProjection(threadId); + assert.deepEqual(compacted.contextHandoffs, first); + assert.equal(compacted.providerTurns.at(-1)?.tokenUsage?.usedTokens, 100); + + assert.equal(yield* run(6), "completed"); + const delivered = (yield* orchestrator.getThreadProjection(threadId)).contextHandoffs; + assert.lengthOf(delivered, 1); + assert.equal(delivered[0]!.id, first[0]!.id); + assert.isUndefined(delivered[0]!.deferred); + assert.equal(delivered[0]!.delivery?.status, "inline"); + const accepted = (yield* Ref.get(capturedTurns)).at(-1)!; + assert.equal(accepted.text.split("Request 2").length - 1, 1); + assert.isTrue(accepted.text.endsWith("Request 6")); + + assert.equal(yield* run(7), "completed"); + assert.equal((yield* Ref.get(capturedTurns)).at(-1)!.text, "Request 7"); + assert.deepEqual( + (yield* orchestrator.getThreadProjection(threadId)).contextHandoffs, + delivered, + ); + assert.equal(yield* Ref.get(generation), 1); + }).pipe( + Effect.provide( + ProviderReplayHarness.layerWithRegistry( + { + name: "deferred-missed-run", + runtimePolicyOverride: { + cwd, + approvalPolicy: "never", + sandboxPolicy: { type: "readOnly" }, + }, + }, + registry, + ), + ), + ); + }), + ), + ); + it.live.each( (["failed", "interrupted"] as const).flatMap((status) => [false, true].flatMap((queued) => From d41b6f18e9105bdbe85f84fd7eff3a3d3b7894d8 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Mon, 19 Oct 2026 04:11:24 +0000 Subject: [PATCH 4/6] test(server): avoid stale handoff delivery narrowing Co-Authored-By: GPT-6.1-Sol at high effort Co-Authored-By: GPT-6.1-Sol at xhigh effort, review subagent for PR 19414 maintenance rebase --- apps/server/src/orchestration-v2/ContextHandoffBudget.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/server/src/orchestration-v2/ContextHandoffBudget.test.ts b/apps/server/src/orchestration-v2/ContextHandoffBudget.test.ts index 01d7c9008055..e63f7d85e600 100644 --- a/apps/server/src/orchestration-v2/ContextHandoffBudget.test.ts +++ b/apps/server/src/orchestration-v2/ContextHandoffBudget.test.ts @@ -691,7 +691,7 @@ describe("handoff delivery", () => { }); assert.equal(deferred.context, ""); assert.equal(durable.deferred, true); - assert.isUndefined(durable.delivery); + assert.equal(durable.delivery, undefined); const send = deliverContextHandoffs({ ...input, @@ -704,7 +704,7 @@ describe("handoff delivery", () => { assert.equal(durable.delivery?.status, "pending"); } else { assert.include((yield* send).context, messages[0]!.text); - assert.isUndefined(durable.delivery); + assert.equal(durable.delivery, undefined); } assert.equal(durable.deferred, true); From 27415185503c36a593b28d75589ef6e8c612a64b Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Mon, 19 Oct 2026 04:17:14 +0000 Subject: [PATCH 5/6] docs: explain deferred oversized handoffs Co-Authored-By: GPT-6.1-Sol at high effort Co-Authored-By: GPT-6.1-Sol at xhigh effort, review subagent for PR 19414 maintenance rebase --- docs/user/portable-handoffs.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/user/portable-handoffs.md b/docs/user/portable-handoffs.md index e2d84458ed93..103f6d3bec8a 100644 --- a/docs/user/portable-handoffs.md +++ b/docs/user/portable-handoffs.md @@ -20,9 +20,9 @@ agent-written summary. ## Context limits A handoff must leave room for existing provider context, your request and attachments, instructions, -tools, and subsequent work. If even its retrieval references cannot fit, T3 Code reports an error -instead of shortening your request. Compact the target conversation or select a larger-context -model before trying again. +tools, and subsequent work. If even its retrieval references cannot fit, T3 Code sends your request +without the handoff and retries it on later turns with the same provider, including after compaction +frees enough room. Your request is never shortened to make the handoff fit. Server operators can set `T3CODE_CONTEXT_HANDOFF_TOKEN_CAP` to change the initial history allowance (default 16,000; clamped to 1,024–64,000). This is an upper bound, not a provider context-window From cdc28c82a5f4a014abc82d0c3de4c3e20cc343c0 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sat, 24 Oct 2026 10:51:12 +0000 Subject: [PATCH 6/6] test(server): reproduce Codex handoff failure after a run interrupted before provider start Regression test for #23275. Fails on main with ContextHandoffBudgetError. Co-Authored-By: Claude Opus 5.5 --- .../ProviderSwitch.integration.test.ts | 138 +++++++++++++++++- 1 file changed, 137 insertions(+), 1 deletion(-) diff --git a/apps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts b/apps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts index 13d2ce44e856..45cb6fcbc08c 100644 --- a/apps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts +++ b/apps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts @@ -1,5 +1,8 @@ import { vi } from "vite-plus/test"; -import { historyResponseItems } from "@t3tools/provider-core/server/handoffBudget"; +import { + historyResponseItems, + latestNativeContextUsage, +} from "@t3tools/provider-core/server/handoffBudget"; import { assert, describe, it } from "@effect/vitest"; import { CommandId, @@ -1589,6 +1592,139 @@ describe("orchestration v2 provider switching", () => { ), ); + it.live( + "starts a near-full same-thread Codex turn after a run interrupted before provider start", + () => + Effect.scoped( + Effect.gen(function* () { + const cwd = yield* checkpointWorkspace("handoff-near-full"); + const capturedTurns = yield* Ref.make>([]); + const injectedHistory = yield* Ref.make>([]); + // Real incident numbers: Codex reported 196,345 of 258,400 tokens used, + // which leaves no handoff budget once the reserve is subtracted + const usage = { usedTokens: 196_345, maxTokens: 258_400 }; + const layerRegistry = ProviderAdapterRegistry.layerFromAdapters([ + makeTestAdapter({ + instanceId: CODEX_MODEL_SELECTION.instanceId, + driver: CODEX_DRIVER, + capabilities: CodexProviderCapabilitiesV2, + modelSelection: CODEX_MODEL_SELECTION, + responseByRunOrdinal: {}, + capturedTurns, + injectedHistory, + canReuseContextUsage: canReuseCodexContextUsage, + tokenUsageByRunOrdinal: { 1: usage }, + }), + ]); + yield* Effect.gen(function* () { + const orchestrator = yield* Orchestrator.OrchestratorV2; + const worker = yield* EffectWorker.OrchestrationEffectWorkerV2; + const dispatch = ( + key: string, + text: string, + dispatchMode: { readonly type: "start_immediately" | "defer_start" }, + ) => + orchestrator.dispatch({ + type: "message.dispatch", + commandId: CommandId.make(`command:near-full:${key}`), + threadId, + messageId: MessageId.make(`message:near-full:${key}`), + createdBy: "user", + creationSource: "web", + text, + attachments: [], + modelSelection: CODEX_MODEL_SELECTION, + dispatchMode, + }); + const waitForTerminal = (ordinal: number) => + orchestrator.streamStoredEvents.pipe( + Stream.filter( + ({ event }) => + event.type === "run.updated" && + event.threadId === threadId && + event.payload.ordinal === ordinal && + (event.payload.status === "completed" || event.payload.status === "failed"), + ), + Stream.runHead, + Effect.andThen(worker.drain()), + ); + yield* orchestrator.dispatch({ + type: "thread.create", + commandId: CommandId.make("command:near-full:create"), + threadId, + projectId, + createdBy: "user", + creationSource: "web", + title: "Near-full Codex thread", + modelSelection: CODEX_MODEL_SELECTION, + runtimeMode: "full-access", + interactionMode: "default", + branch: null, + worktreePath: null, + }); + yield* dispatch("first", "Earlier completed request.", { type: "start_immediately" }); + yield* waitForTerminal(1); + const afterFirst = yield* orchestrator.getThreadProjection(threadId); + assert.deepEqual( + latestNativeContextUsage(afterFirst, afterFirst.providerThreads[0]!)?.usage, + usage, + ); + + yield* dispatch("deferred", "Never sent to Codex.", { type: "defer_start" }); + const deferred = (yield* orchestrator.getThreadProjection(threadId)).runs.at(-1)!; + assert.equal(deferred.status, "preparing"); + yield* orchestrator.dispatch({ + type: "run.interrupt", + commandId: CommandId.make("command:near-full:interrupt"), + threadId, + runId: deferred.id, + }); + yield* worker.drain(); + const interrupted = yield* orchestrator.getThreadProjection(threadId); + const interruptedRun = interrupted.runs.at(-1)!; + assert.equal(interruptedRun.status, "interrupted"); + assert.equal(interruptedRun.providerThreadId, afterFirst.providerThreads[0]?.id); + assert.isFalse( + interrupted.providerTurns.some( + (turn) => turn.runAttemptId === interruptedRun.activeAttemptId, + ), + ); + + yield* dispatch("target", "Continue", { type: "start_immediately" }); + yield* waitForTerminal(3); + const projection = yield* orchestrator.getThreadProjection(threadId); + const target = projection.runs.at(-1)!; + const failure = projection.turnItems.find( + (item) => item.type === "error" && item.runId === target.id, + ); + assert.deepEqual( + { + status: target.status, + failure: failure?.type === "error" ? failure.failure.message : null, + }, + { status: "completed", failure: null }, + ); + assert.equal((yield* Ref.get(capturedTurns)).at(-1)?.text, "Continue"); + assert.isEmpty(yield* Ref.get(injectedHistory)); + }).pipe( + Effect.provide( + ProviderReplayHarness.layerWithRegistry( + { + name: "handoff-near-full", + runtimePolicyOverride: { + cwd, + approvalPolicy: "never", + sandboxPolicy: { type: "readOnly" }, + }, + }, + layerRegistry, + ), + ), + ); + }), + ), + ); + it.live("checks the queued provider's capability while the current provider stays running", () => Effect.scoped( Effect.gen(function* () {