Concrete inventory of the remaining makeRuntime(...)-backed facades in packages/opencode.
Current status on this branch:
src/ has 5 makeRuntime(...) call sites total.src/bus/index.ts and src/effect/cross-spawn-spawner.ts.src/npm/index.ts and src/cli/cmd/tui/config/tui.ts.Recent progress:
Pty, Skill, Vcs, ToolRegistry, Auth.Config, Provider, File, LSP, MCP.src/cli/cmd/tui/config/tui.ts still exports makeRuntime(...) plus async facade helpers for get() and waitForDependencies().src/npm/index.ts still exports makeRuntime(...) plus async facade helpers for install(), add(), outdated(), and which().Low-risk batch, all merged:
src/pty/index.tssrc/skill/index.tssrc/project/vcs.tssrc/tool/registry.tssrc/auth/index.tsCaller-heavy batch, all merged:
src/config/config.tssrc/provider/provider.ts../core/src/filesystem.tssrc/lsp/index.tssrc/mcp/index.tsShared pattern:
makeRuntime(...) + async facadesyield* svc.method(...)makeRuntime(...), remove async facade exports, and drop the makeRuntime importFor each service in the low-risk batch, the work is complete only when all of these are true:
Namespace.method(...) facade callsmakeRuntime(...)grep for the migrated facade methods only finds the service implementation itself or unrelated namesUse one AppRuntime.runPromise(Effect.gen(...)) body and yield the service inside it.
const value = await AppRuntime.runPromise(
Effect.gen(function* () {
const pty = yield* Pty.Service
return yield* pty.list()
}),
)
If two service calls are independent, keep them in the same effect body and use Effect.all(...).
If the caller is not itself an Effect service yet, still prefer one contiguous AppRuntime.runPromise(Effect.gen(...)) block for the whole unit of work.
const skills = await AppRuntime.runPromise(
Effect.gen(function* () {
const auth = yield* Auth.Service
const skill = yield* Skill.Service
yield* auth.set(key, info)
return yield* skill.all()
}),
)
Only fall back to AppRuntime.runPromise(Service.use(...)) for truly isolated one-off calls or awkward callback boundaries. Do not stack multiple tiny runPromise(...) calls in the same contiguous workflow.
This is the right intermediate state. Do not block facade removal on effectifying the whole CLI file.
If the old facade call existed only to kick off initialization, call the service through the existing runtime for that file.
void BootstrapRuntime.runPromise(Vcs.Service.use((svc) => svc.init()))
Do not reintroduce a dedicated runtime in the service just for bootstrap.
Convert facade tests to full effect style.
it.effect("does the thing", () =>
Effect.gen(function* () {
const svc = yield* Pty.Service
const info = yield* svc.create({ command: "cat", title: "a" })
yield* svc.remove(info.id)
}).pipe(Effect.provide(Pty.defaultLayer)),
)
If the repo test already uses testEffect(...), prefer testEffect(Service.defaultLayer) and yield* Service.Service inside the test body.
Do not route tests through AppRuntime unless the test is explicitly exercising the app runtime. For facade removal, tests should usually provide the specific service layer they need.
If the test uses provideTmpdirInstance(...), remember that fixture needs a live ChildProcessSpawner layer. For services whose defaultLayer does not already provide that infra, prefer the repo-standard cross-spawn layer:
const infra = CrossSpawnSpawner.defaultLayer
const it = testEffect(Layer.mergeAll(MyService.defaultLayer, infra))
Without that extra layer, tests fail at runtime with Service not found: effect/process/ChildProcessSpawner.
No.
AppRuntime.runPromise(Effect.gen(...))AppRuntime.runPromise(Service.use(...))Facade removal does not require a bigger refactor than that.
No. Convert them now.
The end state is yield* svc.method(...), not await Namespace.method(...) inside async tests.
runPromise exported for convenience?No. For this batch the goal is to delete the service-local runtime entirely.
Keep the route shape, but replace each facade call with AppRuntime.runPromise(Service.use(...)) or wrap the surrounding async section in one Effect.gen(...) when practical. Do not keep the service facade just because the route has callback-shaped code.
runPromise per service call?No.
Default to one contiguous AppRuntime.runPromise(Effect.gen(...)) block per handler, command, or workflow. Yield every service you need inside that block.
Multiple tiny runPromise(...) calls are only acceptable when the caller structure forces it, such as websocket lifecycle callbacks, external callback APIs, or genuinely unrelated one-off operations.
Effect.gen(...)?Usually no.
Prefer the direct form when there is only one expression:
await Effect.runPromise(FileSystem.Service.use((svc) => svc.read({ path })))
Use Effect.gen(...) when the workflow actually needs multiple yielded values or branching.
These were the recurring mistakes and useful corrections from the first two batches:
AppRuntime.provideTmpdirInstance(...) and needs child processes, prefer CrossSpawnSpawner.defaultLayer.FileSystem tests, for example, provide Location.Service plus FileSystem.locationLayer.Service.use(...) call in Effect.gen(...) just to return it. Use the direct form.Most of the original facade-removal backlog is already done. The practical remaining work is narrower now:
Npm runtime-backed facade from src/npm/index.tsTuiConfig runtime-backed facade from src/cli/cmd/tui/config/tui.tssrc/npm/index.ts (Npm) - still exports runtime-backed async facade helpers on top of Npm.Servicesrc/cli/cmd/tui/config/tui.ts (TuiConfig) - still exports runtime-backed async facade helpers on top of TuiConfig.Servicesrc/session/session.ts / src/session/prompt.ts / src/session/revert.ts / src/session/summary.ts - service-local facades removedsrc/agent/agent.ts (Agent) - service-local facades removedsrc/permission/index.ts (Permission) - service-local facades removedsrc/worktree/index.ts (Worktree) - service-local facades removedsrc/plugin/index.ts (Plugin) - service-local facades removedsrc/snapshot/index.ts (Snapshot) - service-local facades removed../core/src/filesystem.ts (FileSystem) - legacy kirincode service removedsrc/lsp/index.ts (LSP) - facades removed and mergedsrc/mcp/index.ts (MCP) - facades removed and mergedsrc/config/config.ts (Config) - facades removed and mergedsrc/provider/provider.ts (Provider) - facades removed and mergedsrc/pty/index.ts (Pty) - facades removed and mergedsrc/skill/index.ts (Skill) - facades removed and mergedsrc/project/vcs.ts (Vcs) - facades removed and mergedsrc/tool/registry.ts (ToolRegistry) - facades removed and mergedsrc/auth/index.ts (Auth) - facades removed and mergedmakeRuntime(...) sitessrc/bus/index.ts - core bus plumbing, not a normal facade-removal target.src/effect/cross-spawn-spawner.ts - runtime helper for ChildProcessSpawner, not a service namespace facade.