RuleStack

Configs

Stacks

Compare

Diff

RuleStack

Configs

Stacks

Compare

Diff

Read API

RuleStack

Configs

Stacks

Compare

Diff

Read API

Diff/anomalyco-opencode-agents ↔ anomalyco-opencode-packages-opencode-agents

Comparison

A · AGENTS.md · anomalyco/opencodeB · AGENTS.md · anomalyco/opencode
What each file covers, counted
DimensionSharedOnly in AOnly in BOverlap
Sections013150%
Commands0320%
Section tags34138%

What each file covers

Sections

0 shared · 13 only in A · 15 only in B
  • − Branch Names
  • − Commits and PR Titles
  • − Style Guide
  • − General Principles
  • − Destructuring
  • − Imports
  • − Variables
  • − Control Flow
  • − Complex Logic
  • − Schema Definitions (Drizzle)
  • − Testing
  • − Type Checking
  • − V2 Session Core
  • + opencode database guide
  • + Database
  • + Development server
  • + Module shape
  • + When the file is an `index.ts`
  • + Multi-sibling directories
  • + opencode Effect rules
  • + Core
  • + Module conventions
  • + Schemas and errors
  • + Runtime vs InstanceState
  • + Effect v4 beta API
  • + Preferred Effect services
  • + Effect.cached for deduplication
  • + Callback boundaries

Commands

0 shared · 3 only in A · 2 only in B
  • − bun run generate
  • − bun typecheck
  • − tsc
  • + bun dev
  • + node-pty

Section tags

3 shared · 4 only in A · 1 only in B
  • − test
  • − lint-format
  • − types
  • − git-pr
  • + api
  •   code-style
  •   database
  •   do-not

Line diff

+88 added−118 removed44 unchanged27.2% identical
anomalyco/opencode · AGENTS.md
@@ −1 @@
1- To regenerate the legacy JavaScript SDK, run `./packages/sdk/js/script/build.ts`.
2- After changing the public Protocol or Server `HttpApi`, run `bun run generate` from `packages/client`. Do not edit `src/generated` or `src/generated-effect` directly.
3- Keep runtime dependencies directed from Schema to Core and Protocol, then from Core and Protocol to Server. Client runtime code may depend on Schema and Protocol but never Core or Server; `sdk-next` composes Client, Core, and Server.
4- The default branch in this repo is `dev`.
5- Local `main` ref may not exist; use `dev` or `origin/dev` for diffs.
6 
7## Branch Names
8 
9Use a short branch name of at most three words, separated by hyphens. Do not use slashes or type prefixes such as `feat/` or `fix/`.
 
10 
11Examples: `session-recovery`, `fix-scroll-state`, `regenerate-sdk`.
12 
13## Commits and PR Titles
 
 
 
14 
15Use conventional commit-style messages and PR titles: `type(scope): summary`.
16 
17Valid types are `feat`, `fix`, `docs`, `chore`, `refactor`, and `test`. Scopes are optional; use the affected package or area when helpful, e.g. `core`, `opencode`, `tui`, `app`, `desktop`, `sdk`, or `plugin`.
 
 
 
18 
19Examples: `fix(tui): simplify thinking toggle styling`, `docs: update contributing guide`, `chore(sdk): regenerate types`.
 
 
 
 
 
20 
21## Style Guide
 
22 
23### General Principles
24 
25- Keep things in one function unless composable or reusable
26- Do not extract single-use helpers preemptively. Inline the logic at the call site unless the helper is reused, hides a genuinely complex boundary, or has a clear independent name that improves the caller.
27- Avoid `try`/`catch` where possible
28- Avoid using the `any` type
29- Use Bun APIs when possible, like `Bun.file()`
30- Rely on type inference when possible; avoid explicit type annotations or interfaces unless necessary for exports or clarity
31- Prefer functional array methods (flatMap, filter, map) over for loops; use type guards on filter to maintain type inference downstream
32- In `src/config`, follow the existing self-export pattern at the top of the file (for example `export * as ConfigAgent from "./agent"`) when adding a new config module.
33- In Effect generators, bind services to named variables before calling methods. Do not use nested service yields such as `yield* (yield* Foo.Service).bar()`.
34 
35Reduce total variable count by inlining when a value is only used once.
36 
37```ts
38// Good
39const journal = await Bun.file(path.join(dir, "journal.json")).json()
40 
41// Bad
42const journalPath = path.join(dir, "journal.json")
43const journal = await Bun.file(journalPath).json()
44```
45 
46### Destructuring
 
 
47 
48Avoid unnecessary destructuring. Use dot notation to preserve context.
49 
 
 
 
50```ts
51// Good
52obj.a
53obj.b
54 
55// Bad
56const { a, b } = obj
57```
58 
59### Imports
60 
61- Never alias imports. Do not use `import { foo as bar } from "..."` or renamed imports like `resolve as pathResolve`.
62- Never use star imports. Do not use `import * as Foo from "..."` or `import type * as Foo from "..."`.
63- If a namespace-style value is needed, import the module's own exported namespace by name, for example `import { Project } from "@opencode-ai/core/project"`, then reference `Project.ID`.
64- Prefer dynamic imports for heavy modules that are only needed in selected code paths, especially in startup-sensitive entrypoints. Destructure dynamic import bindings near the top of the narrowest scope that needs them so they read like normal imports. Avoid inline chains such as `await import("./module").then((mod) => mod.value())` or `(await import("./module")).value()`. Keep branch-specific imports inside the branch that needs them to preserve lazy loading.
65 
66### Variables
67 
68Prefer `const` over `let`. Use ternaries or early returns instead of reassignment.
69 
70```ts
71// Good
72const foo = condition ? 1 : 2
73 
74// Bad
75let foo
76if (condition) foo = 1
77else foo = 2
78```
79 
80### Control Flow
 
81 
82Avoid `else` statements. Prefer early returns.
83 
84```ts
85// Good
86function foo() {
87 if (condition) return 1
88 return 2
89}
90 
91// Bad
92function foo() {
93 if (condition) return 1
94 else return 2
95}
96```
97 
98### Complex Logic
99 
100When a function has several validation branches or supporting details, make the main function read as the happy path and move supporting details into small helpers below it.
 
 
 
 
 
101 
102```ts
103// Good
104export function loadThing(input: unknown) {
105 const config = requireConfig(input)
106 const metadata = readMetadata(input)
107 return createThing({ config, metadata })
108}
109 
110function requireConfig(input: unknown) {
111 ...
112}
113```
114 
115- Keep helpers close to the code they support, below the main export when that improves readability.
116- Do not over-abstract simple expressions into many single-use helpers; extract only when it names a real concept like `requireConfig` or `readMetadata`.
117- Do not return `Effect` from helpers unless they actually perform effectful work. Synchronous parsing, validation, and option building should stay synchronous.
118- Prefer Effect schema helpers such as `Schema.UnknownFromJsonString` and `Schema.decodeUnknownOption` over manual `JSON.parse` wrapped in `Effect.try` when parsing untrusted JSON strings.
119- Add comments for non-obvious constraints and surprising behavior, not for obvious assignments or control flow.
120 
121### Schema Definitions (Drizzle)
 
 
 
 
122 
123Use snake_case for field names so column names don't need to be redefined as strings.
124 
125```ts
126// Good
127const table = sqliteTable("session", {
128 id: text().primaryKey(),
129 project_id: text().notNull(),
130 created_at: integer().notNull(),
131})
 
132 
133// Bad
134const table = sqliteTable("session", {
135 id: text("id").primaryKey(),
136 projectID: text("project_id").notNull(),
137 createdAt: integer("created_at").notNull(),
138})
139```
140 
141## Testing
142 
143- Avoid mocks as much as possible, you shouldn't be using globalThis.\* at all unless it's the only option.
144- Test actual implementation, do not duplicate logic into tests
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.
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.
156- Keep `SessionRunner`, model resolution, tool registry, permissions, and filesystem Location-scoped. Omitted `Location.workspaceID` means implicit-local placement; explicit workspace identity remains reserved for future placement semantics.
157- Preserve one explicit `llm.stream(request)` call per provider turn and reload projected history before durable continuation. Do not bridge through legacy `SessionPrompt.loop(...)` or delegate orchestration to an in-memory tool loop.
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.
159- Keep delivery vocabulary explicit. Prompts steer by default and promote at the next safe provider-turn boundary while the current drain requires continuation. An explicit `queue` input remains pending until the Session would otherwise become idle; promote one queued input at that boundary, then reevaluate continuation before promoting another. Promoting any new user input resets the selected agent's provider-turn allowance; a batch of steers resets it once.
160- Keep EventV2 replay owner claims separate from clustered Session execution ownership.
161- Keep the System Context algebra, registry, and built-ins in `src/system-context`; keep Context Source producers with their observed domains, and keep Session History selection plus Context Epoch persistence Session-owned.
162 
anomalyco/opencode · packages/opencode/AGENTS.md
@@ +1 @@
1# opencode database guide
 
 
 
 
2 
3## Database
4 
5- **Schema**: Drizzle schema lives in `packages/core/src/**/*.sql.ts`.
6- **Migrations**: database migrations live in `packages/core` and are applied by core.
7 
8## Development server
9 
10- Running `bun dev` from `packages/opencode` starts the live interactive TUI. Do not run it as a blocking foreground command when you need to inspect the result.
11- Start it in `tmux` instead: `tmux new-session -d -s opencode-dev 'bun dev'`.
12- Capture the current TUI output with: `tmux capture-pane -pt opencode-dev`.
13- Stop the session explicitly when done: `tmux kill-session -t opencode-dev`.
14 
15# Module shape
16 
17Do not use `export namespace Foo { ... }` for module organization. It is not
18standard ESM, it prevents tree-shaking, and it breaks Node's native TypeScript
19runner. Use flat top-level exports combined with a self-reexport at the bottom
20of the file:
21 
22```ts
23// src/foo/foo.ts
24export interface Interface { ... }
25export class Service extends Context.Service<Service, Interface>()("@opencode/Foo") {}
26export const layer = Layer.effect(Service, ...)
27export const defaultLayer = layer.pipe(...)
28 
29export * as Foo from "./foo"
30```
31 
32Consumers import the namespace projection:
33 
 
 
 
 
 
 
 
 
 
 
 
 
34```ts
35import { Foo } from "@/foo/foo"
 
36 
37yield * Foo.Service
38Foo.layer
39Foo.defaultLayer
40```
41 
42Namespace-private helpers stay as non-exported top-level declarations in the
43same file — they remain inaccessible to consumers (they are not projected by
44`export * as`) but are usable by the file's own code.
45 
46## When the file is an `index.ts`
47 
48If the module is `foo/index.ts` (single-namespace directory), use `"."` for
49the self-reexport source rather than `"./index"`:
50 
51```ts
52// src/foo/index.ts
53export const thing = ...
 
54 
55export * as Foo from "."
 
56```
57 
58## Multi-sibling directories
59 
60For directories with several independent modules (e.g. `src/session/`,
61`src/config/`), keep each sibling as its own file with its own self-reexport,
62and do not add a barrel `index.ts`. Consumers import the specific sibling:
 
63 
 
 
 
 
64```ts
65import { SessionRetry } from "@/session/retry"
66import { SessionStatus } from "@/session/status"
 
 
 
 
 
67```
68 
69Barrels in multi-sibling directories force every import through the barrel to
70evaluate every sibling, which defeats tree-shaking and slows module load.
71 
72# opencode Effect rules
73 
74Use these rules when writing or migrating Effect code.
 
 
 
 
 
75 
76See `specs/effect/migration.md` for the compact pattern reference and examples.
 
 
 
 
 
77 
78## Core
79 
80- Use `Effect.gen(function* () { ... })` for composition.
81- Use `Effect.fn("Domain.method")` for named/traced effects and `Effect.fnUntraced` for internal helpers.
82- `Effect.fn` / `Effect.fnUntraced` accept pipeable operators as extra arguments, so avoid unnecessary outer `.pipe()` wrappers.
83- Use `Effect.callback` for callback-based APIs.
84- Use `Effect.void` instead of `Effect.succeed(undefined)` or `Effect.succeed(void 0)`.
85- Prefer `DateTime.nowAsDate` over `new Date(yield* Clock.currentTimeMillis)` when you need a `Date`.
86 
87## Module conventions
 
 
 
 
 
 
88 
89- In `src/config`, follow the existing self-export pattern at the top of the file (for example `export * as ConfigAgent from "./agent"`) when adding a new config module.
 
 
 
90 
91## Schemas and errors
 
 
 
 
92 
93- Use `Schema.Class` for multi-field data.
94- Use branded schemas (`Schema.brand`) for single-value types.
95- Use `Schema.TaggedErrorClass` for typed errors.
96- Use `Schema.Defect` instead of `unknown` for defect-like causes.
97- In `Effect.gen` / `Effect.fn`, prefer `yield* new MyError(...)` over `yield* Effect.fail(new MyError(...))` for direct early-failure branches.
98 
99## Runtime vs InstanceState
100 
101- Use `makeRuntime` (from `src/effect/run-service.ts`) for all services. It returns `{ runPromise, runFork, runCallback }` backed by a shared `memoMap` that deduplicates layers.
102- 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.
103- If two open directories should not share one copy of the service, it needs `InstanceState`.
104- 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.
105- Use `Effect.addFinalizer` or `Effect.acquireRelease` inside the `InstanceState.make` closure for cleanup (subscriptions, process teardown, etc.).
106- Use `Effect.forkScoped` inside the closure for background stream consumers — the fiber is interrupted when the instance is disposed.
107- To make a service's `init()` non-blocking, fork `InstanceState.get(state)` at the `init()` call site (e.g. `Effect.forkIn(scope)`), not by forking work inside the `InstanceState.make` closure. Forking inside the closure leaves state incomplete for other methods that read it.
108- `src/project/bootstrap.ts` already wraps every service `init()` in `Effect.forkDetach`, so `init()` is fire-and-forget in production. Keep `init()` methods synchronous internally; the caller controls concurrency.
109 
110## Effect v4 beta API
 
 
 
 
 
 
111 
112- `Effect.fork` and `Effect.forkDaemon` do not exist. Use `Effect.forkIn(scope)` to fork a fiber into a specific scope.
113 
114## Preferred Effect services
 
 
115 
116- In effectified services, prefer yielding existing Effect services over dropping down to ad hoc platform APIs.
117- Prefer `FileSystem.FileSystem` instead of raw `fs/promises` for effectful file I/O.
118- Prefer `ChildProcessSpawner.ChildProcessSpawner` with `ChildProcess.make(...)` instead of custom process wrappers.
119- Prefer `HttpClient.HttpClient` instead of raw `fetch`.
120- Prefer `Path.Path`, `Config`, `Clock`, and `DateTime` when those concerns are already inside Effect code.
121- For background loops or scheduled tasks, use `Effect.repeat` or `Effect.schedule` with `Effect.forkScoped` in the layer definition.
122 
123## Effect.cached for deduplication
124 
125Use `Effect.cached` when multiple concurrent callers should share a single in-flight computation rather than storing `Fiber | undefined` or `Promise | undefined` manually. See `specs/effect/migration.md` for the full pattern.
126 
127## Callback boundaries
128 
129Use `EffectBridge` for native or external callbacks (`@parcel/watcher`, `node-pty`, native `fs.watch`, plugin callbacks, etc.) that need to re-enter Effect services with instance/workspace context.
130 
131Plain async code should pass explicit context or stay inside an Effect fiber; do not add ambient instance context shims.
 
 
 
 
132 
@@ −1 +1 @@
1−- To regenerate the legacy JavaScript SDK, run `./packages/sdk/js/script/build.ts`.
2−- After changing the public Protocol or Server `HttpApi`, run `bun run generate` from `packages/client`. Do not edit `src/generated` or `src/generated-effect` directly.
3−- Keep runtime dependencies directed from Schema to Core and Protocol, then from Core and Protocol to Server. Client runtime code may depend on Schema and Protocol but never Core or Server; `sdk-next` composes Client, Core, and Server.
4−- The default branch in this repo is `dev`.
5−- Local `main` ref may not exist; use `dev` or `origin/dev` for diffs.
1+# opencode database guide
62  
7−## Branch Names
3+## Database
84  
9−Use a short branch name of at most three words, separated by hyphens. Do not use slashes or type prefixes such as `feat/` or `fix/`.
5+- **Schema**: Drizzle schema lives in `packages/core/src/**/*.sql.ts`.
6+- **Migrations**: database migrations live in `packages/core` and are applied by core.
107  
11−Examples: `session-recovery`, `fix-scroll-state`, `regenerate-sdk`.
8+## Development server
129  
13−## Commits and PR Titles
10+- Running `bun dev` from `packages/opencode` starts the live interactive TUI. Do not run it as a blocking foreground command when you need to inspect the result.
11+- Start it in `tmux` instead: `tmux new-session -d -s opencode-dev 'bun dev'`.
12+- Capture the current TUI output with: `tmux capture-pane -pt opencode-dev`.
13+- Stop the session explicitly when done: `tmux kill-session -t opencode-dev`.
1414  
15−Use conventional commit-style messages and PR titles: `type(scope): summary`.
15+# Module shape
1616  
17−Valid types are `feat`, `fix`, `docs`, `chore`, `refactor`, and `test`. Scopes are optional; use the affected package or area when helpful, e.g. `core`, `opencode`, `tui`, `app`, `desktop`, `sdk`, or `plugin`.
17+Do not use `export namespace Foo { ... }` for module organization. It is not
18+standard ESM, it prevents tree-shaking, and it breaks Node's native TypeScript
19+runner. Use flat top-level exports combined with a self-reexport at the bottom
20+of the file:
1821  
19−Examples: `fix(tui): simplify thinking toggle styling`, `docs: update contributing guide`, `chore(sdk): regenerate types`.
22+```ts
23+// src/foo/foo.ts
24+export interface Interface { ... }
25+export class Service extends Context.Service<Service, Interface>()("@opencode/Foo") {}
26+export const layer = Layer.effect(Service, ...)
27+export const defaultLayer = layer.pipe(...)
2028  
21−## Style Guide
29+export * as Foo from "./foo"
30+```
2231  
23−### General Principles
32+Consumers import the namespace projection:
2433  
25−- Keep things in one function unless composable or reusable
26−- Do not extract single-use helpers preemptively. Inline the logic at the call site unless the helper is reused, hides a genuinely complex boundary, or has a clear independent name that improves the caller.
27−- Avoid `try`/`catch` where possible
28−- Avoid using the `any` type
29−- Use Bun APIs when possible, like `Bun.file()`
30−- Rely on type inference when possible; avoid explicit type annotations or interfaces unless necessary for exports or clarity
31−- Prefer functional array methods (flatMap, filter, map) over for loops; use type guards on filter to maintain type inference downstream
32−- In `src/config`, follow the existing self-export pattern at the top of the file (for example `export * as ConfigAgent from "./agent"`) when adding a new config module.
33−- In Effect generators, bind services to named variables before calling methods. Do not use nested service yields such as `yield* (yield* Foo.Service).bar()`.
34− 
35−Reduce total variable count by inlining when a value is only used once.
36− 
3734 ```ts
38−// Good
39−const journal = await Bun.file(path.join(dir, "journal.json")).json()
35+import { Foo } from "@/foo/foo"
4036  
41−// Bad
42−const journalPath = path.join(dir, "journal.json")
43−const journal = await Bun.file(journalPath).json()
37+yield * Foo.Service
38+Foo.layer
39+Foo.defaultLayer
4440 ```
4541  
46−### Destructuring
42+Namespace-private helpers stay as non-exported top-level declarations in the
43+same file — they remain inaccessible to consumers (they are not projected by
44+`export * as`) but are usable by the file's own code.
4745  
48−Avoid unnecessary destructuring. Use dot notation to preserve context.
46+## When the file is an `index.ts`
4947  
48+If the module is `foo/index.ts` (single-namespace directory), use `"."` for
49+the self-reexport source rather than `"./index"`:
50+ 
5051 ```ts
51−// Good
52−obj.a
53−obj.b
52+// src/foo/index.ts
53+export const thing = ...
5454  
55−// Bad
56−const { a, b } = obj
55+export * as Foo from "."
5756 ```
5857  
59−### Imports
58+## Multi-sibling directories
6059  
61−- Never alias imports. Do not use `import { foo as bar } from "..."` or renamed imports like `resolve as pathResolve`.
62−- Never use star imports. Do not use `import * as Foo from "..."` or `import type * as Foo from "..."`.
63−- If a namespace-style value is needed, import the module's own exported namespace by name, for example `import { Project } from "@opencode-ai/core/project"`, then reference `Project.ID`.
64−- Prefer dynamic imports for heavy modules that are only needed in selected code paths, especially in startup-sensitive entrypoints. Destructure dynamic import bindings near the top of the narrowest scope that needs them so they read like normal imports. Avoid inline chains such as `await import("./module").then((mod) => mod.value())` or `(await import("./module")).value()`. Keep branch-specific imports inside the branch that needs them to preserve lazy loading.
60+For directories with several independent modules (e.g. `src/session/`,
61+`src/config/`), keep each sibling as its own file with its own self-reexport,
62+and do not add a barrel `index.ts`. Consumers import the specific sibling:
6563  
66−### Variables
67− 
68−Prefer `const` over `let`. Use ternaries or early returns instead of reassignment.
69− 
7064 ```ts
71−// Good
72−const foo = condition ? 1 : 2
73− 
74−// Bad
75−let foo
76−if (condition) foo = 1
77−else foo = 2
65+import { SessionRetry } from "@/session/retry"
66+import { SessionStatus } from "@/session/status"
7867 ```
7968  
80−### Control Flow
69+Barrels in multi-sibling directories force every import through the barrel to
70+evaluate every sibling, which defeats tree-shaking and slows module load.
8171  
82−Avoid `else` statements. Prefer early returns.
72+# opencode Effect rules
8373  
84−```ts
85−// Good
86−function foo() {
87− if (condition) return 1
88− return 2
89−}
74+Use these rules when writing or migrating Effect code.
9075  
91−// Bad
92−function foo() {
93− if (condition) return 1
94− else return 2
95−}
96−```
76+See `specs/effect/migration.md` for the compact pattern reference and examples.
9777  
98−### Complex Logic
78+## Core
9979  
100−When a function has several validation branches or supporting details, make the main function read as the happy path and move supporting details into small helpers below it.
80+- Use `Effect.gen(function* () { ... })` for composition.
81+- Use `Effect.fn("Domain.method")` for named/traced effects and `Effect.fnUntraced` for internal helpers.
82+- `Effect.fn` / `Effect.fnUntraced` accept pipeable operators as extra arguments, so avoid unnecessary outer `.pipe()` wrappers.
83+- Use `Effect.callback` for callback-based APIs.
84+- Use `Effect.void` instead of `Effect.succeed(undefined)` or `Effect.succeed(void 0)`.
85+- Prefer `DateTime.nowAsDate` over `new Date(yield* Clock.currentTimeMillis)` when you need a `Date`.
10186  
102−```ts
103−// Good
104−export function loadThing(input: unknown) {
105− const config = requireConfig(input)
106− const metadata = readMetadata(input)
107− return createThing({ config, metadata })
108−}
87+## Module conventions
10988  
110−function requireConfig(input: unknown) {
111− ...
112−}
113−```
89+- In `src/config`, follow the existing self-export pattern at the top of the file (for example `export * as ConfigAgent from "./agent"`) when adding a new config module.
11490  
115−- Keep helpers close to the code they support, below the main export when that improves readability.
116−- Do not over-abstract simple expressions into many single-use helpers; extract only when it names a real concept like `requireConfig` or `readMetadata`.
117−- Do not return `Effect` from helpers unless they actually perform effectful work. Synchronous parsing, validation, and option building should stay synchronous.
118−- Prefer Effect schema helpers such as `Schema.UnknownFromJsonString` and `Schema.decodeUnknownOption` over manual `JSON.parse` wrapped in `Effect.try` when parsing untrusted JSON strings.
119−- Add comments for non-obvious constraints and surprising behavior, not for obvious assignments or control flow.
91+## Schemas and errors
12092  
121−### Schema Definitions (Drizzle)
93+- Use `Schema.Class` for multi-field data.
94+- Use branded schemas (`Schema.brand`) for single-value types.
95+- Use `Schema.TaggedErrorClass` for typed errors.
96+- Use `Schema.Defect` instead of `unknown` for defect-like causes.
97+- In `Effect.gen` / `Effect.fn`, prefer `yield* new MyError(...)` over `yield* Effect.fail(new MyError(...))` for direct early-failure branches.
12298  
123−Use snake_case for field names so column names don't need to be redefined as strings.
99+## Runtime vs InstanceState
124100  
125−```ts
126−// Good
127−const table = sqliteTable("session", {
128− id: text().primaryKey(),
129− project_id: text().notNull(),
130− created_at: integer().notNull(),
131−})
101+- Use `makeRuntime` (from `src/effect/run-service.ts`) for all services. It returns `{ runPromise, runFork, runCallback }` backed by a shared `memoMap` that deduplicates layers.
102+- 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.
103+- If two open directories should not share one copy of the service, it needs `InstanceState`.
104+- 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.
105+- Use `Effect.addFinalizer` or `Effect.acquireRelease` inside the `InstanceState.make` closure for cleanup (subscriptions, process teardown, etc.).
106+- Use `Effect.forkScoped` inside the closure for background stream consumers — the fiber is interrupted when the instance is disposed.
107+- To make a service's `init()` non-blocking, fork `InstanceState.get(state)` at the `init()` call site (e.g. `Effect.forkIn(scope)`), not by forking work inside the `InstanceState.make` closure. Forking inside the closure leaves state incomplete for other methods that read it.
108+- `src/project/bootstrap.ts` already wraps every service `init()` in `Effect.forkDetach`, so `init()` is fire-and-forget in production. Keep `init()` methods synchronous internally; the caller controls concurrency.
132109  
133−// Bad
134−const table = sqliteTable("session", {
135− id: text("id").primaryKey(),
136− projectID: text("project_id").notNull(),
137− createdAt: integer("created_at").notNull(),
138−})
139−```
110+## Effect v4 beta API
140111  
141−## Testing
112+- `Effect.fork` and `Effect.forkDaemon` do not exist. Use `Effect.forkIn(scope)` to fork a fiber into a specific scope.
142113  
143−- Avoid mocks as much as possible, you shouldn't be using globalThis.\* at all unless it's the only option.
144−- Test actual implementation, do not duplicate logic into tests
145−- Tests cannot run from repo root (guard: `do-not-run-tests-from-root`); run from package dirs like `packages/opencode`.
114+## Preferred Effect services
146115  
147−## Type Checking
116+- In effectified services, prefer yielding existing Effect services over dropping down to ad hoc platform APIs.
117+- Prefer `FileSystem.FileSystem` instead of raw `fs/promises` for effectful file I/O.
118+- Prefer `ChildProcessSpawner.ChildProcessSpawner` with `ChildProcess.make(...)` instead of custom process wrappers.
119+- Prefer `HttpClient.HttpClient` instead of raw `fetch`.
120+- Prefer `Path.Path`, `Config`, `Clock`, and `DateTime` when those concerns are already inside Effect code.
121+- For background loops or scheduled tasks, use `Effect.repeat` or `Effect.schedule` with `Effect.forkScoped` in the layer definition.
148122  
149−- Always run `bun typecheck` from package directories (e.g., `packages/opencode`), never `tsc` directly.
123+## Effect.cached for deduplication
150124  
151−## V2 Session Core
125+Use `Effect.cached` when multiple concurrent callers should share a single in-flight computation rather than storing `Fiber | undefined` or `Promise | undefined` manually. See `specs/effect/migration.md` for the full pattern.
152126  
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.
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.
156−- Keep `SessionRunner`, model resolution, tool registry, permissions, and filesystem Location-scoped. Omitted `Location.workspaceID` means implicit-local placement; explicit workspace identity remains reserved for future placement semantics.
157−- Preserve one explicit `llm.stream(request)` call per provider turn and reload projected history before durable continuation. Do not bridge through legacy `SessionPrompt.loop(...)` or delegate orchestration to an in-memory tool loop.
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.
159−- Keep delivery vocabulary explicit. Prompts steer by default and promote at the next safe provider-turn boundary while the current drain requires continuation. An explicit `queue` input remains pending until the Session would otherwise become idle; promote one queued input at that boundary, then reevaluate continuation before promoting another. Promoting any new user input resets the selected agent's provider-turn allowance; a batch of steers resets it once.
160−- Keep EventV2 replay owner claims separate from clustered Session execution ownership.
161−- Keep the System Context algebra, registry, and built-ins in `src/system-context`; keep Context Source producers with their observed domains, and keep Session History selection plus Context Epoch persistence Session-owned.
127+## Callback boundaries
128+ 
129+Use `EffectBridge` for native or external callbacks (`@parcel/watcher`, `node-pty`, native `fs.watch`, plugin callbacks, etc.) that need to re-enter Effect services with instance/workspace context.
130+ 
131+Plain async code should pass explicit context or stay inside an Effect fiber; do not add ambient instance context shims.
162132  
RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack

RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack

RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack