-
Notifications
You must be signed in to change notification settings - Fork 1.1k
feat(coding-agent/prompts): added a model-family prompt overlay #2669
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| export type ModelOverlayFamily = "gpt-5" | "claude-opus" | "kimi-k2"; | ||
|
|
||
| const GPT5_FAMILY_PATTERN = /(?:^|[/@._-])gpt-5(?:$|[/@._:-]|[._-]\d)/; | ||
| const CLAUDE_OPUS_FAMILY_PATTERN = /(?:^|[/@._-])(?:claude-)?opus-4(?:$|[/@._:-]|[._-]\d)/; | ||
| const KIMI_K2_FAMILY_PATTERN = /(?:^|[/@._-])kimi-k2(?:(?:[._-]|p)\d+)?(?:$|[/@._:-])/; | ||
|
|
||
| export function detectModelOverlayFamily(model: string | undefined): ModelOverlayFamily | undefined { | ||
| if (model === undefined || model.trim() === "") { | ||
| return undefined; | ||
| } | ||
|
|
||
| const normalized = model.toLowerCase().replace(/\s+/g, "-"); | ||
| if (GPT5_FAMILY_PATTERN.test(normalized)) { | ||
| return "gpt-5"; | ||
| } | ||
| if (CLAUDE_OPUS_FAMILY_PATTERN.test(normalized)) { | ||
| return "claude-opus"; | ||
| } | ||
| if (KIMI_K2_FAMILY_PATTERN.test(normalized)) { | ||
| return "kimi-k2"; | ||
| } | ||
|
|
||
| return undefined; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export * from "./model-overlay"; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import { describe, expect, it } from "bun:test"; | ||
| import { resolveModelOverlay } from "./index"; | ||
|
|
||
| describe("resolveModelOverlay", () => { | ||
| it("detects GPT-5 family models in auto mode", () => { | ||
| expect(resolveModelOverlay("openai/gpt-5.5", "auto")).toContain("## Model overlay: GPT-5"); | ||
| expect(resolveModelOverlay("openai/gpt-5.2", "auto")).toContain("## Model overlay: GPT-5"); | ||
| expect(resolveModelOverlay("OpenAI/GPT 5.5", "auto")).toContain("## Model overlay: GPT-5"); | ||
| }); | ||
|
|
||
| it("detects Claude Opus family models in auto mode", () => { | ||
| expect(resolveModelOverlay("anthropic/claude-opus-4-6", "auto")).toContain("## Model overlay: Claude Opus"); | ||
| }); | ||
|
|
||
| it("detects Kimi K2 family models in auto mode", () => { | ||
| expect(resolveModelOverlay("moonshotai/kimi-k2-7", "auto")).toContain("## Model overlay: Kimi K2"); | ||
| }); | ||
|
|
||
| it("does not match unrelated model families in auto mode", () => { | ||
| expect(resolveModelOverlay("anthropic/claude-sonnet-4-6", "auto")).toBeUndefined(); | ||
| }); | ||
|
|
||
| it("returns undefined when overlay mode is off", () => { | ||
| expect(resolveModelOverlay("openai/gpt-5.5", "off")).toBeUndefined(); | ||
| }); | ||
|
|
||
| it("forces a requested family regardless of model id", () => { | ||
| expect(resolveModelOverlay("anthropic/claude-opus-4-6", "gpt-5")).toContain("## Model overlay: GPT-5"); | ||
| }); | ||
|
|
||
| it("returns undefined for missing auto-mode model ids", () => { | ||
| expect(resolveModelOverlay(undefined, "auto")).toBeUndefined(); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import { detectModelOverlayFamily, type ModelOverlayFamily } from "./detect"; | ||
| import { getModelOverlaySection } from "./sections"; | ||
|
|
||
| export type ModelOverlayMode = "auto" | "off" | "gpt-5" | "claude-opus" | "kimi-k2"; | ||
|
|
||
| function resolveForcedFamily(mode: ModelOverlayMode): ModelOverlayFamily | undefined { | ||
| if (mode === "gpt-5" || mode === "claude-opus" || mode === "kimi-k2") { | ||
| return mode; | ||
| } | ||
|
|
||
| return undefined; | ||
| } | ||
|
|
||
| export function resolveModelOverlay(model: string | undefined, mode: ModelOverlayMode): string | undefined { | ||
| if (mode === "off") { | ||
| return undefined; | ||
| } | ||
|
|
||
| const family = resolveForcedFamily(mode) ?? detectModelOverlayFamily(model); | ||
| if (family === undefined) { | ||
| return undefined; | ||
| } | ||
|
|
||
| return getModelOverlaySection(family); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| import type { ModelOverlayFamily } from "./detect"; | ||
|
|
||
| export const GPT5_OVERLAY_SECTION = `## Model overlay: GPT-5 | ||
| - Prefer outcome-first work: name the destination, constraints, and stopping condition, then act without mechanical step-by-step narration. | ||
| - Default to modest reasoning and escalate only for subtle bugs, multi-constraint design, or root-cause uncertainty. | ||
| - For multi-step work, use concise structure; when a shape is required, state exact fields and order. | ||
| - Use read for inspection, edit for file changes, search/find for lookup, bash only for commands that compute or verify. | ||
| - Dig one layer past the first plausible symptom before settling on a fix.`; | ||
|
|
||
| export const CLAUDE_OPUS_OVERLAY_SECTION = `## Model overlay: Claude Opus | ||
| - Preserve the full requested scope: words like every, all, and for each apply to the complete set, not the first matching item. | ||
| - Follow ordered instructions in order; if asked to do X then Y, do not invert or merge the sequence. | ||
| - Maintain precise state across long tool workflows without drifting from the original goal. | ||
| - Use task for parallelizable multi-file work, and keep edits narrow and grounded in observed files.`; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This overlay hard-codes Useful? React with 👍 / 👎. |
||
|
|
||
| export const KIMI_K2_OVERLAY_SECTION = `## Model overlay: Kimi K2 | ||
| - Be restrained and outcome-first: choose one path, act, and reopen it only when new evidence contradicts it. | ||
| - Execute mechanical or already-specified work directly; reserve deeper reasoning for ambiguity, failure, or irreversible operations. | ||
| - Keep tool calls disciplined: read before editing, use edit/write/search/find rather than bash substitutes, and verify before claiming done. | ||
| - Write leanly; do not restate choices already made in the same turn.`; | ||
|
|
||
| export function getModelOverlaySection(family: ModelOverlayFamily): string { | ||
| if (family === "gpt-5") { | ||
| return GPT5_OVERLAY_SECTION; | ||
| } | ||
| if (family === "claude-opus") { | ||
| return CLAUDE_OPUS_OVERLAY_SECTION; | ||
| } | ||
|
|
||
| return KIMI_K2_OVERLAY_SECTION; | ||
| } | ||
|
Comment on lines
+22
to
+31
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2188,6 +2188,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} | |
| memoryRootEnabled: memoryBackend.id === "local", | ||
| model: settings.get("includeModelInPrompt") ? getActiveModelString() : undefined, | ||
| personality: agentKind === "sub" ? "none" : settings.get("personality"), | ||
| modelOverlay: settings.get("prompt.modelOverlay"), | ||
|
Comment on lines
2189
to
+2191
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Useful? React with 👍 / 👎. |
||
| }); | ||
|
|
||
| if (options.systemPrompt === undefined) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| import { describe, expect, it } from "bun:test"; | ||
| import { buildSystemPrompt } from "./system-prompt"; | ||
|
|
||
| /** | ||
| * The model-family overlay (resolveModelOverlay) is unit-tested in | ||
| * prompts/model-overlay; this guards the integration wiring: buildSystemPrompt | ||
| * must append the matched family's section to block 0 when modelOverlay is on | ||
| * and the active model matches, and append nothing otherwise. Discovery is | ||
| * stubbed via provided skills/contextFiles/workspaceTree so the build stays fast. | ||
| */ | ||
| const baseOpts = { | ||
| toolNames: ["read"], | ||
| skills: [], | ||
| contextFiles: [], | ||
| workspaceTree: { | ||
| rootPath: process.cwd(), | ||
|
Comment on lines
+15
to
+16
|
||
| rendered: "", | ||
| truncated: false, | ||
| totalLines: 0, | ||
| agentsMdFiles: [] as string[], | ||
| }, | ||
| }; | ||
|
|
||
| describe("buildSystemPrompt model overlay", () => { | ||
| it("appends the matching family overlay to block 0 in auto mode", async () => { | ||
| const { systemPrompt } = await buildSystemPrompt({ ...baseOpts, model: "openai/gpt-5.5", modelOverlay: "auto" }); | ||
| expect(systemPrompt[0]).toContain("## Model overlay: GPT-5"); | ||
| }); | ||
|
|
||
| it("omits the overlay when disabled", async () => { | ||
| const { systemPrompt } = await buildSystemPrompt({ ...baseOpts, model: "openai/gpt-5.5", modelOverlay: "off" }); | ||
| expect(systemPrompt[0]).not.toContain("## Model overlay"); | ||
| }); | ||
|
|
||
| it("omits the overlay for an unmatched model in auto mode", async () => { | ||
| const { systemPrompt } = await buildSystemPrompt({ | ||
| ...baseOpts, | ||
| model: "anthropic/claude-sonnet-4-6", | ||
| modelOverlay: "auto", | ||
| }); | ||
| expect(systemPrompt[0]).not.toContain("## Model overlay"); | ||
| }); | ||
|
|
||
| it("forces a family overlay regardless of the active model", async () => { | ||
| const { systemPrompt } = await buildSystemPrompt({ | ||
| ...baseOpts, | ||
| model: "anthropic/claude-sonnet-4-6", | ||
| modelOverlay: "kimi-k2", | ||
| }); | ||
| expect(systemPrompt[0]).toContain("## Model overlay: Kimi K2"); | ||
| }); | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These constants are provider-facing prompt text, but the repo-level AGENTS.md requires prompts to live in static
.mdfiles and be imported withwith { type: "text" }, not built as inline strings/template literals. Keeping the overlay copy in TS violates that prompt-file contract and bypasses the same prompt asset workflow used by the rest ofprompts/system, so please move each section to.mdand import it.Useful? React with 👍 / 👎.