-
Notifications
You must be signed in to change notification settings - Fork 1.1k
fix(claude): stabilize Responses instructions for Muse/Go prompt cache #4052
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
Draft
Warexpor
wants to merge
3
commits into
lidge-jun:dev
Choose a base branch
from
Warexpor:cursor/feat-claude-instructions-cache-stabilize-915b
base: dev
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
e3a4330
fix(claude): stabilize Responses instructions for Muse/Go prompt cache
cursoragent f027fba
fix(claude): hash Desktop prompt_cache_key from stabilized instructions
cursoragent f58fa1a
fix(claude): match only canonical total_tokens and TaskCreate notices
cursoragent File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| # Claude instructions cache stabilize (OCXFIX) | ||
|
|
||
| OpenCodex inbound conversion puts Claude Code system text into OpenAI Responses | ||
| `instructions`. Claude Code then appends a growing `<total_tokens>…</total_tokens>` | ||
| footer (and occasional TaskCreate nudges) on every turn. That prefix churn | ||
| causes prompt-cache misses on Muse/Go. | ||
|
|
||
| This change strips those dynamic footers from `instructions` and reattaches the | ||
| latest notice as a trailing `input` message so the cacheable prefix stays stable. | ||
|
|
||
| ## Paper | ||
|
|
||
| See [PAPER_OCXFIX.pdf](./PAPER_OCXFIX.pdf) (Warexpor). | ||
|
|
||
| Measured cache-hit rates on Muse Spark 1.3 via OpenCode Go / OpenCodex: | ||
|
|
||
| | Slice | Baseline mean | OCXFIX mean | | ||
| | --- | ---: | ---: | | ||
| | Claude Code (n=75) | 0.168384 | 0.864374 | | ||
| | Claude S/T4 (n=5) | 0.134922 | 0.982700 | | ||
| | Grok Build (n=75) | 0.966526 | 0.941345 | | ||
|
|
||
| Cause: Anthropic→Responses conversion stores Claude system text in `instructions`; | ||
| Claude Code appends growing `<total_tokens>` (and rare TaskCreate nudges), so | ||
| `instructions_sha` changes every turn. Grok traffic has no `instructions` field | ||
| and is the control. | ||
|
|
||
| Code: `src/claude/inbound-cache-stabilize.ts`, wired from `src/claude/inbound.ts`. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| /** | ||
| * Claude Code appends growing `<total_tokens>…</total_tokens>` footers (and | ||
| * occasional TaskCreate nudges) into system text that becomes Responses | ||
| * `instructions`. That churn breaks Muse/Go prefix cache on the instructions | ||
| * prefix even when tools stay stable. Strip dynamics from instructions; | ||
| * surface the latest notice on `input` instead. | ||
| * | ||
| * Only canonical harness notices are stripped: a standalone integer | ||
| * `<total_tokens>` line, and the exact TaskCreate reminder paragraph. | ||
| * Inline documentation of those tags/tools is left in `instructions`. | ||
| */ | ||
|
|
||
| /** Standalone harness footer: own line, integer payload, not mid-sentence docs. */ | ||
| const TOTAL_TOKENS_RE = | ||
| /(?:^|\r?\n)[ \t]*(<total_tokens>\d+<\/total_tokens>)[ \t]*(?=\r?\n|$)/g; | ||
|
|
||
| /** Canonical Claude Code reminder: distinctive opening sentence through closing reminder. */ | ||
| const TASKCREATE_NUDGE_RE = | ||
| /(?:^|\r?\n)[ \t]*(The task tools haven't been used recently\.\s+If you're working on tasks that would benefit from tracking, consider using TaskCreate to add them\.\s+Only use these if relevant to the current work\.\s+This is just a gentle reminder - ignore if not applicable\.)[ \t]*(?=\r?\n|$)/g; | ||
|
|
||
| export function stabilizeClaudeInstructionsForPromptCache( | ||
| instructions: string, | ||
| ): { instructions: string; dynamicNotice: string | null } { | ||
| if (!instructions) { | ||
| return { instructions: "", dynamicNotice: null }; | ||
| } | ||
|
|
||
| let latestTotal: string | null = null; | ||
| for (const m of instructions.matchAll(TOTAL_TOKENS_RE)) { | ||
| latestTotal = m[1] ?? m[0]; | ||
| } | ||
|
|
||
| let latestNudge: string | null = null; | ||
| for (const m of instructions.matchAll(TASKCREATE_NUDGE_RE)) { | ||
| latestNudge = (m[1] ?? m[0]).trim(); | ||
| } | ||
|
|
||
| let cleaned = instructions.replace(TOTAL_TOKENS_RE, ""); | ||
| cleaned = cleaned.replace(TASKCREATE_NUDGE_RE, ""); | ||
| cleaned = cleaned.replace(/\n{3,}/g, "\n\n").trim(); | ||
|
|
||
| const noticeParts: string[] = []; | ||
| if (latestTotal) noticeParts.push(latestTotal); | ||
| if (latestNudge) noticeParts.push(latestNudge); | ||
| const dynamicNotice = noticeParts.length > 0 ? noticeParts.join("\n\n") : null; | ||
|
|
||
| return { instructions: cleaned, dynamicNotice }; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
135 changes: 135 additions & 0 deletions
135
tests/claude-integration/claude-inbound-cache-stabilize.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| import { describe, expect, test } from "bun:test"; | ||
| import { stabilizeClaudeInstructionsForPromptCache } from "../../src/claude/inbound-cache-stabilize"; | ||
| import { anthropicToResponsesTranslation } from "../../src/claude/inbound"; | ||
|
|
||
| const TASKCREATE_NUDGE = [ | ||
| "The task tools haven't been used recently. If you're working on tasks that would benefit from tracking, consider using TaskCreate to add them.", | ||
| "Only use these if relevant to the current work. This is just a gentle reminder - ignore if not applicable.", | ||
| ].join(" "); | ||
|
|
||
| function footer(used: number): string { | ||
| return `<total_tokens>${used}</total_tokens>`; | ||
| } | ||
|
|
||
| describe("stabilizeClaudeInstructionsForPromptCache", () => { | ||
| test("empty input is a no-op", () => { | ||
| expect(stabilizeClaudeInstructionsForPromptCache("")).toEqual({ | ||
| instructions: "", | ||
| dynamicNotice: null, | ||
| }); | ||
| }); | ||
|
|
||
| test("stable instructions without dynamics pass through", () => { | ||
| const instructions = "You are Claude Code.\n\nPrefer terse answers."; | ||
| expect(stabilizeClaudeInstructionsForPromptCache(instructions)).toEqual({ | ||
| instructions, | ||
| dynamicNotice: null, | ||
| }); | ||
| }); | ||
|
|
||
| test("three total_tokens footers keep only the latest in the notice", () => { | ||
| const stable = "You are Claude Code."; | ||
| const first = footer(1000); | ||
| const second = footer(4000); | ||
| const third = footer(8000); | ||
| const result = stabilizeClaudeInstructionsForPromptCache( | ||
| [stable, first, second, third].join("\n\n"), | ||
| ); | ||
| expect(result.instructions).toBe(stable); | ||
| expect(result.instructions).not.toContain("<total_tokens>"); | ||
| expect(result.dynamicNotice).toBe(third); | ||
| }); | ||
|
|
||
| test("instructions contain zero total_tokens after stripping", () => { | ||
| const result = stabilizeClaudeInstructionsForPromptCache( | ||
| `System.\n${footer(1)}\nMore system.\n${footer(3)}`, | ||
| ); | ||
| expect(result.instructions).not.toMatch(/<total_tokens>/); | ||
| expect(result.instructions).toContain("System."); | ||
| expect(result.instructions).toContain("More system."); | ||
| expect(result.dynamicNotice).toBe(footer(3)); | ||
| }); | ||
|
|
||
| test("TaskCreate nudge is stripped from instructions and kept in the notice", () => { | ||
| const stable = "You are Claude Code."; | ||
| const result = stabilizeClaudeInstructionsForPromptCache( | ||
| `${stable}\n\n${TASKCREATE_NUDGE}`, | ||
| ); | ||
| expect(result.instructions).toBe(stable); | ||
| expect(result.instructions).not.toContain("TaskCreate"); | ||
| expect(result.dynamicNotice).toBe(TASKCREATE_NUDGE); | ||
| }); | ||
|
|
||
| test("latest footer and latest nudge both surface in the notice", () => { | ||
| const stable = "Stay stable."; | ||
| const older = footer(10); | ||
| const latest = footer(50); | ||
| const result = stabilizeClaudeInstructionsForPromptCache( | ||
| [stable, older, TASKCREATE_NUDGE, latest].join("\n\n"), | ||
| ); | ||
| expect(result.instructions).toBe(stable); | ||
| expect(result.dynamicNotice).toBe(`${latest}\n\n${TASKCREATE_NUDGE}`); | ||
| }); | ||
|
|
||
| test("inline documentation of total_tokens tags stays in instructions", () => { | ||
| const docs = "The harness may emit a <total_tokens>123</total_tokens> footer; do not invent one."; | ||
| const result = stabilizeClaudeInstructionsForPromptCache(docs); | ||
| expect(result.instructions).toBe(docs); | ||
| expect(result.dynamicNotice).toBeNull(); | ||
| }); | ||
|
|
||
| test("TaskCreate mentioned in docs is not treated as the harness nudge", () => { | ||
| const docs = "The task tools haven't been used recently. You may mention TaskCreate in docs without the reminder."; | ||
| const result = stabilizeClaudeInstructionsForPromptCache(docs); | ||
| expect(result.instructions).toBe(docs); | ||
| expect(result.dynamicNotice).toBeNull(); | ||
| }); | ||
|
|
||
| test("docs plus a real footer keep the docs and move only the latest footer", () => { | ||
| const docs = "Describe <total_tokens>0</total_tokens> in the protocol guide."; | ||
| const latest = footer(8000); | ||
| const result = stabilizeClaudeInstructionsForPromptCache( | ||
| [docs, footer(1), latest].join("\n\n"), | ||
| ); | ||
| expect(result.instructions).toBe(docs); | ||
| expect(result.dynamicNotice).toBe(latest); | ||
| }); | ||
| }); | ||
|
|
||
| describe("anthropicToResponsesTranslation cache-stabilize wire-in", () => { | ||
| test("moves the latest total_tokens footer onto a trailing input user message", () => { | ||
| const first = footer(1000); | ||
| const latest = footer(8000); | ||
| const { body } = anthropicToResponsesTranslation({ | ||
| model: "m", | ||
| max_tokens: 1, | ||
| system: ["You are Claude Code.", first, latest].join("\n\n"), | ||
| messages: [{ role: "user", content: "hi" }], | ||
| }); | ||
| expect(body.instructions).toBe("You are Claude Code."); | ||
| expect(String(body.instructions)).not.toContain("<total_tokens>"); | ||
| const input = body.input as Array<Record<string, unknown>>; | ||
| const last = input[input.length - 1]!; | ||
| expect(last).toEqual({ | ||
| type: "message", | ||
| role: "user", | ||
| content: [{ type: "input_text", text: latest }], | ||
| }); | ||
| expect(input.some(item => item.role === "user" && item !== last)).toBe(true); | ||
| }); | ||
|
|
||
| test("Desktop prompt_cache_key fallback hashes stabilized instructions, not total_tokens footers", () => { | ||
| const stable = "You are Claude Code."; | ||
| const keyOf = (system: string) => | ||
| anthropicToResponsesTranslation({ | ||
| model: "m", | ||
| max_tokens: 1, | ||
| system, | ||
| messages: [{ role: "user", content: "hi" }], | ||
| }).body.prompt_cache_key as string; | ||
| const stableKey = keyOf(stable); | ||
| expect(stableKey).toMatch(/^[0-9a-f]{32}$/); | ||
| expect(keyOf([stable, footer(1000), footer(8000)].join("\n\n"))).toBe(stableKey); | ||
| expect(keyOf([stable, footer(99999)].join("\n\n"))).toBe(stableKey); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.