Skip to content
Open
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
2 changes: 1 addition & 1 deletion docs/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -616,7 +616,7 @@ Provider credentials and custom model definitions are configured separately —

### Other groups

`omp config list` exposes many more grouped settings, including: `task.*` (subagent concurrency, isolation, model overrides), `skills.*` and `commands.*` (discovery toggles), `mcp.*`, `github.*`, `async.*`, `goal.*`, `loop.*`, `todo.*`, `magicKeywords.*`, `ttsr.*` (sticky rules), `display.*`, `startup.*`, `share.*`, `collab.*`, `stt.*`/`tts.*`, `memories.*`/`hindsight.*`/`mnemopi.*` (memory backends), and `bashInterceptor.*`. Each follows the same type/default rules shown above.
`omp config list` exposes many more grouped settings, including: `task.*` (subagent concurrency, isolation, model overrides), `skills.*` and `commands.*` (discovery toggles), `mcp.*`, `github.*`, `async.*`, `goal.*`, `loop.*`, `todo.*`, `magicKeywords.*`, `prompt.*` (model-family prompt overlay), `ttsr.*` (sticky rules), `display.*`, `startup.*`, `share.*`, `collab.*`, `stt.*`/`tts.*`, `memories.*`/`hindsight.*`/`mnemopi.*` (memory backends), and `bashInterceptor.*`. Each follows the same type/default rules shown above.

## Legacy migration

Expand Down
4 changes: 4 additions & 0 deletions packages/coding-agent/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## [Unreleased]

### Added

- Added a model-family prompt overlay: a short family-specific tuning section (GPT-5, Claude Opus, or Kimi K2) is appended to the system prompt based on the active model, controlled by the `prompt.modelOverlay` setting (`auto` detects the family, `off` disables, or force a specific family; default `auto`).

## [15.13.3] - 2026-06-15

### Added
Expand Down
20 changes: 20 additions & 0 deletions packages/coding-agent/src/config/settings-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -834,6 +834,26 @@ export const SETTINGS_SCHEMA = {
},
},

"prompt.modelOverlay": {
type: "enum",
values: ["auto", "off", "gpt-5", "claude-opus", "kimi-k2"] as const,
default: "auto",
ui: {
tab: "model",
group: "Prompt",
label: "Model Prompt Overlay",
description:
"Append a short model-family tuning section to the system prompt. Auto detects the family from the active model; off disables it; the named values force one family's overlay.",
options: [
{ value: "auto", label: "Auto", description: "Detect the family from the active model" },
{ value: "off", label: "Off", description: "No model-family overlay" },
{ value: "gpt-5", label: "GPT-5", description: "Force the GPT-5 overlay" },
{ value: "claude-opus", label: "Claude Opus", description: "Force the Claude Opus overlay" },
{ value: "kimi-k2", label: "Kimi K2", description: "Force the Kimi K2 overlay" },
],
},
},

personality: {
type: "enum",
values: ["default", "friendly", "pragmatic", "none"] as const,
Expand Down
24 changes: 24 additions & 0 deletions packages/coding-agent/src/prompts/model-overlay/detect.ts
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;
}
1 change: 1 addition & 0 deletions packages/coding-agent/src/prompts/model-overlay/index.ts
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();
});
});
25 changes: 25 additions & 0 deletions packages/coding-agent/src/prompts/model-overlay/model-overlay.ts
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);
}
31 changes: 31 additions & 0 deletions packages/coding-agent/src/prompts/model-overlay/sections.ts
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Move model overlay copy into prompt files

These constants are provider-facing prompt text, but the repo-level AGENTS.md requires prompts to live in static .md files and be imported with with { 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 of prompts/system, so please move each section to .md and import it.

Useful? React with 👍 / 👎.

- 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.`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Render overlay tool advice conditionally

This overlay hard-codes task (and sibling bullets hard-code other tool names) without checking the active tool set or wire-visible names. In contexts such as subagents at task.maxRecursionDepth where task is not registered, read-only sessions with edit/bash disabled, or modes where a tool has a custom wire name, the system prompt now tells the model to call tools it cannot use; the existing Handlebars prompt avoids this by gating tool advice on availability and rendering through toolRefs.

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
1 change: 1 addition & 0 deletions packages/coding-agent/src/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Decouple overlay detection from model-name rendering

When includeModelInPrompt is false, the model argument passed to buildSystemPromptInternal is undefined, so the newly added prompt.modelOverlay: auto path has no active model to inspect and silently omits the default overlay. This affects users who hide the literal model name but leave the overlay at its default/auto setting; force modes still work, but auto does not match the documented setting. Pass a separate active model value for overlay detection (and rebuild on model switches) instead of reusing the rendered model field.

Useful? React with 👍 / 👎.

});

if (options.systemPrompt === undefined) {
Expand Down
52 changes: 52 additions & 0 deletions packages/coding-agent/src/system-prompt-model-overlay.test.ts
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");
});
});
7 changes: 6 additions & 1 deletion packages/coding-agent/src/system-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { type ContextFile, loadCapability, type SystemPrompt as SystemPromptFile
import { expandAtImports } from "./discovery/at-imports";
import { loadSkills, type Skill } from "./extensibility/skills";
import { hasObsidian } from "./internal-urls/vault-protocol";
import { type ModelOverlayMode, resolveModelOverlay } from "./prompts/model-overlay";
import customSystemPromptTemplate from "./prompts/system/custom-system-prompt.md" with { type: "text" };
import defaultPersonality from "./prompts/system/personalities/default.md" with { type: "text" };
import friendlyPersonality from "./prompts/system/personalities/friendly.md" with { type: "text" };
Expand Down Expand Up @@ -415,6 +416,8 @@ export interface BuildSystemPromptOptions {
model?: string;
/** Personality preset rendered into the default system prompt. "none" omits the block. Default: "default" */
personality?: Personality;
/** Model-family prompt overlay mode. "auto" detects the family from `model`; "off" disables. Default: "auto" */
modelOverlay?: ModelOverlayMode;
}

/** Result of building provider-facing system prompt messages. */
Expand Down Expand Up @@ -453,6 +456,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
memoryRootEnabled = false,
model,
personality = "default",
modelOverlay = "auto",
} = options;
const resolvedCwd = cwd ?? getProjectDir();

Expand Down Expand Up @@ -654,7 +658,8 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
hasObsidian: hasObsidian(),
};
const rendered = prompt.render(resolvedCustomPrompt ? customSystemPromptTemplate : systemPromptTemplate, data);
const systemPrompt = [rendered];
const overlaySection = resolveModelOverlay(model, modelOverlay);
const systemPrompt = [overlaySection ? `${rendered}\n\n${overlaySection}` : rendered];
const projectPrompt = resolvedCustomPrompt ? "" : prompt.render(projectPromptTemplate, data).trim();
if (projectPrompt) {
systemPrompt.push(projectPrompt);
Expand Down
Loading