Skip to content

Commit 2bfe908

Browse files
committed
fix(orchestration): persist model selection in thread projection during handoff
1 parent a3831df commit 2bfe908

2 files changed

Lines changed: 48 additions & 0 deletions

File tree

‎apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2612,9 +2612,42 @@ describe("ProviderCommandReactor", () => {
26122612
expect(thread?.session?.threadId).toBe("thread-1");
26132613
expect(thread?.session?.providerName).toBe("claudeAgent");
26142614
expect(thread?.session?.providerInstanceId).toBe("claudeAgent");
2615+
expect(thread?.modelSelection).toMatchObject({
2616+
instanceId: ProviderInstanceId.make("claudeAgent"),
2617+
model: "claude-opus-4-6",
2618+
});
26152619
expect(
26162620
thread?.activities.find((activity) => activity.kind === "provider.turn.start.failed"),
26172621
).toBeUndefined();
2622+
2623+
// Subsequent turn without modelSelection should continue on the switched provider
2624+
await Effect.runPromise(
2625+
harness.engine.dispatch({
2626+
type: "thread.turn.start",
2627+
commandId: CommandId.make("cmd-turn-start-provider-switch-3"),
2628+
threadId: ThreadId.make("thread-1"),
2629+
message: {
2630+
messageId: asMessageId("user-message-provider-switch-3"),
2631+
role: "user",
2632+
text: "third turn without modelSelection",
2633+
attachments: [],
2634+
},
2635+
interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,
2636+
runtimeMode: "approval-required",
2637+
createdAt: now,
2638+
}),
2639+
);
2640+
2641+
await waitFor(() => harness.sendTurn.mock.calls.length === 3);
2642+
const thirdTurnInput = harness.sendTurn.mock.calls[2]?.[0] as
2643+
| {
2644+
modelSelection?: ModelSelection;
2645+
input?: string;
2646+
}
2647+
| undefined;
2648+
expect(thirdTurnInput?.input).toBe("third turn without modelSelection");
2649+
// Ensure it stayed with claudeAgent and did not revert to codex
2650+
expect(harness.startSession.mock.calls.length).toBe(2);
26182651
});
26192652

26202653
it("retries provider continuity context after a failed switched-provider turn", async () => {

‎apps/server/src/orchestration/Layers/ProviderCommandReactor.ts‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -973,6 +973,21 @@ const make = Effect.gen(function* () {
973973
const normalizedAttachments = input.attachments ?? [];
974974

975975
const needsContinuityContext = threadContinuityPending.has(input.threadId);
976+
if (
977+
input.modelSelection !== undefined &&
978+
(needsContinuityContext ||
979+
input.modelSelection.instanceId !== thread.modelSelection.instanceId ||
980+
input.modelSelection.model !== thread.modelSelection.model)
981+
) {
982+
yield* orchestrationEngine.dispatch({
983+
type: "thread.meta.update",
984+
commandId: yield* serverCommandId(
985+
needsContinuityContext ? "thread-model-handoff" : "thread-model-selection",
986+
),
987+
threadId: input.threadId,
988+
modelSelection: input.modelSelection,
989+
});
990+
}
976991
let effectiveInput = normalizedInput;
977992
let effectiveAttachments = normalizedAttachments;
978993
let continuityContextIncluded = false;

0 commit comments

Comments
 (0)