You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: packages/opencode/specs/effect-migration.md
+41-38Lines changed: 41 additions & 38 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -178,7 +178,9 @@ That is fine for leaf files like `schema.ts`. Keep the service surface in the ow
178
178
179
179
## Migration checklist
180
180
181
-
Fully migrated (single namespace, InstanceState where needed, flattened facade):
181
+
Service-shape migrated (single namespace, traced methods, `InstanceState` where needed).
182
+
183
+
This checklist is only about the service shape migration. Many of these services still keep `makeRuntime(...)` plus async facade exports; that facade-removal phase is tracked separately in [Destroying the facades](#destroying-the-facades).
-[ ]`SyncEvent` — `sync/index.ts` (deferred pending sync with James)
231
+
-[ ]`Workspace` — `control-plane/workspace.ts` (deferred pending sync with James)
230
232
231
233
## Tool interface → Effect
232
234
233
-
`Tool.Def.execute` and `Tool.Info.init` already return `Effect` on this branch. Tool definitions should now stay Effect-native all the way through initialization instead of using Promise-returning init callbacks. Tools can still use lazy init callbacks when they need instance-bound state at init time, but those callbacks should return `Effect`, not `Promise`. Remaining work is:
235
+
`Tool.Def.execute` and `Tool.Info.init` already return `Effect` on this branch, and the current tools in `src/tool/*.ts` have been migrated to the Effect-native `Tool.define(...)` shape.
234
236
235
-
1. Migrate each tool body to return Effects
236
-
2. Keep `Tool.define()` inputs Effect-native
237
-
3. Update remaining callers to `yield*` tool initialization instead of `await`ing
237
+
The remaining work here is follow-on cleanup rather than the top-level tool interface migration:
238
+
239
+
1. Remove internal `Effect.promise(...)` bridges where practical
240
+
2. Keep replacing raw platform helpers with Effect services inside tool bodies
241
+
3. Update remaining callers and tests to prefer `yield* info.init()` / `Tool.init(...)` over older Promise-oriented patterns
238
242
239
243
### Tool migration details
240
244
@@ -254,52 +258,49 @@ This keeps migrated tool tests aligned with the production service graph today,
254
258
255
259
Individual tools, ordered by value:
256
260
257
-
-[ ]`apply_patch.ts` — HIGH: multi-step orchestration, error accumulation, Bus events
-[x]`webfetch.ts` — MEDIUM: fetch with UA retry, size limits → HttpClient
269
+
-[x]`websearch.ts` — MEDIUM: MCP over HTTP → HttpClient
270
+
-[x]`task.ts` — MEDIUM: task state management
271
+
-[x]`ls.ts` — MEDIUM: bounded directory listing over ripgrep-backed traversal
272
+
-[x]`multiedit.ts` — MEDIUM: sequential edit orchestration over `edit.ts`
273
+
-[x]`glob.ts` — LOW: simple async generator
274
+
-[x]`lsp.ts` — LOW: dispatch switch over LSP operations
275
+
-[x]`question.ts` — LOW: prompt wrapper
276
+
-[x]`skill.ts` — LOW: skill tool adapter
277
+
-[x]`todo.ts` — LOW: todo persistence wrapper
278
+
-[x]`invalid.ts` — LOW: invalid-tool fallback
279
+
-[x]`plan.ts` — LOW: plan file operations
280
+
281
+
`batch.ts` was removed from `src/tool/` and is no longer tracked here.
277
282
278
283
## Effect service adoption in already-migrated code
279
284
280
285
Some already-effectified areas still use raw `Filesystem.*` or `Process.spawn` in their implementation or helper modules. These are low-hanging fruit — the layers already exist, they just need the dependency swap.
281
286
282
287
### `Filesystem.*` → `AppFileSystem.Service` (yield in layer)
`util/filesystem.ts` (raw fs wrapper) is currently imported by **34 files**. The effectified `AppFileSystem` service (`filesystem/index.ts`) is currently imported by **15 files**. As services and tools are effectified, they should switch from `Filesystem.*` to yielding `AppFileSystem.Service` — this happens naturally during each migration, not as a separate effort.
296
-
297
-
Similarly, **21 files** still import raw `fs` or `fs/promises` directly. These should migrate to `AppFileSystem` or `Filesystem.*` as they're touched.
299
+
`util/filesystem.ts` is still used widely across `src/`, and raw `fs` / `fs/promises` imports still exist in multiple tooling and infrastructure files. As services and tools are effectified, they should switch from `Filesystem.*` to yielding `AppFileSystem.Service` where possible — this should happen naturally during each migration, not as a separate sweep.
298
300
299
301
Current raw fs users that will convert during tool migration:
300
302
301
303
-`tool/read.ts` — fs.createReadStream, readline
302
-
-`tool/apply_patch.ts` — fs/promises
303
304
-`file/ripgrep.ts` — fs/promises
304
305
-`patch/index.ts` — fs, fs/promises
305
306
@@ -312,7 +313,9 @@ Current raw fs users that will convert during tool migration:
312
313
313
314
## Destroying the facades
314
315
315
-
Every service currently exports async facade functions at the bottom of its namespace — `export async function read(...) { return runPromise(...) }` — backed by a per-service `makeRuntime`. These exist because cyclic imports used to force each service to build its own independent runtime. Now that the layer DAG is acyclic and `AppRuntime` (`src/effect/app-runtime.ts`) composes everything into one `ManagedRuntime`, we're removing them.
316
+
This phase is still broadly open. As of 2026-04-11 there are still 31 `makeRuntime(...)` call sites under `src/`, and many service namespaces still export async facade helpers like `export async function read(...) { return runPromise(...) }`.
317
+
318
+
These facades exist because cyclic imports used to force each service to build its own independent runtime. Now that the layer DAG is acyclic and `AppRuntime` (`src/effect/app-runtime.ts`) composes everything into one `ManagedRuntime`, we're removing them.
0 commit comments