From fae51d853dc2679f46669ba981939fa407dee88f Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 2 Jul 2026 20:56:56 -0400 Subject: [PATCH] refactor(core): dedupe context epoch initialization Share one insertInitial path between initialize and prepare, and document the runner's ordering constraint: epoch initialization runs before promotion so a blocked System Context leaves prompts pending, while reconciliation runs after promotion so ContextUpdated messages sequence after promoted input. --- packages/core/src/session/context-epoch.ts | 19 +++++++++++-------- packages/core/src/session/runner/llm.ts | 4 ++++ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/core/src/session/context-epoch.ts b/packages/core/src/session/context-epoch.ts index 65b17a86a4..227214569b 100644 --- a/packages/core/src/session/context-epoch.ts +++ b/packages/core/src/session/context-epoch.ts @@ -8,7 +8,6 @@ import { SystemContext } from "../system-context/index" import { ContextSnapshotDecodeError } from "./error" import { SessionEvent } from "./event" import { SessionHistory } from "./history" -import { SessionInput } from "./input" import { SessionMessage } from "./message" import { SessionSchema } from "./schema" import { SessionContextEpochTable } from "./sql" @@ -47,11 +46,7 @@ const prepareOnce = Effect.fnUntraced(function* ( [context, find(db, sessionID), SessionHistory.latestCompaction(db, sessionID)], { concurrency: "unbounded" }, ) - if (!stored) { - const generation = yield* SystemContext.initialize(value) - const baselineSeq = yield* insert(db, sessionID, generation) - return { baseline: generation.baseline, baselineSeq } - } + if (!stored) return yield* insertInitial(db, sessionID, value) const snapshot = yield* Schema.decodeUnknownEffect(SystemContext.Snapshot)(stored.snapshot).pipe( Effect.mapError((error) => new ContextSnapshotDecodeError({ sessionID, details: String(error) })), @@ -82,8 +77,16 @@ const initializeOnce = Effect.fnUntraced(function* ( context: Effect.Effect, sessionID: SessionSchema.ID, ) { - if (yield* exists(db, sessionID)) return - const generation = yield* context.pipe(Effect.flatMap(SystemContext.initialize)) + if (yield* exists(db, sessionID)) return undefined + return yield* insertInitial(db, sessionID, yield* context) +}) + +const insertInitial = Effect.fnUntraced(function* ( + db: DatabaseService, + sessionID: SessionSchema.ID, + value: SystemContext.SystemContext, +) { + const generation = yield* SystemContext.initialize(value) const baselineSeq = yield* insert(db, sessionID, generation) return { baseline: generation.baseline, baselineSeq } }) diff --git a/packages/core/src/session/runner/llm.ts b/packages/core/src/session/runner/llm.ts index f9f5e37ffb..66760e1cbb 100644 --- a/packages/core/src/session/runner/llm.ts +++ b/packages/core/src/session/runner/llm.ts @@ -168,6 +168,10 @@ const layer = Layer.effect( if (session.location.directory !== location.directory || session.location.workspaceID !== location.workspaceID) return yield* Effect.interrupt const agent = yield* agents.select(session.agent) + // Epoch initialization runs before promotion so a blocked System Context fails the + // turn while prompts are still pending in the durable inbox; a retry re-drains them. + // Reconciliation (`prepare` below) runs after promotion so ContextUpdated messages + // sequence after the promoted user input. const initialized = yield* SessionContextEpoch.initialize(db, loadSystemContext(agent), session.id) const toolFibers = yield* FiberSet.make() let needsContinuation = false