From 2137c4c68d3107a558e2e08397736c3b93c2a92d Mon Sep 17 00:00:00 2001 From: Andrew Date: Sun, 23 Aug 2026 19:40:59 -0700 Subject: [PATCH] perf(opencode): load session history once per turn, append the id-tail per step Co-Authored-By: Claude Fable 5 --- packages/opencode/src/session/message-v2.ts | 14 ++++++++++++ packages/opencode/src/session/prompt.ts | 25 ++++++++++++++++++--- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 1bea9f52c3ec..890257338360 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -25,6 +25,7 @@ import { and } from "drizzle-orm" import { desc } from "drizzle-orm" import { eq } from "drizzle-orm" import { inArray } from "drizzle-orm" +import { gt } from "drizzle-orm" import { lt } from "drizzle-orm" import { or } from "drizzle-orm" import { MessageTable, PartTable, SessionTable } from "@opencode-ai/core/session/sql" @@ -489,6 +490,19 @@ export function stream(sessionID: SessionID) { }) } +/** Messages created after the given id; ids are monotonic per session (MessageID.ascending). */ +export const after = Effect.fn("MessageV2.after")(function* (input: { sessionID: SessionID; id: MessageID }) { + const { db } = yield* Database.Service + const rows = yield* db + .select() + .from(MessageTable) + .where(and(eq(MessageTable.session_id, input.sessionID), gt(MessageTable.id, input.id))) + .orderBy(MessageTable.id) + .all() + .pipe(Effect.orDie) + return yield* hydrate(db, rows) +}) + export function parts(messageID: MessageID) { return Effect.gen(function* () { const { db } = yield* Database.Service diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 80363a2f29a1..bd399bf9934d 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -1093,6 +1093,7 @@ const layer = Layer.effect( let firstRequestStartAt: number | undefined let ttftStep: number | undefined let historyMs = 0 + let cachedMsgs: SessionV1.WithParts[] | undefined let toolsMs = 0 let contextMs = 0 let snapshotMs = 0 @@ -1103,9 +1104,26 @@ const layer = Layer.effect( yield* Effect.logInfo("loop", { "session.id": sessionID, step }) const historyStart = Date.now() - let msgs = yield* MessageV2.filterCompactedEffect(sessionID).pipe( - Effect.provideService(Database.Service, database), - ) + let msgs: SessionV1.WithParts[] + if (cachedMsgs === undefined) { + msgs = yield* MessageV2.filterCompactedEffect(sessionID).pipe( + Effect.provideService(Database.Service, database), + ) + } else { + const maxId = cachedMsgs.reduce((max, m) => (m.info.id > max ? m.info.id : max), cachedMsgs[0]!.info.id) + const fresh = yield* MessageV2.after({ sessionID, id: maxId }).pipe( + Effect.provideService(Database.Service, database), + ) + // Compaction/summary rows change the retained window; recompute it from scratch. + const rewritesHistory = fresh.some( + (m) => m.parts.some((p) => p.type === "compaction") || (m.info.role === "assistant" && m.info.summary), + ) + msgs = rewritesHistory + ? yield* MessageV2.filterCompactedEffect(sessionID).pipe( + Effect.provideService(Database.Service, database), + ) + : [...cachedMsgs, ...fresh] + } historyMs += Date.now() - historyStart const { user: lastUser, assistant: lastAssistant, finished: lastFinished, tasks } = MessageV2.latest(msgs) @@ -1197,6 +1215,7 @@ const layer = Layer.effect( Effect.provideService(FSUtil.Service, fsys), Effect.provideService(Session.Service, sessions), ) + cachedMsgs = msgs const msg: SessionV1.Assistant = { id: MessageID.ascending(),