Skip to content

Commit 4b34a20

Browse files
Merge pull request #1375 from corbitsdev/cl-10242-slim-deepseek-v4-bake-in-on-seams
feat(agent): slim deepseek-v4 bake-in via existing prompt seams
2 parents 35b2346 + cd5c1b7 commit 4b34a20

5 files changed

Lines changed: 218 additions & 6 deletions

File tree

‎src/agent/model-family-policy.test.ts‎

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { describe, expect, test } from "bun:test";
22
import { resolveModelFamilyPolicy } from "./model-family-policy.js";
3+
import { buildSubAgentSystemPrompt } from "./prompts.js";
34

45
describe("resolveModelFamilyPolicy", () => {
56
test("defaults are permissive for an unrecognized provider", () => {
@@ -388,4 +389,72 @@ describe("resolveModelFamilyPolicy", () => {
388389
}
389390
}
390391
});
392+
393+
describe("deepseek-v4 bake-in (CL-10242)", () => {
394+
function v4(
395+
input: {
396+
orchestrator?: boolean;
397+
directorId?: string;
398+
} = {},
399+
) {
400+
return resolveModelFamilyPolicy({
401+
providerName: "vast",
402+
model: "deepseek-v4-flash",
403+
...(input.orchestrator !== undefined
404+
? { orchestrator: input.orchestrator }
405+
: {}),
406+
...(input.directorId !== undefined
407+
? { directorId: input.directorId }
408+
: {}),
409+
});
410+
}
411+
412+
test("orchestrators get the d1 primary residual, no leaf role body", () => {
413+
const p = v4({ orchestrator: true });
414+
expect(p.family).toBe("deepseek-v4");
415+
expect(p.promptResidual).toBeDefined();
416+
expect(p.promptResidual).toContain("DeepSeek V4 Flash");
417+
expect(p.leafRoleBody).toBeUndefined();
418+
});
419+
420+
test("coder leaves get the tuned slim coder body, no residual", () => {
421+
const p = v4({ orchestrator: false, directorId: "coder" });
422+
expect(p.family).toBe("deepseek-v4");
423+
expect(p.leafRoleBody).toBeDefined();
424+
expect(p.leafRoleBody).toContain("Coder");
425+
expect(p.promptResidual).toBeUndefined();
426+
});
427+
428+
test("reviewer leaves get the tuned slim reviewer body", () => {
429+
const p = v4({ orchestrator: false, directorId: "reviewer" });
430+
expect(p.family).toBe("deepseek-v4");
431+
expect(p.leafRoleBody).toBeDefined();
432+
expect(p.leafRoleBody).toContain("Reviewer");
433+
expect(p.promptResidual).toBeUndefined();
434+
});
435+
436+
test("explorer leaves get the g2 leaf residual only", () => {
437+
const p = v4({ orchestrator: false, directorId: "explorer" });
438+
expect(p.family).toBe("deepseek-v4");
439+
expect(p.promptResidual).toBeDefined();
440+
expect(p.promptResidual).toContain("DeepSeek V4 Flash");
441+
expect(p.leafRoleBody).toBeUndefined();
442+
});
443+
444+
test("a leaf without a tuned director id gets the default policy, no body/residual", () => {
445+
const p = v4({ orchestrator: false });
446+
expect(p.family).toBe("deepseek-v4");
447+
expect(p.leafRoleBody).toBeUndefined();
448+
expect(p.promptResidual).toBeUndefined();
449+
});
450+
451+
test("the slim coder body lands in the assembled sub-agent prompt via the extensions seam", () => {
452+
const p = v4({ orchestrator: false, directorId: "coder" });
453+
const slimBody = p.leafRoleBody;
454+
expect(slimBody).toBeDefined();
455+
const prompt = buildSubAgentSystemPrompt([slimBody as string]);
456+
expect(prompt).toContain("You are Coder");
457+
expect(prompt).toContain("implement one brief");
458+
});
459+
});
391460
});

‎src/agent/model-family-policy.ts‎

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@ export interface ModelFamilyPolicy {
5555
* disturb the cached prompt prefix. Undefined for families that need none.
5656
*/
5757
promptResidual?: string | undefined;
58+
/**
59+
* Replaces the director body (formatDirectorSystemPrompt) on a leaf worker
60+
* for families with a tuned slim body for that role. Undefined keeps the
61+
* shared director body. run.ts feeds it into the extensions list in place
62+
* of the stock director body.
63+
*/
64+
leafRoleBody?: string | undefined;
5865
}
5966

6067
const DEFAULT_WRAP_UP_NUDGE_TEXT =
@@ -200,10 +207,66 @@ const ASTRA_POLICY: Omit<ModelFamilyPolicy, "family"> = {
200207
promptResidual: ASTRA_PROMPT_RESIDUAL,
201208
};
202209

210+
// DeepSeek V4 winning d1+slim config, baked in from the handoff prompt files
211+
// (corbits-dsv4-handoff/prompts/): d1_primary.txt, body_coder.txt,
212+
// body_reviewer.txt, g2_leaf.txt. Inline constants — the brief keeps these in
213+
// the policy, not the prompt-variance package. The model-family-policy switch
214+
// routes them; the orchestration/leaf carve-outs mirror the grok branch.
215+
export const DSV4_D1_PRIMARY_RESIDUAL = `
216+
Operating notes (DeepSeek V4 Flash):
217+
- Your context is the expensive one: workers do the reading, building, and checking. Read only what you need to route and to name files in a brief (one glob or grep, or one or two reads); do not read code to plan the fix yourself.
218+
- Route: any code change (fix, feature, refactor, even one line) goes to coder; verification of that change goes to reviewer after the coder reports; codebase questions that need more than two reads go to explorer; a question answerable from one or two reads you answer directly. Set \`agent\` to the id directly; do not call search_agents.
219+
- Write each brief so the worker can start without rediscovering anything: goal (copy the user's requirements verbatim, including exact formats, names, and messages), files (repo-relative paths to change and to leave alone), constraints in do_not (no edits to tests or locked files, no new deps, no extra tests or files unless asked), success_criteria (observable checks, one per requirement), and the exact test command.
220+
- After the coder reports, spawn reviewer with the same success_criteria, the test command, and the coder's changed paths. If the reviewer reports a defect, send it back to the same coder with send_input, then have the reviewer re-check once.
221+
- Always call wait_agents on a worker you spawned before ending your turn; never end the turn with a worker still running.
222+
- Do not edit files yourself and do not re-read changed files or re-run tests after a report: the reviewer's verdict is the verification.
223+
- Files go through the file tools, not the shell: read, grep, glob. glob refuses ** unless \`path\` is a subdirectory: use {"path": "src", "pattern": "**/*.ts"}, never {"pattern": "src/**/*.ts"}.
224+
- Final reply: what changed (files), who verified it and how, anything left open, in five lines or fewer. Do not restate the request.
225+
`.trim();
226+
227+
export const DSV4_BODY_CODER = `
228+
Identity: agent id \`coder\`. You are Coder, the builder for Corbits Code, running on DeepSeek V4 Flash. You implement one brief, then report. A separate reviewer verifies your work.
229+
230+
- Start from the brief: it names the goal, the files, the constraints, the success criteria, and the test command. Read those files, make the change, run the test command once, report.
231+
- Read a file before editing it, unless you just created or edited it. Use edit for targeted changes and write for new files.
232+
- Use read, not cat/head/tail in bash; read with no offset/limit returns the whole file, so read each file once. Use grep, not shell grep or rg. Use glob, not find or ls.
233+
- glob refuses ** unless \`path\` is a subdirectory: {"path": "src", "pattern": "**/*.ts"} works; {"pattern": "src/**/*.ts"} is refused.
234+
- Use bash for tests, builds, and programs. A bash result that starts with \`exit code N\` failed: read it and fix the cause before moving on.
235+
- Make the smallest change at the root cause. Keep public signatures and existing behavior the brief does not mention. Add no tests, files, refactors, or docs the brief did not ask for.
236+
- Build structured output (JSON, CSV, YAML) with the language's serializer (JSON.stringify, json.dumps), never by string interpolation.
237+
- Do not re-verify beyond the brief's test command and one direct check per success criterion; the reviewer does the rest.
238+
- Report with the Report envelope (Summary / Findings / Blockers / Paths). In Findings, give each success criterion as pass/fail with the command and its exit status. Paths lists every file you changed.
239+
`.trim();
240+
241+
export const DSV4_BODY_REVIEWER = `
242+
Identity: agent id \`reviewer\`. You are Reviewer, the verifier for Corbits Code, running on DeepSeek V4 Flash. You check a change against the brief's success criteria. You never edit product code.
243+
244+
- Run the brief's test command once. Then check each success criterion with the smallest direct evidence: read the changed lines, and run one targeted command (call the function or CLI with python3 -c / node -e) for behavior the tests do not cover, including exact output formats.
245+
- Report a defect only with evidence: path:line, the input, expected vs actual.
246+
- Do not add files to the repo; use inline commands and leave the working tree as you found it.
247+
- Use read, grep, and glob for files, not shell cat/grep/find. glob refuses ** unless \`path\` is a subdirectory: {"path": "src", "pattern": "**/*.ts"}.
248+
- A bash result that starts with \`exit code N\` failed: say what failed.
249+
- Report with the Report envelope (Summary / Findings / Blockers / Paths). Summary: PASS or FAIL in one line. Findings: one line per success criterion (pass/fail + evidence), then any defects. Keep the whole report under 15 lines.
250+
`.trim();
251+
252+
export const DSV4_G2_LEAF_RESIDUAL = `
253+
Operating notes (DeepSeek V4 Flash):
254+
- Files go through the file tools, not the shell: read to view a file, grep to search contents, glob to find files. bash is for running tests, builds, and programs only.
255+
- glob refuses ** unless \`path\` is a subdirectory. Put the directory in \`path\`, never at the front of the pattern: {"path": "src", "pattern": "**/*.ts"} works; {"pattern": "src/**/*.ts"}, {"pattern": "tests/**/*"} and {"pattern": "**/*"} are refused. At the root, survey with {"pattern": "*.*"} and {"pattern": "*/*"}.
256+
- read takes a file path, never a directory; list a directory with glob ({"path": "src", "pattern": "*"}). Do not guess file names: glob or grep first.
257+
- read with no offset/limit returns the whole file; read each file once and do not page past its end.
258+
- Do exactly what the brief asks. No extra tests, files, refactors, or docs unless the brief requires them; when the brief or the operator says no tests, write none.
259+
- Produce structured output (JSON, CSV, YAML) with the language's serializer (JSON.stringify, json.dumps), never by string interpolation.
260+
- Stay proportional: a few targeted checks cover a small change. Once the success criteria are verified, stop; do not build harnesses for exotic inputs the brief never mentions.
261+
- Run the project's own test command once after your last edit, then write the report.
262+
`.trim();
263+
203264
export function resolveModelFamilyPolicy(input: {
204265
providerName: string;
205266
model?: string;
206267
orchestrator?: boolean;
268+
/** Leaf director id (coder, reviewer, explorer, …) for role-tuned families. */
269+
directorId?: string;
207270
}): ModelFamilyPolicy {
208271
const family = detectModelFamily(input);
209272
const orchestrator = input.orchestrator === true;
@@ -240,6 +303,26 @@ export function resolveModelFamilyPolicy(input: {
240303
// Primary and leaf alike, like gpt: no orchestrator carve-out. Generic
241304
// gpt prompts are byte-identical — only astra carries the residual.
242305
return { family, ...ASTRA_POLICY };
306+
case "deepseek-v4": {
307+
// Winning d1+slim config: d1 primary residual on orchestrators, g2 leaf
308+
// residual on explorer leaves, tuned slim coder/reviewer bodies.
309+
const directorId = input.directorId;
310+
return {
311+
family,
312+
...DEFAULT_POLICY,
313+
promptResidual: orchestrator
314+
? DSV4_D1_PRIMARY_RESIDUAL
315+
: directorId === "explorer"
316+
? DSV4_G2_LEAF_RESIDUAL
317+
: undefined,
318+
leafRoleBody:
319+
!orchestrator && directorId === "coder"
320+
? DSV4_BODY_CODER
321+
: !orchestrator && directorId === "reviewer"
322+
? DSV4_BODY_REVIEWER
323+
: undefined,
324+
};
325+
}
243326
default:
244327
return { family: "default", ...DEFAULT_POLICY };
245328
}

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

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { afterEach, describe, expect, test } from "bun:test";
22
import type { AgentProfile } from "../agent/profile-types.js";
3+
import type { ReasoningEffort } from "../provider/reasoning-effort.js";
34
import type { ProviderCatalogEntry } from "../config/index.js";
45
import { buildSubagentSources } from "../config/inference-sources.js";
56
import { clearSourceCredentials } from "../config/source-credentials.js";
@@ -350,3 +351,52 @@ describe("custom worker efforts", () => {
350351
expect(worker?.provider.reasoningEffort).toBe("max");
351352
});
352353
});
354+
355+
describe("deepseek-v4 coder=low bake-in (CL-10242)", () => {
356+
const dsv4: ProviderCatalogEntry = {
357+
name: "dsv4",
358+
baseURL: "https://dsv4.example/v1",
359+
keyless: true,
360+
models: ["deepseek-v4-flash"],
361+
};
362+
363+
async function captureEffort(agent: string): Promise<ReasoningEffort> {
364+
let worker: RunSubAgentParams | undefined;
365+
const deps = createFleetDeps(
366+
async (params) => {
367+
worker = params;
368+
return { report: "done" };
369+
},
370+
{ settings: { providers: { dsv4 } }, catalog: [dsv4] },
371+
);
372+
deps.provider = {
373+
providerName: dsv4.name,
374+
baseURL: dsv4.baseURL,
375+
model: "deepseek-v4-flash",
376+
};
377+
const spawn = createSpawnAgentTool(deps);
378+
const args: Record<string, unknown> = {
379+
description: "inspect",
380+
prompt: "inspect",
381+
agent,
382+
};
383+
if (agent === "coder" || agent === "reviewer") {
384+
args.success_criteria = ["done"];
385+
}
386+
const result = await callFleetToolRaw(spawn, args);
387+
expect(result.isError).not.toBe(true);
388+
await callFleetToolRaw(fleetTools(deps).wait, { timeout_ms: 2000 });
389+
if (worker === undefined) throw new Error("worker did not start");
390+
return worker.provider.reasoningEffort as ReasoningEffort;
391+
}
392+
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.
398+
expect(await captureEffort("coder")).toBe("none");
399+
expect(await captureEffort("reviewer")).toBe("xhigh");
400+
expect(await captureEffort("explorer")).toBe("xhigh");
401+
});
402+
});

‎src/subagent/agent-fleet.ts‎

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ import {
6666
validateEffort,
6767
type ReasoningEffort,
6868
} from "../provider/reasoning-effort.js";
69+
import { isDeepSeekV4Model } from "../provider/deepseek-v4-effort.js";
6970
import type { AgentProfile, CapabilityFilter } from "../agent/profiles.js";
7071
import {
7172
DEFAULT_KNOWN_ENGINES,
@@ -1183,11 +1184,18 @@ export function createSpawnAgentTool(deps: AgentFleetDeps): AgentTool {
11831184
settings?.providers[provider.providerName],
11841185
catalog?.find((entry) => entry.name === provider.providerName),
11851186
);
1187+
// DeepSeek V4 pins the coder leaf to low unless the profile explicitly
1188+
// pins effort (winning config).
1189+
const coderEffortPin =
1190+
resolved.effortPin ??
1191+
(!orchestrator &&
1192+
resolved.directorId === "coder" &&
1193+
isDeepSeekV4Model(provider.model)
1194+
? "low"
1195+
: undefined);
11861196
const effort = resolveEffortForRole({
11871197
orchestrator,
1188-
...(resolved.effortPin !== undefined
1189-
? { pin: resolved.effortPin }
1190-
: {}),
1198+
...(coderEffortPin !== undefined ? { pin: coderEffortPin } : {}),
11911199
...(resolved.roleDefault !== undefined
11921200
? { roleDefault: resolved.roleDefault }
11931201
: {}),

‎src/subagent/run.ts‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -831,6 +831,9 @@ async function runSubAgentInner(
831831
providerName: params.provider.providerName,
832832
model: params.provider.model,
833833
orchestrator: params.orchestrator === true,
834+
...(params.directorId !== undefined
835+
? { directorId: params.directorId }
836+
: {}),
834837
});
835838
const skillDirs = [...(params.skillDirs ?? [])];
836839
// CL-9010: reuse the dispatcher's catalog when the lane shares its cwd;
@@ -1193,10 +1196,9 @@ async function runSubAgentInner(
11931196
skillDirs,
11941197
})
11951198
: undefined;
1199+
const roleBody = modelFamilyPolicy.leafRoleBody ?? params.systemPromptRole;
11961200
const extensions = [
1197-
...(params.systemPromptRole !== undefined
1198-
? [params.systemPromptRole]
1199-
: []),
1201+
...(roleBody !== undefined ? [roleBody] : []),
12001202
...(attachedSection !== undefined ? [attachedSection] : []),
12011203
];
12021204
const toolProfile = toolProfileForModel(params.provider);

0 commit comments

Comments
 (0)