Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 6 additions & 1 deletion src/exec/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -714,7 +714,12 @@ export async function runExec(config: Config): Promise<ExecResult> {
});

const liveSubAgentProvider: { current: SubAgentProvider } = {
current: buildSubAgentProvider(config),
current: buildSubAgentProvider({
...config,
...(config.reasoningEffort !== undefined
? { explicitReasoningEffort: true }
: {}),
}),
};
const fleetSessions = createSubAgentSessionStore({
admission: getProcessAdmissionQueue(),
Expand Down
99 changes: 99 additions & 0 deletions src/provider/reasoning-effort.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,61 @@ describe("pickEffortFromCascade (precedence table)", () => {
}),
).toBeUndefined();
});

test("6. explicit parent outranks role default (CL-10227)", () => {
expect(
pickEffortFromCascade({
roleDefault: "medium",
parentEffort: "none",
explicitParentEffort: true,
supported: ["none", "low", "medium", "high"],
}),
).toBe("none");
});

test("6b. explicit parent wins even when role default is supported", () => {
expect(
pickEffortFromCascade({
roleDefault: "medium",
parentEffort: "high",
explicitParentEffort: true,
supported: ["low", "medium", "high"],
}),
).toBe("high");
});

test("6c. unsupported explicit parent is clamped onto supported", () => {
expect(
pickEffortFromCascade({
roleDefault: "medium",
parentEffort: "xhigh",
explicitParentEffort: true,
supported: ["low", "medium", "high"],
}),
).toBe("high");
});

test("7. explicit pin still wins over explicit parent", () => {
expect(
pickEffortFromCascade({
pin: "low",
roleDefault: "medium",
parentEffort: "xhigh",
explicitParentEffort: true,
supported: ["low", "medium", "high"],
}),
).toBe("low");
});

test("8. unset explicitParentEffort keeps role default above parent (CL-5162)", () => {
expect(
pickEffortFromCascade({
roleDefault: "medium",
parentEffort: "high",
supported: ["low", "medium", "high"],
}),
).toBe("medium");
});
});

describe("resolveEffortForRole", () => {
Expand Down Expand Up @@ -455,6 +510,50 @@ describe("resolveEffortForRole", () => {
}),
).toBe("high");
});

test("explicit parent none wins over leaf role default (CL-10227)", () => {
expect(
resolveEffortForRole({
orchestrator: false,
parentEffort: "none",
explicitParentEffort: true,
model: "gpt-5.1",
}),
).toBe("none");
});

test("explicit parent overrides orchestrator high default", () => {
expect(
resolveEffortForRole({
orchestrator: true,
parentEffort: "low",
explicitParentEffort: true,
model: "gpt-5",
}),
).toBe("low");
});

test("unset parent keeps leaf role default (CL-5162)", () => {
expect(
resolveEffortForRole({
orchestrator: false,
parentEffort: "high",
model: "gpt-5",
}),
).toBe("medium");
});

test("explicit pin wins over explicit parent", () => {
expect(
resolveEffortForRole({
orchestrator: false,
pin: "low",
parentEffort: "high",
explicitParentEffort: true,
model: "gpt-5",
}),
).toBe("low");
});
});

describe("defaultEffortForModel", () => {
Expand Down
34 changes: 26 additions & 8 deletions src/provider/reasoning-effort.ts
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,8 @@ export interface ResolveEffortForRoleOpts {
roleDefault?: ReasoningEffort;
/** Parent session effort — used only when the role default is not supported. */
parentEffort?: ReasoningEffort;
/** Marker when the parent effort was operator-chosen (see SubAgentProvider). */
explicitParentEffort?: true;
model: string;
isCodex?: boolean;
providerEfforts?: readonly ReasoningEffort[];
Expand All @@ -393,10 +395,12 @@ export interface ResolveEffortForRoleOpts {
*
* Precedence (first match wins):
* 1. Explicit pin (clamped onto supported when the pin is not in the set)
* 2. Role default when present in `supported`
* 3. Parent effort when present in `supported`
* 4. Clamp of role default onto `supported`
* 5. undefined when `supported` is empty
* 2. Explicit parent effort (operator-chosen fleet pin) when present in
* `supported` — clamped onto the set when not (CL-10227)
* 3. Role default when present in `supported`
* 4. Parent effort when present in `supported`
* 5. Clamp of role default onto `supported`
* 6. undefined when `supported` is empty
*
* Pins are still highest precedence, but an unsupported pin is clamped so the
* pure API owns the "never emit an unsupported effort" invariant (callers that
Expand All @@ -406,6 +410,7 @@ export function pickEffortFromCascade(opts: {
pin?: ReasoningEffort;
roleDefault: ReasoningEffort;
parentEffort?: ReasoningEffort;
explicitParentEffort?: true;
supported: readonly ReasoningEffort[];
}): ReasoningEffort | undefined {
if (opts.supported.length === 0) return undefined;
Expand All @@ -414,6 +419,13 @@ export function pickEffortFromCascade(opts: {
? opts.pin
: clampEffort(opts.pin, opts.supported);
}
// An explicit (operator-chosen) parent effort is a fleet-wide pin: it beats
// the role default so a primary cycled to none really gives none workers.
if (opts.explicitParentEffort === true && opts.parentEffort !== undefined) {
return opts.supported.includes(opts.parentEffort)
? opts.parentEffort
: clampEffort(opts.parentEffort, opts.supported);
}
if (opts.supported.includes(opts.roleDefault)) return opts.roleDefault;
if (
opts.parentEffort !== undefined &&
Expand All @@ -427,10 +439,13 @@ export function pickEffortFromCascade(opts: {
/**
* Resolve reasoning effort for a sub-agent spawn.
*
* Why parent is below role default: a /agent high selection on the primary must
* not force every leaf onto high — that multiplies the sol+high latency cliff
* across the fleet. Parent still fills gaps when the role default is not in the
* model's supported set but the parent effort is.
* Precedence: explicit pin > explicit parent (operator-chosen fleet pin) > role
* default > derived parent > clamp. An explicit primary effort — including
* `none`/off — is fleet-wide, so it beats both the leaf (medium) and the
* orchestrator (high) role default. The unset parent case keeps CL-5162: the
* role default outranks a derived parent so a /agent high selection on the
* primary does not force every leaf onto high (which would multiply the
* sol+high latency cliff across the fleet).
*/
export function resolveEffortForRole(
opts: ResolveEffortForRoleOpts,
Expand All @@ -452,6 +467,9 @@ export function resolveEffortForRole(
...(opts.parentEffort !== undefined
? { parentEffort: opts.parentEffort }
: {}),
...(opts.explicitParentEffort === true
? { explicitParentEffort: true }
: {}),
supported,
});
}
36 changes: 36 additions & 0 deletions src/session/runtime-assembly.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,42 @@ describe("buildSubAgentProvider", () => {
bifrostVirtualKey: true,
});
});

test("omits explicitReasoningEffort when not operator-chosen", () => {
expect(
buildSubAgentProvider({
providerName: "openai",
baseURL: "https://api.openai.com/v1",
model: "gpt-5",
reasoningEffort: "medium",
providers: [{ name: "openai" }],
}),
).toEqual({
providerName: "openai",
baseURL: "https://api.openai.com/v1",
model: "gpt-5",
reasoningEffort: "medium",
});
});

test("threads explicitReasoningEffort when operator-chosen", () => {
expect(
buildSubAgentProvider({
providerName: "openai",
baseURL: "https://api.openai.com/v1",
model: "gpt-5",
reasoningEffort: "none",
explicitReasoningEffort: true,
providers: [{ name: "openai" }],
}),
).toEqual({
providerName: "openai",
baseURL: "https://api.openai.com/v1",
model: "gpt-5",
reasoningEffort: "none",
explicitReasoningEffort: true,
});
});
});

describe("createLiveSubAgentSources", () => {
Expand Down
5 changes: 5 additions & 0 deletions src/session/runtime-assembly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ export interface SubAgentProviderConfig {
apiKey?: string;
model: string;
reasoningEffort?: ReasoningEffort;
/** Present when operator-chosen — see SubAgentProvider.explicitReasoningEffort. */
explicitReasoningEffort?: true;
providers: readonly Pick<
ProviderCatalogEntry,
"name" | "bifrostVirtualKey"
Expand All @@ -94,6 +96,9 @@ export function buildSubAgentProvider(
...(config.reasoningEffort !== undefined
? { reasoningEffort: config.reasoningEffort }
: {}),
...(config.explicitReasoningEffort === true
? { explicitReasoningEffort: true }
: {}),
...(config.providers.find((p) => p.name === config.providerName)
?.bifrostVirtualKey === true
? { bifrostVirtualKey: true }
Expand Down
Loading
Loading