fix(core): preserve instruction state on move (#42398)

This commit is contained in:
Kit Langton
2026-08-13 16:20:14 -04:00
committed by GitHub
parent 4836356c17
commit b882ccc57d
4 changed files with 13 additions and 4 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@opencode-ai/core": patch
---
Preserve prompt cache prefixes when sessions move between locations with unchanged instructions.
+1 -1
View File
@@ -171,4 +171,4 @@ const table = sqliteTable("session", {
- One step is one logical LLM call; its durable record covers only the model-visible span. Do not write "provider turn", and do not use bare "turn" for a single call: "turn" is reserved for the future assistant-turn unit containing all steps from prompt promotion until the session would go idle.
- Keep EventV2 replay owner claims separate from clustered Session execution ownership.
- Keep the Instructions algebra and built-ins in `src/instructions`; keep instruction producers with their observed domains, and keep Session History selection plus `InstructionState` and `InstructionEntry` persistence Session-owned. `InstructionDiscovery` observes ambient global and upward-project instructions. The runner composes built-ins, discovery, guidance, and entries explicitly in `loadInstructions`; there is no instruction registry.
- `session.instructions.updated` stores only changed source keys and content hashes. Blob values live once in `instruction_blob`; `instruction_state` is a rebuildable fold cache, never primary state. Render initial instructions and chronological updates from values during request assembly. Completed compaction moves the instruction epoch; Session movement and committed revert clear it. Unavailable sources retain the last value and block only the initial complete delta.
- `session.instructions.updated` stores only changed source keys and content hashes. Blob values live once in `instruction_blob`; `instruction_state` is a rebuildable fold cache, never primary state. Render initial instructions and chronological updates from values during request assembly. Completed compaction moves the instruction epoch; Session movement retains it so destination instruction changes are chronological, while committed revert clears it. Unavailable sources retain the last value and block only the initial complete delta.
-1
View File
@@ -436,7 +436,6 @@ const layer = Layer.effectDiscard(
.where(eq(SessionTable.id, event.data.sessionID))
.run()
.pipe(Effect.orDie)
yield* InstructionState.reset(db, event.data.sessionID)
}),
)
// Sessions whose ownership came from the directory's previous resolution
+7 -2
View File
@@ -1204,12 +1204,17 @@ describe("SessionRunnerLLM", () => {
}),
)
it.effect("interrupts a source Location runner after a Session moves", () =>
it.effect("preserves instruction state and interrupts the source Location runner after a Session moves", () =>
Effect.gen(function* () {
const session = yield* setup
const bus = yield* Bus.Service
const { db } = yield* Database.Service
yield* runPrompt(session, "First")
const instructionState = yield* db
.select()
.from(InstructionStateTable)
.where(eq(InstructionStateTable.session_id, sessionID))
.get()
yield* bus.publish(SessionEvent.Moved, {
sessionID,
@@ -1218,7 +1223,7 @@ describe("SessionRunnerLLM", () => {
})
expect(
yield* db.select().from(InstructionStateTable).where(eq(InstructionStateTable.session_id, sessionID)).get(),
).toBeUndefined()
).toEqual(instructionState)
yield* admit(session, "Second")
const exit = yield* session.resume(sessionID).pipe(Effect.exit)