Skip to content

Commit 33b91bb

Browse files
committed
feat(subagent): per-role deepseek-v4 effort policy
Wire a per-role V4 effort table (CL-10294) through the existing fleet effort-pin seam: dispatch/planner/reviewer max, coder cheapest, other leaves xhigh. coder's low clamps to the V4 none wire rung (thinking off) per the operator's simplest-implementer-as-none intent. Explicit profile effort pins and role defaults still win. Non-V4 models are untouched.
1 parent cd5c1b7 commit 33b91bb

3 files changed

Lines changed: 127 additions & 16 deletions

File tree

‎src/provider/deepseek-v4-effort.ts‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,25 @@
11
import type { ReasoningEffort } from "../agent/profile-types.js";
2+
import type { DirectorId } from "../agent/directors/types.js";
3+
4+
/**
5+
* Per-role V4 effort defaults (CL-10294). dispatch/planner/reviewer dispatch
6+
* and judge — max. coder is the cheapest spawn: 'low' clamps to the V4 'none'
7+
* wire rung (thinking off), per the operator's "simplest implementer = none"
8+
* intent. Every other leaf reasons at xhigh.
9+
*/
10+
export const DEEPSEEK_V4_ROLE_EFFORT: Record<DirectorId, ReasoningEffort> = {
11+
dispatch: "max",
12+
planner: "max",
13+
reviewer: "max",
14+
coder: "low",
15+
explorer: "xhigh",
16+
artist: "xhigh",
17+
"qa-lead": "xhigh",
18+
prober: "xhigh",
19+
designer: "xhigh",
20+
shakespeare: "xhigh",
21+
warden: "xhigh",
22+
};
223

324
// DeepSeek V4 effort translator shared by the adapter and the effort picker.
425
// V4 ships under several ids across two catalogs — deepseek-v4-pro/-flash/

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

Lines changed: 92 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ describe("custom worker efforts", () => {
352352
});
353353
});
354354

355-
describe("deepseek-v4 coder=low bake-in (CL-10242)", () => {
355+
describe("deepseek-v4 per-role effort policy (CL-10294)", () => {
356356
const dsv4: ProviderCatalogEntry = {
357357
name: "dsv4",
358358
baseURL: "https://dsv4.example/v1",
@@ -390,13 +390,98 @@ describe("deepseek-v4 coder=low bake-in (CL-10242)", () => {
390390
return worker.provider.reasoningEffort as ReasoningEffort;
391391
}
392392

393-
test("coder pins low on the wire while reviewer/explorer keep their role defaults", async () => {
394-
// V4's native ladder is ["none","xhigh","max"] — the requested low coder
395-
// pin clamps to its lowest rung (none), while unstubbed reviewer/explorer
396-
// leaves resolve their medium role default up to xhigh. The assertion is
397-
// that coder is the cheapest spawn; the others are not pinned.
393+
test("planner/reviewer max, coder cheapest, other leaves xhigh", async () => {
394+
// V4's native ladder is ["none","xhigh","max"]. coder's low clamps to the
395+
// cheapest wire rung (none, thinking off) per the operator's "simplest
396+
// implementer = none" intent; planner/reviewer resolve max. dispatch is the
397+
// primary session (not a spawned worker) and already defaults to max for
398+
// V4 via defaultEffortForModel.
399+
expect(await captureEffort("planner")).toBe("max");
400+
expect(await captureEffort("reviewer")).toBe("max");
398401
expect(await captureEffort("coder")).toBe("none");
399-
expect(await captureEffort("reviewer")).toBe("xhigh");
400402
expect(await captureEffort("explorer")).toBe("xhigh");
403+
expect(await captureEffort("artist")).toBe("xhigh");
404+
expect(await captureEffort("qa-lead")).toBe("xhigh");
405+
expect(await captureEffort("prober")).toBe("xhigh");
406+
expect(await captureEffort("designer")).toBe("xhigh");
407+
expect(await captureEffort("shakespeare")).toBe("xhigh");
408+
expect(await captureEffort("warden")).toBe("xhigh");
409+
});
410+
411+
test("an explicit profile effortPin outranks the V4 role table", async () => {
412+
let worker: RunSubAgentParams | undefined;
413+
const deps = createFleetDeps(
414+
async (params) => {
415+
worker = params;
416+
return { report: "done" };
417+
},
418+
{
419+
settings: { providers: { dsv4 } },
420+
catalog: [dsv4],
421+
profiles: [
422+
{
423+
id: "pinned-coder",
424+
inference: {
425+
mode: "pin",
426+
order: [
427+
{
428+
provider: dsv4.name,
429+
model: "deepseek-v4-flash",
430+
reasoningEffort: "max",
431+
},
432+
],
433+
},
434+
},
435+
],
436+
},
437+
);
438+
deps.provider = {
439+
providerName: dsv4.name,
440+
baseURL: dsv4.baseURL,
441+
model: "deepseek-v4-flash",
442+
};
443+
const result = await callFleetToolRaw(createSpawnAgentTool(deps), {
444+
description: "inspect",
445+
prompt: "inspect",
446+
agent: "pinned-coder",
447+
});
448+
expect(result.isError).not.toBe(true);
449+
await callFleetToolRaw(fleetTools(deps).wait, { timeout_ms: 2000 });
450+
if (worker === undefined) throw new Error("worker did not start");
451+
expect(worker.provider.reasoningEffort).toBe("max");
452+
});
453+
454+
test("non-V4 roles are untouched by the V4 table", async () => {
455+
// A non-V4 model spawns the same director without any V4 role pin — the
456+
// leaf role default (medium, here clamped to the custom ladder's low) wins.
457+
let worker: RunSubAgentParams | undefined;
458+
const nonV4: ProviderCatalogEntry = {
459+
name: "plain",
460+
baseURL: "https://plain.example/v1",
461+
keyless: true,
462+
models: ["plain-model"],
463+
reasoningEfforts: ["low", "max"],
464+
};
465+
const deps = createFleetDeps(
466+
async (params) => {
467+
worker = params;
468+
return { report: "done" };
469+
},
470+
{ settings: { providers: { plain: nonV4 } }, catalog: [nonV4] },
471+
);
472+
deps.provider = {
473+
providerName: nonV4.name,
474+
baseURL: nonV4.baseURL,
475+
model: "plain-model",
476+
};
477+
const result = await callFleetToolRaw(createSpawnAgentTool(deps), {
478+
description: "inspect",
479+
prompt: "inspect",
480+
intent: "explore",
481+
});
482+
expect(result.isError).not.toBe(true);
483+
await callFleetToolRaw(fleetTools(deps).wait, { timeout_ms: 2000 });
484+
if (worker === undefined) throw new Error("worker did not start");
485+
expect(worker.provider.reasoningEffort).toBe("low");
401486
});
402487
});

‎src/subagent/agent-fleet.ts‎

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,10 @@ import {
6666
validateEffort,
6767
type ReasoningEffort,
6868
} from "../provider/reasoning-effort.js";
69-
import { isDeepSeekV4Model } from "../provider/deepseek-v4-effort.js";
69+
import {
70+
DEEPSEEK_V4_ROLE_EFFORT,
71+
isDeepSeekV4Model,
72+
} from "../provider/deepseek-v4-effort.js";
7073
import type { AgentProfile, CapabilityFilter } from "../agent/profiles.js";
7174
import {
7275
DEFAULT_KNOWN_ENGINES,
@@ -1184,18 +1187,20 @@ export function createSpawnAgentTool(deps: AgentFleetDeps): AgentTool {
11841187
settings?.providers[provider.providerName],
11851188
catalog?.find((entry) => entry.name === provider.providerName),
11861189
);
1187-
// DeepSeek V4 pins the coder leaf to low unless the profile explicitly
1188-
// pins effort (winning config).
1189-
const coderEffortPin =
1190+
// DeepSeek V4 pins per-role effort (CL-10294): dispatch/planner/reviewer
1191+
// max, coder cheapest, other leaves xhigh. The explicit effortPin and the
1192+
// role default still win over this table when supported.
1193+
const roleEffortPin =
11901194
resolved.effortPin ??
1191-
(!orchestrator &&
1192-
resolved.directorId === "coder" &&
1193-
isDeepSeekV4Model(provider.model)
1194-
? "low"
1195+
(isDeepSeekV4Model(provider.model) &&
1196+
resolved.directorId in DEEPSEEK_V4_ROLE_EFFORT
1197+
? DEEPSEEK_V4_ROLE_EFFORT[
1198+
resolved.directorId as keyof typeof DEEPSEEK_V4_ROLE_EFFORT
1199+
]
11951200
: undefined);
11961201
const effort = resolveEffortForRole({
11971202
orchestrator,
1198-
...(coderEffortPin !== undefined ? { pin: coderEffortPin } : {}),
1203+
...(roleEffortPin !== undefined ? { pin: roleEffortPin } : {}),
11991204
...(resolved.roleDefault !== undefined
12001205
? { roleDefault: resolved.roleDefault }
12011206
: {}),

0 commit comments

Comments
 (0)