Skip to content

Commit 27330b8

Browse files
logancyangclaude
andcommitted
feat: implement auto-compact context with map-reduce summarization
- Add ContextCompactor class for map-reduce style context summarization - Implement auto-compact when context exceeds configurable threshold - Track note paths for tags/folders in L3 segment metadata for L2 deduplication - Add loadAndAddChatHistory for streamlined chat history loading - Store compacted paths in envelope metadata for multi-turn deduplication - Fix: Only track L3 context paths in compactedPaths (excludes L5 user message files) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 538033e commit 27330b8

17 files changed

+1128
-198
lines changed

src/LLMProviders/chainRunner/AutonomousAgentChainRunner.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { err2String, getMessageRole, withSuppressedTokenWarnings } from "@/utils
1313
import { formatErrorChunk, processToolResults } from "@/utils/toolResultUtils";
1414
import { AIMessage, BaseMessage, HumanMessage } from "@langchain/core/messages";
1515
import { CopilotPlusChainRunner } from "./CopilotPlusChainRunner";
16-
import { addChatHistoryToMessages } from "./utils/chatHistoryUtils";
16+
import { loadAndAddChatHistory } from "./utils/chatHistoryUtils";
1717
import {
1818
joinPromptSections,
1919
messageRequiresTools,
@@ -280,7 +280,11 @@ ${params}
280280

281281
logInfo("[Agent] Using envelope-based context construction");
282282

283-
const context = await this.prepareAgentConversation(userMessage, chatModel);
283+
const context = await this.prepareAgentConversation(
284+
userMessage,
285+
chatModel,
286+
options.updateLoadingMessage
287+
);
284288

285289
try {
286290
const loopResult = await this.executeAgentLoop({
@@ -364,11 +368,13 @@ ${params}
364368
*
365369
* @param userMessage - The initiating user message from the UI.
366370
* @param chatModel - The active chat model instance.
371+
* @param updateLoadingMessage - Optional callback to show loading status.
367372
* @returns Aggregated context required for the autonomous agent loop.
368373
*/
369374
private async prepareAgentConversation(
370375
userMessage: ChatMessage,
371-
chatModel: any
376+
chatModel: any,
377+
updateLoadingMessage?: (message: string) => void
372378
): Promise<AgentRunContext> {
373379
const conversationMessages: ConversationMessage[] = [];
374380
const iterationHistory: string[] = [];
@@ -391,10 +397,8 @@ ${params}
391397
debug: false,
392398
});
393399

394-
// Get memory for chat history
400+
// Get memory for chat history loading
395401
const memory = this.chainManager.memoryManager.getMemory();
396-
const memoryVariables = await memory.loadMemoryVariables({});
397-
const rawHistory = memoryVariables.history || [];
398402

399403
// Build system message: L1+L2 from envelope + tool-only sections
400404
const systemMessage = baseMessages.find((m) => m.role === "system");
@@ -432,14 +436,14 @@ ${params}
432436
});
433437
}
434438

435-
// Insert L4 (chat history) between system and user
436-
addChatHistoryToMessages(rawHistory, conversationMessages);
437-
438439
// Extract L5 for original prompt and adapter enhancement
439440
const l5User = envelope.layers.find((l) => l.id === "L5_USER");
440441
const l5Text = l5User?.text || "";
441442
const originalUserPrompt = l5Text || userMessage.originalMessage || userMessage.message;
442443

444+
// Insert L4 (chat history) between system and user
445+
await loadAndAddChatHistory(memory, conversationMessages);
446+
443447
// Extract user content (L3 smart references + L5) from base messages
444448
const userMessageContent = baseMessages.find((m) => m.role === "user");
445449
if (userMessageContent) {

src/LLMProviders/chainRunner/CopilotPlusChainRunner.ts

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import { getApiErrorMessage, getMessageRole, withSuppressedTokenWarnings } from
2929
import { BaseChatModel } from "@langchain/core/language_models/chat_models";
3030
import { BaseChainRunner } from "./BaseChainRunner";
3131
import { ActionBlockStreamer } from "./utils/ActionBlockStreamer";
32-
import { addChatHistoryToMessages } from "./utils/chatHistoryUtils";
32+
import { loadAndAddChatHistory } from "./utils/chatHistoryUtils";
3333
import {
3434
addFallbackSources,
3535
formatSourceCatalog,
@@ -337,7 +337,12 @@ OUTPUT ONLY XML - NO OTHER TEXT.`;
337337
*/
338338
private async extractImagesFromContextBlock(
339339
l3Text: string,
340-
source: { tagName: string; identifierTag: string; displayName: string; useForResolution: boolean }
340+
source: {
341+
tagName: string;
342+
identifierTag: string;
343+
displayName: string;
344+
useForResolution: boolean;
345+
}
341346
): Promise<string[]> {
342347
// Match the context block
343348
const blockRegex = new RegExp(`<${source.tagName}>([\\s\\S]*?)<\\/${source.tagName}>`);
@@ -353,7 +358,9 @@ OUTPUT ONLY XML - NO OTHER TEXT.`;
353358
if (!content) return [];
354359

355360
// Extract identifier (path or url) for logging and optional resolution
356-
const identifierRegex = new RegExp(`<${source.identifierTag}>(.*?)<\\/${source.identifierTag}>`);
361+
const identifierRegex = new RegExp(
362+
`<${source.identifierTag}>(.*?)<\\/${source.identifierTag}>`
363+
);
357364
const identifierMatch = identifierRegex.exec(block);
358365
const identifier = identifierMatch ? identifierMatch[1] : undefined;
359366

@@ -471,8 +478,18 @@ OUTPUT ONLY XML - NO OTHER TEXT.`;
471478
// - displayName: human-readable name for logs
472479
// - useForResolution: whether to use identifier for vault path resolution
473480
const contextSources = [
474-
{ tagName: "active_note", identifierTag: "path", displayName: "active note", useForResolution: true },
475-
{ tagName: "active_web_tab", identifierTag: "url", displayName: "active web tab", useForResolution: false },
481+
{
482+
tagName: "active_note",
483+
identifierTag: "path",
484+
displayName: "active note",
485+
useForResolution: true,
486+
},
487+
{
488+
tagName: "active_web_tab",
489+
identifierTag: "url",
490+
displayName: "active web tab",
491+
useForResolution: false,
492+
},
476493
];
477494

478495
for (const source of contextSources) {
@@ -548,12 +565,11 @@ OUTPUT ONLY XML - NO OTHER TEXT.`;
548565
allToolOutputs: any[],
549566
abortController: AbortController,
550567
thinkStreamer: ThinkBlockStreamer,
551-
originalUserQuestion: string
568+
originalUserQuestion: string,
569+
updateLoadingMessage?: (message: string) => void
552570
): Promise<void> {
553-
// Get chat history
571+
// Get memory for chat history loading
554572
const memory = this.chainManager.memoryManager.getMemory();
555-
const memoryVariables = await memory.loadMemoryVariables({});
556-
const rawHistory = memoryVariables.history || [];
557573

558574
// Get chat model
559575
const chatModel = this.chainManager.chatModelManager.getChatModel();
@@ -589,9 +605,9 @@ OUTPUT ONLY XML - NO OTHER TEXT.`;
589605
}
590606

591607
// Insert L4 (chat history) between system and user
592-
addChatHistoryToMessages(rawHistory, messages);
608+
await loadAndAddChatHistory(memory, messages);
593609

594-
// Find user message (L3 smart references + L5)
610+
// Process user message (L3 smart references + L5)
595611
const userMessageContent = baseMessages.find((m) => m.role === "user");
596612
if (userMessageContent) {
597613
let finalUserContent;
@@ -826,7 +842,8 @@ OUTPUT ONLY XML - NO OTHER TEXT.`;
826842
allToolOutputs,
827843
abortController,
828844
thinkStreamer,
829-
cleanedUserMessage
845+
cleanedUserMessage,
846+
updateLoadingMessage
830847
);
831848
} catch (error: any) {
832849
// Reset loading message to default

src/LLMProviders/chainRunner/LLMChainRunner.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import { LayerToMessagesConverter } from "@/context/LayerToMessagesConverter";
33
import { logInfo } from "@/logger";
44
import { getSettings } from "@/settings/model";
55
import { ChatMessage } from "@/types/message";
6-
import { extractChatHistory, findCustomModel, withSuppressedTokenWarnings } from "@/utils";
6+
import { findCustomModel, withSuppressedTokenWarnings } from "@/utils";
77
import { BaseChainRunner } from "./BaseChainRunner";
8+
import { loadAndAddChatHistory } from "./utils/chatHistoryUtils";
89
import { recordPromptPayload } from "./utils/promptPayloadRecorder";
910
import { ThinkBlockStreamer } from "./utils/ThinkBlockStreamer";
1011
import { getModelKey } from "@/aiParams";
@@ -24,19 +25,13 @@ export class LLMChainRunner extends BaseChainRunner {
2425

2526
logInfo("[LLMChainRunner] Using envelope-based context");
2627

27-
// Get chat history from memory (L4)
28-
const memory = this.chainManager.memoryManager.getMemory();
29-
const memoryVariables = await memory.loadMemoryVariables({});
30-
const chatHistory = extractChatHistory(memoryVariables);
31-
3228
// Convert envelope to messages (L1 system + L2+L3+L5 user)
3329
const baseMessages = LayerToMessagesConverter.convert(userMessage.contextEnvelope, {
3430
includeSystemMessage: true,
3531
mergeUserContent: true,
3632
debug: false,
3733
});
3834

39-
// Insert L4 (chat history) between system and user
4035
const messages: any[] = [];
4136

4237
// Add system message (L1)
@@ -46,9 +41,8 @@ export class LLMChainRunner extends BaseChainRunner {
4641
}
4742

4843
// Add chat history (L4)
49-
for (const entry of chatHistory) {
50-
messages.push({ role: entry.role, content: entry.content });
51-
}
44+
const memory = this.chainManager.memoryManager.getMemory();
45+
await loadAndAddChatHistory(memory, messages);
5246

5347
// Add user message (L2+L3+L5 merged)
5448
const userMessageContent = baseMessages.find((m) => m.role === "user");

src/LLMProviders/chainRunner/VaultQAChainRunner.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
withSuppressedTokenWarnings,
1616
} from "@/utils";
1717
import { BaseChainRunner } from "./BaseChainRunner";
18+
import { loadAndAddChatHistory } from "./utils/chatHistoryUtils";
1819
import {
1920
formatSourceCatalog,
2021
getQACitationInstructions,
@@ -175,9 +176,7 @@ export class VaultQAChainRunner extends BaseChainRunner {
175176
}
176177

177178
// Insert L4 (chat history) between system and user
178-
for (const entry of chatHistory) {
179-
messages.push({ role: entry.role, content: entry.content });
180-
}
179+
await loadAndAddChatHistory(memory, messages);
181180

182181
// Add user message with RAG prepended
183182
// User message now contains: RAG results + citations + L3 smart references + L5

0 commit comments

Comments
 (0)