Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
785dce3
fix(server): delegated Claude runs report the model that actually ran
Oct 30, 2026
7e95fb2
fix(server): delegated completions say when Claude ran another model
Oct 30, 2026
73f61ba
fix(server): report the model of the delegated continuation
Oct 30, 2026
629dac9
fix(server): remote delegated tasks report the model that ran there
Oct 30, 2026
3e94ca0
fix(server): keep local Claude fallbacks out of root model reports
Oct 30, 2026
3ceb5aa
test(evals): accept explicit model substitution reports
Oct 30, 2026
901607b
fix(server): keep delegated model reports tied to their result attempt
Oct 30, 2026
bfb74e0
fix(claude): retain the model reported by root stream starts
maria-rcks Oct 31, 2026
77a6805
fix(claude): requested aliases are not model substitutions
Oct 31, 2026
98ffaa0
fix(claude): report substitutions without rewriting the model that ran
Oct 31, 2026
1254025
test(server): include provider turns in pagination fixture
Nov 2, 2026
a785062
test(claude): retain execution metadata during compaction
shivamhwp Nov 4, 2026
0cd5d80
fix(claude): discovered model aliases are not substitutions
shivamhwp Nov 4, 2026
4605780
fix(claude): keep discovered model identity stable during a turn
shivamhwp Nov 4, 2026
7621dbd
Merge remote-tracking branch 'github/main' into issue-watch/27705
maria-rcks Nov 6, 2026
7bac2a6
test(claude): keep execution identity across catalog refreshes
shivamhwp Nov 6, 2026
dae6340
fix(claude): keep catalog identity tied to the producing turn
shivamhwp Nov 6, 2026
be302fb
style(server): format Claude catalog regression setup
shivamhwp Nov 6, 2026
88720a1
Merge remote-tracking branch 'origin/main' into issue-watch/27705
shivamhwp Nov 6, 2026
202e253
fix(claude): report the init model that lands before the first turn a…
shivamhwp Nov 6, 2026
8450b66
Merge commit '0aae72244e4238bf1e6e800a35def7adab3cc778' into issue-wa…
shivamhwp Nov 6, 2026
0099452
Merge remote-tracking branch 'origin/main' into prbatch/27753
shivamhwp Nov 7, 2026
a6d4fb4
refactor(server): report the executed model with the smallest change
shivamhwp Nov 7, 2026
2418490
Merge origin/main into issue-watch/27705
shivamhwp Nov 8, 2026
f7c2ed2
test(server): cut executed-model tests to assertions that fail on main
shivamhwp Nov 8, 2026
54536a6
fix(server): a dated Claude model id no longer reads as another model
shivamhwp Nov 8, 2026
19adb18
Merge remote-tracking branch 'origin/main' into fix-27753-model-date
shivamhwp Nov 8, 2026
1a75f4c
Merge branch 'main' into issue-watch/27705
shivamhwp Nov 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions apps/server/src/mcp/OrchestratorMcpService.activity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ it("readThread preserves caller access errors and prefers activity-run status ov
makeRun({ id: cancelledRunId, ordinal: 2, status: "cancelled" }),
],
visibleTurnItems: [],
providerTurns: [],
runtimeRequests: [],
messages: [],
contextTransfers: [],
Expand Down Expand Up @@ -222,6 +223,7 @@ it("readThread pages cut-off text without splitting an emoji across pages", asyn
}),
runs: [makeRun({ id: activeRunId, ordinal: 1, status: "running" })],
visibleTurnItems: [],
providerTurns: [],
runtimeRequests: [],
messages: [],
contextTransfers: [],
Expand Down Expand Up @@ -312,6 +314,7 @@ it("taskStatus returns task.providerInstanceId rather than the driver kind", asy
}),
runs: [makeRun({ id: activeRunId, ordinal: 1, status: "running" })],
visibleTurnItems: [],
providerTurns: [],
runtimeRequests: [],
messages: [],
contextTransfers: [],
Expand Down Expand Up @@ -365,6 +368,7 @@ it("taskStatus returns task.providerInstanceId rather than the driver kind", asy
}),
],
visibleTurnItems: [],
providerTurns: [],
runtimeRequests: [],
messages: [],
contextTransfers: [
Expand Down Expand Up @@ -438,6 +442,7 @@ it("readThread and sendToThread reach threads in other projects", async () => {
return parentRuns;
},
visibleTurnItems: [],
providerTurns: [],
runtimeRequests: [],
messages: [],
contextTransfers: [],
Expand Down Expand Up @@ -474,6 +479,7 @@ it("readThread and sendToThread reach threads in other projects", async () => {
},
},
],
providerTurns: [],
runtimeRequests: [],
messages: [],
contextTransfers: [],
Expand Down Expand Up @@ -670,6 +676,7 @@ it("requestSecret saves a pasted value to a file without opening a card", async
}),
runs: [{ ...makeRun({ id: activeRunId, ordinal: 1, status: "running" }), rootNodeId: taskId }],
visibleTurnItems: [],
providerTurns: [],
runtimeRequests: [],
messages: [],
contextTransfers: [],
Expand Down Expand Up @@ -791,6 +798,7 @@ it("requestSecret returns the unsaved note for a declined, cancelled or timed-ou
card === undefined
? []
: [{ type: "secret_request", id: card.turnItemId, secretStatus: outcome }],
providerTurns: [],
runtimeRequests: [],
messages: [],
contextTransfers: [],
Expand Down
26 changes: 25 additions & 1 deletion apps/server/src/mcp/OrchestratorMcpService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ describe("OrchestratorMcpService", () => {
turnItems: [],
subagents: [],
providerThreads: [],
providerTurns: [],
runtimeRequests: [],
} as unknown as OrchestrationV2ThreadProjection;
let hasNestedWork = true;
Expand Down Expand Up @@ -288,6 +289,7 @@ describe("OrchestratorMcpService", () => {
messages: [],
subagents: [],
providerThreads: [],
providerTurns: [],
runtimeRequests: [],
turnItems: [],
} as unknown as OrchestrationV2ThreadProjection;
Expand Down Expand Up @@ -385,6 +387,7 @@ describe("OrchestratorMcpService", () => {
messages: [],
subagents: [],
providerThreads: [],
providerTurns: [],
runtimeRequests: pendingRequest ? [{ status: "pending" }] : [],
nodes: [],
turnItems: [],
Expand Down Expand Up @@ -485,6 +488,7 @@ describe("OrchestratorMcpService", () => {
messages: [],
subagents: [],
providerThreads: [],
providerTurns: [],
runtimeRequests: [],
} as unknown as OrchestrationV2ThreadProjection;
const layerDependencies = Layer.mergeAll(
Expand Down Expand Up @@ -575,6 +579,7 @@ describe("OrchestratorMcpService", () => {
messages: [],
subagents: [],
providerThreads: [],
providerTurns: [],
runtimeRequests: [],
turnItems: [],
} as unknown as OrchestrationV2ThreadProjection;
Expand Down Expand Up @@ -678,6 +683,7 @@ describe("OrchestratorMcpService", () => {
messages: [],
subagents: [],
providerThreads: [],
providerTurns: [],
runtimeRequests: [],
} as unknown as OrchestrationV2ThreadProjection;
const layerDependencies = Layer.mergeAll(
Expand Down Expand Up @@ -761,6 +767,7 @@ describe("OrchestratorMcpService", () => {
messages: [],
subagents: [],
providerThreads: [],
providerTurns: [],
runtimeRequests: [],
} as unknown as OrchestrationV2ThreadProjection;
const layerDependencies = Layer.mergeAll(
Expand Down Expand Up @@ -850,6 +857,7 @@ describe("OrchestratorMcpService", () => {
messages: [],
subagents: [],
providerThreads: [],
providerTurns: [],
runtimeRequests: [],
} as unknown as OrchestrationV2ThreadProjection;
const layerDependencies = Layer.mergeAll(
Expand Down Expand Up @@ -973,6 +981,7 @@ describe("OrchestratorMcpService", () => {
appOwnedTask("node:mcp-cancel-grandchild-below", childThreadId, grandchildThreadId),
],
providerThreads: [],
providerTurns: [],
runtimeRequests: [],
},
],
Expand All @@ -997,6 +1006,7 @@ describe("OrchestratorMcpService", () => {
messages: [],
subagents: [],
providerThreads: [],
providerTurns: [],
runtimeRequests: [],
},
],
Expand Down Expand Up @@ -1233,6 +1243,7 @@ describe("OrchestratorMcpService provider resolution", () => {
messages: [],
subagents: [],
providerThreads: [],
providerTurns: [],
runtimeRequests: [],
turnItems: [],
} as unknown as OrchestrationV2ThreadProjection;
Expand Down Expand Up @@ -3060,9 +3071,18 @@ describe("OrchestratorMcpService provider resolution", () => {
ordinal: 1,
status: "running",
modelSelection: runSelection,
activeAttemptId: "attempt:mcp-restarted-child",
},
]
: [],
// Claude ran the requested Opus on Sonnet.
providerTurns: [
{
runAttemptId: "attempt:mcp-restarted-child",
ordinal: 1,
model: "claude-sonnet-4-6",
},
],
} as unknown as OrchestrationV2ThreadProjection),
),
}),
Expand All @@ -3076,11 +3096,15 @@ describe("OrchestratorMcpService provider resolution", () => {
);
yield* Effect.gen(function* () {
const service = yield* OrchestratorMcpService.OrchestratorMcpService;
for (const selection of [runSelection, threadSelection]) {
for (const [selection, executedModel] of [
[runSelection, "claude-sonnet-4-6"],
[threadSelection, null],
] as const) {
const result = yield* service.taskStatus(scope, taskId);
assert.equal(result.providerInstanceId, selection.instanceId);
assert.equal(result.model, selection.model);
assert.deepEqual(result.options, selection.options);
assert.equal(result.executedModel, executedModel);
hasRun = false;
}
}).pipe(Effect.provide(layerOrchestratorService.pipe(Layer.provide(dependencies))));
Expand Down
33 changes: 28 additions & 5 deletions apps/server/src/mcp/OrchestratorMcpService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import {
type ServerProviderModel,
ThreadId,
isOrchestrationV2WorkActive,
latestProviderTurnForAttempt,
resolveProjectProviderInstanceEnabled,
SECRET_REQUEST_UNSAVED_NOTE,
} from "@t3tools/contracts";
Expand Down Expand Up @@ -614,7 +615,7 @@ function pageIncludesTerminalTaskResult(input: {
readonly task: OrchestrationV2Subagent;
readonly target: Pick<
OrchestrationV2ThreadProjection,
"thread" | "runs" | "contextTransfers" | "messages" | "turnItems"
"thread" | "runs" | "contextTransfers" | "messages" | "turnItems" | "providerTurns"
>;
readonly maxChars: number;
}): boolean {
Expand Down Expand Up @@ -875,13 +876,26 @@ function threadDetail(
};
}

function threadRun(run: OrchestrationV2Run): OrchestratorMcpThreadRun {
function runExecutedModel(
projection: Pick<OrchestrationV2ThreadProjection, "providerTurns">,
run: Pick<OrchestrationV2Run, "activeAttemptId"> | undefined,
): string | null {
return run === undefined
? null
: (latestProviderTurnForAttempt(projection.providerTurns, run.activeAttemptId)?.model ?? null);
}

function threadRun(
projection: Pick<OrchestrationV2ThreadProjection, "providerTurns">,
run: OrchestrationV2Run,
): OrchestratorMcpThreadRun {
return {
runId: run.id,
ordinal: run.ordinal,
status: run.status,
providerInstanceId: run.modelSelection.instanceId,
model: run.modelSelection.model,
executedModel: runExecutedModel(projection, run),
requestedAt: DateTime.formatIso(run.requestedAt),
startedAt: run.startedAt === null ? null : DateTime.formatIso(run.startedAt),
completedAt: run.completedAt === null ? null : DateTime.formatIso(run.completedAt),
Expand Down Expand Up @@ -1280,10 +1294,11 @@ const make = Effect.gen(function* () {
const records =
parent === undefined
? threadManagement
.getProjectThreadRecords(input, ["runs", "runtimeRequests"])
.getProjectThreadRecords(input, ["runs", "providerTurns", "runtimeRequests"])
.pipe(Effect.map((target) => ({ ...target, contextTransfers: [] })))
: threadManagement.getProjectThreadRecords(input, [
"runs",
"providerTurns",
"runtimeRequests",
"contextTransfers",
]);
Expand Down Expand Up @@ -1509,6 +1524,7 @@ const make = Effect.gen(function* () {
"contextTransfers",
"subagents",
"providerThreads",
"providerTurns",
"runtimeRequests",
"nodes",
],
Expand Down Expand Up @@ -1601,7 +1617,12 @@ const make = Effect.gen(function* () {
: null;
const resultContextTransferId = resultTransfer?.id ?? null;
const latestTerminalResultContextTransferId = resultTransferForRun(terminalRun)?.id ?? null;
const modelSelection = childRun?.modelSelection ??
const executionRun =
task.result === null
? (taskResultRun ?? childRun)
: (childControls.runs.find((run) => run.id === resultTransfer?.sourcePoint.runId) ??
childRun);
const modelSelection = executionRun?.modelSelection ??
childControls.thread.modelSelection ?? {
instanceId: task.providerInstanceId,
model: task.model,
Expand All @@ -1620,6 +1641,7 @@ const make = Effect.gen(function* () {
hasPendingChildRuns: hasPendingChildRuns(childProjection, childRun),
providerInstanceId: modelSelection.instanceId,
model: modelSelection.model,
executedModel: runExecutedModel(childControls, executionRun),
options: modelSelection.options ?? [],
summary: derivedResult,
resultContextTransferId,
Expand Down Expand Up @@ -1790,6 +1812,7 @@ const make = Effect.gen(function* () {
hasPendingChildRuns: false,
providerInstanceId: task.providerInstanceId,
model: task.model,
executedModel: null,
summary: task.result,
resultContextTransferId: null,
latestTerminalRunId: null,
Expand Down Expand Up @@ -3130,7 +3153,7 @@ const make = Effect.gen(function* () {
recentRuns: target.runs
.toSorted((left, right) => right.ordinal - left.ordinal)
.slice(0, input.runLimit ?? DEFAULT_THREAD_RUN_LIMIT)
.map(threadRun),
.map((run) => threadRun(target, run)),
items: page.map((row) =>
timelineItem({
row,
Expand Down
16 changes: 13 additions & 3 deletions apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2970,11 +2970,15 @@ describe("ClaudeAdapterV2 background wake turns", () => {
uuid: "00000000-0000-4000-8000-000000000101",
session_id: WAKE_NATIVE_SESSION,
});
const makeAssistantTextFrame = (input: { readonly uuid: string; readonly text: string }) =>
const makeAssistantTextFrame = (input: {
readonly uuid: string;
readonly text: string;
readonly model?: string;
}) =>
claudeSdkFrame({
type: "assistant",
message: {
model: "claude-sonnet-4-6",
model: input.model ?? "claude-sonnet-4-6",
id: `msg_${input.uuid}`,
type: "message",
role: "assistant",
Expand Down Expand Up @@ -6838,7 +6842,12 @@ describe("ClaudeAdapterV2 background wake turns", () => {
}),
);
yield* Queue.offerAll(harness.sdkMessages, [
makeAssistantTextFrame({ uuid: mainUuid, text: "Delegating." }),
// Claude reports the model it ran, which can differ from the one requested.
makeAssistantTextFrame({
uuid: mainUuid,
text: "Delegating.",
model: "claude-opus-4-5-20251101",
}),
makeAssistantErrorFrame({
uuid: "00000000-0000-4000-8000-000000000642",
error: undefined,
Expand All @@ -6850,6 +6859,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {

const providerTurn = lastProviderTurn(harness.events);
assert.equal(providerTurn.nativeTurnRef?.nativeId, mainUuid);
assert.equal(providerTurn.model, "claude-opus-4-5");
const rolledBack = yield* harness.runtime.rollbackThread({
providerThread: harness.providerThread,
target: {
Expand Down
12 changes: 12 additions & 0 deletions apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ import {
type ClaudeModelCatalog,
getClaudeCatalogModelCapabilities,
resolveClaudeModelSlug,
resolveClaudeReportedModelSlug,
scopeClaudeModelCatalog,
} from "../../provider/ClaudeModelCatalog.ts";
import {
Expand Down Expand Up @@ -4398,6 +4399,8 @@ interface ActiveClaudeTurnContext {
nativeWakeStarted: boolean;
gatedFramesBeforeEcho: number;
readonly heldRootFrames: Array<SDKMessage>;
// Claude can run another model than requested, such as when an organization disallows it.
executedModel: string | undefined;
}

interface ActiveClaudeProviderRetry {
Expand Down Expand Up @@ -5498,6 +5501,7 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
startedAt: context.startedAt,
completedAt: null,
tokenUsage,
...(context.executedModel === undefined ? {} : { model: context.executedModel }),
},
});
});
Expand Down Expand Up @@ -6008,6 +6012,9 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
status: input.status,
startedAt: input.context.startedAt,
completedAt: input.completedAt,
...(input.context.executedModel === undefined
? {}
: { model: input.context.executedModel }),
});

const buildToolCallArtifacts = (input: {
Expand Down Expand Up @@ -9134,6 +9141,10 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
// CLI notices and API errors arrive as assistant messages from "<synthetic>".
if (message.message.model && message.message.model !== "<synthetic>") {
context.rootModels.add(message.message.model);
context.executedModel = resolveClaudeReportedModelSlug(
modelCatalog,
message.message.model,
);
}
// Subagent uuids exist only in the sidechain transcript, so resume and fork cannot target them.
context.nativeMessageCursor = message.uuid;
Expand Down Expand Up @@ -11262,6 +11273,7 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
nativeWakeStarted: false,
gatedFramesBeforeEcho: 0,
heldRootFrames: [],
executedModel: undefined,
};
const attachTurn = Effect.gen(function* () {
querySession.lastTurn = context;
Expand Down
Loading
Loading