Skip to content

fix: move context files after cache boundary to prevent invalidation#61422

Closed
daniel-rudaev wants to merge 10000 commits into
openclaw:mainfrom
D1DX:fix/cache-context-files-order
Closed

fix: move context files after cache boundary to prevent invalidation#61422
daniel-rudaev wants to merge 10000 commits into
openclaw:mainfrom
D1DX:fix/cache-context-files-order

Conversation

@daniel-rudaev

@daniel-rudaev daniel-rudaev commented Apr 5, 2026

Copy link
Copy Markdown

Summary

Context files (MEMORY.md, SOUL.md, USER.md, etc.) are currently rendered in the stable prefix before SYSTEM_PROMPT_CACHE_BOUNDARY. When memory plugins write between turns, the prefix changes and Anthropic's prompt cache is invalidated — resulting in ~0% cache hit rate for anyone using workspace files that change between turns.

This PR moves the # Project Context section to after the cache boundary, keeping the stable prefix (system prompt, identity, tools, skills, silent replies) constant across turns.

  • Dynamic context files were already placed after the boundary and are unaffected.
  • No logic changes — pure block move.

Impact

Measured on a production instance using memory-core plugin with frequent MEMORY.md writes:

  • Before: 0% cache hit rate (every turn invalidated the prefix)
  • After: cache reads observed (cache_read_input_tokens > 0 starting from turn 2)

This affects anyone using workspace files that are modified between turns (memory plugins, auto-updated context files, etc.).

Test plan

  • Verify prompt ordering: stable prefix → cache boundary → context files → dynamic context → extra system prompt
  • Verify cache_read_input_tokens > 0 on consecutive turns with Anthropic provider
  • Verify no regression in context file rendering (files still appear in system prompt)

steipete and others added 30 commits April 5, 2026 08:57
* plugins: include resolved workspaceDir in provider hook cache keys

resolveProviderPluginsForHooks, resolveProviderPluginsForCatalogHooks, and
resolveProviderRuntimePlugin used the raw params.workspaceDir for cache keys
and plugin-id discovery while resolvePluginProviders already fell back to
the active registry workspace. Resolve workspaceDir once at the top of each
function so cache keys, candidate filtering, and loading all use the same
workspace root.

* fix(plugins): inherit runtime workspace for snapshot loads

* test(gateway): stub runtime registry seam

* fix(plugins): restore workspace fallback after rebase

---------

Co-authored-by: Vincent Koc <[email protected]>
…8943) (thanks @imechZhangLY)

* fix(infra): windows-task-restart fallback to startup entry when schtasks task is unregistered

* fix code style problem

* use /min for startup fallback and assert schtasks pre-check in test

* fix: windows restart fallback when scheduled task is unregistered (#58943) (thanks @imechZhangLY)

---------

Co-authored-by: Luyao Zhang <[email protected]>
Co-authored-by: Ayaan Zaidi <[email protected]>
steipete and others added 17 commits April 5, 2026 16:20
…fahmedjoy)

* fix: implement Windows stale gateway process cleanup before restart

findGatewayPidsOnPortSync() returned [] immediately on Windows, causing
cleanStaleGatewayProcessesSync() to skip killing old gateway processes
during self-restart (triggerOpenClawRestart -> schtasks path). This led
to an infinite retry loop: 'gateway already running under schtasks;
waiting 5000ms before retrying startup'.

Changes:
- Extract Windows port/process helpers into shared windows-port-pids.ts
  to break the circular import between restart-stale-pids.ts and
  gateway-processes.ts, with configurable timeoutMs for poll compliance
- findGatewayPidsOnPortSync: discover + verify Windows gateway PIDs via
  readWindowsListeningPidsOnPortSync + readWindowsProcessArgsSync
- pollPortOnceWindows: use short POLL_SPAWN_TIMEOUT_MS (400ms) so a
  single slow PowerShell call cannot exceed the 2s polling budget
- terminateStaleProcessesSync: add terminateStaleProcessesWindows using
  taskkill.exe (graceful /T first, then /F force-kill)

Fixes the Windows gateway restart infinite loop caused by the schtasks
supervisor detecting a port conflict it cannot resolve.

* fix: tighten windows stale gateway cleanup

* fix: preserve windows restart probe failures

* refactor: unify windows gateway pid verification

* fix: preserve windows argv probe failures

* fix: windows self-restart stale gateway cleanup (#60480) (thanks @arifahmedjoy)

---------

Co-authored-by: Ayaan Zaidi <[email protected]>
* fix(memory): avoid recursive provider discovery during register

* test(memory): remove resetModules from provider adapter regression

* fix: avoid recursive provider discovery during register (#61402) (thanks @ngutman)
Context files (MEMORY.md, SOUL.md, USER.md, etc.) were rendered in the
stable prefix before SYSTEM_PROMPT_CACHE_BOUNDARY. When memory plugins
write between turns, the prefix changes and Anthropic's prompt cache
is invalidated — resulting in 0% cache hit rate.

Move the "# Project Context" section to after the cache boundary so
that the stable prefix (system prompt, identity, tools, skills) remains
constant across turns. Dynamic context files were already after the
boundary and are unaffected.

Measured impact: 0% → cache hits observed after the fix on a production
instance using memory-core plugin with frequent MEMORY.md writes.
@greptile-apps

greptile-apps Bot commented Apr 5, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Moves the # Project Context block (MEMORY.md, SOUL.md, AGENTS.md, etc.) to after SYSTEM_PROMPT_CACHE_BOUNDARY so that memory-plugin writes between turns no longer invalidate the stable cached prefix. The implementation in src/agents/system-prompt.ts is correct, but the test at line 686 of src/agents/system-prompt.test.ts was not updated: line 710 asserts expect(boundaryIndex).toBeGreaterThan(memoryIndex), which describes the old ordering and will fail in CI with the new code.

Confidence Score: 4/5

Safe to merge after fixing the stale test assertion that directly contradicts the new ordering.

The implementation correctly addresses the cache invalidation problem described in the PR. One P1 test assertion on line 710 inverts the expected index relationship and will cause a CI failure.

src/agents/system-prompt.test.ts — the test at line 686 needs its assertion on line 710 and its name updated to match the new post-boundary ordering.

Comments Outside Diff (2)

  1. src/agents/system-prompt.test.ts, line 686-716 (link)

    P1 Stale test assertion contradicts new ordering

    Line 710 asserts expect(boundaryIndex).toBeGreaterThan(memoryIndex) — the boundary comes after MEMORY.md (old behavior). After this PR, all stable context files are placed after the boundary, so memoryIndex > boundaryIndex. This assertion will fail in CI. The test name also describes the old behavior ("orders stable project context before the cache boundary").

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/agents/system-prompt.test.ts
    Line: 686-716
    
    Comment:
    **Stale test assertion contradicts new ordering**
    
    Line 710 asserts `expect(boundaryIndex).toBeGreaterThan(memoryIndex)` — the boundary comes after MEMORY.md (old behavior). After this PR, all stable context files are placed after the boundary, so `memoryIndex > boundaryIndex`. This assertion will fail in CI. The test name also describes the old behavior ("orders stable project context **before** the cache boundary").
    
    
    
    How can I resolve this? If you propose a fix, please make it concise.
  2. src/agents/system-prompt.ts, line 711-713 (link)

    P2 Stale comment contradicts new behavior

    This comment says "Keep large stable prompt context above this seam", but the code immediately below now places stable context files (SOUL.md, AGENTS.md, etc.) after the seam. Update it to describe the new intent.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/agents/system-prompt.ts
    Line: 711-713
    
    Comment:
    **Stale comment contradicts new behavior**
    
    This comment says "Keep large stable prompt context above this seam", but the code immediately below now places stable context files (SOUL.md, AGENTS.md, etc.) after the seam. Update it to describe the new intent.
    
    
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/agents/system-prompt.test.ts
Line: 686-716

Comment:
**Stale test assertion contradicts new ordering**

Line 710 asserts `expect(boundaryIndex).toBeGreaterThan(memoryIndex)` — the boundary comes after MEMORY.md (old behavior). After this PR, all stable context files are placed after the boundary, so `memoryIndex > boundaryIndex`. This assertion will fail in CI. The test name also describes the old behavior ("orders stable project context **before** the cache boundary").

```suggestion
  it("places all context files after the cache boundary, preserving stable-file sort order", () => {
    const prompt = buildAgentSystemPrompt({
      workspaceDir: "/tmp/openclaw",
      contextFiles: [
        { path: "HEARTBEAT.md", content: "Check inbox." },
        { path: "MEMORY.md", content: "Long-term notes." },
        { path: "AGENTS.md", content: "Follow repo rules." },
        { path: "SOUL.md", content: "Warm but direct." },
        { path: "TOOLS.md", content: "Prefer rg." },
      ],
    });

    const agentsIndex = prompt.indexOf("## AGENTS.md");
    const soulIndex = prompt.indexOf("## SOUL.md");
    const toolsIndex = prompt.indexOf("## TOOLS.md");
    const memoryIndex = prompt.indexOf("## MEMORY.md");
    const boundaryIndex = prompt.indexOf(SYSTEM_PROMPT_CACHE_BOUNDARY);
    const heartbeatHeadingIndex = prompt.indexOf("# Dynamic Project Context");
    const heartbeatFileIndex = prompt.indexOf("## HEARTBEAT.md");

    expect(agentsIndex).toBeGreaterThan(-1);
    // All stable context files appear after the cache boundary
    expect(agentsIndex).toBeGreaterThan(boundaryIndex);
    // Stable files maintain their defined sort order
    expect(soulIndex).toBeGreaterThan(agentsIndex);
    expect(toolsIndex).toBeGreaterThan(soulIndex);
    expect(memoryIndex).toBeGreaterThan(toolsIndex);
    // Dynamic context follows stable context
    expect(heartbeatHeadingIndex).toBeGreaterThan(boundaryIndex);
    expect(heartbeatFileIndex).toBeGreaterThan(heartbeatHeadingIndex);
    expect(prompt).toContain(
      "The following frequently-changing project context files are kept below the cache boundary when possible:",
    );
  });
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/agents/system-prompt.ts
Line: 711-713

Comment:
**Stale comment contradicts new behavior**

This comment says "Keep large stable prompt context above this seam", but the code immediately below now places stable context files (SOUL.md, AGENTS.md, etc.) after the seam. Update it to describe the new intent.

```suggestion
  // Core instruction sections (identity, tooling, safety, workspace) live above this seam
  // so Anthropic-family transports can reuse them across turns. All context files —
  // including stable ones like AGENTS.md/SOUL.md — live below so memory writes between
  // turns do not invalidate the cached prefix.
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix: move context files after cache boun..." | Re-trigger Greptile

@clawsweeper

clawsweeper Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs changes before merge.

Summary
The PR moves Project Context rendering in buildAgentSystemPrompt from before to after SYSTEM_PROMPT_CACHE_BOUNDARY.

Reproducibility: yes. A high-confidence static reproduction exists by building a prompt with a changing MEMORY.md, checking its position relative to SYSTEM_PROMPT_CACHE_BOUNDARY, and comparing that with Anthropic payload shaping that cache-controls only the prefix.

Next step before merge
A narrow replacement PR can preserve the cache-invalidation goal while keeping stable workspace context cacheable and updating the required guards.

Security
Cleared: The PR only reorders prompt assembly text and does not change dependencies, workflows, permissions, secrets handling, downloads, or execution surfaces.

Review findings

  • [P2] Keep stable context cacheable — src/agents/system-prompt.ts:713-729
  • [P2] Update the prompt-order test — src/agents/system-prompt.test.ts:686-716
Review details

Best possible solution:

Implement a stable-vs-volatile context split: keep AGENTS/SOUL/IDENTITY/USER/TOOLS/BOOTSTRAP cacheable, move only volatile context such as auto-written memory or explicitly marked dynamic context below the boundary, and lock it with tests/docs/changelog.

Do we have a high-confidence way to reproduce the issue?

Yes. A high-confidence static reproduction exists by building a prompt with a changing MEMORY.md, checking its position relative to SYSTEM_PROMPT_CACHE_BOUNDARY, and comparing that with Anthropic payload shaping that cache-controls only the prefix.

Is this the best way to solve the issue?

No. Moving all Project Context below the boundary avoids invalidation but forfeits caching for stable workspace context; a narrower volatility classification is the maintainable fix.

Full review comments:

  • [P2] Keep stable context cacheable — src/agents/system-prompt.ts:713-729
    This moves every stableContextFiles entry after SYSTEM_PROMPT_CACHE_BOUNDARY. The Anthropic shaper only applies cache_control to text before that marker, so stable AGENTS/SOUL/USER/TOOLS/BOOTSTRAP content would stop being cached. Please move only genuinely volatile context below the boundary.
    Confidence: 0.88
  • [P2] Update the prompt-order test — src/agents/system-prompt.test.ts:686-716
    The branch changes Project Context ordering but leaves the existing prompt-order test asserting the old boundary relationship. That will fail or preserve the wrong contract; update it to cover the intended stable-vs-volatile split.
    Confidence: 0.82

Overall correctness: patch is incorrect
Overall confidence: 0.86

Acceptance criteria:

  • pnpm test src/agents/system-prompt.test.ts src/agents/system-prompt-cache-boundary.test.ts src/agents/anthropic-payload-policy.test.ts
  • pnpm exec oxfmt --check --threads=1 src/agents/system-prompt.ts src/agents/system-prompt.test.ts docs/reference/prompt-caching.md docs/concepts/system-prompt.md CHANGELOG.md
  • pnpm check:changed

What I checked:

  • Current main keeps stable context above the boundary: Current main sorts context files, classifies only dynamic files separately, includes stableContextFiles in the stable-prefix cache key, and renders those files before SYSTEM_PROMPT_CACHE_BOUNDARY. (src/agents/system-prompt.ts:758, 4532e5d85803)
  • Dynamic context follows the boundary: Current main renders stableContextFiles as # Project Context, then pushes SYSTEM_PROMPT_CACHE_BOUNDARY, then renders dynamicContextFiles below it. (src/agents/system-prompt.ts:1008, 4532e5d85803)
  • Anthropic cache control only covers the stable prefix: When the marker is present, Anthropic payload shaping emits the text before the boundary with cache_control and emits the suffix without cache control. (src/agents/anthropic-payload-policy.ts:89, 4532e5d85803)
  • Docs describe stable workspace files as cached-prefix content: Prompt-caching docs say workspace files and other relatively static context belong above the boundary, while volatile files such as HEARTBEAT.md belong below it. Public docs: docs/reference/prompt-caching.md. (docs/reference/prompt-caching.md:177, 4532e5d85803)
  • Existing test guards stable context before volatile sections: The current system-prompt test asserts # Project Context appears before the cache boundary and channel/session volatile sections appear after it. (src/agents/system-prompt.test.ts:1040, 4532e5d85803)
  • PR diff moves all stable context below the boundary: The PR removes the stable context block from above the boundary and reinserts it after SYSTEM_PROMPT_CACHE_BOUNDARY, making every Project Context file part of the uncached suffix. (src/agents/system-prompt.ts:713, 6615f97850e1)

Likely related people:

  • vincentkoc: Authored the merged cache-boundary PR that sorted workspace context into stable files above the boundary and HEARTBEAT.md below it. (role: introduced behavior; confidence: high; commits: 41fb09fd8002; files: src/agents/system-prompt.ts, src/agents/system-prompt.test.ts, CHANGELOG.md)
  • Peter Steinberger: Local current-main blame for the prompt assembly and Anthropic cache-policy files points to a recent maintainer refactor touching this area. (role: recent maintainer; confidence: medium; commits: 1466878c36ff; files: src/agents/system-prompt.ts, src/agents/anthropic-payload-policy.ts, docs/reference/prompt-caching.md)

Remaining risk / open question:

  • The replacement fix needs a clear volatile-context rule so frequently rewritten files stop busting the cache without moving stable instruction/profile files out of the cached prefix.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 4532e5d85803.

@daniel-rudaev
daniel-rudaev force-pushed the fix/cache-context-files-order branch from 6615f97 to 1813c29 Compare May 11, 2026 14:08
@openclaw-barnacle openclaw-barnacle Bot added the triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. label May 11, 2026
@daniel-rudaev daniel-rudaev changed the title fix: move context files after cache boundary to prevent invalidation (withdrawn) May 11, 2026
@daniel-rudaev daniel-rudaev changed the title (withdrawn) fix: move context files after cache boundary to prevent invalidation May 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling size: XS triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup.

Projects

None yet

Development

Successfully merging this pull request may close these issues.