🤖 fix: invalidate cached memory context on memory changes and context reset - #3764
🤖 fix: invalidate cached memory context on memory changes and context reset#3764ibetitsmike wants to merge 1 commit into
Conversation
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f7dbb66cdd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
384dc95 to
bb237d4
Compare
f7dbb66 to
935a940
Compare
|
@codex review |
|
Codex Review: Didn't find any major issues. Delightful! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
dd8e248 to
9ed2e61
Compare
935a940 to
c42ef91
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c42ef91d8e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
9ed2e61 to
bb9d398
Compare
c42ef91 to
a62f7d2
Compare
9ed2e61 to
bb9d398
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a62f7d22b3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
a62f7d2 to
6fb63b5
Compare
bb9d398 to
061f154
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6fb63b5189
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
6fb63b5 to
82282f5
Compare
061f154 to
64b67af
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 82282f5512
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
82282f5 to
a93608a
Compare
64b67af to
9cd5ca7
Compare
|
Codex Review: Didn't find any major issues. Can't wait for the next one! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
b6c950f to
8aa83ea
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4753b55db8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
8aa83ea to
ae33861
Compare
4753b55 to
827981c
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 827981c5a8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
ae33861 to
d7693eb
Compare
827981c to
65df018
Compare
|
@codex review |
|
Codex Review: Didn't find any major issues. Nice work! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
d7693eb to
170e1f3
Compare
65df018 to
463fb35
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 463fb352d6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
170e1f3 to
5209d9a
Compare
463fb35 to
22cf298
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 22cf298193
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
22cf298 to
825c5cd
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 825c5cd852
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
825c5cd to
4211b69
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4211b69950
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…eset The per-session memory context cache (index snapshot + hot memories) was cleared only at compaction boundaries, so deleted or renamed memory files kept appearing in the advertised index for the rest of the segment. Now any MemoryService change event invalidates all live sessions' caches, and resetContext invalidates the affected session. Rebuild stays lazy on the next stream. Tradeoff: a memory write mid-segment costs one prompt cache miss on the next request; correct index beats cache stability.
4211b69 to
fdac4dc
Compare
|
@codex review |
|
Codex Review: Didn't find any major issues. Can't wait for the next one! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Summary
Invalidates the per-session cached memory context (memory index snapshot +
<hot_memories>block) whenever memory files change or the workspace context is reset, so agents stop seeing index entries for files that no longer exist.Stack position 2 of 3 (base: #3763; child: #3765).
Background
AgentSession.memoryContextByModelStringwas cleared only when a pending compaction boundary was consumed.MemoryServiceemits change events, but only the ORPC UI layer subscribed, andWorkspaceService.resetContextdid not clear the cache either. A deleted or renamed memory file therefore stayed in the advertised memory index for the rest of the session segment.Implementation
AgentSession.invalidateMemoryContext()clears the cache and bumps a generation counter; a build that races an invalidation is rebuilt before returning, so the current request also sees the post-invalidation state (bounded at 3 attempts so invalidation churn cannot livelock a stream; the final snapshot is then served uncached).AIService.streamMessagere-resolves the context immediately before tool assembly and once more after it (a cache hit is the identical reference; an invalidation during any async preparation window forces a rebuild). A post-assembly change refreshes the already-created memory tool's description and feeds the fresh snapshot into the final system-prompt rebuild; the hot-set upgrade await is covered the same way, so a change landing during it cannot pair a fresh system block with a stale tool index; the refusal-fallback prepare() resolves a fresh context for the fallback model (the hot-set experiment only chooses whether the hot block is included, so renames since the original assembly are picked up even with hot preloading off) and refreshes its rebuilt toolset's description the same way. A final bounded revalidation runs at the request boundary, after the last awaited preparation stage (system-prompt rebuild, tokenizer loading, message preparation, history append): if the snapshot moved, the memory tool description and system prompt are reassembled before startStream, which is reached synchronously from there. The refusal-fallback prepare() runs the same bounded boundary revalidation after its own awaited stages. resetContext's busy guard also covers transient startup-recovery sessions and re-checks after the history read, so a recovery retry that turns busy mid-reset rejects the reset instead of getting a boundary appended into its active turn. If a boundary-revalidation rebuild fails after the assistant placeholder was persisted, the placeholder is deleted (like the abort path) so history is not left with an empty trailing assistant turn. startStream additionally invokes a refresh hook after its own awaited setup (workspace mutex, stream safety, temp-dir creation) and immediately before atomic registration, so a memory change during those awaits still reaches the provider request; a failed rebuild there keeps the request-boundary snapshot instead of failing the stream. Every refresh site rebuilds the memory tool description through one shared helper that re-appends configuredTool: memoryinstructions, so custom instructions survive description refreshes instead of being replaced by the bare index. Once a stream is registered, prepareStep re-runs the bounded revalidation before every tool-loop step (each step is its own provider request): on change the memory tool description is updated in place (the SDK re-reads descriptions from the same reference per step; there is no per-step tools override) and the system prompt is swapped via a per-step instructions override or an exact-match replacement of the cached system message, so a memory change during a long tool execution reaches the next step instead of waiting for the next turn. The refusal-fallback prepare() returns the same hook bound to the fallback toolset. A failed per-step rebuild keeps the current snapshot instead of failing the tool loop. Structured system messages (OpenAI explicit prompt-cache breakpoint) keep their providerOptions across refreshes, and each applied refresh writes the recomputed system token count into the live stream metadata so stream-end attribution matches what was sent.WorkspaceService.invalidateMemoryContexts()fans out to all live sessions, including transient startup-recovery sessions, and is wired toMemoryServicechange events incoreServices. Invalidation is not filtered per mutation type, but it is scoped by the change event: global changes fan out to all sessions, workspace/project changes only to matching sessions. Project matching resolves each session's memory project identity synchronously from its config entry (the same identity the event'sprojectPathis built from), so scratch chats, whose identity is their scratch directory rather than a config bucket key, match correctly. Invalidation runs synchronously inside the change emit, so a mutation route cannot return with a stale cache still in place.WorkspaceService.resetContext()invalidates immediately after the reset boundary is appended and emitted, before awaiting goal acknowledgment, covering transient startup-recovery sessions tracked outside the normal session map; a recovery retry firing during that await cannot build from the pre-reset snapshot. The reset also refuses to run while a turn is active, a startup-recovery auto-retry is queued or merely scheduled, or a startup-recovery check is still mid-decision: the guard resolves transient recovery sessions too and runs both at entry and again after the history read, before the boundary append, so a retry timer firing during that append cannot capture pre-reset history. The recovery check is symmetric: at its retry decision it compares a per-workspace reset-boundary epoch against the value from before its history reads and defers when a reset overlapped them, so the re-run reads post-boundary history.MemoryService.setPinnedthat writes the meta sidecar and emits the same change event, since pins feed hot-set selection.Accepted tradeoff: a memory write mid-segment now costs one prompt-cache miss on the next request for that session.
Validation
Integration tests drive a real
MemoryServiceend to end: create → cache → delete/rename → change event → next-built context reflects disk. A dedicated test pins the in-flight-build race (invalidation during an awaited build must not repopulate the cache).Risks
Low-medium. Touches the request-building path for every provider call, but the only behavior change is when the cache is dropped; rebuild code is unchanged. Worst-case regression is extra prompt-cache misses, not incorrect context.
Generated with
mux• Model:anthropic:claude-fable-5• Thinking:xhigh