Dashboard › opencode › Session 0jQhdg0TsgNf
0jQhdg0TsgNfWoRbfPerform a frozen, read-only adversarial correctness review of the complete V2 interrupted-session recovery candidate in /home/byk/Code/opencode. Do not edit files, run formatters, mutate Git, touch production data, or start services. The exact review base is commit ab9408c81cc9916177641ae8e9f08624fc27b5b9. The currently committed head is dc661f30cb2055ad6248fdc59b14998d834497be plus an uncommitted validation delta. The canonical complete candidate patch is /tmp/opencode/session-recovery-full.patch with SHA-256 f051a53df476dd924bda0a14c4cd91a63c9e7eafff53bbe71f0460571335fcbd. Verify that hash before reviewing. The built SEA is packages/opencode/dist/server/opencode-server with SHA-256 1ecc9afe36c12b31a8892cdbfb8a1560ceec97255c3537c073b795351233be40. Review every file in the canonical patch, using the patch as immutable scope and current files only to get line numbers. Focus on durable lease acquisition/takeover/fencing, heartbeat and expiry races, explicit interruption and scope shutdown, recovery transaction atomicity/idempotence/replay, newest-assistant supersession, ready/safe/unknown semantics, provider and local/hosted tool ambiguity, overflow compaction, pending inbox behavior, startup composition, migrations/fresh and upgraded DBs, and regression quality. Established invariants: never call legacy SessionPrompt; never silently re-execute old calls; uncertain provider/tool outcomes become model-visible unknown errors; never recover an older assistant projection; EventV2 replay ownership stays separate; Core never imports Server; replace local ownership with durable multi-node ownership when clustered. Validation already observed: core typecheck; schema/server/opencode typechecks; 112 focused tests; full Core 1108 tests; migration check; recorded cassette replay; Node/SEA builds; isolated authenticated health+migration+SIGTERM smoke. ...
<skill_content name="effect">
# Skill: effect
# Effect
This codebase uses Effect for typed, composable TypeScript services, schemas, and workflows.
## Source Of Truth
Use the current Effect v4 / effect-smol source, not memory or older Effect v2/v3 examples.
1. If `.opencode/references/effect-smol` is missing, clone `https://github.com/Effect-TS/effect-smol` there. Do this in the project, not in the skill folder.
2. Search `.opencode/references/effect-smol` for exact APIs, examples, tests, and naming patterns before answering or implementing Effect-specific code.
3. Also inspect existing repo code for local house style before introducing new patterns.
4. Prefer answers and implementations backed by specific source files or nearby repo examples.
## Guidelines
- Prefer current Effect v4 APIs and project-local patterns over old blog posts, examples, or package-memory guesses.
- Use `Effect.gen(function* () { ... })` for multi-step workflows.
- Use `Effect.fn("Name")` or ...
<path>/home/byk/Code/opencode</path> <type>directory</type> <entries> .dockerignore .editorconfig .git/ .gitattributes .github/ .gitignore .gitleaksignore .husky/ .lore.md .node-cache/ .opencode/ .oxlintrc.json .prettierignore .turbo/ .vscode/ .zed/ AGENTS.md artifacts/ bun.lock bunfig.toml CONTEXT.md CONTRIBUTING.md flake.lock flake.nix github/ infra/ install LICENSE nix/ node_modules/ package.json packages/ patches/ perf.data perf.data.old perf/ pnpm-lock.yaml pnpm-workspace.yaml README.ar.md README.bn.md README.br.md README.bs.md README.da.md README.de.md README.es.md README.fr.md README.gr.md README.it.md README.ja.md README.ko.md README.md README.no.md README.pl.md README.ru.md README.th.md README.tr.md README.uk.md README.vi.md README.zh.md README.zht.md screenshot-uk.png script/ sdks/ SECURITY.md specs/ sst-env.d.ts sst.config.ts STATS.md tsconfig.json turbo.json (70 entries) </entries>
f051a53df476dd924bda0a14c4cd91a63c9e7eafff53bbe71f0460571335fcbd /tmp/opencode/session-recovery-full.patch 1ecc9afe36c12b31a8892cdbfb8a1560ceec97255c3537c073b795351233be40 packages/opencode/dist/server/opencode-server
dc661f30cb2055ad6248fdc59b14998d834497be commit commit M .lore.md M AGENTS.md M packages/core/schema.json M packages/core/src/database/migration.gen.ts M packages/core/src/database/schema.gen.ts M packages/core/src/session/compaction.ts M packages/core/src/session/execution/local.ts M packages/core/src/session/message-updater.ts M packages/core/src/session/projector.ts M packages/core/src/session/recovery.ts M packages/core/src/session/runner/index.ts M packages/core/src/session/runner/llm.ts M packages/core/src/session/runner/publish-llm-event.ts M packages/core/src/session/sql.ts M packages/core/test/session-execution-local.test.ts M packages/core/test/session-projector.test.ts M packages/core/test/session-runner-recorded.test.ts M packages/core/test/session-runner-tool-events.test.ts M packages/core/test/session-runner.test.ts M packages/schema/src/session-event.ts ?? packages/core/src/database/migration/20260914235657_session-execution-lease.ts ?? perf.data ?? ...
Found 24 matches /tmp/opencode/session-recovery-full.patch: Line 1: diff --git a/packages/core/schema.json b/packages/core/schema.json Line 732: diff --git a/packages/core/src/database/migration.gen.ts b/packages/core/src/database/migration.gen.ts Line 746: diff --git a/packages/core/src/database/migration/20260914170650_session-recovery.ts b/packages/core/src/database/migration/20260914170650_session-recovery.ts Line 777: diff --git a/packages/core/src/database/migration/20260914214636_session-execution.ts b/packages/core/src/database/migration/20260914214636_session-execution.ts Line 809: diff --git a/packages/core/src/database/migration/20260914222526_session-execution-phase.ts b/packages/core/src/database/migration/20260914222526_session-execution-phase.ts Line 826: diff --git a/packages/core/src/database/schema.gen.ts b/packages/core/src/database/schema.gen.ts Line 891: diff --git a/packages/core/src/session/compaction.ts ...
<path>/tmp/opencode/session-recovery-full.patch</path>
<type>file</type>
<content>
1: diff --git a/packages/core/schema.json b/packages/core/schema.json
2: index d0eeeebd5c..25475f6d34 100644
3: --- a/packages/core/schema.json
4: +++ b/packages/core/schema.json
5: @@ -1,8 +1,10 @@
6: {
7: "version": "7",
8: "dialect": "sqlite",
9: - "id": "f14a9b18-8207-487e-a3d3-227e629ba9ad",
10: - "prevIds": ["169a0f0f-d58f-479f-b024-fa1c7b9a09db"],
11: + "id": "ab874548-0fd0-4748-9f7d-762a6e5e7479",
12: + "prevIds": [
13: + "57591e35-6f8f-4cda-932c-6a08e85caae7"
14: + ],
15: "ddl": [
16: {
17: "name": "workspace",
18: @@ -60,6 +62,10 @@
19: "name": "session_context_epoch",
20: "entityType": "tables"
21: },
22: + {
23: + "name": "session_execution",
24: + "entityType": "tables"
25: + },
26: {
27: "name": "session_input",
28: "entityType": "tables"
29: @@ -68,6 +74,10 @@
30: "name": "session_message",
31: ...
<path>/tmp/opencode/session-recovery-full.patch</path>
<type>file</type>
<content>
951: + reason: "auto",
952: + text: summary,
953: + recent: selected.recent,
954: + },
955: + { commit: input.commit },
956: + )
957: return true
958: })
959: - const compactIfNeeded = Effect.fn("SessionCompaction.compactIfNeeded")(function* (input: Input) {
960: + const compactIfNeeded = Effect.fn("SessionCompaction.compactIfNeeded")(function* (input: CompactInput) {
961: if (!config.auto) return false
962: const context = input.model.route.defaults.limits?.context
963: if (context === undefined || context <= 0) return false
964: diff --git a/packages/core/src/session/execution/local.ts b/packages/core/src/session/execution/local.ts
965: index d874c40832..9af6109c6e 100644
966: --- a/packages/core/src/session/execution/local.ts
967: +++ b/packages/core/src/session/execution/local.ts
968: @@ -1,4 +1,6 @@
969: -import { Cause, Effect, Layer } ...
<path>/tmp/opencode/session-recovery-full.patch</path>
<type>file</type>
<content>
1951: +import { ProjectTable } from "@opencode-ai/core/project/sql"
1952: +import { AbsolutePath } from "@opencode-ai/core/schema"
1953: +import { SessionExecutionLocal } from "@opencode-ai/core/session/execution/local"
1954: +import { SessionSchema } from "@opencode-ai/core/session/schema"
1955: +import { SessionExecutionTable, SessionTable } from "@opencode-ai/core/session/sql"
1956: +import { testEffect } from "./lib/effect"
1957: +
1958: +const sessionID = SessionSchema.ID.make("ses_execution_local")
1959: +const it = testEffect(Database.layerFromPath(":memory:"))
1960: +
1961: +const insertSession = Effect.gen(function* () {
1962: + const { db } = yield* Database.Service
1963: + yield* db
1964: + .insert(ProjectTable)
1965: + .values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] })
1966: + .onConflictDoNothing()
1967: + .run()
1968: + yield* db
1969: ...
<path>/home/byk/Code/opencode/packages/core/src/session/execution/local.ts</path>
<type>file</type>
<content>
1: import { and, eq, inArray, lt, or } from "drizzle-orm"
2: import { Cause, Duration, Effect, Exit, Layer, Schedule } from "effect"
3: import { Database } from "../../database/database"
4: import { LocationServiceMap } from "../../location-service-map"
5: import { makeGlobalNode } from "../../effect/app-node"
6: import { SessionRunCoordinator } from "../run-coordinator"
7: import { SessionRunner } from "../runner"
8: import { SessionSchema } from "../schema"
9: import { SessionStore } from "../store"
10: import { SessionExecution } from "../execution"
11: import { SESSION_EXECUTION_LEASE_MS, SessionExecutionTable } from "../sql"
12:
13: /** Current-process routing for implicit-local Locations. Future remote placement belongs here. */
14: export const make = Effect.fnUntraced(function* (input: {
15: readonly run: (
16: sessionID: SessionSchema.ID,
17: force: ...
<path>/home/byk/Code/opencode/packages/core/src/session/recovery.ts</path>
<type>file</type>
<content>
1: export * as SessionRecovery from "./recovery"
2:
3: import { and, desc, eq, isNull, lt } from "drizzle-orm"
4: import { DateTime, Effect, Layer, Schedule } from "effect"
5: import { Database } from "../database/database"
6: import { makeGlobalNode } from "../effect/app-node"
7: import { EventV2 } from "../event"
8: import { SessionEvent } from "./event"
9: import { SessionExecution } from "./execution"
10: import { SessionMessage } from "./message"
11: import { SessionProjector } from "./projector"
12: import { SessionExecutionTable, SessionRecoveryTable, SessionMessageTable } from "./sql"
13:
14: export const recover = Effect.fn("SessionRecovery.recover")(function* () {
15: const { db } = yield* Database.Service
16: const events = yield* EventV2.Service
17: const execution = yield* SessionExecution.Service
18: const claims = yield* db
19: .select()
20: ...
File not found: /home/byk/Code/opencode/packages/core/src/event/index.ts
Continue the frozen, read-only correctness review from your existing context. The server restarted. Verify and review the same exact canonical patch /tmp/opencode/session-recovery-full.patch with SHA-256 f051a53df476dd924bda0a14c4cd91a63c9e7eafff53bbe71f0460571335fcbd, base ab9408c81cc9916177641ae8e9f08624fc27b5b9, and SEA hash 1ecc9afe36c12b31a8892cdbfb8a1560ceec97255c3537c073b795351233be40. Do not mutate anything. Return substantive PASS/CONCERN/MUST-FIX findings with exact file:line evidence and end exactly MERGE or DO-NOT-MERGE. If blocked, report the exact blocker and end DO-NOT-MERGE; never return empty or progress-only output.
sha256sum: /tmp/opencode/session-recovery-full.patch: No such file or directory 1ecc9afe36c12b31a8892cdbfb8a1560ceec97255c3537c073b795351233be40 packages/opencode/dist/server/opencode-server
No files found
dc661f30cb2055ad6248fdc59b14998d834497be commit commit M .lore.md M AGENTS.md M packages/core/schema.json M packages/core/src/database/migration.gen.ts M packages/core/src/database/schema.gen.ts M packages/core/src/session/compaction.ts M packages/core/src/session/execution/local.ts M packages/core/src/session/message-updater.ts M packages/core/src/session/projector.ts M packages/core/src/session/recovery.ts M packages/core/src/session/runner/index.ts M packages/core/src/session/runner/llm.ts M packages/core/src/session/runner/publish-llm-event.ts M packages/core/src/session/sql.ts M packages/core/test/session-execution-local.test.ts M packages/core/test/session-projector.test.ts M packages/core/test/session-runner-recorded.test.ts M packages/core/test/session-runner-tool-events.test.ts M packages/core/test/session-runner.test.ts M packages/schema/src/session-event.ts ?? packages/core/src/database/migration/20260914235657_session-execution-lease.ts ?? perf.data ?? ...
<path>/home/byk/Code/opencode/packages/core/src/session/run-coordinator.ts</path>
<type>file</type>
<content>
1: export * as SessionRunCoordinator from "./run-coordinator"
2:
3: import { Deferred, Effect, Exit, Fiber, FiberSet, Scope } from "effect"
4:
5: /** Serializes execution for each key while allowing different keys to run concurrently. */
6: export interface Coordinator<Key, E> {
7: /** Snapshots keys with an execution owned by this coordinator. */
8: readonly active: Effect.Effect<ReadonlySet<Key>>
9: /** Starts execution while idle or joins the active execution. */
10: readonly run: (key: Key) => Effect.Effect<void, E>
11: /** Registers one coalesced follow-up after newly recorded work. */
12: readonly wake: (key: Key) => Effect.Effect<void>
13: /** Stops active execution and waits for its cleanup. */
14: readonly interrupt: (key: Key) => Effect.Effect<void>
15: }
16:
17: type Entry<E> = {
18: readonly done: Deferred.Deferred<void, E>
19: owner?: ...
<path>/home/byk/Code/opencode/packages/core/src/event.ts</path>
<type>file</type>
<content>
1: export * as EventV2 from "./event"
2:
3: import { Cause, Context, Effect, Layer, Option, PubSub, Queue, Schema, Stream } from "effect"
4: import { Event } from "@opencode-ai/schema/event"
5: import type { Data, Definition, Payload } from "@opencode-ai/schema/event"
6: import { and, asc, eq, gt, inArray } from "drizzle-orm"
7: import { Database } from "./database/database"
8: import { EventSequenceTable, EventTable } from "./event/sql"
9: import { Location } from "./location"
10: import { makeGlobalNode } from "./effect/app-node"
11: import { isDeepStrictEqual } from "node:util"
12: import { Durable } from "@opencode-ai/schema/durable-event-manifest"
13:
14: export const ID = Event.ID
15: export type ID = import("@opencode-ai/schema/event").ID
16: export type { Data, Definition, Payload } from "@opencode-ai/schema/event"
17:
18: export type Subscriber<D extends Definition = Definition> = ...
<path>/home/byk/Code/opencode/packages/core/src/event.ts</path>
<type>file</type>
<content>
316: const committed = {
317: ...event,
318: durable: { aggregateID, seq, version: durable.version },
319: } as Payload
320: for (const projector of list) {
321: yield* projector(committed)
322: }
323: if (commit) yield* commit(seq)
324: yield* db
325: .insert(EventSequenceTable)
326: .values([{ aggregate_id: aggregateID, seq, owner_id: input?.ownerID }])
327: .onConflictDoUpdate({
328: target: EventSequenceTable.aggregate_id,
329: set: {
330: seq,
331: ...
<path>/home/byk/Code/opencode/packages/core/src/session/runner/publish-llm-event.ts</path>
<type>file</type>
<content>
1: import { ToolOutput, type LLMEvent, type ProviderMetadata, type ToolResultValue, type Usage } from "@opencode-ai/llm"
2: import { DateTime, Effect } from "effect"
3: import { EventV2 } from "../../event"
4: import { ModelV2 } from "../../model"
5: import { SessionEvent } from "../event"
6: import { SessionMessage } from "../message"
7: import { SessionSchema } from "../schema"
8:
9: type Input = {
10: readonly sessionID: SessionSchema.ID
11: readonly agent: string
12: readonly model: ModelV2.Ref
13: readonly snapshot?: string
14: readonly startCommit: (assistantMessageID: SessionMessage.ID) => Effect.Effect<void>
15: readonly terminalCommit: (assistantMessageID: SessionMessage.ID) => Effect.Effect<void>
16: readonly eventCommit: () => Effect.Effect<void>
17: }
18:
19: const safe = (value: number | undefined) => Math.max(0, Number.isFinite(value) ? ...
<path>/home/byk/Code/opencode/packages/core/src/session/runner/llm.ts</path>
<type>file</type>
<content>
160: class TurnTransitionError extends Error {
161: constructor(readonly transition: TurnTransition) {
162: super()
163: }
164: }
165:
166: const continueAfterCompaction = (step: number) => new TurnTransitionError({ _tag: "ContinueAfterCompaction", step })
167: const continueAfterOverflowCompaction = (step: number) =>
168: new TurnTransitionError({ _tag: "ContinueAfterOverflowCompaction", step })
169:
170: const loadSystemContext = (agent: AgentV2.Selection) =>
171: Effect.all([systemContext.load(), skillGuidance.load(agent), referenceGuidance.load()], {
172: concurrency: "unbounded",
173: }).pipe(Effect.map(SystemContext.combine))
174:
175: const runTurnAttempt = Effect.fn("SessionRunner.runTurn")(function* (
176: sessionID: SessionSchema.ID,
177: executionID: string,
178: promotion: ...
<path>/home/byk/Code/opencode/packages/core/src/session/compaction.ts</path>
<type>file</type>
<content>
155: const next = total + Token.estimate(conversation[index])
156: if (next > tokens) break
157: total = next
158: split = index
159: }
160: return {
161: head: conversation.slice(0, split).join("\n\n"),
162: recent: conversation.slice(split).join("\n\n"),
163: }
164: }
165:
166: export const buildPrompt = (input: { readonly previousSummary?: string; readonly context: readonly string[] }) => {
167: const conversation = `Here is the conversation so far:\n\n<conversation>\n${input.context.join("\n\n")}\n</conversation>`
168: if (!input.previousSummary)
169: return [
170: conversation,
171: "Create a new anchored summary from the conversation history in the <conversation> tags above so another coding agent can continue the work.",
172: SUMMARY_TEMPLATE,
173: ].join("\n\n")
174: return [
175: conversation,
176: ...
<path>/home/byk/Code/opencode/packages/core/src/session/projector.ts</path>
<type>file</type>
<content>
370: yield* events.project(SessionEvent.PromptAdmitted, (event) =>
371: Effect.gen(function* () {
372: if (event.durable === undefined) return yield* Effect.die("Durable Session event is missing aggregate sequence")
373: yield* SessionInput.projectAdmitted(db, {
374: admittedSeq: event.durable.seq,
375: id: event.data.messageID,
376: sessionID: event.data.sessionID,
377: prompt: event.data.prompt,
378: delivery: event.data.delivery,
379: timeCreated: event.data.timestamp,
380: })
381: }),
382: )
383: yield* events.project(SessionEvent.ContextUpdated, (event) => run(db, event))
384: yield* events.project(SessionEvent.Synthetic, (event) => run(db, event))
385: yield* events.project(SessionEvent.Shell.Started, (event) => run(db, event))
386: yield* ...
<path>/home/byk/Code/opencode/packages/core/src/session/message-updater.ts</path>
<type>file</type>
<content>
215: if (event.data.snapshot || event.data.files)
216: draft.snapshot = {
217: ...draft.snapshot,
218: end: event.data.snapshot,
219: files: event.data.files ? Array.from(event.data.files) : undefined,
220: }
221: })
222: },
223: "session.next.step.failed": (event) => {
224: return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
225: draft.time.completed = event.data.timestamp
226: draft.finish = "error"
227: draft.error = event.data.error
228: })
229: },
230: "session.next.step.interrupted": (event) => {
231: return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
232: draft.time.completed = event.data.timestamp
233: draft.finish = "error"
234: ...
Found 1 matches /home/byk/Code/opencode/packages/core/src/session/runner/llm.ts: Line 49: * `SessionPrompt` monolith. Implement the unchecked items in small reviewed slices:
33e64fbe08c166aefbfc3c5f1afd9e432aaa60c06e95d8e273163fc2c38b5142 -
<path>/home/byk/Code/opencode/packages/core/src/session/projector.ts</path>
<type>file</type>
<content>
1: export * as SessionProjector from "./projector"
2:
3: import { and, desc, eq, gt, or, sql } from "drizzle-orm"
4: import { DateTime, Effect, Layer, Schema } from "effect"
5: import { Database } from "../database/database"
6: import { EventV2 } from "../event"
7: import { makeGlobalNode } from "../effect/app-node"
8: import { SessionEvent } from "./event"
9: import { SessionV1 } from "../v1/session"
10: import { WorkspaceTable } from "../control-plane/workspace.sql"
11: import { SessionMessage } from "./message"
12: import { SessionMessageUpdater } from "./message-updater"
13: import { SessionInput } from "./input"
14: import { Prompt } from "./prompt"
15: import { WorkspaceV2 } from "../workspace"
16: import {
17: MessageTable,
18: PartTable,
19: SessionInputTable,
20: SessionMessageTable,
21: SessionRecoveryTable,
22: SessionTable,
23: } from "./sql"
24: import type ...
Found 40 matches
/home/byk/Code/opencode/packages/core/src/session/recovery.ts:
Line 102: if (recovered) yield* execution.wake(claim.session_id)
/home/byk/Code/opencode/packages/core/src/session/projector.ts:
Line 19: SessionInputTable,
Line 486: .delete(SessionInputTable)
Line 489: eq(SessionInputTable.session_id, event.data.sessionID),
Line 490: or(gt(SessionInputTable.admitted_seq, boundary.seq), gt(SessionInputTable.promoted_seq, boundary.seq)),
/home/byk/Code/opencode/packages/core/src/session/sql.ts:
Line 140: export const SessionInputTable = sqliteTable(
Line 183: .references(() => SessionInputTable.id, { onDelete: "cascade" }),
/home/byk/Code/opencode/packages/core/src/session/input.ts:
Line 12: import { SessionInputTable, SessionMessageTable } from "./sql"
Line 21: const fromRow = (row: typeof SessionInputTable.$inferSelect): Admitted =>
Line 33: const row = yield* ...
<path>/home/byk/Code/opencode/packages/core/src/session.ts</path>
<type>file</type>
<content>
340: return stored?.sessionID === input.sessionID ? stored.message : undefined
341: }),
342: context: Effect.fn("V2Session.context")(function* (sessionID) {
343: yield* result.get(sessionID)
344: return yield* store.context(sessionID)
345: }),
346: events: (input) =>
347: Stream.unwrap(
348: result
349: .get(input.sessionID)
350: .pipe(Effect.as(events.durable({ aggregateID: input.sessionID, after: input.after }))),
351: ).pipe(Stream.filter((event): event is SessionEvent.DurableEvent => isDurableSessionEvent(event))),
352: history: Effect.fn("V2Session.history")(function* (input) {
353: yield* result.get(input.sessionID)
354: return yield* EventV2.readAggregate(db, {
355: ...input,
356: aggregateID: input.sessionID,
357: manifest: ...
<path>/home/byk/Code/opencode/packages/core/src/database/migration.ts</path>
<type>file</type>
<content>
1: export * as DatabaseMigration from "./migration"
2:
3: import { sql } from "drizzle-orm"
4: import { Effect, Semaphore } from "effect"
5: import type { EffectDrizzleSqlite } from "@opencode-ai/effect-drizzle-sqlite"
6: import { migrations } from "./migration.gen"
7: import schema from "./schema.gen"
8:
9: type Database = EffectDrizzleSqlite.EffectSQLiteDatabase
10: type Transaction = Parameters<Parameters<Database["transaction"]>[0]>[0]
11: const lock = Semaphore.makeUnsafe(1)
12:
13: export type Migration = {
14: id: string
15: up: (tx: Transaction) => Effect.Effect<void, unknown>
16: }
17:
18: export function apply(db: Database) {
19: return lock.withPermit(
20: Effect.gen(function* () {
21: const tables = yield* db.all<{ name: string }>(
22: sql`SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'`,
23: )
24: ...
Continue your existing frozen, read-only correctness review after the server restart. The exact evidence is present and unchanged: /tmp/opencode/session-recovery-full.patch SHA-256 f051a53df476dd924bda0a14c4cd91a63c9e7eafff53bbe71f0460571335fcbd; base ab9408c81cc9916177641ae8e9f08624fc27b5b9; HEAD dc661f30cb2055ad6248fdc59b14998d834497be plus the frozen delta; SEA SHA-256 1ecc9afe36c12b31a8892cdbfb8a1560ceec97255c3537c073b795351233be40. Verify hashes, remain read-only, and finish the complete review. Return substantive PASS/CONCERN/MUST-FIX findings with exact file:line evidence. End exactly MERGE or DO-NOT-MERGE. If blocked, quote the exact blocker; never return empty or progress-only output.
f051a53df476dd924bda0a14c4cd91a63c9e7eafff53bbe71f0460571335fcbd /tmp/opencode/session-recovery-full.patch 1ecc9afe36c12b31a8892cdbfb8a1560ceec97255c3537c073b795351233be40 packages/opencode/dist/server/opencode-server dc661f30cb2055ad6248fdc59b14998d834497be commit commit
fatal: path 'packages/core/src/database/migration/20260914235657_session-execution-lease.ts' exists on disk, but not in 'dc661f30cb2055ad6248fdc59b14998d834497be' lease-exists=128
Found 4 matches
/home/byk/.local/share/opencode/repos/github.com/Effect-TS/effect-smol/packages/effect/src/Fiber.ts:
Line 346: export const interrupt: <A, E>(self: Fiber<A, E>) => Effect<void> = effect.fiberInterrupt
Line 387: export const interruptAs: {
Line 462: export const interruptAll: <A extends Iterable<Fiber<any, any>>>(
Line 525: export const interruptAllAs: {
Found 3 matches
/home/byk/.local/share/opencode/repos/github.com/Effect-TS/effect-smol/packages/effect/src/Effect.ts:
Line 4869: * const message = yield* Effect.match(Effect.raceFirst(fastFail, slowSuccess), {
Line 4883: export const raceFirst: {
Line 4901: } = internal.raceFirst
<path>/home/byk/.local/share/opencode/repos/github.com/Effect-TS/effect-smol/packages/effect/src/Effect.ts</path>
<type>file</type>
<content>
4820: * Effect.runPromise(program)
4821: * // Output: winner: slow-success
4822: * ```
4823: *
4824: * @category racing
4825: * @since 2.0.0
4826: */
4827: export const race: {
4828: <A2, E2, R2>(
4829: that: Effect<A2, E2, R2>,
4830: options?: {
4831: readonly onWinner?: (
4832: options: { readonly fiber: Fiber<any, any>; readonly index: number; readonly parentFiber: Fiber<any, any> }
4833: ) => void
4834: }
4835: ): <A, E, R>(self: Effect<A, E, R>) => Effect<A | A2, E | E2, R | R2>
4836: <A, E, R, A2, E2, R2>(
4837: self: Effect<A, E, R>,
4838: that: Effect<A2, E2, R2>,
4839: options?: {
4840: readonly onWinner?: (
4841: options: { readonly fiber: Fiber<any, any>; readonly index: number; readonly parentFiber: Fiber<any, any> }
4842: ) => void
4843: }
4844: ): ...
Found 2 matches
/home/byk/Code/opencode/packages/core/src/session/runner/llm.ts:
Line 121: const failInterruptedTools = Effect.fn("SessionRunner.failInterruptedTools")(function* (
Line 451: yield* failInterruptedTools(input.sessionID)
<path>/home/byk/Code/opencode/packages/core/src/session/runner/llm.ts</path>
<type>file</type>
<content>
95: const layer = Layer.effect(
96: Service,
97: Effect.gen(function* () {
98: const events = yield* EventV2.Service
99: const llm = yield* LLMClient.Service
100: const agents = yield* AgentV2.Service
101: const tools = yield* ToolRegistry.Service
102: const models = yield* SessionRunnerModel.Service
103: const store = yield* SessionStore.Service
104: const location = yield* Location.Service
105: const systemContext = yield* SystemContextRegistry.Service
106: const skillGuidance = yield* SkillGuidance.Service
107: const referenceGuidance = yield* ReferenceGuidance.Service
108: const config = yield* Config.Service
109: const snapshots = yield* Snapshot.Service
110: const db = (yield* Database.Service).db
111: const compaction = SessionCompaction.make({ events, llm, config: yield* config.entries() })
112: const ...
Found 9 matches
/home/byk/Code/opencode/packages/schema/src/session-event.ts:
Line 423: export namespace Compaction {
Line 425: type: "session.next.compaction.started",
Line 436: type: "session.next.compaction.delta",
Line 446: type: "session.next.compaction.ended",
Line 499: Compaction.Started,
Line 500: Compaction.Ended,
Line 535: Compaction.Started,
Line 536: Compaction.Delta,
Line 537: Compaction.Ended,
<path>/home/byk/Code/opencode/packages/schema/src/session-event.ts</path>
<type>file</type>
<content>
410: export interface RetryError extends Schema.Schema.Type<typeof RetryError> {}
411:
412: export const Retried = Event.define({
413: type: "session.next.retried",
414: ...options,
415: schema: {
416: ...Base,
417: attempt: Schema.Finite,
418: error: RetryError,
419: },
420: })
421: export type Retried = typeof Retried.Type
422:
423: export namespace Compaction {
424: export const Started = Event.define({
425: type: "session.next.compaction.started",
426: ...options,
427: schema: {
428: ...Base,
429: messageID: SessionMessage.ID,
430: reason: Schema.Union([Schema.Literal("auto"), Schema.Literal("manual")]),
431: },
432: })
433: export type Started = typeof Started.Type
434:
435: export const Delta = Event.define({
436: type: "session.next.compaction.delta",
437: schema: {
438: ...Base,
439: ...
<path>/home/byk/Code/opencode/packages/opencode/src/server/routes/instance/httpapi/server.ts</path> <type>file</type> <content> 285: ).pipe( 286: Layer.provide([ 287: errorLayer, 288: compressionLayer, 289: corsVaryFix, 290: fenceLayer, 291: cors(corsOptions), 292: AppNodeBuilderV1.build(MoveSession.node, [[LocationServiceMap.node, locationServiceMapV2]]), 293: HttpServer.layerServices, 294: ]), 295: Layer.provide(Layer.succeed(CorsConfig)(corsOptions)), 296: Layer.provide(sessionLocationLayer), 297: Layer.provide(locationLayer), 298: Layer.provide(PtyEnvironment.layer), 299: Layer.provide( 300: AppNodeBuilderV1.build(LayerNode.group([SessionV2.node, SessionRecovery.node]), [ 301: [LocationServiceMap.node, locationServiceMapV2], 302: [SessionExecution.node, SessionExecutionLocal.node], 303: ]), 304: ), 305: Layer.provide(locationServiceMapV2), 306: 307: ...
<path>/home/byk/Code/opencode/packages/server/src/routes.ts</path>
<type>file</type>
<content>
1: import { Database } from "@opencode-ai/core/database/database"
2: import { LayerNode } from "@opencode-ai/core/effect/layer-node"
3: import { httpClient } from "@opencode-ai/core/effect/app-node-platform"
4: import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
5: import { EventV2 } from "@opencode-ai/core/event"
6: import { Credential } from "@opencode-ai/core/credential"
7: import { PermissionSaved } from "@opencode-ai/core/permission/saved"
8: import { PtyTicket } from "@opencode-ai/core/pty/ticket"
9: import { SessionV2 } from "@opencode-ai/core/session"
10: import { SessionExecution } from "@opencode-ai/core/session/execution"
11: import { LocationServiceMap } from "@opencode-ai/core/location-service-map"
12: import { SessionExecutionLocal } from "@opencode-ai/core/session/execution/local"
13: import { SessionRecovery } from ...
<path>/home/byk/Code/opencode/packages/schema/src/session-event.ts</path>
<type>file</type>
<content>
180: files: Schema.Array(RelativePath).pipe(optional),
181: },
182: })
183: export type Ended = typeof Ended.Type
184:
185: export const Failed = Event.define({
186: type: "session.next.step.failed",
187: ...stepSettlementOptions,
188: schema: {
189: ...Base,
190: assistantMessageID: SessionMessage.ID,
191: error: UnknownError,
192: },
193: })
194: export type Failed = typeof Failed.Type
195:
196: export const Interrupted = Event.define({
197: type: "session.next.step.interrupted",
198: ...options,
199: schema: {
200: ...Base,
201: assistantMessageID: SessionMessage.ID,
202: error: UnknownError,
203: },
204: })
205: export type Interrupted = typeof Interrupted.Type
206:
207: export const Recovered = Event.define({
208: type: "session.next.step.recovered",
209: ...options,
210: ...
<path>/home/byk/Code/opencode/packages/core/test/session-execution-local.test.ts</path>
<type>file</type>
<content>
80:
81: it.effect("never starts a second owner while the lease is live", () =>
82: Effect.gen(function* () {
83: yield* insertSession
84: const firstStarted = Deferred.makeUnsafe<void>()
85: const firstScope = yield* Scope.make()
86: const first = yield* SessionExecutionLocal.make({
87: run: () => Deferred.succeed(firstStarted, undefined).pipe(Effect.andThen(Effect.never)),
88: }).pipe(Effect.provideService(Scope.Scope, firstScope))
89: let secondRuns = 0
90: const second = yield* SessionExecutionLocal.make({
91: run: () => Effect.sync(() => secondRuns++).pipe(Effect.asVoid),
92: })
93:
94: yield* first.wake(sessionID)
95: yield* Deferred.await(firstStarted)
96: yield* second.resume(sessionID)
97:
98: expect(secondRuns).toBe(0)
99: expect(yield* ...
<path>/home/byk/Code/opencode/packages/core/src/session/input.ts</path>
<type>file</type>
<content>
85: input: {
86: readonly admittedSeq: number
87: readonly id: SessionMessage.ID
88: readonly sessionID: SessionSchema.ID
89: readonly prompt: Prompt
90: readonly delivery: Delivery
91: readonly timeCreated: DateTime.Utc
92: },
93: ) {
94: const message = yield* db
95: .select({ id: SessionMessageTable.id })
96: .from(SessionMessageTable)
97: .where(eq(SessionMessageTable.id, input.id))
98: .get()
99: .pipe(Effect.orDie)
100: if (message !== undefined) return yield* Effect.die(new LifecycleConflict({ id: input.id }))
101: const stored = yield* db
102: .insert(SessionInputTable)
103: .values({
104: id: input.id,
105: session_id: input.sessionID,
106: admitted_seq: input.admittedSeq,
107: prompt: encodePrompt(input.prompt),
108: delivery: input.delivery,
109: time_created: ...
Continue and finish your one same-session frozen correctness-review retry after the server restart. Do not use Lore recall. Immutable evidence is present and verified unchanged: /tmp/opencode/session-recovery-full.patch SHA-256 f051a53df476dd924bda0a14c4cd91a63c9e7eafff53bbe71f0460571335fcbd; base ab9408c81cc9916177641ae8e9f08624fc27b5b9; HEAD dc661f30cb2055ad6248fdc59b14998d834497be plus frozen delta; SEA SHA-256 1ecc9afe36c12b31a8892cdbfb8a1560ceec97255c3537c073b795351233be40. Remain strictly read-only. Return a non-empty report with PASS/CONCERN/MUST-FIX findings and exact current file:line evidence across ownership, lease, fencing, interruption, recovery, replay, compaction, migration, startup, and test behavior. End exactly MERGE or DO-NOT-MERGE. If blocked, quote the exact command/error and affected scope, then end DO-NOT-MERGE. Never return a context warning, separator, progress note, or empty output.
f051a53df476dd924bda0a14c4cd91a63c9e7eafff53bbe71f0460571335fcbd /tmp/opencode/session-recovery-full.patch 1ecc9afe36c12b31a8892cdbfb8a1560ceec97255c3537c073b795351233be40 packages/opencode/dist/server/opencode-server dc661f30cb2055ad6248fdc59b14998d834497be commit commit
<path>/tmp/opencode/session-recovery-full.patch</path>
<type>file</type>
<content>
1: diff --git a/packages/core/schema.json b/packages/core/schema.json
2: index d0eeeebd5c..25475f6d34 100644
3: --- a/packages/core/schema.json
4: +++ b/packages/core/schema.json
5: @@ -1,8 +1,10 @@
6: {
7: "version": "7",
8: "dialect": "sqlite",
9: - "id": "f14a9b18-8207-487e-a3d3-227e629ba9ad",
10: - "prevIds": ["169a0f0f-d58f-479f-b024-fa1c7b9a09db"],
11: + "id": "ab874548-0fd0-4748-9f7d-762a6e5e7479",
12: + "prevIds": [
13: + "57591e35-6f8f-4cda-932c-6a08e85caae7"
14: + ],
15: "ddl": [
16: {
17: "name": "workspace",
18: @@ -60,6 +62,10 @@
19: "name": "session_context_epoch",
20: "entityType": "tables"
21: },
22: + {
23: + "name": "session_execution",
24: + "entityType": "tables"
25: + },
26: {
27: "name": "session_input",
28: "entityType": "tables"
29: @@ -68,6 +74,10 @@
30: "name": "session_message",
31: ...
<path>/tmp/opencode/session-recovery-full.patch</path>
<type>file</type>
<content>
731: \ No newline at end of file
732: diff --git a/packages/core/src/database/migration.gen.ts b/packages/core/src/database/migration.gen.ts
733: index e6ea4eaa14..495bd89ffd 100644
734: --- a/packages/core/src/database/migration.gen.ts
735: +++ b/packages/core/src/database/migration.gen.ts
736: @@ -40,5 +40,9 @@ export const migrations = (
737: import("./migration/20260622142730_simplify_session_context_epoch"),
738: import("./migration/20260622170816_reset_v2_session_state"),
739: import("./migration/20260622202450_simplify_session_input"),
740: + import("./migration/20260914170650_session-recovery"),
741: + import("./migration/20260914214636_session-execution"),
742: + import("./migration/20260914222526_session-execution-phase"),
743: + import("./migration/20260914235657_session-execution-lease"),
744: ])
745: ).map((module) => module.default) satisfies ...
<path>/tmp/opencode/session-recovery-full.patch</path>
<type>file</type>
<content>
1431: + ),
1432: + )
1433: + yield* updateExecution({ phase: "ready", assistant_message_id: null })
1434: const agent = yield* agents.select(session.agent)
1435: const initialized = yield* SessionContextEpoch.initialize(db, loadSystemContext(agent), session.id)
1436: const toolFibers = yield* FiberSet.make<void, ToolOutputStore.Error>()
1437: @@ -219,7 +236,16 @@ const layer = Layer.effect(
1438: tools: toolMaterialization?.definitions ?? [],
1439: toolChoice: isLastStep ? "none" : undefined,
1440: })
1441: - if (yield* compaction.compactIfNeeded({ sessionID: session.id, entries, model, request }))
1442: + if (
1443: + yield* compaction.compactIfNeeded({
1444: + sessionID: session.id,
1445: + entries,
1446: + model,
1447: + request,
1448: + beforeStream: () => ...
<path>/tmp/opencode/session-recovery-full.patch</path>
<type>file</type>
<content>
2131: + SessionTable,
2132: +} from "@opencode-ai/core/session/sql"
2133: import { testEffect } from "./lib/effect"
2134: import { Snapshot } from "@opencode-ai/core/snapshot"
2135: import { Location } from "@opencode-ai/core/location"
2136:
2137: const it = testEffect(AppNodeBuilder.build(LayerNode.group([Database.node, EventV2.node, SessionProjector.node])))
2138: const sessionsLayer = AppNodeBuilder.build(SessionV2.node, [[SessionExecution.node, SessionExecution.noopLayer]])
2139: +const recoveryExecution = SessionExecution.Service.of({
2140: + active: Effect.succeed(new Set()),
2141: + interrupt: () => Effect.void,
2142: + resume: () => Effect.void,
2143: + wake: () => Effect.void,
2144: +})
2145: const sessionID = SessionV2.ID.make("ses_projector_test")
2146: const created = DateTime.makeUnsafe(0)
2147: const model = { id: ModelV2.ID.make("model"), providerID: ...
<path>/home/byk/Code/opencode/packages/core/src/session/runner/llm.ts</path>
<type>file</type>
<content>
170: const loadSystemContext = (agent: AgentV2.Selection) =>
171: Effect.all([systemContext.load(), skillGuidance.load(agent), referenceGuidance.load()], {
172: concurrency: "unbounded",
173: }).pipe(Effect.map(SystemContext.combine))
174:
175: const runTurnAttempt = Effect.fn("SessionRunner.runTurn")(function* (
176: sessionID: SessionSchema.ID,
177: executionID: string,
178: promotion: SessionInput.Delivery | undefined,
179: step: number,
180: recoverOverflow?: typeof compaction.compactAfterOverflow,
181: ) {
182: const session = yield* getSession(sessionID)
183: if (session.location.directory !== location.directory || session.location.workspaceID !== location.workspaceID)
184: return yield* Effect.interrupt
185: const updateExecution = (values: Partial<typeof ...
<path>/home/byk/Code/opencode/packages/core/src/session/execution/local.ts</path>
<type>file</type>
<content>
1: import { and, eq, inArray, lt, or } from "drizzle-orm"
2: import { Cause, Duration, Effect, Exit, Layer, Schedule } from "effect"
3: import { Database } from "../../database/database"
4: import { LocationServiceMap } from "../../location-service-map"
5: import { makeGlobalNode } from "../../effect/app-node"
6: import { SessionRunCoordinator } from "../run-coordinator"
7: import { SessionRunner } from "../runner"
8: import { SessionSchema } from "../schema"
9: import { SessionStore } from "../store"
10: import { SessionExecution } from "../execution"
11: import { SESSION_EXECUTION_LEASE_MS, SessionExecutionTable } from "../sql"
12:
13: /** Current-process routing for implicit-local Locations. Future remote placement belongs here. */
14: export const make = Effect.fnUntraced(function* (input: {
15: readonly run: (
16: sessionID: SessionSchema.ID,
17: force: ...
<path>/home/byk/Code/opencode/packages/core/src/session/runner/publish-llm-event.ts</path>
<type>file</type>
<content>
1: import { ToolOutput, type LLMEvent, type ProviderMetadata, type ToolResultValue, type Usage } from "@opencode-ai/llm"
2: import { DateTime, Effect } from "effect"
3: import { EventV2 } from "../../event"
4: import { ModelV2 } from "../../model"
5: import { SessionEvent } from "../event"
6: import { SessionMessage } from "../message"
7: import { SessionSchema } from "../schema"
8:
9: type Input = {
10: readonly sessionID: SessionSchema.ID
11: readonly agent: string
12: readonly model: ModelV2.Ref
13: readonly snapshot?: string
14: readonly startCommit: (assistantMessageID: SessionMessage.ID) => Effect.Effect<void>
15: readonly terminalCommit: (assistantMessageID: SessionMessage.ID) => Effect.Effect<void>
16: readonly eventCommit: () => Effect.Effect<void>
17: }
18:
19: const safe = (value: number | undefined) => Math.max(0, Number.isFinite(value) ? ...
<path>/home/byk/Code/opencode/packages/core/src/session/recovery.ts</path>
<type>file</type>
<content>
1: export * as SessionRecovery from "./recovery"
2:
3: import { and, desc, eq, isNull, lt } from "drizzle-orm"
4: import { DateTime, Effect, Layer, Schedule } from "effect"
5: import { Database } from "../database/database"
6: import { makeGlobalNode } from "../effect/app-node"
7: import { EventV2 } from "../event"
8: import { SessionEvent } from "./event"
9: import { SessionExecution } from "./execution"
10: import { SessionMessage } from "./message"
11: import { SessionProjector } from "./projector"
12: import { SessionExecutionTable, SessionRecoveryTable, SessionMessageTable } from "./sql"
13:
14: export const recover = Effect.fn("SessionRecovery.recover")(function* () {
15: const { db } = yield* Database.Service
16: const events = yield* EventV2.Service
17: const execution = yield* SessionExecution.Service
18: const claims = yield* db
19: .select()
20: ...
Found 39 matches
/home/byk/Code/opencode/packages/core/src/session/recovery.ts:
Line 102: if (recovered) yield* execution.wake(claim.session_id)
/home/byk/Code/opencode/packages/core/src/session/projector.ts:
Line 19: SessionInputTable,
Line 486: .delete(SessionInputTable)
Line 489: eq(SessionInputTable.session_id, event.data.sessionID),
Line 490: or(gt(SessionInputTable.admitted_seq, boundary.seq), gt(SessionInputTable.promoted_seq, boundary.seq)),
/home/byk/Code/opencode/packages/core/src/session/sql.ts:
Line 140: export const SessionInputTable = sqliteTable(
Line 183: .references(() => SessionInputTable.id, { onDelete: "cascade" }),
/home/byk/Code/opencode/packages/core/src/session/input.ts:
Line 12: import { SessionInputTable, SessionMessageTable } from "./sql"
Line 21: const fromRow = (row: typeof SessionInputTable.$inferSelect): Admitted =>
Line 33: const row = yield* ...
<path>/home/byk/Code/opencode/packages/core/src/session/run-coordinator.ts</path>
<type>file</type>
<content>
1: export * as SessionRunCoordinator from "./run-coordinator"
2:
3: import { Deferred, Effect, Exit, Fiber, FiberSet, Scope } from "effect"
4:
5: /** Serializes execution for each key while allowing different keys to run concurrently. */
6: export interface Coordinator<Key, E> {
7: /** Snapshots keys with an execution owned by this coordinator. */
8: readonly active: Effect.Effect<ReadonlySet<Key>>
9: /** Starts execution while idle or joins the active execution. */
10: readonly run: (key: Key) => Effect.Effect<void, E>
11: /** Registers one coalesced follow-up after newly recorded work. */
12: readonly wake: (key: Key) => Effect.Effect<void>
13: /** Stops active execution and waits for its cleanup. */
14: readonly interrupt: (key: Key) => Effect.Effect<void>
15: }
16:
17: type Entry<E> = {
18: readonly done: Deferred.Deferred<void, E>
19: owner?: ...
<path>/home/byk/Code/opencode/packages/core/src/session/compaction.ts</path>
<type>file</type>
<content>
180: }
181:
182: export const make = (dependencies: Dependencies) => {
183: const config = settings(dependencies.config)
184: const compactAfterOverflow = Effect.fn("SessionCompaction.compactAfterOverflow")(function* (input: CompactInput) {
185: const context = input.model.route.defaults.limits?.context
186: if (context === undefined || context <= 0) return false
187: const output = input.request.generation?.maxTokens ?? input.model.route.defaults.limits?.output ?? 0
188: const selected = select(input.entries, config.tokens)
189: const previousSummary = input.entries.find((entry) => entry.message.type === "compaction")?.message
190: if (!selected || (selected.head.length === 0 && previousSummary?.type !== "compaction")) return false
191: const summaryPrompt = buildPrompt({
192: previousSummary: previousSummary?.type === "compaction" ? ...
import { and, eq } from "drizzle-orm"
import { Cause, Effect, Exit, Layer } from "effect"
import { Database } from "../../database/database"
import { LocationServiceMap } from "../../location-service-map"
import { makeGlobalNode } from "../../effect/app-node"
import { SessionRunCoordinator } from "../run-coordinator"
import { SessionRunner } from "../runner"
import { SessionSchema } from "../schema"
import { SessionStore } from "../store"
import { SessionExecution } from "../execution"
import { SessionExecutionTable } from "../sql"
/** Current-process routing for implicit-local Locations. Future remote placement belongs here. */
export const make = Effect.fnUntraced(function* (input: {
readonly run: (sessionID: SessionSchema.ID, force: boolean) => Effect.Effect<void, SessionRunner.RunError>
}) {
const { db } = yield* Database.Service
const ownerID = crypto.randomUUID()
const clear = (sessionID: SessionSchema.ID) =>
db
.delete(SessionExecutionTable)
...
<path>/home/byk/Code/opencode/packages/core/src/session/projector.ts</path>
<type>file</type>
<content>
380: })
381: }),
382: )
383: yield* events.project(SessionEvent.ContextUpdated, (event) => run(db, event))
384: yield* events.project(SessionEvent.Synthetic, (event) => run(db, event))
385: yield* events.project(SessionEvent.Shell.Started, (event) => run(db, event))
386: yield* events.project(SessionEvent.Shell.Ended, (event) => run(db, event))
387: yield* events.project(SessionEvent.Step.Started, (event) => run(db, event))
388: yield* events.project(SessionEvent.Step.Ended, (event) => run(db, event))
389: yield* events.project(SessionEvent.Step.Failed, (event) => run(db, event))
390: yield* events.project(SessionEvent.Step.Interrupted, (event) => run(db, event))
391: yield* events.project(SessionEvent.Step.Recovered, (event) =>
392: Effect.gen(function* () {
393: if (event.durable === undefined) return yield* ...
<path>/home/byk/Code/opencode/packages/core/src/event.ts</path>
<type>file</type>
<content>
300: }),
301: )
302: }
303: const stored = yield* db
304: .select({ aggregateID: EventTable.aggregate_id, seq: EventTable.seq })
305: .from(EventTable)
306: .where(eq(EventTable.id, event.id))
307: .get()
308: .pipe(Effect.orDie)
309: if (stored)
310: yield* Effect.die(
311: new InvalidDurableEventError({
312: type: event.type,
313: message: `Event ${event.id} already exists at aggregate ${stored.aggregateID} sequence ${stored.seq}`,
314: }),
315: ...
<path>/home/byk/Code/opencode/packages/core/src/session/runner/llm.ts</path>
<type>file</type>
<content>
112: const getSession = Effect.fn("SessionRunner.getSession")(function* (sessionID: SessionSchema.ID) {
113: const session = yield* store.get(sessionID)
114: if (!session) return yield* Effect.die(`Session not found: ${sessionID}`)
115: return session
116: })
117:
118: const getContext = Effect.fn("SessionRunner.getContext")(function* (sessionID: SessionSchema.ID) {
119: return yield* store.context(sessionID)
120: })
121: const failInterruptedTools = Effect.fn("SessionRunner.failInterruptedTools")(function* (
122: sessionID: SessionSchema.ID,
123: ) {
124: for (const message of yield* getContext(sessionID)) {
125: if (message.type !== "assistant") continue
126: for (const tool of message.content) {
127: if (tool.type !== "tool" || (tool.state.status !== "pending" && tool.state.status !== ...
<path>/home/byk/Code/opencode/packages/core/src/database/migration/20260914235657_session-execution-lease.ts</path>
<type>file</type>
<content>
1: import { Effect } from "effect"
2: import type { DatabaseMigration } from "../migration"
3:
4: export default {
5: id: "20260914235657_session-execution-lease",
6: up(tx) {
7: return Effect.gen(function* () {
8: yield* tx.run(`ALTER TABLE \`session_execution\` ADD \`expires_at\` integer NOT NULL;`)
9: })
10: },
11: } satisfies DatabaseMigration.Migration
(End of file - total 11 lines)
</content>
Found 1 matches
/home/byk/Code/opencode/packages/core/src/session.ts:
Line 12: import { Prompt } from "./session/prompt"
<path>/home/byk/Code/opencode/packages/core/src/database/migration/20260914170650_session-recovery.ts</path>
<type>file</type>
<content>
1: import { Effect } from "effect"
2: import type { DatabaseMigration } from "../migration"
3:
4: export default {
5: id: "20260914170650_session-recovery",
6: up(tx) {
7: return Effect.gen(function* () {
8: yield* tx.run(`
9: CREATE TABLE \`session_recovery\` (
10: \`session_id\` text NOT NULL,
11: \`assistant_message_id\` text NOT NULL,
12: \`continuation_message_id\` text NOT NULL,
13: \`time_created\` integer NOT NULL,
14: CONSTRAINT \`session_recovery_pk\` PRIMARY KEY(\`session_id\`, \`assistant_message_id\`),
15: CONSTRAINT \`fk_session_recovery_session_id_session_id_fk\` FOREIGN KEY (\`session_id\`) REFERENCES \`session\`(\`id\`) ON DELETE CASCADE,
16: CONSTRAINT \`fk_session_recovery_assistant_message_id_session_message_id_fk\` FOREIGN KEY ...
<path>/home/byk/Code/opencode/packages/core/src/database/migration/20260914214636_session-execution.ts</path>
<type>file</type>
<content>
1: import { Effect } from "effect"
2: import type { DatabaseMigration } from "../migration"
3:
4: export default {
5: id: "20260914214636_session-execution",
6: up(tx) {
7: return Effect.gen(function* () {
8: yield* tx.run(`
9: CREATE TABLE \`session_execution\` (
10: \`id\` text PRIMARY KEY,
11: \`session_id\` text NOT NULL,
12: \`owner_id\` text NOT NULL,
13: \`assistant_message_id\` text,
14: \`time_created\` integer NOT NULL,
15: CONSTRAINT \`fk_session_execution_session_id_session_id_fk\` FOREIGN KEY (\`session_id\`) REFERENCES \`session\`(\`id\`) ON DELETE CASCADE
16: );
17: `)
18: yield* tx.run(`ALTER TABLE \`session_recovery\` ADD \`execution_id\` text NOT NULL;`)
19: yield* tx.run(`CREATE UNIQUE INDEX ...
Found 100 matches (more matches available)
/home/byk/Code/opencode/packages/core/test/session-execution-local.test.ts:
Line 104: it.effect("takes over an expired lease and fences the stale owner", () =>
/home/byk/Code/opencode/packages/core/test/session-runner.test.ts:
Line 42: import { ConfigCompaction } from "@opencode-ai/core/config/compaction"
Line 108: const recoveryModel = Model.make({
Line 109: id: "recovery",
Line 221: compaction: new ConfigCompaction.Info({
Line 382: currentModel = recoveryModel
Line 1064: it.effect("rebuilds the baseline directly after completed compaction", () =>
Line 1074: const compactionID = SessionMessage.ID.create()
Line 1077: messageID: compactionID,
Line 1083: messageID: compactionID,
Line 1145: expect(context.map((message) => message.type)).toEqual(["compaction", "assistant"])
Line 1147: type: "compaction",
Line 1170: type: "compaction",
Line ...
Found 36 matches /home/byk/Code/opencode/packages/core/schema.json: Line 4: "id": "ab874548-0fd0-4748-9f7d-762a6e5e7479", Line 6: "57591e35-6f8f-4cda-932c-6a08e85caae7" Line 66: "name": "session_execution", Line 78: "name": "session_recovery", Line 941: "table": "session_execution" Line 951: "table": "session_execution" Line 961: "table": "session_execution" Line 971: "table": "session_execution" Line 981: "table": "session_execution" Line 991: "table": "session_execution" Line 1001: "table": "session_execution" Line 1151: "table": "session_recovery" Line 1161: "table": "session_recovery" Line 1171: "table": "session_recovery" Line 1181: "table": "session_recovery" Line 1191: "table": "session_recovery" Line 1744: "name": "fk_session_execution_session_id_session_id_fk", Line 1746: "table": "session_execution" Line 1789: ...
Found 4 matches
/home/byk/Code/opencode/packages/core/src/database/migration.gen.ts:
Line 43: import("./migration/20260914170650_session-recovery"),
Line 44: import("./migration/20260914214636_session-execution"),
Line 45: import("./migration/20260914222526_session-execution-phase"),
Line 46: import("./migration/20260914235657_session-execution-lease"),
<path>/home/byk/Code/opencode/packages/core/src/session/message-updater.ts</path>
<type>file</type>
<content>
218: end: event.data.snapshot,
219: files: event.data.files ? Array.from(event.data.files) : undefined,
220: }
221: })
222: },
223: "session.next.step.failed": (event) => {
224: return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
225: draft.time.completed = event.data.timestamp
226: draft.finish = "error"
227: draft.error = event.data.error
228: })
229: },
230: "session.next.step.interrupted": (event) => {
231: return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
232: draft.time.completed = event.data.timestamp
233: draft.finish = "error"
234: draft.error = event.data.error
235: })
236: },
237: "session.next.step.recovered": (event) => {
238: const error = ...
<path>/home/byk/Code/opencode/packages/core/test/session-projector.test.ts</path>
<type>file</type>
<content>
175: expect(yield* db.select().from(SessionInputTable).all()).toHaveLength(1)
176: expect(yield* db.select().from(SessionRecoveryTable).all()).toHaveLength(1)
177: }),
178: )
179:
180: it.effect("recovers each abandoned execution once across repeated startup scans", () =>
181: Effect.gen(function* () {
182: const { db } = yield* Database.Service
183: yield* db
184: .insert(ProjectTable)
185: .values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] })
186: .run()
187: yield* db
188: .insert(SessionTable)
189: .values({
190: id: sessionID,
191: project_id: Project.ID.global,
192: slug: "test",
193: directory: "/project",
194: title: "test",
195: version: "test",
196: })
197: .run()
198: ...
<path>/home/byk/Code/opencode/packages/core/test/database-migration.test.ts</path>
<type>file</type>
<content>
110: Effect.all(
111: layers.map((layer) => Effect.scoped(Layer.build(layer))),
112: { concurrency: "unbounded" },
113: ),
114: )
115: })
116: if (process.platform === "linux") {
117: test("declared schema has no ungenerated migrations", async () => {
118: const result = await $`bun ${fileURLToPath(new URL("../script/migration.ts", import.meta.url))} --check`
119: .quiet()
120: .nothrow()
121: expect(result.exitCode, result.stderr.toString()).toBe(0)
122: expect(result.stdout.toString()).toContain("No schema changes, nothing to migrate")
123: }, 30_000)
124: }
125:
126: test("applies tracked migrations to an empty database", async () => {
127: await run(
128: Effect.gen(function* () {
129: const db = yield* makeDb
130: yield* DatabaseMigration.apply(db)
131:
132: ...
Found 11 matches /home/byk/Code/opencode/packages/core/src/database/schema.gen.ts: Line 158: CREATE TABLE \`session_execution\` ( Line 166: CONSTRAINT \`fk_session_execution_session_id_session_id_fk\` FOREIGN KEY (\`session_id\`) REFERENCES \`session\`(\`id\`) ON DELETE CASCADE Line 194: CREATE TABLE \`session_recovery\` ( Line 200: CONSTRAINT \`session_recovery_pk\` PRIMARY KEY(\`session_id\`, \`assistant_message_id\`), Line 201: CONSTRAINT \`fk_session_recovery_session_id_session_id_fk\` FOREIGN KEY (\`session_id\`) REFERENCES \`session\`(\`id\`) ON DELETE CASCADE, Line 202: CONSTRAINT \`fk_session_recovery_assistant_message_id_session_message_id_fk\` FOREIGN KEY (\`assistant_message_id\`) REFERENCES \`session_message\`(\`id\`) ON DELETE CASCADE, Line 203: CONSTRAINT \`fk_session_recovery_continuation_message_id_session_input_id_fk\` FOREIGN KEY (\`continuation_message_id\`) REFERENCES ...
<path>/home/byk/Code/opencode/packages/server/src/routes.ts</path>
<type>file</type>
<content>
20: import { handlers } from "./handlers"
21: import { authorizationLayer } from "./middleware/authorization"
22: import { schemaErrorLayer } from "./middleware/schema-error"
23: import { PtyEnvironment } from "./pty-environment"
24: import { layer as locationLayer } from "./location"
25: import { sessionLocationLayer } from "./middleware/session-location"
26:
27: const applicationServices = LayerNode.group([
28: Database.node,
29: EventV2.node,
30: httpClient,
31: ToolOutputStore.cleanupNode,
32: SessionV2.node,
33: SessionRecovery.node,
34: PermissionSaved.node,
35: PtyTicket.node,
36: Credential.node,
37: PtyEnvironment.node,
38: LocationServiceMap.node,
39: ])
40:
41: export function createRoutes(password?: string) {
42: return makeRoutes(
43: password
44: ? ServerAuth.Config.configLayer({ username: "opencode", password: Option.some(password) })
45: ...
<path>/home/byk/Code/opencode/packages/opencode/src/server/routes/instance/httpapi/server.ts</path> <type>file</type> <content> 285: ).pipe( 286: Layer.provide([ 287: errorLayer, 288: compressionLayer, 289: corsVaryFix, 290: fenceLayer, 291: cors(corsOptions), 292: AppNodeBuilderV1.build(MoveSession.node, [[LocationServiceMap.node, locationServiceMapV2]]), 293: HttpServer.layerServices, 294: ]), 295: Layer.provide(Layer.succeed(CorsConfig)(corsOptions)), 296: Layer.provide(sessionLocationLayer), 297: Layer.provide(locationLayer), 298: Layer.provide(PtyEnvironment.layer), 299: Layer.provide( 300: AppNodeBuilderV1.build(LayerNode.group([SessionV2.node, SessionRecovery.node]), [ 301: [LocationServiceMap.node, locationServiceMapV2], 302: [SessionExecution.node, SessionExecutionLocal.node], 303: ]), 304: ), 305: Layer.provide(locationServiceMapV2), 306: 307: ...
<path>/home/byk/Code/opencode/packages/schema/src/session-event.ts</path>
<type>file</type>
<content>
185: export const Failed = Event.define({
186: type: "session.next.step.failed",
187: ...stepSettlementOptions,
188: schema: {
189: ...Base,
190: assistantMessageID: SessionMessage.ID,
191: error: UnknownError,
192: },
193: })
194: export type Failed = typeof Failed.Type
195:
196: export const Interrupted = Event.define({
197: type: "session.next.step.interrupted",
198: ...options,
199: schema: {
200: ...Base,
201: assistantMessageID: SessionMessage.ID,
202: error: UnknownError,
203: },
204: })
205: export type Interrupted = typeof Interrupted.Type
206:
207: export const Recovered = Event.define({
208: type: "session.next.step.recovered",
209: ...options,
210: schema: {
211: ...Base,
212: executionID: Schema.String,
213: recoveryMessageID: SessionMessage.ID,
214: ...
<path>/home/byk/Code/opencode/packages/core/src/session/sql.ts</path>
<type>file</type>
<content>
135: index("session_message_session_time_created_id_idx").on(table.session_id, table.time_created, table.id),
136: index("session_message_time_created_idx").on(table.time_created),
137: ],
138: )
139:
140: export const SessionInputTable = sqliteTable(
141: "session_input",
142: {
143: id: text().$type<SessionMessage.ID>().primaryKey(),
144: session_id: text()
145: .$type<SessionSchema.ID>()
146: .notNull()
147: .references(() => SessionTable.id, { onDelete: "cascade" }),
148: prompt: text({ mode: "json" }).notNull().$type<Prompt>(),
149: delivery: text().$type<SessionInput.Delivery>().notNull(),
150: admitted_seq: integer().notNull(),
151: promoted_seq: integer(),
152: time_created: integer()
153: .notNull()
154: .$default(() => Date.now()),
155: },
156: (table) => [
157: ...
f0a743c5c616ed7d48e491828e72226bfa4a2cb3bd0104fb785154bcbc4c5870 - 57591e35-6f8f-4cda-932c-6a08e85caae7 [ "9401e0ed-b1e3-4557-bc94-1c9b29e1e09c" ]
<path>/home/byk/Code/opencode/packages/schema/src/session-event.ts</path>
<type>file</type>
<content>
145: export type Ended = typeof Ended.Type
146: }
147:
148: export namespace Step {
149: export const Started = Event.define({
150: type: "session.next.step.started",
151: ...options,
152: schema: {
153: ...Base,
154: assistantMessageID: SessionMessage.ID,
155: agent: Schema.String,
156: model: Model.Ref,
157: snapshot: Schema.String.pipe(optional),
158: },
159: })
160: export type Started = typeof Started.Type
161:
162: export const Ended = Event.define({
163: type: "session.next.step.ended",
164: ...stepSettlementOptions,
165: schema: {
166: ...Base,
167: assistantMessageID: SessionMessage.ID,
168: finish: Schema.String,
169: cost: Schema.Finite,
170: tokens: Schema.Struct({
171: input: Schema.Finite,
172: output: Schema.Finite,
173: reasoning: ...
Found 4 matches
/home/byk/Code/opencode/packages/schema/src/session-event.ts:
Line 38: const options = {
Line 44: const stepSettlementOptions = {
Line 164: ...stepSettlementOptions,
Line 187: ...stepSettlementOptions,
<path>/home/byk/Code/opencode/packages/schema/src/session-event.ts</path>
<type>file</type>
<content>
32: ...Base,
33: messageID: SessionMessage.ID,
34: prompt: Prompt,
35: delivery: Delivery,
36: }
37:
38: const options = {
39: durable: {
40: aggregate: "sessionID",
41: version: 1,
42: },
43: } as const
44: const stepSettlementOptions = {
45: durable: {
46: aggregate: "sessionID",
47: version: 2,
48: },
49: } as const
50:
51: export const UnknownError = SessionMessage.UnknownError
52: export type UnknownError = SessionMessage.UnknownError
53:
54: export const AgentSwitched = Event.define({
55: type: "session.next.agent.switched",
56: ...options,
(Showing lines 32-56 of 550. Use offset=57 to continue.)
</content>
<path>/home/byk/Code/opencode/packages/core/src/database/migration/20260914222526_session-execution-phase.ts</path>
<type>file</type>
<content>
1: import { Effect } from "effect"
2: import type { DatabaseMigration } from "../migration"
3:
4: export default {
5: id: "20260914222526_session-execution-phase",
6: up(tx) {
7: return Effect.gen(function* () {
8: yield* tx.run(`ALTER TABLE \`session_execution\` ADD \`phase\` text NOT NULL;`)
9: })
10: },
11: } satisfies DatabaseMigration.Migration
(End of file - total 11 lines)
</content>
export * as SessionEvent from "./session-event"
import { Schema } from "effect"
import { optional } from "./schema"
import { Event } from "./event"
import { ProviderMetadata, ToolContent } from "./llm"
import { Delivery } from "./session-delivery"
import { Model } from "./model"
import { DateTimeUtcFromMillis, NonNegativeInt, RelativePath } from "./schema"
import { FileAttachment, Prompt } from "./prompt"
import { SessionID } from "./session-id"
import { Location } from "./location"
import { SessionMessage } from "./session-message"
import { Revert } from "./revert"
export { FileAttachment }
export const Source = Schema.Struct({
start: NonNegativeInt,
end: NonNegativeInt,
text: Schema.String,
}).annotate({
identifier: "session.next.event.source",
})
export interface Source extends Schema.Schema.Type<typeof Source> {}
const Base = {
timestamp: DateTimeUtcFromMillis,
sessionID: SessionID,
}
const PromptFields = {
...Base,
messageID: SessionMessage.ID,
prompt: ...
Found 12 matches
/home/byk/Code/opencode/packages/core/src/session/message-updater.ts:
Line 33: return assistant?.type === "assistant" && !assistant.time.completed ? assistant : undefined
Line 180: draft.time.completed = event.data.timestamp
Line 192: draft.time.completed = event.data.timestamp
Line 211: draft.time.completed = event.data.timestamp
Line 225: draft.time.completed = event.data.timestamp
Line 232: draft.time.completed = event.data.timestamp
Line 256: draft.time.completed = event.data.timestamp
Line 270: tool.time.completed = event.data.timestamp
Line 359: match.time.completed = event.data.timestamp
Line 362: status: "completed",
Line 382: match.time.completed = event.data.timestamp
Line 422: match.time = { created: match.time?.created ?? event.data.timestamp, completed: event.data.timestamp }
<path>/home/byk/Code/opencode/packages/core/src/session/message-updater.ts</path>
<type>file</type>
<content>
1: import { castDraft, produce, type WritableDraft } from "immer"
2: import { Effect } from "effect"
3: import { SessionEvent } from "./event"
4: import { SessionMessage } from "./message"
5:
6: export type MemoryState = {
7: messages: SessionMessage.Message[]
8: }
9:
10: export interface Adapter {
11: readonly getCurrentAssistant: () => Effect.Effect<SessionMessage.Assistant | undefined>
12: readonly getAssistant: (messageID: SessionMessage.ID) => Effect.Effect<SessionMessage.Assistant | undefined>
13: readonly getCurrentShell: (callID: string) => Effect.Effect<SessionMessage.Shell | undefined>
14: readonly updateAssistant: (assistant: SessionMessage.Assistant) => Effect.Effect<void>
15: readonly updateShell: (shell: SessionMessage.Shell) => Effect.Effect<void>
16: readonly appendMessage: (message: SessionMessage.Message) => Effect.Effect<void>
17: }
18:
19: ...
<path>/home/byk/Code/opencode/packages/core/test/session-execution-local.test.ts</path>
<type>file</type>
<content>
40:
41: describe("SessionExecutionLocal", () => {
42: it.effect("clears the claim after a successful drain", () =>
43: Effect.gen(function* () {
44: yield* insertSession
45: const execution = yield* SessionExecutionLocal.make({ run: () => Effect.void })
46: yield* execution.resume(sessionID)
47: expect(yield* claims).toEqual([])
48: }),
49: )
50:
51: it.effect("preserves the claim when its scope closes", () =>
52: Effect.gen(function* () {
53: yield* insertSession
54: const started = Deferred.makeUnsafe<void>()
55: const child = yield* Scope.make()
56: const execution = yield* SessionExecutionLocal.make({
57: run: () => Deferred.succeed(started, undefined).pipe(Effect.andThen(Effect.never)),
58: }).pipe(Effect.provideService(Scope.Scope, child))
59: yield* ...
Found 1 matches /home/byk/Code/opencode/packages/core/src/session/projector.ts: Line 63: version: info.version,
Found 1 matches /home/byk/Code/opencode/packages/core/src/session/message-updater.ts: Line 95: const updateOwnedAssistant = (messageID: SessionMessage.ID, recipe: (draft: DraftAssistant) => void) =>
<path>/home/byk/Code/opencode/packages/core/src/session/message-updater.ts</path>
<type>file</type>
<content>
85: assistant?.content.findLast(
86: (item): item is DraftTool => item.type === "tool" && (callID === undefined || item.id === callID),
87: )
88:
89: const latestText = (assistant: DraftAssistant | undefined, textID: string) =>
90: assistant?.content.findLast((item): item is DraftText => item.type === "text" && item.id === textID)
91:
92: const latestReasoning = (assistant: DraftAssistant | undefined, reasoningID: string) =>
93: assistant?.content.findLast((item): item is DraftReasoning => item.type === "reasoning" && item.id === reasoningID)
94:
95: const updateOwnedAssistant = (messageID: SessionMessage.ID, recipe: (draft: DraftAssistant) => void) =>
96: Effect.gen(function* () {
97: const assistant = yield* adapter.getAssistant(messageID)
98: if (assistant) yield* adapter.updateAssistant(produce(assistant, recipe))
99: ...
M .lore.md M AGENTS.md M packages/core/schema.json M packages/core/src/database/migration.gen.ts M packages/core/src/database/schema.gen.ts M packages/core/src/session/compaction.ts M packages/core/src/session/execution/local.ts M packages/core/src/session/message-updater.ts M packages/core/src/session/projector.ts M packages/core/src/session/recovery.ts M packages/core/src/session/runner/index.ts M packages/core/src/session/runner/llm.ts M packages/core/src/session/runner/publish-llm-event.ts M packages/core/src/session/sql.ts M packages/core/test/session-execution-local.test.ts M packages/core/test/session-projector.test.ts M packages/core/test/session-runner-recorded.test.ts M packages/core/test/session-runner-tool-events.test.ts M packages/core/test/session-runner.test.ts M packages/schema/src/session-event.ts .lore.md | 170 +++++++++++---------- AGENTS.md | 6 + packages/core/schema.json ...
import type { DatabaseMigration } from "./migration"
export const migrations = (
await Promise.all([
import("./migration/20260127222353_familiar_lady_ursula"),
import("./migration/20260211171708_add_project_commands"),
import("./migration/20260213144116_wakeful_the_professor"),
import("./migration/20260225215848_workspace"),
import("./migration/20260227213759_add_session_workspace_id"),
import("./migration/20260228203230_blue_harpoon"),
import("./migration/20260303231226_add_workspace_fields"),
import("./migration/20260309230000_move_org_to_state"),
import("./migration/20260312043431_session_message_cursor"),
import("./migration/20260323234822_events"),
import("./migration/20260410174513_workspace-name"),
import("./migration/20260413175956_chief_energizer"),
import("./migration/20260423070820_add_icon_url_override"),
import("./migration/20260427172553_slow_nightmare"),
...
Found 5 matches /home/byk/Code/opencode/AGENTS.md: Line 11: Examples: `session-recovery`, `fix-scroll-state`, `regenerate-sdk`. Line 155: - Keep `SessionExecution` process-global and Session-ID based. Its local implementation owns the process-local Session coordinator and discovers placement through `SessionStore` plus `LocationServiceMap.get(session.location)` only when a drain starts; no layer should take a Session ID. V2 interruption targets the active process-local ownership chain for that Session; idle or missing interruption is a no-op. Line 158: - Keep local Session drains process-local until clustering is implemented. `SessionRunCoordinator` joins explicit same-Session resumes, coalesces prompt wakeups, and allows different Sessions to run concurrently. Advisory wakes drain eligible durable inbox rows only; post-crash continuation recovery requires a separate explicit design before it may retry provider work. A drain has no durable identity or transcript boundary. ...
<path>/home/byk/Code/opencode/AGENTS.md</path> <type>file</type> <content> 145: - Tests cannot run from repo root (guard: `do-not-run-tests-from-root`); run from package dirs like `packages/opencode`. 146: 147: ## Type Checking 148: 149: - Always run `bun typecheck` from package directories (e.g., `packages/opencode`), never `tsc` directly. 150: 151: ## V2 Session Core 152: 153: - Keep durable prompt admission separate from model execution. `SessionV2.prompt(...)` admits one durable `session_input` row before scheduling advisory `SessionExecution.wake(sessionID)` unless `resume: false` requests admit-only behavior. The serialized runner promotes admitted inputs into visible user messages at safe boundaries. 154: - Reusing a Session ID adopts the existing Session. Reusing a prompt message ID reconciles an exact retry only when Session, prompt, and delivery mode match; conflicting reuse fails. Historical projected prompts lazily synthesize promoted inbox records during exact retry. ...
...output truncated...
Full output saved to: /home/byk/.local/share/opencode/tool-output/tool_0a552ee4c001ly5YOp9XTq0TP8
}),
)
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
draft.time.completed = event.data.timestamp
draft.finish = "error"
- draft.error = event.data.error
+ draft.error = error
draft.content.push(
castDraft(
SessionMessage.AssistantText.make({
type: "text",
id: `recovery:${event.data.executionID}`,
- text: event.data.error.message,
+ text: error.message,
}),
),
)
for (const tool of draft.content) {
if (tool.type !== "tool" || (tool.state.status !== "pending" && tool.state.status !== "running")) continue
tool.time.completed = event.data.timestamp
tool.state = castDraft(
...
Found 38 matches /home/byk/Code/opencode/.lore.md: Line 32: * **Legacy V1 execution durability**: Legacy V1 durably stores transcript events and SQL projections, but not execution ownership: \`SessionRunState\`, public busy/retry status, permissions, and background jobs are instance-memory state. Chose transcript reconciliation over automatic restart replay because startup has no recovery scan or durable attempt boundary; after a crash, incomplete provider/tool work is uncertain and another process may execute the same Session concurrently. Line 62: * **Physical Attempt llm.stream**: Chose exactly one explicit \`llm.stream(request)\` per physical attempt of a logical Step, with projected-history reload before continuation, over an in-memory tool loop because continuation must remain durable and replayable. This does not guarantee one network request: the LLM executor may retry retryable transport failures. ...
<path>/home/byk/Code/opencode/packages/core/src/session/message-updater.ts</path>
<type>file</type>
<content>
155: type: "synthetic",
156: time: { created: event.data.timestamp },
157: }),
158: )
159: },
160: "session.next.shell.started": (event) => {
161: return adapter.appendMessage(
162: SessionMessage.Shell.make({
163: id: event.data.messageID,
164: type: "shell",
165: metadata: event.metadata,
166: callID: event.data.callID,
167: command: event.data.command,
168: output: "",
169: time: { created: event.data.timestamp },
170: }),
171: )
172: },
173: "session.next.shell.ended": (event) => {
174: return Effect.gen(function* () {
175: const currentShell = yield* adapter.getCurrentShell(event.data.callID)
176: if (currentShell) {
177: yield* ...
Found 24 matches /tmp/opencode/session-recovery-full.patch: Line 1: diff --git a/packages/core/schema.json b/packages/core/schema.json Line 732: diff --git a/packages/core/src/database/migration.gen.ts b/packages/core/src/database/migration.gen.ts Line 746: diff --git a/packages/core/src/database/migration/20260914170650_session-recovery.ts b/packages/core/src/database/migration/20260914170650_session-recovery.ts Line 777: diff --git a/packages/core/src/database/migration/20260914214636_session-execution.ts b/packages/core/src/database/migration/20260914214636_session-execution.ts Line 809: diff --git a/packages/core/src/database/migration/20260914222526_session-execution-phase.ts b/packages/core/src/database/migration/20260914222526_session-execution-phase.ts Line 826: diff --git a/packages/core/src/database/schema.gen.ts b/packages/core/src/database/schema.gen.ts Line 891: diff --git a/packages/core/src/session/compaction.ts ...
<path>/home/byk/Code/opencode/packages/core/src/session/runner/index.ts</path>
<type>file</type>
<content>
15: | ContextSnapshotDecodeError
16: | SystemContext.InitializationBlocked
17: | ToolOutputStore.Error
18:
19: /** Runs one local continuation from already-recorded Session history. */
20: export interface Interface {
21: /** Drains eligible durable work. Explicit runs perform one provider attempt even when no work is eligible. */
22: readonly run: (input: {
23: readonly sessionID: SessionSchema.ID
24: readonly force: boolean
25: readonly executionID: string
26: }) => Effect.Effect<void, RunError>
27: }
28:
29: export class Service extends Context.Service<Service, Interface>()("@opencode/v2/SessionRunner") {}
(End of file - total 29 lines)
</content>
<path>/home/byk/Code/opencode/packages/core/src/session/projector.ts</path>
<type>file</type>
<content>
90: }
91:
92: function partData(part: (typeof SessionV1.Event.PartUpdated.Type)["data"]["part"]): typeof PartTable.$inferInsert.data {
93: const { id: _, messageID: __, sessionID: ___, ...rest } = part
94: return rest as DeepMutable<typeof rest>
95: }
96:
97: function applyUsage(
98: db: DatabaseService,
99: sessionID: (typeof SessionV1.Event.MessageUpdated.Type)["data"]["sessionID"],
100: value: Usage,
101: sign = 1,
102: ) {
103: return db
104: .update(SessionTable)
105: .set({
106: cost: sql`${SessionTable.cost} + ${value.cost * sign}`,
107: tokens_input: sql`${SessionTable.tokens_input} + ${value.tokens.input * sign}`,
108: tokens_output: sql`${SessionTable.tokens_output} + ${value.tokens.output * sign}`,
109: tokens_reasoning: sql`${SessionTable.tokens_reasoning} + ${value.tokens.reasoning * sign}`,
110: tokens_cache_read: ...
<path>/home/byk/Code/opencode/packages/core/src/session/input.ts</path>
<type>file</type>
<content>
85: input: {
86: readonly admittedSeq: number
87: readonly id: SessionMessage.ID
88: readonly sessionID: SessionSchema.ID
89: readonly prompt: Prompt
90: readonly delivery: Delivery
91: readonly timeCreated: DateTime.Utc
92: },
93: ) {
94: const message = yield* db
95: .select({ id: SessionMessageTable.id })
96: .from(SessionMessageTable)
97: .where(eq(SessionMessageTable.id, input.id))
98: .get()
99: .pipe(Effect.orDie)
100: if (message !== undefined) return yield* Effect.die(new LifecycleConflict({ id: input.id }))
101: const stored = yield* db
102: .insert(SessionInputTable)
103: .values({
104: id: input.id,
105: session_id: input.sessionID,
106: admitted_seq: input.admittedSeq,
107: prompt: encodePrompt(input.prompt),
108: delivery: input.delivery,
109: time_created: ...
<path>/home/byk/Code/opencode/packages/core/test/session-runner-recorded.test.ts</path>
<type>file</type>
<content>
80: [Location.node, Location.boundNode({ directory: AbsolutePath.make("/project") })],
81: [SkillGuidance.node, skillGuidance],
82: [ReferenceGuidance.node, referenceGuidance],
83: [Config.node, config],
84: [PermissionV2.node, permission],
85: [ToolOutputStore.node, ToolOutputStore.nodeWithoutConfig],
86: ])
87: const execution = Layer.effect(
88: SessionExecution.Service,
89: Effect.gen(function* () {
90: const sessionRunner = yield* SessionRunner.Service
91: return yield* SessionExecutionLocal.make({
92: run: (sessionID, force, executionID) => sessionRunner.run({ sessionID, force, executionID }),
93: })
94: }),
95: ).pipe(Layer.provide([runnerLayer, LayerNode.compile(Database.node)]))
96: const it = testEffect(
97: AppNodeBuilder.build(
98: LayerNode.group([
99: Database.node,
100: EventV2.node,
101: ...
<path>/home/byk/Code/opencode/packages/core/src/database/schema.gen.ts</path> <type>file</type> <content> 150: \`session_id\` text PRIMARY KEY, 151: \`baseline\` text NOT NULL, 152: \`snapshot\` text NOT NULL, 153: \`baseline_seq\` integer NOT NULL, 154: CONSTRAINT \`fk_session_context_epoch_session_id_session_id_fk\` FOREIGN KEY (\`session_id\`) REFERENCES \`session\`(\`id\`) ON DELETE CASCADE 155: ); 156: `) 157: yield* tx.run(` 158: CREATE TABLE \`session_execution\` ( 159: \`id\` text PRIMARY KEY, 160: \`session_id\` text NOT NULL, 161: \`owner_id\` text NOT NULL, 162: \`assistant_message_id\` text, 163: \`phase\` text NOT NULL, 164: \`time_created\` integer NOT NULL, 165: \`expires_at\` integer NOT NULL, 166: CONSTRAINT \`fk_session_execution_session_id_session_id_fk\` FOREIGN KEY (\`session_id\`) REFERENCES ...
<path>/home/byk/Code/opencode/packages/core/schema.json</path>
<type>file</type>
<content>
930: "entityType": "columns",
931: "table": "session_context_epoch"
932: },
933: {
934: "type": "text",
935: "notNull": false,
936: "autoincrement": false,
937: "default": null,
938: "generated": null,
939: "name": "id",
940: "entityType": "columns",
941: "table": "session_execution"
942: },
943: {
944: "type": "text",
945: "notNull": true,
946: "autoincrement": false,
947: "default": null,
948: "generated": null,
949: "name": "session_id",
950: "entityType": "columns",
951: "table": "session_execution"
952: },
953: {
954: "type": "text",
955: "notNull": true,
956: "autoincrement": false,
957: "default": null,
958: "generated": null,
959: "name": "owner_id",
960: "entityType": "columns",
961: "table": ...
<path>/home/byk/Code/opencode/packages/core/src/database/migration.gen.ts</path>
<type>file</type>
<content>
35: import("./migration/20260605003541_add_session_context_snapshot"),
36: import("./migration/20260605042240_add_context_epoch_agent"),
37: import("./migration/20260611035744_credential"),
38: import("./migration/20260611192811_lush_chimera"),
39: import("./migration/20260612174303_project_dir_strategy"),
40: import("./migration/20260622142730_simplify_session_context_epoch"),
41: import("./migration/20260622170816_reset_v2_session_state"),
42: import("./migration/20260622202450_simplify_session_input"),
43: import("./migration/20260914170650_session-recovery"),
44: import("./migration/20260914214636_session-execution"),
45: import("./migration/20260914222526_session-execution-phase"),
46: import("./migration/20260914235657_session-execution-lease"),
47: ])
48: ).map((module) => module.default) satisfies ...
<path>/home/byk/Code/opencode/packages/schema/src/session-event.ts</path> <type>file</type> <content> 475: ModelSwitched, 476: Moved, 477: Prompted, 478: PromptAdmitted, 479: ContextUpdated, 480: Synthetic, 481: Shell.Started, 482: Shell.Ended, 483: Step.Started, 484: Step.Ended, 485: Step.Failed, 486: Step.Interrupted, 487: Step.Recovered, 488: Text.Started, 489: Text.Ended, 490: Tool.Input.Started, 491: Tool.Input.Ended, 492: Tool.Called, 493: Tool.Progress, 494: Tool.Success, 495: Tool.Failed, 496: Reasoning.Started, 497: Reasoning.Ended, 498: Retried, 499: Compaction.Started, 500: Compaction.Ended, 501: RevertEvent.Staged, 502: RevertEvent.Cleared, 503: RevertEvent.Committed, 504: ) 505: 506: export const Definitions = Event.inventory( 507: AgentSwitched, 508: ModelSwitched, 509: Moved, 510: Prompted, 511: PromptAdmitted, 512: ContextUpdated, 513: Synthetic, 514: Shell.Started, 515: Shell.Ended, 516: ...
PASS
f051a53df476dd924bda0a14c4cd91a63c9e7eafff53bbe71f0460571335fcbd.1ecc9afe36c12b31a8892cdbfb8a1560ceec97255c3537c073b795351233be40.dc661f30cb2055ad6248fdc59b14998d834497be.ab9408c81cc9916177641ae8e9f08624fc27b5b9 and HEAD exist as immutable commits.An expired runner can renew its lease and continue provider or tool work.
renew checks only execution and owner IDs; it never requires the current lease to remain unexpired (packages/core/src/session/execution/local.ts:36-45). Runner mutations likewise check only Session and execution IDs (packages/core/src/session/runner/llm.ts:185-197). Those mutations guard provider dispatch at packages/core/src/session/runner/llm.ts:318-321, tool execution at packages/core/src/session/runner/llm.ts:287-312, and durable event commits through packages/core/src/session/runner/publish-llm-event.ts:58-65.
After expiry—but before takeover—the stale runner can renew, change phases, publish events, and dispatch side effects. Every boundary must require the exact execution fence and an unexpired lease, with exactly one affected row.
The heartbeat repeatedly writes one fixed expiry timestamp.
Date.now() + leaseDuration is evaluated while constructing renew(executionID) (packages/core/src/session/execution/local.ts:36-40), then that same Effect is repeated at packages/core/src/session/execution/local.ts:82-84. A healthy silent provider, compaction, or tool operation therefore becomes expired after 30 seconds despite heartbeat updates.
The timestamp must be evaluated inside each repetition. Renewal must also fail once the current lease expires.
Acquisition bypasses recovery and can silently replace uncertain work.
Conflict acquisition replaces every expired claim, regardless of phase (packages/core/src/session/execution/local.ts:60-76). ...
### Current State - **Date: Sep 15, 2026.** A frozen, strictly read-only adversarial correctness review is still in progress for the complete V2 interrupted-session recovery candidate in `/home/byk/Code/opencode`. No files may be edited, no formatters/tests/services may be run if they mutate state, Git must not be changed, production data must not be touched, and Lore recall was prohibited for th…
Date: Sep 15, 2026 * 🟡 (14:30) `/home/byk/Code/opencode/packages/core/schema.json:934-1001` defines `session_execution` column metadata: `id` is `text` with `notNull: false`; `session_id`, `owner_id`, and `phase` are `text` with `notNull: true`; `assistant_message_id` is `text` with `notNull: false`; and `time_created` and `expires_at` are `integer` with `notNull: true`. Every shown column has `…
Date: Sep 15, 2026 * 🟡 (14:27) `/home/byk/Code/opencode/packages/core/src/database/schema.gen.ts:150-156` finishes the `session_context_epoch` table definition: `session_id text PRIMARY KEY`, `baseline text NOT NULL`, `snapshot text NOT NULL`, `baseline_seq integer NOT NULL`, and foreign key `fk_session_context_epoch_session_id_session_id_fk` from `session_id` to `session(id)` with `ON DELETE CA…
Date: Sep 15, 2026 * 🟡 (14:24) In `/home/byk/Code/opencode/packages/core/test/session-runner-recorded.test.ts:80-86`, a layer/node mapping includes `[Location.node, Location.boundNode({ directory: AbsolutePath.make("/project") })]`, `[SkillGuidance.node, skillGuidance]`, `[ReferenceGuidance.node, referenceGuidance]`, `[Config.node, config]`, `[PermissionV2.node, permission]`, and `[ToolOutputSto…
Date: Sep 15, 2026 * 🟡 (14:20) In `packages/core/src/session/input.ts:85-116`, the input-admission projection accepts `admittedSeq`, `id`, `sessionID`, `prompt`, `delivery`, and `timeCreated`. It first checks `SessionMessageTable` for the same `input.id`; an existing message causes `Effect.die(new LifecycleConflict({ id: input.id }))`. It then inserts into `SessionInputTable` with `id`, `session…
Date: Sep 15, 2026 * 🔴 (14:17) User established the session projection invariant: “never resume an older assistant projection”; a newer turn supersedes stale incomplete assistant rows. * 🟡 (14:17) In `packages/core/src/session/projector.ts:141-157`, `SessionMessageUpdater.Adapter.getCurrentAssistant()` selects the newest assistant row for `event.data.sessionID` via `orderBy(desc(SessionMessageT…
Date: Sep 15, 2026 * 🟡 (14:14) `packages/core/src/session/runner/index.ts` defines `Interface.run(input)` as one local continuation from already-recorded `Session` history. Input fields are `sessionID: SessionSchema.ID`, `force: boolean`, and `executionID: string`; return type is `Effect.Effect<void, RunError>`. The method drains eligible durable work, while explicit runs perform one provider at…
Date: Sep 15, 2026 * 🟡 (14:11) Search found 24 diff headers in `/tmp/opencode/session-recovery-full.patch`, in order: 1. line 1 — `packages/core/schema.json`; 2. line 732 — `packages/core/src/database/migration.gen.ts`; 3. line 746 — `packages/core/src/database/migration/20260914170650_session-recovery.ts`; 4. line 777 — `packages/core/src/database/migration/20260914214636_session-execution.ts`;…
Date: Sep 15, 2026 * 🟡 (13:57) Inspected `/home/byk/Code/opencode/packages/core/src/session/message-updater.ts` lines 155–239. Event handlers shown: `session.next.shell.started` appends a `SessionMessage.Shell` with `output: ""`; `session.next.shell.ended` finds the shell via `adapter.getCurrentShell(event.data.callID)` and updates its output and completion timestamp; `session.next.step.started`…
Date: Sep 15, 2026 * 🔴 (13:53) User requires plugin activation teardown to update inventory and release activation-lock ownership before asynchronously closing scopes; joining disabled-plugin finalizers while holding the activation lock or readiness hold can deadlock when user finalizers await readiness. The readiness token must always be released even if cleanup or reporting fails. Documented i…
Date: Sep 15, 2026 * 🔴 (13:51) User requires replacing local Session ownership with durable multi-node ownership when running in clustered mode. * 🔴 (13:51) Current Session execution coordinates one local active drain per Session; explicit resumes join the active drain, and prompt wakeups coalesce.
Date: Sep 15, 2026 * 🔴 [enforced-workflow] (13:47) User requires type checking with `bun typecheck` from package directories such as `packages/opencode`; never run `tsc` directly. * 🔴 [enforced-workflow] (13:47) User stated tests cannot run from the repository root because of the `do-not-run-tests-from-root` guard; tests must run from package directories such as `packages/opencode`. * 🔴 (13:47…
Date: Sep 15, 2026 * 🟡 (13:42) `packages/core/src/database/migration.gen.ts` currently imports 38 generated migrations, ordered from `20260127222353_familiar_lady_ursula` through `20260622202450_simplify_session_input`, via `Promise.all([...]).map((module) => module.default) satisfies DatabaseMigration.Migration[]`; it does not yet list the 3 newly added September migrations. * 🟡 (13:42) Git st…
Date: Sep 15, 2026 * 🟡 (13:38) Git working tree showed 20 modified files with 490 insertions and 237 deletions total: `.lore.md` (170 changed lines), `AGENTS.md` (6 additions), `packages/core/schema.json` (14 changed lines), `packages/core/src/database/migration.gen.ts` (1 addition), `packages/core/src/database/schema.gen.ts` (1 addition), `packages/core/src/session/compaction.ts` (11 changed li…
Date: Sep 15, 2026 * 🟡 (13:35) `/home/byk/Code/opencode/packages/core/src/session/message-updater.ts` lines 89-93 define `latestText(assistant, textID)` and `latestReasoning(assistant, reasoningID)` using `assistant?.content.findLast(...)`, matching the respective `"text"`/`"reasoning"` type and exact item ID. * 🟡 (13:35) `/home/byk/Code/opencode/packages/core/src/session/message-updater.ts` li…
Date: Sep 15, 2026 * 🟡 (13:29) Search found exactly 1 match in `/home/byk/Code/opencode/packages/core/src/session/projector.ts` at line 63: `version: info.version,` * 🟡 (13:30) Search found exactly 1 match in `/home/byk/Code/opencode/packages/core/src/session/message-updater.ts` at line 95: `const updateOwnedAssistant = (messageID: SessionMessage.ID, recipe: (draft: DraftAssistant) => void) =>`
Date: Sep 15, 2026 * 🔴 (13:26) User established execution-lease invariant: `SessionExecutionLocal` “never starts a second owner while the lease is live.” * 🔴 (13:26) User established execution-lease invariant: `SessionExecutionLocal` “never re-enters its own uncertain live lease.” * 🟡 (13:26) `/home/byk/Code/opencode/packages/core/test/session-execution-local.test.ts` contains 5 `SessionExecut…
Date: Sep 15, 2026 * 🔴 (13:24) User established the session-projection rule: “A newer turn supersedes stale incomplete rows; never resume an older assistant projection.” * 🟡 (13:23) Search found 12 matches for assistant/shell completion handling in `/home/byk/Code/opencode/packages/core/src/session/message-updater.ts`: current-assistant completion guard at line 33; `draft.time.completed = event…
Date: Sep 15, 2026 * 🟡 (13:22) Tool displayed a TypeScript session-event module that exports `SessionEvent`, imports Effect `Schema`, and defines shared event fields `timestamp: DateTimeUtcFromMillis` and `sessionID: SessionID`. * 🟡 (13:22) The module defines `Source` with `start: NonNegativeInt`, `end: NonNegativeInt`, and `text: Schema.String`, annotated with identifier `"session.next.event.s…
Date: Sep 15, 2026 * 🟡 (13:20) Tool displayed `/home/byk/Code/opencode/packages/core/src/database/migration/20260914222526_session-execution-phase.ts` (11 lines). The default export satisfies `DatabaseMigration.Migration`, uses migration ID `"20260914222526_session-execution-phase"`, and defines `up(tx)` with `Effect.gen`; it runs SQL `ALTER TABLE \`session_execution\` ADD \`phase\` text NOT NUL…
Date: Sep 15, 2026 * 🟡 (13:18) Tool displayed `/home/byk/Code/opencode/packages/schema/src/session-event.ts` lines 32–56. `options` sets `durable.aggregate: "sessionID"` and `durable.version: 1`; `stepSettlementOptions` sets `durable.aggregate: "sessionID"` and `durable.version: 2`; both use `as const`. `UnknownError` is exported as both a value and type alias of `SessionMessage.UnknownError`. `…
Date: Sep 15, 2026 * 🟡 (13:13) Tool displayed `/home/byk/Code/opencode/packages/schema/src/session-event.ts` lines 145–192. `Step.Started` defines event type `"session.next.step.started"` with `...options` and schema fields `...Base`, `assistantMessageID: SessionMessage.ID`, `agent: Schema.String`, `model: Model.Ref`, and optional `snapshot`. `Step.Ended` defines `"session.next.step.ended"` with…
Date: Sep 15, 2026 * 🟡 (13:12) Tool result returned `f0a743c5c616ed7d48e491828e72226bfa4a2cb3bd0104fb785154bcbc4c5870 -` followed by `57591e35-6f8f-4cda-932c-6a08e85caae7 [ "9401e0ed-b1e3-4557-bc94-1c9b29e1e09c" ]`.
Date: Sep 15, 2026 * 🟡 (13:09) `packages/core/src/session/sql.ts:135-136` defines indexes `session_message_session_time_created_id_idx` on `SessionMessageTable` columns `(session_id, time_created, id)` and `session_message_time_created_idx` on `(time_created)`. * 🟡 (13:09) `packages/core/src/session/sql.ts:140-166` defines SQLite table `SessionInputTable` as `"session_input"` with fields: `id` …
Date: Sep 15, 2026 * 🟡 (13:07) `packages/schema/src/session-event.ts:185-194` defines `SessionEvent.NextStep.Failed` via `Event.define()` with event type `"session.next.step.failed"`, `...stepSettlementOptions`, and schema fields `...Base`, `assistantMessageID: SessionMessage.ID`, and `error: UnknownError`; exports `type Failed = typeof Failed.Type`. * 🟡 (13:07) `packages/schema/src/session-eve…
Date: Sep 15, 2026 * 🟡 (13:05) `packages/opencode/src/server/routes/instance/httpapi/server.ts:299-304` provides `AppNodeBuilderV1.build(LayerNode.group([SessionV2.node, SessionRecovery.node]), [[LocationServiceMap.node, locationServiceMapV2], [SessionExecution.node, SessionExecutionLocal.node]])`, so both `SessionV2.node` and `SessionRecovery.node` receive the `LocationServiceMap.node` and `Ses…
Date: Sep 15, 2026 * 🟡 (13:02) `packages/server/src/routes.ts:27-39` defines `applicationServices` via `LayerNode.group([Database.node, EventV2.node, httpClient, ToolOutputStore.cleanupNode, SessionV2.node, SessionRecovery.node, PermissionSaved.node, PtyTicket.node, Credential.node, PtyEnvironment.node, LocationServiceMap.node])`; `SessionRecovery.node` is therefore included in server applicatio…
Date: Sep 15, 2026 * 🟡 (12:50) Migration `packages/core/src/database/migration/20260914170650_session-recovery.ts`, ID `20260914170650_session-recovery`, creates `session_recovery` with `session_id`, `assistant_message_id`, `continuation_message_id`, and `time_created`; primary key is (`session_id`, `assistant_message_id`), all three referenced IDs have `ON DELETE CASCADE` foreign keys, and `ses…
Date: Sep 15, 2026 * 🟡 (12:39) `packages/core/src/session/recovery.ts` queries only expired execution claims with `lt(SessionExecutionTable.expires_at, Date.now())`, rather than scanning all `session_execution` rows. * 🟡 (12:39) For each expired claim, `SessionRecovery.recover()` loads the latest assistant message for `claim.session_id`, ordered by `SessionMessageTable.seq` descending with `lim…
Date: Sep 15, 2026 * 🔴 [required-concurrency-safety] (12:36) User specified that session recovery must “never recover a live execution lease.” * 🔴 [required-concurrency-safety] (12:36) User specified that session recovery must “never recover an execution superseded by a newer assistant.” * 🟡 (12:36) Frozen patch adds Effect tests named `it.effect("never recovers a live execution lease", ...)` …
Date: Sep 15, 2026 * 🔴 [required-concurrency-safety] (12:35) User specified that session recovery must “never start a second owner while the lease is live.” * 🔴 [required-concurrency-safety] (12:35) User specified that session recovery must “never re-enter its own uncertain live lease.” * 🟡 (12:35) Frozen patch adds Effect tests named `it.effect("never starts a second owner while the lease is …
Date: Sep 15, 2026 * 🟡 (12:35) Frozen patch evidence adds migration file `packages/core/src/database/migration/20260914170650_session-recovery.ts`, creating a `session_recovery` table with fields including `session_id`, `execution_id`, `continuation_message_id`, and `phase`. * 🟡 (12:35) The session-recovery migration creates unique index `session_recovery_execution_idx` on `session_recovery.exe…
Date: Sep 15, 2026 * 🔴 [requested-review] (12:35) User asked to continue and finish one same-session frozen correctness-review retry after a server restart, covering ownership, lease, fencing, interruption, recovery, replay, compaction, migration, startup, and test behavior. * 🔴 [enforced-workflow] (12:35) User required the correctness review to remain strictly read-only and prohibited use of L…
Date: Sep 15, 2026 * 🟡 (08:20) `/home/byk/Code/opencode/packages/core/src/session/input.ts:85-116` stores an admitted session input after checking `SessionMessageTable` for an existing row with the same `input.id`; an existing message causes `Effect.die(new LifecycleConflict({ id: input.id }))`. It inserts into `SessionInputTable` with `id`, `session_id`, `admitted_seq`, `prompt: encodePrompt(in…
Date: Sep 15, 2026 * 🟡 (08:15) `/home/byk/Code/opencode/packages/schema/src/session-event.ts:185-219` defines `Step.Failed`, `Step.Interrupted`, and `Step.Recovered`: `Failed` uses type `"session.next.step.failed"`, `...stepSettlementOptions`, `...Base`, `assistantMessageID: SessionMessage.ID`, and `error: UnknownError`; `Interrupted` uses type `"session.next.step.interrupted"`, `...options`, `.…
Date: Sep 15, 2026 * 🟡 (08:07) `/home/byk/Code/opencode/packages/schema/src/session-event.ts:423-456` defines `Compaction.Started`, `Compaction.Delta`, and `Compaction.Ended`. `Started` uses type `"session.next.compaction.started"` with `...options`, `...Base`, `messageID: SessionMessage.ID`, and `reason: Schema.Union([Schema.Literal("auto"), Schema.Literal("manual")])`; `Delta` uses type `"sess…
Date: Sep 15, 2026 * 🟡 (08:01) Inspection of `/home/byk/Code/opencode/packages/core/src/session/runner/llm.ts:95-164` showed the `layer` initializes `EventV2.Service`, `LLMClient.Service`, `AgentV2.Service`, `ToolRegistry.Service`, `SessionRunnerModel.Service`, `SessionStore.Service`, `Location.Service`, `SystemContextRegistry.Service`, `SkillGuidance.Service`, `ReferenceGuidance.Service`, `Conf…
Date: Sep 15, 2026 * 🟡 (07:58) Search found 2 references to `failInterruptedTools` in `/home/byk/Code/opencode/packages/core/src/session/runner/llm.ts`: definition via `Effect.fn("SessionRunner.failInterruptedTools")` at line 121 and invocation `yield* failInterruptedTools(input.sessionID)` at line 451.
Date: Sep 15, 2026 * 🔴 [requested-review] (07:42) User requested continuation and completion of the existing frozen, read-only correctness review after the server restart, with substantive `PASS`/`CONCERN`/`MUST-FIX` findings supported by exact `file:line` evidence. * 🔴 [enforced-workflow] (07:42) User required the review to remain read-only, verify all supplied hashes, and end with exactly `ME…
Date: Sep 15, 2026 * 🟡 (06:17) Review inspection of `packages/core/src/session/compaction.ts:155-163` showed token-bounded conversation splitting: entries are accumulated until `Token.estimate(conversation[index])` would exceed `tokens`, then returned as `head: conversation.slice(0, split).join("\n\n")` and `recent: conversation.slice(split).join("\n\n")`. * 🟡 (06:17) `packages/core/src/session…
Date: Sep 15, 2026 * 🔴 [requested-review] (06:07) User requested continuation of the frozen correctness review against the exact canonical patch `/tmp/opencode/session-recovery-full.patch`, specifying SHA-256 `f051a53df476dd924bda0a14c4cd91a63c9e7eafff53bbe71f0460571335fcbd`, base `ab9408c81cc9916177641ae8e9f08624fc27b5b9`, and SEA hash `1ecc9afe36c12b31a8892cdbfb8a1560ceec97255c3537c073b7953512…
Date: Sep 15, 2026 * 🔴 (01:39) User specified the session-execution invariant: “never starts a second owner while the lease is live.” * 🔴 (01:39) User specified the session-execution invariant: “never re-enters its own uncertain live lease.” * 🔴 (01:39) User specified the recovery invariant: “never recovers a live execution lease.” * 🔴 (01:39) User specified the recovery invariant: “never rec…
Date: Sep 15, 2026 * 🟡 (01:39) Patch excerpt for `packages/core/src/session/execution/local.ts` showed database-backed session execution leasing: obtains `db` via `Database.Service`, creates or retrieves execution records using `executionID`, and repeatedly calls `renew(executionID)` with `Effect.repeat(Schedule.spaced("10 seconds"))`. * 🟡 (01:39) Patch excerpt for `packages/core/src/session/ru…
Date: Sep 15, 2026 * 🟡 (01:38) Tool output confirmed the checked-out Git HEAD is `dc661f30cb2055ad6248fdc59b14998d834497be`. * 🟡 (01:38) Tool output showed 20 modified tracked files: `.lore.md`, `AGENTS.md`, `packages/core/schema.json`, `packages/core/src/database/migration.gen.ts`, `packages/core/src/database/schema.gen.ts`, `packages/core/src/session/compaction.ts`, `packages/core/src/session…
Date: Sep 15, 2026 * 🟡 [requested-review] (01:34) User requested a frozen, read-only adversarial correctness review of the complete V2 interrupted-session recovery candidate in `/home/byk/Code/opencode`. * 🔴 [enforced-read-only-review] (01:34) User prohibited editing files, running formatters, mutating Git, touching production data, or starting services during the review. * 🔴 (01:34) User spec…