Practical plan for retiring the promise-backed / ALS-backed Instance helper in src/project/instance.ts and moving instance selection fully into Effect-provided scope.
End state:
Instance.provide(...)InstanceRef or its eventual replacement, not from ALS-backed sync gettersMap<string, Promise<InstanceContext>>Today src/project/instance.ts still owns two separate concerns:
LocalContext / AsyncLocalStoragecache: Map<string, Promise<InstanceContext>>At the same time, the Effect side already exists:
src/effect/instance-ref.ts provides InstanceRef and WorkspaceRefsrc/effect/run-service.ts already attaches those refs when a runtime starts inside an active instance ALS contextsrc/effect/instance-state.ts already prefers InstanceRef and only falls back to ALS when neededThat means the migration is not "invent instance context in Effect". The migration is "stop relying on the legacy helper as the primary source of truth".
Near-term target shape:
InstanceScope.with({ directory, workspaceID }, effect)
Responsibilities of InstanceScope.with(...):
directory, project, and worktreeInstanceRef and WorkspaceRefCode inside the boundary should then do one of these:
const ctx = yield * InstanceState.context
const dir = yield * InstanceState.directory
Long-term, once InstanceState itself is replaced by keyed layers / LayerMap, those reads can move to an InstanceContext service without changing the outer migration order.
Rules for all new code:
Instance.directory, Instance.worktree, Instance.project, or Instance.current reads inside Effect codeInstance.provide(...) boundaries unless there is no Effect-native seam yetInstanceState.context, InstanceState.directory, or an explicit ctx parameter inside Effect codeSuccess condition:
Convert Effect services first, before replacing the top-level boundary. These modules already run inside Effect and mostly need yield* InstanceState.context or a yielded ctx instead of ambient sync access.
Primary batch, highest payoff:
src/file/index.tssrc/lsp/server.tssrc/worktree/index.tssrc/file/watcher.tssrc/format/formatter.tssrc/session/index.tssrc/project/vcs.tsMechanical replacement rule:
Instance.directory -> ctx.directory or yield* InstanceState.directoryInstance.worktree -> ctx.worktreeInstance.project -> ctx.projectDo not thread strings manually through every public method if the service already has access to Effect context.
After the service bodies stop assuming ALS, move the top-level boundaries to shift into Effect explicitly.
Main boundaries:
HttpApi entrypointsThese boundaries should become Effect-native wrappers that:
InstanceRef and WorkspaceRefAt that point Instance.provide(...) becomes a legacy adapter instead of the normal code path.
Once boundaries and services both rely on Effect context, replace the module-level promise cache in src/project/instance.ts.
Target replacement:
ScopedCache, LayerMap, or another keyed Effect resource managerdisposeAll() iterating a Promise mapThis phase should absorb the current responsibilities of:
cache in src/project/instance.tsboot(...)disposeInstance(...)reload(...) / disposeAll() fan-out logicKeep ALS only where a library invokes callbacks outside the Effect fiber tree and we still need to call code that reads instance context synchronously.
Known bridge cases today:
src/file/watcher.tssrc/session/llm.tsIf those libraries become fully wrapped in Effect services, the remaining Instance.bind(...) uses can disappear too.
Only after earlier phases land:
Instance.current, Instance.directory, Instance.worktree, Instance.projectsrc/project/instance.ts to a thin compatibility shim or delete it entirelyInstanceState.contextDirect legacy usage means any source file that still calls one of:
Instance.currentInstance.directoryInstance.worktreeInstance.projectInstance.provide(...)Instance.bind(...)Instance.restore(...)Instance.reload(...)Instance.dispose() / Instance.disposeAll()Current total: 56 files in packages/opencode/src.
These files define or adapt the current bridge. They should change last, after callers have moved.
src/project/instance.tssrc/effect/run-service.tssrc/effect/instance-state.tssrc/project/bootstrap.tssrc/config/config.tsMigration rule:
These are the current request-entry seams that still create or consume instance context through the legacy helper.
src/server/routes/instance/middleware.tssrc/server/routes/instance/index.tssrc/server/routes/instance/project.tssrc/server/routes/control/workspace.tssrc/server/routes/instance/file.tssrc/server/routes/instance/experimental.tssrc/server/routes/global.tsMigration rule:
InstanceRef / WorkspaceRefThese commands still enter an instance through Instance.provide(...) or read sync getters directly.
src/cli/bootstrap.tssrc/cli/cmd/agent.tssrc/cli/cmd/debug/agent.tssrc/cli/cmd/debug/ripgrep.tssrc/cli/cmd/github.tssrc/cli/cmd/import.tssrc/cli/cmd/mcp.tssrc/cli/cmd/models.tssrc/cli/cmd/plug.tssrc/cli/cmd/pr.tssrc/cli/cmd/providers.tssrc/cli/cmd/stats.tssrc/cli/cmd/tui/attach.tssrc/cli/cmd/tui/plugin/runtime.tssrc/cli/cmd/tui/thread.tssrc/cli/cmd/tui/worker.tsMigration rule:
withInstance(...) Effect entry helper instead of open-coded Instance.provide(...)These tools mostly use direct getters for path resolution and repo-relative display logic.
src/tool/apply_patch.tssrc/tool/bash.tssrc/tool/edit.tssrc/tool/lsp.tssrc/tool/plan.tssrc/tool/read.tssrc/tool/write.tsMigration rule:
These modules are already the best near-term migration targets because they are in Effect code but still read sync getters from the legacy helper.
src/agent/agent.tssrc/cli/cmd/tui/config/tui-migrate.tssrc/file/index.tssrc/file/watcher.tssrc/format/formatter.tssrc/lsp/client.tssrc/lsp/index.tssrc/lsp/server.tssrc/mcp/index.tssrc/project/vcs.tssrc/provider/provider.tssrc/pty/index.tssrc/session/session.tssrc/session/instruction.tssrc/session/llm.tssrc/session/system.tssrc/sync/index.tssrc/worktree/index.tsMigration rule:
yield* InstanceState.context or a yielded ctxInstance.bind(...) callers and convert only the truly callback-driven edges to bridge modeCurrent highest direct-usage counts by file:
src/file/index.ts - 18src/lsp/server.ts - 14src/worktree/index.ts - 12src/file/watcher.ts - 9src/cli/cmd/mcp.ts - 8src/format/formatter.ts - 8src/tool/apply_patch.ts - 8src/cli/cmd/github.ts - 7These files should drive the first measurable burn-down.
file, lsp, worktree, format, and session.Instance.provide(...).HttpApi entrypoints to that helper so the new server stack proves the pattern.This migration is done when all of the following are true:
Instance.directory, Instance.worktree, Instance.project, or Instance.currentInstance.provide(...) is gone from normal request / CLI / tool executionInstance.bind(...) is either gone or confined to a tiny set of native callback adaptersActive tracker items:
lh7l73 - overall HttpApi migrationyobwlk - remove direct Instance.* reads inside Effect services7irl1e - replace InstanceState / legacy instance caching with keyed Effect layersDedicated worktree for this transition:
/Users/kit/code/open-source/opencode-worktrees/instance-effect-shiftkit/instance-effect-shift