Skip to content

feat(js/middleware/context-compression): Add history tracking and resolution - #6270

Open
ssbushi wants to merge 7 commits into
sb/context-compression-3-summarizefrom
sb/context-compression-4-history
Open

ssbushi wants to merge 7 commits into
sb/context-compression-3-summarizefrom
sb/context-compression-4-history

Conversation

@ssbushi

@ssbushi ssbushi commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

No description provided.

@github-actions github-actions Bot added docs Improvements or additions to documentation js labels Sep 2, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread js/plugins/middleware/src/context-compression.ts Outdated
@pavelgj pavelgj changed the title feat(middleware/context-compression): Add history tracking and resolution feat(js/middleware/context-compression): Add history tracking and resolution Sep 10, 2026
…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
@ssbushi
ssbushi marked this pull request as ready for review September 24, 2026 18:55
return {
model: async (req, ctx, next) => {
const result = await next(req, ctx);
const resolvedMessages = resolveCompressedHistory(req.messages || []);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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[]),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 =

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Improvements or additions to documentation js

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants