10 KiB
10 KiB
Effect Service Dependency Graph — Simulated Routes
Generated for createSimulatedRoutes in packages/opencode/src/server/routes/instance/httpapi/server.ts.
Notation
→ Xmeans "yieldsX.Servicefrom itsEffect.genbody"(lazy)means "usesInstanceState.contextor similar at call time, not at layer construction"(opt)means "usesEffect.serviceOption(X)— not strictly required"
Service → Dependencies
─── External / Platform ─────────────────────────────────────────
NodePath (no app deps) provides Path.Path
HttpClient (FetchHttp) (no app deps) provides HttpClient.HttpClient
HttpServer.layerServices (no app deps)
ChildProcessSpawner (from SimulationSpawner) (no app deps)
─── Leaf services (no app deps) ─────────────────────────────────
Global (no app deps)
Env (no app deps)
Bus (lazy via InstanceState)
SyncEvent (lazy via InstanceState)
AccountRepo (uses Database directly)
PtyTicket (Cache only)
Truncate → AppFileSystem (+ Config opt)
─── Middleware/route layers (no app service deps) ───────────────
errorLayer
compressionLayer → HttpServerRequest (builtin)
corsVaryFix
fenceLayer → HttpServerRequest (builtin)
runtime
simulationShareNextLayer provides ShareNext (Layer.succeed override)
─── Simulation overrides ────────────────────────────────────────
simulationBoundary:
SimulationFileSystem provides AppFileSystem + FileSystem.FileSystem(noop)
SimulationSpawner provides ChildProcessSpawner
SimulationNetwork provides SimulationNetwork.Service + HttpClient
(httpClientLayer → SimulationNetwork)
SimulationGit → AppFileSystem (overrides Git tag)
SimulationProvider → Simulation (overrides Provider tag)
Simulation → AppFileSystem, SimulationNetwork
─── Core services ───────────────────────────────────────────────
EffectFlock → Global, AppFileSystem
Auth → AppFileSystem
Account → AccountRepo, HttpClient
Npm → AppFileSystem, Global, FileSystem.FileSystem, EffectFlock
Config → AppFileSystem, Auth, Account, Env, Npm
Permission → Bus
Plugin → Bus, Config
Discovery → AppFileSystem, Path, HttpClient
Skill → Discovery, Config, Bus, AppFileSystem, Global
SystemPrompt → Skill
─── File / git ──────────────────────────────────────────────────
Ripgrep → AppFileSystem, HttpClient, ChildProcessSpawner
File → AppFileSystem, Ripgrep, Git, Scope
FileWatcher → Config, Git
Format → Config, ChildProcessSpawner
Snapshot → AppFileSystem, ChildProcessSpawner, Config
Storage → AppFileSystem, Git
Vcs → Git, Bus, Scope
Worktree → Scope, AppFileSystem, Path, ChildProcessSpawner,
Git, Project, InstanceStore
Project → AppFileSystem, Path, ChildProcessSpawner, Bus
─── Provider / LSP / MCP ────────────────────────────────────────
ModelsDev → AppFileSystem, HttpClient
ProviderAuth → Auth, Plugin
LSP → Config
McpAuth → AppFileSystem
MCP → ChildProcessSpawner, McpAuth, Bus, Config
─── Session graph ───────────────────────────────────────────────
Todo → Bus
Question → Bus
SessionStatus → Bus
SessionRunState → SessionStatus
Instruction → Config, AppFileSystem, Global, HttpClient
Session → Bus, Storage, SyncEvent
SessionSummary → Session, Snapshot, Storage, Bus
SessionRevert → Session, Snapshot, Storage, Bus,
SessionSummary, SessionRunState, SyncEvent
LLM → Auth, Config, Provider, Plugin, Permission
Agent → Config, Auth, Plugin, Skill, Provider
Command → Config, MCP, Skill
SessionProcessor → Session, Config, Bus, Snapshot, Agent, LLM,
Permission, Plugin, SessionSummary, Scope,
SessionStatus
SessionCompaction → Bus, Config, Session, Agent, Plugin,
SessionProcessor, Provider
SessionPrompt → Bus, SessionStatus, Session, Agent, Provider,
SessionProcessor, SessionCompaction, Plugin,
Command, Config, Permission, AppFileSystem,
MCP, LSP, ToolRegistry, Truncate,
ChildProcessSpawner, Scope, Instruction,
SessionRunState, SessionRevert,
SessionSummary, SystemPrompt, LLM
ToolRegistry → Config, Plugin, Agent, Skill, Truncate (+ many
tool deps: Question, Todo, Session, Provider,
Git, LSP, Instruction, AppFileSystem, Bus,
HttpClient, ChildProcessSpawner, Ripgrep,
Format, Truncate)
─── Share / Workspace ───────────────────────────────────────────
ShareNext (provided by simulationShareNextLayer in sim)
SessionShare → Config, Session, ShareNext, Scope, SyncEvent
Workspace → Auth, Session, SessionPrompt, HttpClient,
SyncEvent, Vcs
(also uses InstanceStore/Bootstrap via
Effect.provide inside runInWorkspace)
─── Misc ────────────────────────────────────────────────────────
Installation → HttpClient, ChildProcessSpawner
Pty → Config, Bus, Plugin
─── Instance lifecycle ──────────────────────────────────────────
InstanceBootstrap → Config, File, FileWatcher, Format, LSP, Plugin,
Project, ShareNext, Snapshot, Vcs
InstanceStore → Project, InstanceBootstrap, Scope
Observability (no app deps; provides Logger + tracer)
Dependency Tiers (topological order)
Roughly, build order from leaves to roots:
Tier 0 (no deps):
Global, Env, NodePath, AccountRepo, PtyTicket, Bus (lazy),
SyncEvent (lazy), AppFileSystem (sim), ChildProcessSpawner (sim),
HttpClient (sim), SimulationNetwork, errorLayer, compressionLayer,
corsVaryFix, fenceLayer, runtime, simulationShareNextLayer
Tier 1:
Auth, Truncate, EffectFlock, Permission, Todo, Question,
SessionStatus, McpAuth, Discovery, SimulationGit, Simulation,
Ripgrep, Storage, Vcs
Tier 2:
Account, Npm, LSP, Skill, SessionRunState, ModelsDev, File,
Project, Worktree (needs InstanceStore — cycle hint), MCP,
Installation, Pty, SystemPrompt
Tier 3:
Config, FileWatcher, Format, Snapshot, ProviderAuth, Session,
SessionShare, SessionSummary
Tier 4:
Plugin, Agent, Command, LLM, SessionRevert,
SimulationProvider (= Provider)
Tier 5:
SessionProcessor, SessionCompaction, Workspace
Tier 6:
SessionPrompt, ToolRegistry
Tier 7:
InstanceBootstrap (needs many of the above)
Tier 8:
InstanceStore (needs InstanceBootstrap + Project)
Potential cycles / hazards
Worktree → InstanceStore → InstanceBootstrap → Project → (back to Worktree?)
- InstanceBootstrap requires Project (yes)
- Project does NOT require Worktree directly
- Worktree requires InstanceStore — only used inside its methods,
but yielded at layer init, so it's a true requirement
→ Worktree must be built AFTER InstanceStore.
SimulationProvider provides Provider tag, depends on Simulation.
Many downstream services depend on Provider — those resolve to
SimulationProvider in this layer chain.
SimulationGit provides Git tag, used by File, FileWatcher,
Storage, Vcs, Worktree, ToolRegistry tools, Project (no — Project
uses ChildProcessSpawner instead).
LLM.layer pipes Layer.provide(Permission.defaultLayer) internally.
That means LLM brings its own Permission — beware double-providing
Permission (sim chain also provides Permission.layer separately).
Why the simulated chain fails with stepwise provideMerge
Layer.provideMerge(self, that) builds that independently and uses it to satisfy self's reqs. that cannot see services already provided inside self. So when you write:
withCoreAppServices // contains Config.layer, Plugin.layer, ...
.pipe(Layer.provideMerge(Npm.layer)) // Npm needs EffectFlock
Npm.layer is built in isolation and its EffectFlock requirement must be filled from OUTSIDE the entire pipe — not from inside withCoreAppServices. Same for every other "consumer added later" combination.
Production avoids this because every service is added via Layer.provide([... defaultLayer, defaultLayer, ...]) — flat mergeAll of self-contained layers, all visible to each other.
See also
dependency-graph.html— interactive visualization of the same data.