You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix: use originalWorkdir for Primary working directory to preserve prompt cache
Use immutable originalWorkdir instead of dynamic workdir (which tracks
cd changes) for the Primary working directory field in the system prompt
<env> section. This prevents CWD changes from invalidating the cached
system prompt prefix.
Changes:
- buildSystemPrompt: add originalWorkdir option, use it for Primary
working directory field (renamed from Working directory)
- enhanceSystemPromptWithEnvDetails: add originalWorkdir parameter
- aiManager: pass originalWorkdir at buildSystemPrompt call site
- Remove [Working Directory] section from buildPostCompactContext
(Claude Code doesn't include it; redundant with system prompt)
- Update spec 021 FR-002, User Story 5, and edge cases
- Add research documentation for CWD stability decision
Copy file name to clipboardExpand all lines: packages/agent-sdk/src/prompts/index.ts
+4-2Lines changed: 4 additions & 2 deletions
Original file line number
Diff line number
Diff line change
@@ -236,6 +236,7 @@ export function buildSystemPrompt(
236
236
tools: ToolPlugin[],
237
237
options: {
238
238
workdir?: string;
239
+
originalWorkdir?: string;
239
240
memory?: string;
240
241
language?: string;
241
242
isSubagent?: boolean;
@@ -283,7 +284,7 @@ export function buildSystemPrompt(
283
284
284
285
Here is useful information about the environment you are running in:
285
286
<env>
286
-
Working directory: ${options.workdir}${worktreeSession ? `\nThis is a git worktree — an isolated copy of the repository. Run all commands from this directory. Do NOT \`cd\` to the original repository root at ${worktreeSession.originalCwd}.` : ""}
287
+
Primary working directory: ${options.originalWorkdir??options.workdir}${worktreeSession ? `\nThis is a git worktree — an isolated copy of the repository. Run all commands from this directory. Do NOT \`cd\` to the original repository root at ${worktreeSession.originalCwd}.` : ""}
287
288
Is directory a git repo: ${isGitRepo}
288
289
Platform: ${platform}
289
290
Shell: ${shellName}
@@ -310,6 +311,7 @@ Today's date: ${today}
310
311
exportfunctionenhanceSystemPromptWithEnvDetails(
311
312
existingSystemPrompt: string,
312
313
workdir: string,
314
+
originalWorkdir?: string,
313
315
): string{
314
316
constisGitRepo=isGitRepository(workdir);
315
317
constplatform=os.platform();
@@ -336,7 +338,7 @@ ${notes}
336
338
337
339
Here is useful information about the environment you are running in:
338
340
<env>
339
-
Working directory: ${workdir}${worktreeSession ? `\nThis is a git worktree — an isolated copy of the repository. Run all commands from this directory. Do NOT \`cd\` to the original repository root at ${worktreeSession.originalCwd}.` : ""}
341
+
Primary working directory: ${originalWorkdir??workdir}${worktreeSession ? `\nThis is a git worktree — an isolated copy of the repository. Run all commands from this directory. Do NOT \`cd\` to the original repository root at ${worktreeSession.originalCwd}.` : ""}
- Simplified cache metrics: Rejected due to insufficient cost tracking granularity
115
115
- Breaking usage interface changes: Rejected for backward compatibility requirements
116
116
117
+
## System Prompt Stability: CWD Changes
118
+
119
+
**Decision**: Use `originalWorkdir` (immutable) instead of `workdir` (dynamic, tracks `cd`) for the `Primary working directory` field in the system prompt's `<env>` section.
120
+
121
+
**Rationale**:
122
+
- The system prompt is rebuilt every AI turn with `this.getWorkdir()` (current CWD from DI container)
123
+
- When an agent runs `cd subdir` in Bash, `workdir` updates, causing the `<env>` section to change
124
+
- This invalidates the entire cached system prompt prefix, negating cache benefits
125
+
- Claude Code accidentally avoids this by caching/freezing the env section at first computation
126
+
- Using `originalWorkdir` (set once at session start, never updated) keeps the `<env>` section stable
127
+
- The model still learns about CWD changes from the Bash tool's `"Shell working directory changed to X"` output
128
+
- The `buildPostCompactContext``[Working Directory]` section was removed since it's redundant (system prompt already shows `Primary working directory`) and could vary across compactions
129
+
130
+
**Implementation**:
131
+
-`buildSystemPrompt` options: added `originalWorkdir?: string` field
-`buildPostCompactContext`: removed `[Working Directory]` section (Claude Code doesn't include it)
136
+
137
+
**Alternatives considered**:
138
+
- Show both `Primary working directory` and `Current shell directory`: Rejected because adding a varying field to the `<env>` section would still break prompt cache
139
+
- Reset CWD after every bash command (like Claude Code's opt-in `CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR`): Rejected as too disruptive to agent workflow; the model may legitimately need to run commands in a subdirectory
140
+
- Freeze/cached the env section like Claude Code: Rejected because Wave rebuilds the system prompt every turn (intentionally, for freshness of other fields like date); the better fix is to make the env section content stable
141
+
117
142
## Type Safety Strategy
118
143
119
144
**Decision**: Create Claude-specific type extensions without breaking existing contracts
Copy file name to clipboardExpand all lines: specs/021-prompt-cache-control/spec.md
+3-1Lines changed: 3 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -86,6 +86,7 @@ As a user who switches between permission modes (e.g., default → plan → acce
86
86
2.**Given** plan mode is active, **When** the system sends the next API request, **Then** plan mode instructions MUST appear as `<system-reminder>` wrapped user messages in the messages array, not in the system prompt.
87
87
3.**Given** the user exits plan mode, **When** the next API request is made, **Then** the system prompt MUST remain unchanged and usage tracking SHOULD show cache_read_input_tokens indicating a cache hit on the system message.
88
88
4.**Given** a non-Claude model is configured, **When** the user enters plan mode, **Then** plan mode instructions still appear as `<system-reminder>` user messages (the injection pattern is model-agnostic, but caching benefits only apply to Claude models).
89
+
5.**Given** a Claude model is configured and the system prompt has been cached, **When** the agent changes CWD via `cd subdir` in the Bash tool, **Then** the system prompt's `Primary working directory` field MUST remain unchanged (showing the original project root), and usage tracking SHOULD show cache_read_input_tokens indicating a cache hit on the system message.
89
90
90
91
---
91
92
@@ -96,13 +97,14 @@ As a user who switches between permission modes (e.g., default → plan → acce
96
97
-**Edge Case 3**: Empty conversation history MUST skip user message caching, apply system message caching only
97
98
-**Edge Case 4**: Streaming and non-streaming requests MUST apply identical cache_control transformation logic
98
99
-**Edge Case 5**: Token tracking MUST handle missing cache token fields gracefully (treat undefined as 0)
100
+
-**Edge Case 6**: CWD changes via `cd` in Bash MUST NOT change the system prompt's `Primary working directory` field (it uses immutable `originalWorkdir`), preserving the cached system prompt prefix
99
101
100
102
## Requirements *(mandatory)*
101
103
102
104
### Functional Requirements
103
105
104
106
-**FR-001**: System MUST detect cache-supporting models using the `WAVE_PROMPT_CACHE_REGEX` environment variable (default: "claude"), which allows configurable regex patterns for model matching
105
-
-**FR-002**: System MUST add cache_control markers with type "ephemeral" to the first system message when using Claude models. This ensures core instructions are always cached even if reminders are added later. The system prompt MUST remain constant across plan mode transitions — plan mode instructions are injected as `<system-reminder>` user messages rather than system prompt changes to preserve the cached system prompt prefix.
107
+
-**FR-002**: System MUST add cache_control markers with type "ephemeral" to the first system message when using Claude models. This ensures core instructions are always cached even if reminders are added later. The system prompt MUST remain constant across plan mode transitions — plan mode instructions are injected as `<system-reminder>` user messages rather than system prompt changes to preserve the cached system prompt prefix. The `<env>` section's `Primary working directory` field MUST use the immutable `originalWorkdir` (set once at session start) rather than the dynamic `workdir` (which tracks `cd` changes), so that CWD changes do not invalidate the cached system prompt.
106
108
-**FR-003**: System MUST create a cache marker when total message count reaches multiples of 20 (20, 40, 60, etc.)
107
109
-**FR-004**: System MUST NOT create cache markers when total message count is below 20 or not a multiple of 20
108
110
-**FR-005**: System MUST maintain cache markers at the most recent multiple-of-20 message position (sliding window)
0 commit comments