Repository navigation
fix(session): keep the cached system prompt prefix stable across steps - #1631
RaghwendraSingh wants to merge 1 commit into
Conversation
Two pieces of text in the cached system prompt changed between requests of the same session, so the provider prompt cache missed and the whole prefix (system prompt plus tool schemas) was written again at the full input rate: - The Altimate Workspace section rendered one sentence for a fresh link check and another for a memoised one, so it flipped on the 30 s memo timer. A re-check that outlasted the 1.5 s deadline also marked the previous outcome stale, which dropped the workspace contents and memory line. The section now renders one hedged sentence per link state, and a slow re-check reuses the previous outcome as it was. Only the resolver marks a link stale. - Hoisted reminders (the post-compaction task pin, the plan switch) were appended to the system array on non-Anthropic-like models, which includes the Altimate gateway model. They now ride in the trailing environment message, after the cache breakpoint, and still reach the model as system text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. |
|
Thanks for your contribution! This PR doesn't have a linked issue. All PRs must reference an existing issue. Please:
See CONTRIBUTING.md for details. |
Request-body capture:
|
| scenario | build | agent requests | distinct first-system hashes | changes |
|---|---|---|---|---|
| 9 steps, 50 s apart (crosses the 5-min miss memo) | main |
9 | 2 | 2 (requests 7 and 8) |
| this branch | 9 | 1 | 0 | |
| compaction on step 2 | main |
5 | 2 | 1 (first request after compaction) |
| this branch | 5 | 1 | 0 |
Timer, main. At t≈301 s the miss memo expires and that step's resolve asks the server itself, so it renders the fresh wording; 50 s later the step reads the memoised miss again and renders the other wording:
-No Altimate Workspace was linked to this project as of the last check, up to five minutes ago; a link made elsewhere since then would not show yet.
+No Altimate Workspace is linked to this project.and back at request 8. That is two full-prefix cache writes every five minutes on an unlinked project. On this branch all nine requests have the same first system message.
Compaction, main. The first request after the compaction adds the hoisted Original task — authoritative over any summary… reminder to the first system message. On this branch that message is unchanged, and the reminder is in the trailing system message after the breakpoint.
On both builds the tools hash and the number of cache-marked system messages (2) are constant within each run.
|
Thanks for updating your PR! It now meets our contributing guidelines. 👍 |
Issue for this PR
Closes #1633
Type of change
What does this PR do?
The system prompt is the cached prefix of every request, so any change to it between two requests of one session is a full cache miss: the gateway bills the whole prefix (system prompt plus tool schemas, about 75K tokens on a typical repo) again as cache writes at the full input rate, instead of as cache reads at 0.1×. Two parts of the prompt changed for reasons unrelated to the task.
1. The Altimate Workspace section flipped on a timer (
altimate/workspace/identity.ts).lastKnownmarked the previous (fresh) outcome stale. For a linked repo that removed the workspace contents and the team-memory line from the prompt for that step.Fix: one hedged sentence per link state, true whether or not the last check is fresh ("…as of the last check. The link is re-checked every few minutes, so a change made elsewhere may not show yet."), and a slow re-check reuses the previous outcome as it was. Only the resolver marks a link stale (the server could not confirm it), and that case still withholds contents and the memory line, as before.
2. Hoisted reminders went into the cached system message (
session/prompt.ts).After a compaction, the original-task pin (and the plan-switch reminders) are hoisted out of the user turn for models that are not Anthropic-like (#887/#888). The
altimate-backend/altimate-defaultmodel is registered as OpenAI-compatible, so it takes that path, and the hoisted text was appended to the system array, which is the message carrying the cache breakpoint. The first request after every compaction therefore missed on the whole prefix, not just on the replaced history.Fix: the hoisted text goes with the trailing environment system message, after the breakpoint (
llm.tsalready splits that message out for providers with explicit breakpoints). It is still sent as system text, so the #887 behaviour is unchanged.Behaviour change to review: the model no longer distinguishes "last known" from "is linked" in the identity sentence; both read as "as of the last check". The stale-and-unconfirmed case still drops contents and the memory line.
Not in this PR: freezing the whole system prompt per session (skills synced from the workspace, memory injection and team rules can still change it mid-session, though on events rather than a timer), and a per-request prefix-hash log/counter.
How did you verify your code works?
New
test/session/system-prefix-stability.test.ts: on the gateway model, twoLLM.streamrequests with and without a hoisted task pin have a byte-identical first system message (cache marker included), and the pin appears only in the trailing message. A source guard keeps hoisted reminders out of the system array.identity.test.ts: stale and fresh renders are byte-identical for unbound and for bound (pinned × team memory).identity-section.test.ts: a verified link whose re-check outlasts the deadline renders byte-identical to the previous step (memory line kept); a memoised miss renders byte-identical to the server's own answer.The new tests fail against
main(checked by reverting the source files) and pass on this branch.Updated the tests that asserted the old stale wording, including the v0.12.1 release-contract tests.
bun test(packages/opencode): 19,937 pass, 11 fail. The same tests fail onmain(mcp.headers,mcp HttpApistatus,--time-budgetcases injson-output.test.ts), exceptapply_patch"Unicode punctuation", which timed out under load and passes alone. Thejson-outputtime-budget cases are timing-sensitive: "silent-turn followup" failed 2/8 runs on bothmainand this branch.Typecheck clean;
script/check-tracker-leaks.tsandanalyze.ts --markers --strictpass.Request-body capture of both builds against a local mock gateway (comment): on
mainthe first system message changed twice in a 7-minute session on an unlinked project, and once after a compaction; on this branch it was byte-identical across every request in both scenarios.Still to add: a short gateway A/B (
cache_readon the request after a timer flip and after a compaction).Cost side effects
Reduces cost; adds no model calls, queries or resources. In a headless evaluation of about 5,400 requests, the system-prompt hash changed 785 times; 315 of those were full mid-session cache misses, and prefix re-writes were about 43% of billed tokens. This PR removes the two causes identified there; the remaining misses need the per-request hash log to attribute.
Screenshots / recordings
N/A
Checklist
🤖 Generated with Claude Code