fix(compaction): inject workspace context into summarization

The compaction system was creating context-blind summaries because
the summarization LLM never saw the workspace context files (AGENTS.md,
TOOLS.md, MEMORY.md, etc.). While contextFiles were correctly resolved
during session creation, they were not passed through to the
summarization call chain.

This fix:
- Extends CompactionSafeguardRuntimeValue type to include workspaceContext
- Serializes contextFiles in compact.ts and stores them in the runtime
- Retrieves and injects workspace context into summarization instructions
  in compaction-safeguard.ts

The summarizing LLM now receives the actual workspace file contents,
allowing it to generate summaries that preserve:
- User identity and preferences
- Agent identity and capabilities
- Tool configurations and infrastructure
- Ongoing work items and project context

This is a proper architectural fix replacing the previous heuristic
approach that only added generic hints to the instructions.

Fixes context loss after compaction in workspace-aware sessions.
This commit is contained in:
Jeremy Corbello 2026-01-29 07:41:04 -06:00
parent 5f4715acfc
commit 5ca0224fed
3 changed files with 40 additions and 3 deletions

View File

@ -69,6 +69,7 @@ import type { EmbeddedPiCompactResult } from "./types.js";
import { formatUserTime, resolveUserTimeFormat, resolveUserTimezone } from "../date-time.js";
import { describeUnknownError, mapThinkingLevel, resolveExecToolDefaults } from "./utils.js";
import { buildTtsSystemPromptHint } from "../../tts/tts.js";
import { setCompactionSafeguardRuntime } from "../pi-extensions/compaction-safeguard-runtime.js";
export type CompactEmbeddedPiSessionParams = {
sessionId: string;
@ -210,6 +211,12 @@ export async function compactEmbeddedPiSessionDirect(
sessionId: params.sessionId,
warn: makeBootstrapWarn({ sessionLabel, warn: (message) => log.warn(message) }),
});
const workspaceContextForCompaction = contextFiles
.filter((f) => f.content?.trim())
.map((f) => `## ${f.path}\n${f.content}`)
.join("\n\n---\n\n");
const runAbortController = new AbortController();
const toolsRaw = createMoltbotCodingTools({
exec: {
@ -365,6 +372,10 @@ export async function compactEmbeddedPiSessionDirect(
allowSyntheticToolResults: transcriptPolicy.allowSyntheticToolResults,
});
trackSessionManagerAccess(params.sessionFile);
setCompactionSafeguardRuntime(sessionManager, {
workspaceContext: workspaceContextForCompaction || undefined,
});
const settingsManager = SettingsManager.create(effectiveWorkspace, agentDir);
ensurePiCompactionReserveTokens({
settingsManager,

View File

@ -1,5 +1,6 @@
export type CompactionSafeguardRuntimeValue = {
maxHistoryShare?: number;
workspaceContext?: string;
};
// Session-scoped runtime registry keyed by object identity.

View File

@ -177,6 +177,31 @@ export default function compactionSafeguardExtension(api: ExtensionAPI): void {
const runtime = getCompactionSafeguardRuntime(ctx.sessionManager);
const maxHistoryShare = runtime?.maxHistoryShare ?? 0.5;
const workspaceContext = runtime?.workspaceContext;
const buildEnhancedInstructions = (baseInstructions?: string): string | undefined => {
if (!workspaceContext) {
return baseInstructions;
}
const contextSection = `
## Workspace Context Files
The following files define this workspace's identity, user, and configuration:
${workspaceContext}
IMPORTANT: Your summary MUST preserve:
- References to projects, tasks, and goals mentioned in these context files
- User identity and preferences from USER.md
- Agent identity and capabilities from SOUL.md or AGENTS.md
- Tool configurations and infrastructure from TOOLS.md
- Any ongoing work items referenced in MEMORY.md
The summary will be used to continue this work - ensure the next agent session understands WHO the user is, WHAT they're working on, and WHY.`;
return baseInstructions ? `${baseInstructions}\n\n${contextSection}` : contextSection;
};
const enhancedCustomInstructions = buildEnhancedInstructions(customInstructions);
const tokensBefore =
typeof preparation.tokensBefore === "number" && Number.isFinite(preparation.tokensBefore)
@ -228,7 +253,7 @@ export default function compactionSafeguardExtension(api: ExtensionAPI): void {
reserveTokens: Math.max(1, Math.floor(preparation.settings.reserveTokens)),
maxChunkTokens: droppedMaxChunkTokens,
contextWindow: contextWindowTokens,
customInstructions,
customInstructions: enhancedCustomInstructions,
previousSummary: preparation.previousSummary,
});
} catch (droppedError) {
@ -261,7 +286,7 @@ export default function compactionSafeguardExtension(api: ExtensionAPI): void {
reserveTokens,
maxChunkTokens,
contextWindow: contextWindowTokens,
customInstructions,
customInstructions: enhancedCustomInstructions,
previousSummary: effectivePreviousSummary,
});
@ -275,7 +300,7 @@ export default function compactionSafeguardExtension(api: ExtensionAPI): void {
reserveTokens,
maxChunkTokens,
contextWindow: contextWindowTokens,
customInstructions: TURN_PREFIX_INSTRUCTIONS,
customInstructions: buildEnhancedInstructions(TURN_PREFIX_INSTRUCTIONS),
previousSummary: undefined,
});
summary = `${historySummary}\n\n---\n\n**Turn Context (split turn):**\n\n${prefixSummary}`;