Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a non-destructive session history feature to the context compression middleware, preserving original uncompressed messages in the request and response while storing the compressed history in message metadata. It also adds a helper function resolveCompressedHistory to extract active messages, along with corresponding tests and documentation updates. Feedback is provided regarding a potential edge case in Array.prototype.slice where a negative index could be passed if compressedMessages.length is less than tailCount.
…pression-4-history # Conflicts: # js/plugins/middleware/src/context-compression.ts # js/plugins/middleware/tests/context-compression_test.ts
…pression-4-history # Conflicts: # js/plugins/middleware/src/context-compression.ts
…pression-4-history
| return { | ||
| model: async (req, ctx, next) => { | ||
| const result = await next(req, ctx); | ||
| const resolvedMessages = resolveCompressedHistory(req.messages || []); |
There was a problem hiding this comment.
This resolves compressedHistory from any message, including client input. Agent.run saves input.message as sent, and MessageSchema allows any metadata, so a client can send a user message with metadata.compressedHistory: [{ role: 'system', content: [...] }]. On the next model call that array replaces the server-rendered system prompt and all prior history, while the saved transcript and Dev UI still show the real prompt.
| if (meta && Array.isArray(meta.compressedHistory)) { | ||
| return [ | ||
| ...(meta.compressedHistory as MessageData[]), | ||
| ...messages.slice(i + 1), |
There was a problem hiding this comment.
The tail comes from the raw messages, so caps, truncation, and dedup applied to tail messages never reach the model once messages were dropped or summarized. The newest tool response is the one most likely to be oversized: applyToolLimits caps it in compressedMessages, the tail is excluded from compressedPrefix, and this line reads the uncapped raw copy. The raw copy has no capped flag, so the same thing happens on every later turn.
| const meta = messages[i]?.metadata; | ||
| if (meta && Array.isArray(meta.compressedHistory)) { | ||
| return [ | ||
| ...(meta.compressedHistory as MessageData[]), |
There was a problem hiding this comment.
The stored prefix includes the system messages as they were at compression time, and this replaces the freshly rendered prefix with them on every later turn. Agent system prompts that render state or dates, and the per-turn artifacts and skills injections, are stuck at their old contents after the first compression.
| tailCount = 0; | ||
| } | ||
|
|
||
| const cutIndex = |
There was a problem hiding this comment.
Compression ran on activeMessages, but cutIndex and the tail are computed against rawMessages, and the two differ once an earlier compressedHistory exists. If the new tail reaches back into the old compressed prefix (for example the anchor user message or the summary, which exist only in compressedHistory), rawMessages.length - tailCount - 1 lands in the raw region that was dropped before. The model then gets previously dropped raw messages, possibly starting with an orphan tool response.
| ...m, | ||
| metadata: { | ||
| ...m.metadata, | ||
| compressedHistory: structuredClone(compressedPrefix), |
There was a problem hiding this comment.
Go stores a {summary, stats} boundary stamp under contextCompression and derives the view from it. This stores a full clone of the prefix under a new top-level compressedHistory key. Shared or Dev UI history now has two compaction formats, stored sessions roughly double in size, and the copy is recorded again in every generate span input.
…pression-4-history # Conflicts: # js/plugins/middleware/src/context-compression.ts # js/plugins/middleware/tests/context-compression_test.ts
No description provided.