Skip to content

Commit 7d27fce

Browse files
committed
fix(subagent): tighten explicit effort, drop flag on clamp
1 parent 7534699 commit 7d27fce

6 files changed

Lines changed: 88 additions & 30 deletions

File tree

‎src/provider/reasoning-effort.test.ts‎

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ describe("pickEffortFromCascade (precedence table)", () => {
385385
pickEffortFromCascade({
386386
roleDefault: "medium",
387387
parentEffort: "none",
388-
parentExplicit: true,
388+
explicitParentEffort: true,
389389
supported: ["none", "low", "medium", "high"],
390390
}),
391391
).toBe("none");
@@ -396,7 +396,7 @@ describe("pickEffortFromCascade (precedence table)", () => {
396396
pickEffortFromCascade({
397397
roleDefault: "medium",
398398
parentEffort: "high",
399-
parentExplicit: true,
399+
explicitParentEffort: true,
400400
supported: ["low", "medium", "high"],
401401
}),
402402
).toBe("high");
@@ -407,7 +407,7 @@ describe("pickEffortFromCascade (precedence table)", () => {
407407
pickEffortFromCascade({
408408
roleDefault: "medium",
409409
parentEffort: "xhigh",
410-
parentExplicit: true,
410+
explicitParentEffort: true,
411411
supported: ["low", "medium", "high"],
412412
}),
413413
).toBe("high");
@@ -419,18 +419,17 @@ describe("pickEffortFromCascade (precedence table)", () => {
419419
pin: "low",
420420
roleDefault: "medium",
421421
parentEffort: "xhigh",
422-
parentExplicit: true,
422+
explicitParentEffort: true,
423423
supported: ["low", "medium", "high"],
424424
}),
425425
).toBe("low");
426426
});
427427

428-
test("8. falsy parentExplicit keeps role default above parent (CL-5162)", () => {
428+
test("8. unset explicitParentEffort keeps role default above parent (CL-5162)", () => {
429429
expect(
430430
pickEffortFromCascade({
431431
roleDefault: "medium",
432432
parentEffort: "high",
433-
parentExplicit: false,
434433
supported: ["low", "medium", "high"],
435434
}),
436435
).toBe("medium");
@@ -517,7 +516,7 @@ describe("resolveEffortForRole", () => {
517516
resolveEffortForRole({
518517
orchestrator: false,
519518
parentEffort: "none",
520-
parentExplicit: true,
519+
explicitParentEffort: true,
521520
model: "gpt-5.1",
522521
}),
523522
).toBe("none");
@@ -528,7 +527,7 @@ describe("resolveEffortForRole", () => {
528527
resolveEffortForRole({
529528
orchestrator: true,
530529
parentEffort: "low",
531-
parentExplicit: true,
530+
explicitParentEffort: true,
532531
model: "gpt-5",
533532
}),
534533
).toBe("low");
@@ -539,7 +538,6 @@ describe("resolveEffortForRole", () => {
539538
resolveEffortForRole({
540539
orchestrator: false,
541540
parentEffort: "high",
542-
parentExplicit: false,
543541
model: "gpt-5",
544542
}),
545543
).toBe("medium");
@@ -551,7 +549,7 @@ describe("resolveEffortForRole", () => {
551549
orchestrator: false,
552550
pin: "low",
553551
parentEffort: "high",
554-
parentExplicit: true,
552+
explicitParentEffort: true,
555553
model: "gpt-5",
556554
}),
557555
).toBe("low");

‎src/provider/reasoning-effort.ts‎

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -382,12 +382,8 @@ export interface ResolveEffortForRoleOpts {
382382
roleDefault?: ReasoningEffort;
383383
/** Parent session effort — used only when the role default is not supported. */
384384
parentEffort?: ReasoningEffort;
385-
/**
386-
* True when the parent effort was operator-chosen (explicit) rather than a
387-
* resolved/derived value. An explicit parent effort is a fleet-wide pin that
388-
* outranks the role default; the unset case keeps CL-5162.
389-
*/
390-
parentExplicit?: boolean;
385+
/** Marker when the parent effort was operator-chosen (see SubAgentProvider). */
386+
explicitParentEffort?: true;
391387
model: string;
392388
isCodex?: boolean;
393389
providerEfforts?: readonly ReasoningEffort[];
@@ -414,7 +410,7 @@ export function pickEffortFromCascade(opts: {
414410
pin?: ReasoningEffort;
415411
roleDefault: ReasoningEffort;
416412
parentEffort?: ReasoningEffort;
417-
parentExplicit?: boolean;
413+
explicitParentEffort?: true;
418414
supported: readonly ReasoningEffort[];
419415
}): ReasoningEffort | undefined {
420416
if (opts.supported.length === 0) return undefined;
@@ -425,7 +421,7 @@ export function pickEffortFromCascade(opts: {
425421
}
426422
// An explicit (operator-chosen) parent effort is a fleet-wide pin: it beats
427423
// the role default so a primary cycled to none really gives none workers.
428-
if (opts.parentExplicit === true && opts.parentEffort !== undefined) {
424+
if (opts.explicitParentEffort === true && opts.parentEffort !== undefined) {
429425
return opts.supported.includes(opts.parentEffort)
430426
? opts.parentEffort
431427
: clampEffort(opts.parentEffort, opts.supported);
@@ -471,7 +467,9 @@ export function resolveEffortForRole(
471467
...(opts.parentEffort !== undefined
472468
? { parentEffort: opts.parentEffort }
473469
: {}),
474-
...(opts.parentExplicit === true ? { parentExplicit: true } : {}),
470+
...(opts.explicitParentEffort === true
471+
? { explicitParentEffort: true }
472+
: {}),
475473
supported,
476474
});
477475
}

‎src/session/runtime-assembly.ts‎

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,8 @@ export interface SubAgentProviderConfig {
7676
apiKey?: string;
7777
model: string;
7878
reasoningEffort?: ReasoningEffort;
79-
/**
80-
* True when `reasoningEffort` was operator-chosen (Config explicitly set it)
81-
* rather than derived. Absent = role defaults apply (CL-5162).
82-
*/
83-
explicitReasoningEffort?: boolean;
79+
/** Present when operator-chosen — see SubAgentProvider.explicitReasoningEffort. */
80+
explicitReasoningEffort?: true;
8481
providers: readonly Pick<
8582
ProviderCatalogEntry,
8683
"name" | "bifrostVirtualKey"

‎src/subagent/agent-fleet-effort.test.ts‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,45 @@ describe("custom worker efforts", () => {
208208
},
209209
);
210210

211+
test("clamped explicit parent drops the explicit marker (CL-10227)", async () => {
212+
// An explicit parent `none` on a model whose ladder rejects `none` is
213+
// clamped onto the supported set (low). The clamped value is derived, not
214+
// operator-chosen, so the explicit marker must not survive — otherwise the
215+
// worker would carry a fleet-wide explicit pin it never actually received.
216+
let worker: RunSubAgentParams | undefined;
217+
const noNoneProvider: ProviderCatalogEntry = {
218+
...provider,
219+
reasoningEfforts: ["low", "high", "max"],
220+
};
221+
const deps = createFleetDeps(
222+
async (params) => {
223+
worker = params;
224+
return { report: "done" };
225+
},
226+
{
227+
settings: { providers: { custom: noNoneProvider } },
228+
catalog: [noNoneProvider],
229+
},
230+
);
231+
deps.provider = {
232+
providerName: noNoneProvider.name,
233+
baseURL: noNoneProvider.baseURL,
234+
model: "custom-model",
235+
reasoningEffort: "none",
236+
explicitReasoningEffort: true,
237+
};
238+
const result = await callFleetToolRaw(createSpawnAgentTool(deps), {
239+
description: "inspect",
240+
prompt: "inspect",
241+
intent: "explore",
242+
});
243+
expect(result.isError).not.toBe(true);
244+
await callFleetToolRaw(fleetTools(deps).wait, { timeout_ms: 2000 });
245+
expect(worker?.provider.reasoningEffort).toBe("low");
246+
// The clamped value is derived, so the flag is dropped (absent), not `true`.
247+
expect(worker?.provider.explicitReasoningEffort).toBeUndefined();
248+
});
249+
211250
test("explicit effortPin still wins over an explicit parent", async () => {
212251
// An explicit parent is a fleet pin, but the cascade still lets an explicit
213252
// effortPin outrank it — the legacy pin-max case stays.

‎src/subagent/agent-fleet.ts‎

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1184,7 +1184,7 @@ export function createSpawnAgentTool(deps: AgentFleetDeps): AgentTool {
11841184
: {}),
11851185
...(parentEffort !== undefined ? { parentEffort } : {}),
11861186
...(parentEffort !== undefined && provider.explicitReasoningEffort
1187-
? { parentExplicit: true }
1187+
? { explicitParentEffort: true }
11881188
: {}),
11891189
model: provider.model,
11901190
isCodex: isCodexProviderName(provider.providerName),
@@ -1193,9 +1193,31 @@ export function createSpawnAgentTool(deps: AgentFleetDeps): AgentTool {
11931193
: {}),
11941194
});
11951195
if (effort !== undefined) {
1196-
provider = { ...provider, reasoningEffort: effort };
1196+
// The explicit marker travels only when the re-resolved effort is
1197+
// exactly the operator-chosen parent value. Clamping an explicit
1198+
// parent effort onto the supported set yields a derived (not
1199+
// operator-chosen) value, so the flag is dropped — a clamped value
1200+
// must not mislabel itself as a fleet-wide explicit pin for every
1201+
// descendant. `true` is the only value the type allows.
1202+
const carriedExplicit =
1203+
provider.explicitReasoningEffort === true &&
1204+
parentEffort !== undefined &&
1205+
effort === parentEffort;
1206+
provider = {
1207+
...provider,
1208+
reasoningEffort: effort,
1209+
...(carriedExplicit ? { explicitReasoningEffort: true } : {}),
1210+
};
1211+
if (!carriedExplicit) {
1212+
const { explicitReasoningEffort: _drop, ...rest } = provider;
1213+
provider = rest;
1214+
}
11971215
} else {
1198-
const { reasoningEffort: _drop, ...rest } = provider;
1216+
const {
1217+
reasoningEffort: _drop,
1218+
explicitReasoningEffort: _dropExplicit,
1219+
...rest
1220+
} = provider;
11991221
provider = rest;
12001222
}
12011223

‎src/subagent/types.ts‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,13 @@ export interface SubAgentProvider {
3333
// orchestrators to high; an operator-chosen (explicit) primary effort is a
3434
// fleet-wide pin and beats the role default.
3535
reasoningEffort?: ReasoningEffort;
36-
// True when `reasoningEffort` was operator-chosen (Config sent an explicit
37-
// reasoningEffort) rather than derived. Absent = role defaults apply (CL-5162).
38-
explicitReasoningEffort?: boolean;
36+
/**
37+
* Present (true) when `reasoningEffort` was operator-chosen (Config sent an
38+
* explicit reasoningEffort) rather than derived. A present marker is a
39+
* fleet-wide pin that outranks the role default; absent = role defaults
40+
* apply (CL-5162).
41+
*/
42+
explicitReasoningEffort?: true;
3943
// Mirrors ProviderCatalogEntry.bifrostVirtualKey. Without it the dispatch
4044
// path builds a plain openai-compatible source and the gateway never
4145
// receives the x-bf-vk header.

0 commit comments

Comments
 (0)