Skip to content
Closed
71 changes: 71 additions & 0 deletions apps/server/src/orchestration-v2/ContextHandoffBudget.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
ThreadId,
TurnItemId,
OrchestrationV2ContextHandoff,
OrchestrationV2ContextHandoffJson,
type OrchestrationV2HistoricalMessage,
type OrchestrationV2ProviderThread,
} from "@t3tools/contracts";
Expand All @@ -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");
Expand Down Expand Up @@ -653,4 +657,71 @@ describe("handoff delivery", () => {
assert.equal(calls, 0);
}),
);

it.effect.each([true, false])(
"retains deferred history until an interrupted send is confirmed (native=%s)",
(native) =>
Effect.gen(function* () {
let durable = handoff;
const input = {
providerThread: native ? providerThread : { ...providerThread, nativeThreadRef: null },
alreadyDeliveredItemIds: new Set<string>(),
deferOversized: true,
persist: (value: OrchestrationV2ContextHandoff) =>
Effect.sync(() => {
durable = decodePersistedHandoff(encodeHandoff(value));
}),
};
const deferred = yield* deliverContextHandoffs({
...input,
handoffs: [durable],
budget: 0,
});
assert.equal(deferred.context, "");
assert.equal(durable.deferred, true);
assert.equal(durable.delivery, undefined);

const send = deliverContextHandoffs({
...input,
handoffs: [durable],
budget: 16_000,
...(native ? { inject: () => Effect.fail("lost injection acknowledgement") } : {}),
});
if (native) {
assert.equal(yield* send.pipe(Effect.flip), "lost injection acknowledgement");
assert.equal(durable.delivery?.status, "pending");
} else {
assert.include((yield* send).context, messages[0]!.text);
assert.equal(durable.delivery, undefined);
}
assert.equal(durable.deferred, true);

let injectedText = "";
const recovered = yield* deliverContextHandoffs({
...input,
handoffs: [durable],
budget: 16_000,
...(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(native ? injectedText : recovered.context, messages[0]!.text);
yield* recovered.delivered;
assert.isUndefined(durable.deferred);
if (native) assert.equal(durable.delivery?.status, "injected");
}),
);
});
18 changes: 17 additions & 1 deletion apps/server/src/orchestration-v2/ContextHandoffDelivery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export const deliverContextHandoffs = Effect.fn("orchestrationV2.deliverContextH
readonly providerThread: OrchestrationV2ProviderThread;
readonly budget: number | Effect.Effect<number, BudgetError>;
readonly deferInline?: boolean;
/** Persist oversized handoffs as deferred and send without them instead of failing. */
readonly deferOversized?: boolean;
readonly alreadyDeliveredItemIds: ReadonlySet<string>;
readonly inject?: (
history: ProviderAdapter.ProviderAdapterV2HistoricalContext,
Expand Down Expand Up @@ -76,7 +78,18 @@ 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();
if (!input.deferOversized) return yield* new ContextHandoffBudgetError();
yield* Effect.forEach(
pending.filter((handoff) => handoff.deferred !== true),
(handoff) => input.persist({ ...handoff, deferred: true }),
Comment thread
Alb11747 marked this conversation as resolved.
Resolved
{ 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") =>
Expand All @@ -85,6 +98,9 @@ export const deliverContextHandoffs = Effect.fn("orchestrationV2.deliverContextH
(handoff) =>
input.persist({
...handoff,
// Keep retries selectable until delivery settles, including after
// an ambiguous send forces replacement of the native thread.
...(status === "pending" ? {} : { deferred: undefined }),
...(nativeThreadId === undefined
? {}
: {
Expand Down
16 changes: 16 additions & 0 deletions apps/server/src/orchestration-v2/ProviderTurnStartService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,8 @@ export const layer: Layer.Layer<
const isPendingForRun = (handoff: OrchestrationV2ContextHandoff, anyProviderThread = false) =>
handoff.status === "ready" &&
(handoff.targetRunId === run.id ||
(handoff.deferred === true &&
(anyProviderThread || handoff.toProviderThreadId === run.providerThreadId)) ||
((anyProviderThread || handoff.toProviderThreadId === run.providerThreadId) &&
projection.runs.some(
(source) =>
Expand Down Expand Up @@ -1033,6 +1035,17 @@ export const layer: Layer.Layer<
const prepareRetryHandoff = Effect.fn("ProviderTurnStartService.prepareRetryHandoff")(
function* (context: ReturnType<typeof handoffContextForThread>) {
const { missedRunIds, coveredItemIds, missedRuns } = context;
// Deferred history is retried as is; do not re-allocate a recovery handoff for it.
const deferredItemIds = new Set(
handoffs.flatMap((handoff) =>
handoff.deferred === true
? [
...(handoff.history?.messages.map((message) => message.itemId) ?? []),
...(handoff.history?.omittedItemIds ?? []),
]
: [],
),
);
const missedItems =
missedRunIds.size === 0
? []
Expand All @@ -1043,6 +1056,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 retryHandoff =
Expand Down Expand Up @@ -1583,6 +1597,8 @@ export const layer: Layer.Layer<
const delivery = yield* deliverContextHandoffs({
handoffs: [...effectiveHandoffs, ...retryHandoff],
deferInline: compact || goalCommand,
// Recovery above already tried a fresh conversation; never wedge the thread on history.
deferOversized: true,
providerThread: runningProviderThread,
budget,
alreadyDeliveredItemIds: deliveredItemIds,
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -803,19 +806,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) {
Expand Down Expand Up @@ -858,24 +860,30 @@ describe("orchestration v2 provider switching", () => {
yield* wait(targetOrdinal + 3);
}
const projection = yield* orchestrator.getThreadProjection(threadId);
if (
scenario.includes("target-oversized") ||
scenario.includes("target-tight") ||
scenario.includes("target-background") ||
scenario.includes("target-subagent")
) {
if (scenario.includes("target-oversized") || scenario.includes("target-tight")) {
assert.equal(projection.runs.at(-1)?.status, "completed");
assert.equal(yield* Ref.get(generation), 1);
const sent = (yield* Ref.get(capturedTurns)).at(-1)!;
assert.equal(sent.text, current);
assert.deepEqual(sent.attachments, screenshots);
assert.isTrue(
projection.contextHandoffs.some(
(handoff) => handoff.deferred === true && handoff.delivery === undefined,
),
);
return;
}
if (scenario.includes("target-background") || scenario.includes("target-subagent")) {
assert.equal(projection.runs.at(-1)?.status, "failed");
assert.equal(yield* Ref.get(generation), 1);
assert.equal(projection.messages.at(-1)!.text, current);
assert.deepEqual(projection.messages.at(-1)!.attachments, screenshots);
if (scenario.includes("target-background") || scenario.includes("target-subagent")) {
assert.isTrue(yield* Ref.get(pendingBackgroundWork));
const failure = projection.turnItems.findLast((item) => item.type === "error");
assert.equal(
failure?.type === "error" ? failure.failure.message : null,
"The target provider still has background work running. Wait for it to finish or stop it before retrying the handoff.",
);
}
assert.isTrue(yield* Ref.get(pendingBackgroundWork));
const failure = projection.turnItems.findLast((item) => item.type === "error");
assert.equal(
failure?.type === "error" ? failure.failure.message : null,
"The target provider still has background work running. Wait for it to finish or stop it before retrying the handoff.",
);
return;
}
assert.equal(projection.runs.at(-1)?.status, "completed");
Expand Down Expand Up @@ -1773,6 +1781,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<ReadonlyArray<CapturedTurn>>([]);
const injectedHistory = yield* Ref.make<ReadonlyArray<unknown>>([]);
// 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* () {
Expand Down
Loading
Loading