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/AGENTS.md
+8-6Lines changed: 8 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -31,12 +31,14 @@ See `specs/effect-migration.md` for the compact pattern reference and examples.
31
31
- Use `Schema.Defect` instead of `unknown` for defect-like causes.
32
32
- In `Effect.gen` / `Effect.fn`, prefer `yield* new MyError(...)` over `yield* Effect.fail(new MyError(...))` for direct early-failure branches.
33
33
34
-
## Runtime vs Instances
34
+
## Runtime vs InstanceState
35
35
36
-
- Use the shared runtime for process-wide services with one lifecycle for the whole app.
37
-
- Use `src/effect/instances.ts` for per-directory or per-project services that need `InstanceContext`, per-instance state, or per-instance cleanup.
38
-
- If two open directories should not share one copy of the service, it belongs in `Instances`.
39
-
- Instance-scoped services should read context from `InstanceContext`, not `Instance.*` globals.
36
+
- Use `makeRuntime` (from `src/effect/run-service.ts`) for all services. It returns `{ runPromise, runFork, runCallback }` backed by a shared `memoMap` that deduplicates layers.
37
+
- Use `InstanceState` (from `src/effect/instance-state.ts`) for per-directory or per-project state that needs per-instance cleanup. It uses `ScopedCache` keyed by directory — each open project gets its own state, automatically cleaned up on disposal.
38
+
- If two open directories should not share one copy of the service, it needs `InstanceState`.
39
+
- Do the work directly in the `InstanceState.make` closure — `ScopedCache` handles run-once semantics. Don't add fibers, `ensure()` callbacks, or `started` flags on top.
40
+
- Use `Effect.addFinalizer` or `Effect.acquireRelease` inside the `InstanceState.make` closure for cleanup (subscriptions, process teardown, etc.).
41
+
- Use `Effect.forkScoped` inside the closure for background stream consumers — the fiber is interrupted when the instance is disposed.
40
42
41
43
## Preferred Effect services
42
44
@@ -51,7 +53,7 @@ See `specs/effect-migration.md` for the compact pattern reference and examples.
51
53
52
54
`Instance.bind(fn)` captures the current Instance AsyncLocalStorage context and restores it synchronously when called.
53
55
54
-
Use it for native addon callbacks (`@parcel/watcher`, `node-pty`, native `fs.watch`, etc.) that need to call `Bus.publish`, `Instance.state()`, or anything that reads `Instance.directory`.
56
+
Use it for native addon callbacks (`@parcel/watcher`, `node-pty`, native `fs.watch`, etc.) that need to call `Bus.publish` or anything that reads `Instance.directory`.
55
57
56
58
You do not need it for `setTimeout`, `Promise.then`, `EventEmitter.on`, or Effect fibers.
Copy file name to clipboardExpand all lines: packages/opencode/specs/effect-migration.md
+27-22Lines changed: 27 additions & 22 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -6,7 +6,7 @@ Practical reference for new and migrated Effect code in `packages/opencode`.
6
6
7
7
Use `InstanceState` (from `src/effect/instance-state.ts`) for services that need per-directory state, per-instance cleanup, or project-bound background work. InstanceState uses a `ScopedCache` keyed by directory, so each open project gets its own copy of the state that is automatically cleaned up on disposal.
8
8
9
-
Use `makeRunPromise` (from `src/effect/run-service.ts`) to create a per-service `ManagedRuntime` that lazily initializes and shares layers via a global `memoMap`.
9
+
Use `makeRuntime` (from `src/effect/run-service.ts`) to create a per-service `ManagedRuntime` that lazily initializes and shares layers via a global `memoMap`. Returns `{ runPromise, runFork, runCallback }`.
10
10
11
11
- Global services (no per-directory state): Account, Auth, Installation, Truncate
12
12
- Instance-scoped (per-directory state via InstanceState): File, FileTime, FileWatcher, Format, Permission, Question, Skill, Snapshot, Vcs, ProviderAuth
@@ -79,29 +79,34 @@ See `Auth.ZodInfo` for the canonical example.
79
79
80
80
The `InstanceState.make` init callback receives a `Scope`, so you can use `Effect.acquireRelease`, `Effect.addFinalizer`, and `Effect.forkScoped` inside it. Resources acquired this way are automatically cleaned up when the instance is disposed or invalidated by `ScopedCache`. This makes it the right place for:
81
81
82
-
-**Subscriptions**: Use `Effect.acquireRelease` to subscribe and auto-unsubscribe:
82
+
-**Subscriptions**: Yield `Bus.Service` at the layer level, then use `Stream` + `forkScoped` inside the init closure. The fiber is automatically interrupted when the instance scope closes:
0 commit comments