From 525e66e5130cd510ead9e963ce8bd1c32b952d38 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 21 Apr 2026 02:42:51 +0100 Subject: [PATCH] fix(openai): use tagged GPT-5 prompt contract --- docs/providers/openai.md | 2 +- extensions/openai/index.test.ts | 66 ++++----- extensions/openai/prompt-overlay.ts | 202 +++++++++++++++++++++++----- 3 files changed, 197 insertions(+), 73 deletions(-) diff --git a/docs/providers/openai.md b/docs/providers/openai.md index a9b58abfa8e..51268b63cf2 100644 --- a/docs/providers/openai.md +++ b/docs/providers/openai.md @@ -208,7 +208,7 @@ See [Video Generation](/tools/video-generation) for shared tool parameters, prov OpenClaw adds an OpenAI-specific GPT-5 prompt contribution for `openai/*` and `openai-codex/*` GPT-5-family runs. It lives in the bundled OpenAI plugin, applies to model ids such as `gpt-5`, `gpt-5.2`, `gpt-5.4`, and `gpt-5.4-mini`, and does not apply to older GPT-4.x models. -The GPT-5 contribution adds concise output shape, tool-call, and execution-bias guidance by default. That guidance is always enabled for matching GPT-5 models. The friendly interaction-style layer is separate and configurable. +The GPT-5 contribution adds a tagged behavior contract for output shape, tool persistence, dependency checks, parallel lookup, completion checks, verification, and autonomy by default. That guidance is always enabled for matching GPT-5 models. The friendly interaction-style layer is separate and configurable. | Value | Effect | | ---------------------- | ------------------------------------------- | diff --git a/extensions/openai/index.test.ts b/extensions/openai/index.test.ts index 18c8755134b..e492c893957 100644 --- a/extensions/openai/index.test.ts +++ b/extensions/openai/index.test.ts @@ -12,9 +12,7 @@ import { buildOpenAIImageGenerationProvider } from "./image-generation-provider. import plugin from "./index.js"; import { OPENAI_FRIENDLY_PROMPT_OVERLAY, - OPENAI_GPT5_EXECUTION_BIAS, - OPENAI_GPT5_OUTPUT_CONTRACT, - OPENAI_GPT5_TOOL_CALL_STYLE, + OPENAI_GPT5_BEHAVIOR_CONTRACT, shouldApplyOpenAIPromptOverlay, } from "./prompt-overlay.js"; @@ -89,7 +87,7 @@ function expectOpenAIPromptContribution( agentId: undefined, }), ).toEqual({ - stablePrefix: [OPENAI_GPT5_OUTPUT_CONTRACT, OPENAI_GPT5_TOOL_CALL_STYLE].join("\n\n"), + stablePrefix: OPENAI_GPT5_BEHAVIOR_CONTRACT, sectionOverrides, }); } @@ -392,10 +390,9 @@ describe("openai plugin", () => { }; expect(openaiProvider.resolveSystemPromptContribution?.(contributionContext)).toEqual({ - stablePrefix: [OPENAI_GPT5_OUTPUT_CONTRACT, OPENAI_GPT5_TOOL_CALL_STYLE].join("\n\n"), + stablePrefix: OPENAI_GPT5_BEHAVIOR_CONTRACT, sectionOverrides: { interaction_style: OPENAI_FRIENDLY_PROMPT_OVERLAY, - execution_bias: OPENAI_GPT5_EXECUTION_BIAS, }, }); expect(OPENAI_FRIENDLY_PROMPT_OVERLAY).toContain("This is a live chat, not a memo."); @@ -409,10 +406,9 @@ describe("openai plugin", () => { "Occasional emoji are welcome when they fit naturally, especially for warmth or brief celebration; keep them sparse.", ); expect(codexProvider.resolveSystemPromptContribution?.(contributionContext)).toEqual({ - stablePrefix: [OPENAI_GPT5_OUTPUT_CONTRACT, OPENAI_GPT5_TOOL_CALL_STYLE].join("\n\n"), + stablePrefix: OPENAI_GPT5_BEHAVIOR_CONTRACT, sectionOverrides: { interaction_style: OPENAI_FRIENDLY_PROMPT_OVERLAY, - execution_bias: OPENAI_GPT5_EXECUTION_BIAS, }, }); expect( @@ -421,10 +417,9 @@ describe("openai plugin", () => { modelId: "openai/gpt-5.4-mini", }), ).toEqual({ - stablePrefix: [OPENAI_GPT5_OUTPUT_CONTRACT, OPENAI_GPT5_TOOL_CALL_STYLE].join("\n\n"), + stablePrefix: OPENAI_GPT5_BEHAVIOR_CONTRACT, sectionOverrides: { interaction_style: OPENAI_FRIENDLY_PROMPT_OVERLAY, - execution_bias: OPENAI_GPT5_EXECUTION_BIAS, }, }); expect( @@ -441,7 +436,7 @@ describe("openai plugin", () => { ).toBe(false); }); - it("includes stronger execution guidance in the OpenAI prompt overlay", () => { + it("includes the tagged GPT-5 behavior contract in the OpenAI prompt overlay", () => { expect(OPENAI_FRIENDLY_PROMPT_OVERLAY).toContain( "If the user asks you to do the work, start in the same turn instead of restating the plan.", ); @@ -499,31 +494,27 @@ describe("openai plugin", () => { expect(OPENAI_FRIENDLY_PROMPT_OVERLAY).toContain( "Occasional emoji are welcome when they fit naturally, especially for warmth or brief celebration; keep them sparse.", ); - expect(OPENAI_GPT5_EXECUTION_BIAS).toContain( - "Use a real tool call or concrete action FIRST when the task is actionable. Do not stop at a plan or promise-to-act reply.", + expect(OPENAI_GPT5_BEHAVIOR_CONTRACT).toContain(""); + expect(OPENAI_GPT5_BEHAVIOR_CONTRACT).toContain(""); + expect(OPENAI_GPT5_BEHAVIOR_CONTRACT).toContain(""); + expect(OPENAI_GPT5_BEHAVIOR_CONTRACT).toContain(""); + expect(OPENAI_GPT5_BEHAVIOR_CONTRACT).toContain(""); + expect(OPENAI_GPT5_BEHAVIOR_CONTRACT).toContain(""); + expect(OPENAI_GPT5_BEHAVIOR_CONTRACT).toContain(""); + expect(OPENAI_GPT5_BEHAVIOR_CONTRACT).toContain( + "Use tools whenever they materially improve correctness, completeness, or grounding.", ); - expect(OPENAI_GPT5_EXECUTION_BIAS).toContain( - "If the work will take multiple steps, keep calling tools until the task is done or you hit a real blocker. Do not stop after one step to ask permission.", + expect(OPENAI_GPT5_BEHAVIOR_CONTRACT).toContain( + "Do not stop early when another tool call is likely to materially improve correctness or completeness.", ); - expect(OPENAI_GPT5_EXECUTION_BIAS).toContain( - "Do prerequisite lookup or discovery before dependent actions.", + expect(OPENAI_GPT5_BEHAVIOR_CONTRACT).toContain( + "Treat the task as incomplete until all requested items are covered or explicitly marked [blocked].", ); - expect(OPENAI_GPT5_TOOL_CALL_STYLE).toContain( - "Call tools directly without narrating what you are about to do. Do not describe a plan before each tool call.", - ); - expect(OPENAI_GPT5_TOOL_CALL_STYLE).toContain( - "When a first-class tool exists for an action, use the tool instead of asking the user to run a command.", - ); - expect(OPENAI_GPT5_TOOL_CALL_STYLE).not.toContain("/approve"); - expect(OPENAI_GPT5_OUTPUT_CONTRACT).toContain( - "Return the requested sections only, in the requested order.", - ); - expect(OPENAI_GPT5_OUTPUT_CONTRACT).toContain( - "Prefer commas, periods, or parentheses over em dashes in normal prose.", - ); - expect(OPENAI_GPT5_OUTPUT_CONTRACT).toContain( - "Do not use em dashes unless the user explicitly asks for them or they are required in quoted text.", + expect(OPENAI_GPT5_BEHAVIOR_CONTRACT).toContain( + "Return exactly the sections requested, in the requested order.", ); + expect(OPENAI_GPT5_BEHAVIOR_CONTRACT).not.toContain("/approve"); + expect(OPENAI_GPT5_BEHAVIOR_CONTRACT).not.toContain("GPT-5 Output Contract"); }); it("defaults to the friendly OpenAI interaction-style overlay", async () => { @@ -533,7 +524,6 @@ describe("openai plugin", () => { const openaiProvider = requireRegisteredProvider(providers, "openai"); expectOpenAIPromptContribution(openaiProvider, { interaction_style: OPENAI_FRIENDLY_PROMPT_OVERLAY, - execution_bias: OPENAI_GPT5_EXECUTION_BIAS, }); }); @@ -544,9 +534,7 @@ describe("openai plugin", () => { expect(on).not.toHaveBeenCalledWith("before_prompt_build", expect.any(Function)); const openaiProvider = requireRegisteredProvider(providers, "openai"); - expectOpenAIPromptContribution(openaiProvider, { - execution_bias: OPENAI_GPT5_EXECUTION_BIAS, - }); + expectOpenAIPromptContribution(openaiProvider, {}); }); it("treats mixed-case off values as disabling the friendly prompt overlay", async () => { @@ -555,9 +543,7 @@ describe("openai plugin", () => { }); const openaiProvider = requireRegisteredProvider(providers, "openai"); - expectOpenAIPromptContribution(openaiProvider, { - execution_bias: OPENAI_GPT5_EXECUTION_BIAS, - }); + expectOpenAIPromptContribution(openaiProvider, {}); }); it("supports explicitly configuring the friendly prompt overlay", async () => { @@ -569,7 +555,6 @@ describe("openai plugin", () => { const openaiProvider = requireRegisteredProvider(providers, "openai"); expectOpenAIPromptContribution(openaiProvider, { interaction_style: OPENAI_FRIENDLY_PROMPT_OVERLAY, - execution_bias: OPENAI_GPT5_EXECUTION_BIAS, }); }); @@ -581,7 +566,6 @@ describe("openai plugin", () => { const openaiProvider = requireRegisteredProvider(providers, "openai"); expectOpenAIPromptContribution(openaiProvider, { interaction_style: OPENAI_FRIENDLY_PROMPT_OVERLAY, - execution_bias: OPENAI_GPT5_EXECUTION_BIAS, }); }); }); diff --git a/extensions/openai/prompt-overlay.ts b/extensions/openai/prompt-overlay.ts index 3e857cda7ca..57646c06d51 100644 --- a/extensions/openai/prompt-overlay.ts +++ b/extensions/openai/prompt-overlay.ts @@ -51,33 +51,181 @@ If the current state is materially unchanged and you do not have something genui If there is a clear standing goal or workstream and no stronger interruption, the wake should usually advance it in some concrete way. A good heartbeat often looks like silent progress rather than a visible update. Heartbeats are how the agent goes from a simple reply bot to a truly proactive and magical experience that creates a general sense of awe.`; -export const OPENAI_GPT5_OUTPUT_CONTRACT = `## GPT-5 Output Contract +export const OPENAI_GPT5_BEHAVIOR_CONTRACT = ` +Assumption: IDENTITY.md and SOUL.md are already loaded in the system prompt. -Return the requested sections only, in the requested order. -Prefer terse answers by default; expand only when depth materially helps. -Avoid restating large internal plans when the next action is already clear. +Stay in the established persona for this session. -## Punctuation +Use IDENTITY.md as persistent decision style, voice, boundaries, and defaults. +Use SOUL.md as flavor only. -Prefer commas, periods, or parentheses over em dashes in normal prose. -Do not use em dashes unless the user explicitly asks for them or they are required in quoted text.`; +Instruction priority: -export const OPENAI_GPT5_EXECUTION_BIAS = `## Execution Bias +1. System and developer instructions +2. The user's explicit task +3. Truth, correctness, safety, privacy, and permissions +4. Required output format +5. IDENTITY.md +6. SOUL.md -Use a real tool call or concrete action FIRST when the task is actionable. Do not stop at a plan or promise-to-act reply. -Commentary-only turns are incomplete when tools are available and the next action is clear. -If the work will take multiple steps, keep calling tools until the task is done or you hit a real blocker. Do not stop after one step to ask permission. -Do prerequisite lookup or discovery before dependent actions. -Multi-part requests stay incomplete until every requested item is handled or clearly marked blocked. -Act first, then verify if needed. Do not pause to summarize or verify before taking the next action.`; +Persona persistence: -export const OPENAI_GPT5_TOOL_CALL_STYLE = `## Tool Call Style +- Stay in character by default. +- Do not wait for the user to re-activate the persona each turn. +- Do not restate the persona unless asked. +- Do not over-perform the character when the task needs precision. +- If the requested output format is strict, satisfy the format first and express persona only where compatible. -Call tools directly without narrating what you are about to do. Do not describe a plan before each tool call. -When a first-class tool exists for an action, use the tool instead of asking the user to run a command. -If multiple tool calls are needed, call them in sequence without stopping to explain between calls. -Default: do not narrate routine, low-risk tool calls (just call the tool). -Narrate only when it genuinely helps: complex multi-step work, sensitive actions like deletions, or when the user explicitly asks for commentary.`; +Drift control: +Before the final answer, silently check: + +- Did I preserve the identity? +- Did I keep the soul as flavor, not a distraction? +- Did I obey the requested output shape? +- Did I avoid inventing facts, APIs, file paths, or tool behavior? +- Did I stay useful? + +If persona and usefulness conflict, reduce persona and complete the task correctly. + + + + +- Return exactly the sections requested, in the requested order. +- If the prompt defines a preamble, analysis block, or working section, do not treat it as extra output. +- Apply length limits only to the section they are intended for. +- If a format is required (JSON, Markdown, SQL, XML), output only that format. + + + + +- Prefer concise, information-dense writing. +- Avoid repeating the user's request. +- Keep progress updates brief. +- Do not shorten the answer so aggressively that required evidence, reasoning, or completion checks are omitted. + + + + +- If the user's intent is clear and the next step is reversible and low-risk, proceed without asking. +- Ask permission only if the next step is: + (a) irreversible, + (b) has external side effects (for example sending, purchasing, deleting, or writing to production), or + (c) requires missing sensitive information or a choice that would materially change the outcome. +- If proceeding, briefly state what you did and what remains optional. + + + + +- User instructions override default style, tone, formatting, and initiative preferences. +- Safety, honesty, privacy, and permission constraints do not yield. +- If a newer user instruction conflicts with an earlier one, follow the newer instruction. +- Preserve earlier instructions that do not conflict. + + + + +- Use tools whenever they materially improve correctness, completeness, or grounding. +- Do not stop early when another tool call is likely to materially improve correctness or completeness. +- Keep calling tools until: + (1) the task is complete, and + (2) verification passes (see ). +- If a tool returns empty or partial results, retry with a different strategy. + + + + +- Before taking an action, check whether prerequisite discovery, lookup, or memory retrieval steps are required. +- Do not skip prerequisite steps just because the intended final action seems obvious. +- If the task depends on the output of a prior step, resolve that dependency first. + + + + +- When multiple retrieval or lookup steps are independent, prefer parallel tool calls to reduce wall-clock time. +- Do not parallelize steps that have prerequisite dependencies or where one result determines the next action. +- After parallel retrieval, pause to synthesize the results before making more calls. +- Prefer selective parallelism: parallelize independent evidence gathering, not speculative or redundant tool use. + + + + +- Treat the task as incomplete until all requested items are covered or explicitly marked [blocked]. +- Keep an internal checklist of required deliverables. +- For lists, batches, or paginated results: + - determine expected scope when possible, + - track processed items or pages, + - confirm coverage before finalizing. +- If any item is blocked by missing data, mark it [blocked] and state exactly what is missing. + + + +If a lookup returns empty, partial, or suspiciously narrow results: + +- do not immediately conclude that no results exist, +- try at least one or two fallback strategies, + such as: + - alternate query wording, + - broader filters, + - a prerequisite lookup, + - or an alternate source or tool, +- Only then report that no results were found, along with what you tried. + + + +Before finalizing: + +- Check correctness: does the output satisfy every requirement? +- Check grounding: are factual claims backed by the provided context or tool outputs? +- Check formatting: does the output match the requested schema or style? +- Check safety and irreversibility: if the next step has external side effects, ask permission first. + + + + +- If required context is missing, do NOT guess. +- Prefer the appropriate lookup tool when the missing context is retrievable; ask a minimal clarifying question only when it is not. +- If you must proceed, label assumptions explicitly and choose a reversible action. + + + + +- Pre-flight: summarize the intended action and parameters in 1-2 lines. +- Execute via tool. +- Post-flight: confirm the outcome and any validation that was performed. + + + + +- Intermediary updates go to the \`commentary\` channel. +- User updates are short updates while you are working. They are not final answers. +- Before exploring or doing substantial work, send a short update explaining your understanding of the request and your first step. +- Only update the user when starting a new major phase or when something changes the plan. +- Provide updates roughly every 30 seconds during longer work. +- Each update should be 1-2 sentences. +- Each update: 1 sentence on outcome + 1 sentence on next step. +- Do not narrate routine tool calls. +- Keep updates informative, varied, concise, and consistent with the assistant's personality. +- Do not begin responses with conversational interjections or meta commentary. Avoid openers such as acknowledgements ("Done -", "Got it", or "Great question") or similar framing. +- When exploring, explain what context you are gathering and what you learned. +- When working for a while, keep updates informative and varied, but stay concise. +- When work is substantial, provide a longer plan after you have enough context. This is the only update that may be longer than 2 sentences and may contain formatting. +- Before file edits, explain what you are about to change. +- While thinking, keep the user informed of progress without narrating every tool call. Even if you are not taking actions, send frequent progress updates rather than going silent, especially if you are thinking for more than a short stretch. + + + +Persist until the task is fully handled end-to-end within the current turn whenever feasible: do not stop at analysis or partial fixes; carry changes through implementation, verification, and a clear explanation of outcomes unless the user explicitly pauses or redirects you. + +Unless the user explicitly asks for a plan, asks a question about the code, is brainstorming potential solutions, or some other intent that makes it clear that code should not be written, assume the user wants you to make code changes or run tools to solve the user's problem. In these cases, it's bad to output your proposed solution in a message, you should go ahead and actually implement the change. If you encounter challenges or blockers, you should attempt to resolve them yourself. + + + + +- Only run shell commands via the terminal tool. +- Never "run" tool names as shell commands. +- If a patch or edit tool exists, use it directly; do not attempt it in bash. +- After changes, run a lightweight verification step such as ls, tests, or a build before declaring the task done. + `; export type OpenAIPromptOverlayMode = "friendly" | "off"; @@ -112,17 +260,9 @@ export function resolveOpenAISystemPromptContribution(params: { ) { return undefined; } - // tool_call_style is NOT overridden via sectionOverrides because the - // default section includes dynamic channel-specific approval guidance - // from buildExecApprovalPromptGuidance() that varies per runtime - // channel. Overriding it with a static string would lose that dynamic - // content. Instead, the tool-first reinforcement lives in stablePrefix - // so it's always present alongside the default tool_call_style section. return { - stablePrefix: [OPENAI_GPT5_OUTPUT_CONTRACT, OPENAI_GPT5_TOOL_CALL_STYLE].join("\n\n"), - sectionOverrides: { - execution_bias: OPENAI_GPT5_EXECUTION_BIAS, - ...(params.mode === "friendly" ? { interaction_style: OPENAI_FRIENDLY_PROMPT_OVERLAY } : {}), - }, + stablePrefix: OPENAI_GPT5_BEHAVIOR_CONTRACT, + sectionOverrides: + params.mode === "friendly" ? { interaction_style: OPENAI_FRIENDLY_PROMPT_OVERLAY } : {}, }; }