From 68cb0914f217fae938973ac608f9767d86729e52 Mon Sep 17 00:00:00 2001 From: opencode Date: Sat, 31 Jan 2026 17:18:58 +0100 Subject: [PATCH 1/6] feat: add experimental compaction prompt and preserve prefix support --- packages/opencode/src/flag/flag.ts | 4 +++ packages/opencode/src/session/compaction.ts | 39 ++++++++++++++++++--- packages/opencode/src/session/prompt.ts | 2 +- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/packages/opencode/src/flag/flag.ts b/packages/opencode/src/flag/flag.ts index 64ae801d18f..91365368f42 100644 --- a/packages/opencode/src/flag/flag.ts +++ b/packages/opencode/src/flag/flag.ts @@ -48,6 +48,10 @@ export namespace Flag { export const OPENCODE_EXPERIMENTAL_MARKDOWN = truthy("OPENCODE_EXPERIMENTAL_MARKDOWN") export const OPENCODE_MODELS_URL = process.env["OPENCODE_MODELS_URL"] export const OPENCODE_MODELS_PATH = process.env["OPENCODE_MODELS_PATH"] + export const OPENCODE_EXPERIMENTAL_COMPACTION_PRESERVE_PREFIX = truthy( + "OPENCODE_EXPERIMENTAL_COMPACTION_PRESERVE_PREFIX", + ) + export const OPENCODE_EXPERIMENTAL_COMPACTION_PROMPT = process.env["OPENCODE_EXPERIMENTAL_COMPACTION_PROMPT"] function number(key: string) { const value = process.env[key] diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index fb382530291..a49426febec 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -14,6 +14,9 @@ import { fn } from "@/util/fn" import { Agent } from "@/agent/agent" import { Plugin } from "@/plugin" import { Config } from "@/config/config" +import { Flag } from "../flag/flag" +import { SystemPrompt } from "./system" +import { InstructionPrompt } from "./instruction" export namespace SessionCompaction { const log = Log.create({ service: "session.compaction" }) @@ -97,7 +100,8 @@ export namespace SessionCompaction { auto: boolean }) { const userMessage = input.messages.findLast((m) => m.info.id === input.parentID)!.info as MessageV2.User - const agent = await Agent.get("compaction") + const usePrefixCache = Flag.OPENCODE_EXPERIMENTAL_COMPACTION_PRESERVE_PREFIX + const agent = usePrefixCache ? await Agent.get(userMessage.agent) : await Agent.get("compaction") const model = agent.model ? await Provider.getModel(agent.model.providerID, agent.model.modelID) : await Provider.getModel(userMessage.model.providerID, userMessage.model.modelID) @@ -132,6 +136,21 @@ export namespace SessionCompaction { model, abort: input.abort, }) + const session = usePrefixCache ? await Session.get(input.sessionID) : undefined + let tools = {} + let system: string[] = [] + if (usePrefixCache && session) { + tools = await SessionPrompt.resolveTools({ + agent, + model, + session, + tools: userMessage.tools, + processor, + bypassAgentCheck: false, + messages: input.messages, + }) + system = [...(await SystemPrompt.environment(model)), ...(await InstructionPrompt.system())] + } // Allow plugins to inject context or replace compaction prompt const compacting = await Plugin.trigger( "experimental.session.compacting", @@ -139,15 +158,27 @@ export namespace SessionCompaction { { context: [], prompt: undefined }, ) const defaultPrompt = - "Provide a detailed prompt for continuing our conversation above. Focus on information that would be helpful for continuing the conversation, including what we did, what we're doing, which files we're working on, and what we're going to do next considering new session will not have access to our conversation." + Flag.OPENCODE_EXPERIMENTAL_COMPACTION_PROMPT ?? + ` +Summarize this development session to continue work seamlessly. Include: + +1. **Current state**: Which files are open/being edited (full paths) +2. **Recent changes**: Code modifications made (additions/deletions per file) +3. **Work context**: Current directory, git branch, any build/test status +4. **Issues encountered**: Errors faced and how they were resolved +5. **Next steps**: Specific actions planned, tools needed, remaining tasks +6. **Critical decisions**: Architectural choices or implementation details to preserve + +The summary must enable another developer to continue exactly where we left off. +` const promptText = compacting.prompt ?? [defaultPrompt, ...compacting.context].join("\n\n") const result = await processor.process({ user: userMessage, agent, abort: input.abort, sessionID: input.sessionID, - tools: {}, - system: [], + tools, + system, messages: [ ...MessageV2.toModelMessages(input.messages, model), { diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 94eabdef7f4..c7812fe125c 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -647,7 +647,7 @@ export namespace SessionPrompt { return Provider.defaultModel() } - async function resolveTools(input: { + export async function resolveTools(input: { agent: Agent.Info model: Provider.Model session: Session.Info From 815b2e7a70c595574dfbf329e4e12215522803fa Mon Sep 17 00:00:00 2001 From: dixoxib <132107919+dixoxib@users.noreply.github.com> Date: Sat, 31 Jan 2026 18:07:44 +0100 Subject: [PATCH 2/6] Update compaction.ts reverted change to default compaction prompt --- packages/opencode/src/session/compaction.ts | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index a49426febec..732adf8738f 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -157,20 +157,9 @@ export namespace SessionCompaction { { sessionID: input.sessionID }, { context: [], prompt: undefined }, ) - const defaultPrompt = - Flag.OPENCODE_EXPERIMENTAL_COMPACTION_PROMPT ?? - ` -Summarize this development session to continue work seamlessly. Include: - -1. **Current state**: Which files are open/being edited (full paths) -2. **Recent changes**: Code modifications made (additions/deletions per file) -3. **Work context**: Current directory, git branch, any build/test status -4. **Issues encountered**: Errors faced and how they were resolved -5. **Next steps**: Specific actions planned, tools needed, remaining tasks -6. **Critical decisions**: Architectural choices or implementation details to preserve - -The summary must enable another developer to continue exactly where we left off. -` + const defaultPrompt = Flag.OPENCODE_EXPERIMENTAL_COMPACTION_PROMPT ?? + "Provide a detailed prompt for continuing our conversation above. Focus on information that would be helpful for continuing the conversation, including what we did, what we're doing, which files we're working on, and what we're going to do next considering new session will not have access to our conversation." + const promptText = compacting.prompt ?? [defaultPrompt, ...compacting.context].join("\n\n") const result = await processor.process({ user: userMessage, From 1c4614a7c036f6f50f56bc9373ad8f0cb9ac22f2 Mon Sep 17 00:00:00 2001 From: opencode Date: Sat, 31 Jan 2026 20:15:26 +0100 Subject: [PATCH 3/6] fix: sync compaction message agent with actual agent when preserving prefix --- packages/opencode/src/session/compaction.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index 732adf8738f..e4a5003f2c5 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -111,7 +111,7 @@ export namespace SessionCompaction { parentID: input.parentID, sessionID: input.sessionID, mode: "compaction", - agent: "compaction", + agent: usePrefixCache ? userMessage.agent : "compaction", summary: true, path: { cwd: Instance.directory, @@ -157,9 +157,10 @@ export namespace SessionCompaction { { sessionID: input.sessionID }, { context: [], prompt: undefined }, ) - const defaultPrompt = Flag.OPENCODE_EXPERIMENTAL_COMPACTION_PROMPT ?? + const defaultPrompt = + Flag.OPENCODE_EXPERIMENTAL_COMPACTION_PROMPT ?? "Provide a detailed prompt for continuing our conversation above. Focus on information that would be helpful for continuing the conversation, including what we did, what we're doing, which files we're working on, and what we're going to do next considering new session will not have access to our conversation." - + const promptText = compacting.prompt ?? [defaultPrompt, ...compacting.context].join("\n\n") const result = await processor.process({ user: userMessage, From 8a5f73dd4e94929d4bbede73347ea7591ba88685 Mon Sep 17 00:00:00 2001 From: opencode Date: Sun, 1 Feb 2026 04:50:14 +0100 Subject: [PATCH 4/6] fix: use compaction agent for processing but original agent's tools/system for prefix caching --- packages/opencode/src/session/compaction.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index e4a5003f2c5..5683533a184 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -101,9 +101,11 @@ export namespace SessionCompaction { }) { const userMessage = input.messages.findLast((m) => m.info.id === input.parentID)!.info as MessageV2.User const usePrefixCache = Flag.OPENCODE_EXPERIMENTAL_COMPACTION_PRESERVE_PREFIX - const agent = usePrefixCache ? await Agent.get(userMessage.agent) : await Agent.get("compaction") - const model = agent.model - ? await Provider.getModel(agent.model.providerID, agent.model.modelID) + const compactionAgent = await Agent.get("compaction") + const originalAgent = usePrefixCache ? await Agent.get(userMessage.agent) : undefined + const agent = compactionAgent + const model = compactionAgent.model + ? await Provider.getModel(compactionAgent.model.providerID, compactionAgent.model.modelID) : await Provider.getModel(userMessage.model.providerID, userMessage.model.modelID) const msg = (await Session.updateMessage({ id: Identifier.ascending("message"), @@ -139,9 +141,9 @@ export namespace SessionCompaction { const session = usePrefixCache ? await Session.get(input.sessionID) : undefined let tools = {} let system: string[] = [] - if (usePrefixCache && session) { + if (usePrefixCache && session && originalAgent) { tools = await SessionPrompt.resolveTools({ - agent, + agent: originalAgent, model, session, tools: userMessage.tools, @@ -183,8 +185,8 @@ export namespace SessionCompaction { ], model, }) - - if (result === "continue" && input.auto) { + console.log(result + " " + input.auto) + if ((result === "continue" || result === "compact") && input.auto) { const continueMsg = await Session.updateMessage({ id: Identifier.ascending("message"), role: "user", From 494f6a18f833e8323a320b4ee5d3d49201fefab8 Mon Sep 17 00:00:00 2001 From: opencode Date: Sun, 1 Feb 2026 05:17:09 +0100 Subject: [PATCH 5/6] fix: add overflow check after compaction and handle compact result properly --- packages/opencode/src/session/compaction.ts | 27 +++++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index 5683533a184..6165d93c0ad 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -101,11 +101,9 @@ export namespace SessionCompaction { }) { const userMessage = input.messages.findLast((m) => m.info.id === input.parentID)!.info as MessageV2.User const usePrefixCache = Flag.OPENCODE_EXPERIMENTAL_COMPACTION_PRESERVE_PREFIX - const compactionAgent = await Agent.get("compaction") - const originalAgent = usePrefixCache ? await Agent.get(userMessage.agent) : undefined - const agent = compactionAgent - const model = compactionAgent.model - ? await Provider.getModel(compactionAgent.model.providerID, compactionAgent.model.modelID) + const agent = usePrefixCache ? await Agent.get(userMessage.agent) : await Agent.get("compaction") + const model = agent.model + ? await Provider.getModel(agent.model.providerID, agent.model.modelID) : await Provider.getModel(userMessage.model.providerID, userMessage.model.modelID) const msg = (await Session.updateMessage({ id: Identifier.ascending("message"), @@ -141,9 +139,9 @@ export namespace SessionCompaction { const session = usePrefixCache ? await Session.get(input.sessionID) : undefined let tools = {} let system: string[] = [] - if (usePrefixCache && session && originalAgent) { + if (usePrefixCache && session) { tools = await SessionPrompt.resolveTools({ - agent: originalAgent, + agent, model, session, tools: userMessage.tools, @@ -186,7 +184,14 @@ export namespace SessionCompaction { model, }) console.log(result + " " + input.auto) - if ((result === "continue" || result === "compact") && input.auto) { + + // Handle continue case with overflow check + if (result === "continue" && input.auto) { + const finalTokens = processor.message.tokens + if (await SessionCompaction.isOverflow({ tokens: finalTokens, model })) { + return "compact" // Need another compaction round + } + const continueMsg = await Session.updateMessage({ id: Identifier.ascending("message"), role: "user", @@ -210,6 +215,12 @@ export namespace SessionCompaction { }, }) } + + // If processor returned compact (overflow during processing), propagate it + if (result === "compact") { + return "compact" + } + if (processor.message.error) return "stop" Bus.publish(Event.Compacted, { sessionID: input.sessionID }) return "continue" From 1218711f5061affbcb05b24720e4af00555faef9 Mon Sep 17 00:00:00 2001 From: dixoxib <132107919+dixoxib@users.noreply.github.com> Date: Sun, 1 Feb 2026 18:09:31 +0100 Subject: [PATCH 6/6] fix autocontinue in compaction.ts usePrefixCache does not shorten context by removing tool, so "compact" flag is still set in turn without recalc. This fix sets it manually to "continue". longterm the recal should be performed before setting compact flag. --- packages/opencode/src/session/compaction.ts | 26 +++++++-------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index 6165d93c0ad..b722ae9a0e3 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -111,7 +111,7 @@ export namespace SessionCompaction { parentID: input.parentID, sessionID: input.sessionID, mode: "compaction", - agent: usePrefixCache ? userMessage.agent : "compaction", + agent: "compaction", summary: true, path: { cwd: Instance.directory, @@ -157,12 +157,11 @@ export namespace SessionCompaction { { sessionID: input.sessionID }, { context: [], prompt: undefined }, ) - const defaultPrompt = - Flag.OPENCODE_EXPERIMENTAL_COMPACTION_PROMPT ?? + const defaultPrompt = Flag.OPENCODE_EXPERIMENTAL_COMPACTION_PROMPT ?? "Provide a detailed prompt for continuing our conversation above. Focus on information that would be helpful for continuing the conversation, including what we did, what we're doing, which files we're working on, and what we're going to do next considering new session will not have access to our conversation." - + const promptText = compacting.prompt ?? [defaultPrompt, ...compacting.context].join("\n\n") - const result = await processor.process({ + let result = await processor.process({ user: userMessage, agent, abort: input.abort, @@ -183,15 +182,12 @@ export namespace SessionCompaction { ], model, }) - console.log(result + " " + input.auto) - // Handle continue case with overflow check + // ignore compact-flag for usePrefixCache + // needsCompact is currently set before context recalc and we don't utilize remove-tools-trick + result = usePrefixCache && result == "compact" ? "continue" : result + if (result === "continue" && input.auto) { - const finalTokens = processor.message.tokens - if (await SessionCompaction.isOverflow({ tokens: finalTokens, model })) { - return "compact" // Need another compaction round - } - const continueMsg = await Session.updateMessage({ id: Identifier.ascending("message"), role: "user", @@ -215,12 +211,6 @@ export namespace SessionCompaction { }, }) } - - // If processor returned compact (overflow during processing), propagate it - if (result === "compact") { - return "compact" - } - if (processor.message.error) return "stop" Bus.publish(Event.Compacted, { sessionID: input.sessionID }) return "continue"