Conversation language servers
Conversation-owned language servers
The registry LSP plugin owns one language-server slot per durable conversation id. Automatic
Write/Edit diagnostics and explicit Lsp tools use the same slot in the process executing the
conversation. The existing sub-agent dispatch routes delegated turns to their runner; daemon
projections and HTTP status reads do not execute those tools or acquire servers. Runner reclamation
awaits BrainService.disposeChannel, including the LSP cleanup promise, before a daemon turn can
take ownership of that conversation. A runner answers a release request with one of three outcomes
(RunnerReleaseOutcome, src/brain/delegatedTurn.ts): released, busy while a turn runs on the
channel, and failed when the channel is idle but its cleanup threw. failed preserves the runner's
route and ownership, so cleanup failure cannot create a second owner, and it is never reported as a
running turn. The idle route sweep treats failed like busy and asks again on a later sweep. The
owner's model switch answers it with a retry message, and a daemon-side continuation or result
delivery refuses with the same cause and steers nothing into the runner.
Cleanup on run settlement, disposal and idle expiry
The existing lsp control adds shutdownSession(sessionId). BrainService.releaseLspSession reaches it
through the already-loaded registry (peek, never a plugin load at teardown) on the final run settlement
(agent_settled, which calls onRunSettled in src/brain/persistence.ts), and that settlement awaits the
cleanup; LiveSessionRegistry's disposal callback starts the same call without blocking. agent_end does
not release resources because a run may continue.
The plugin fences late acquisition, cancels diagnostics and drains transport closure. Repeated
cleanup calls join the same promise. Root or server-language replacement waits for the old transport
to close; idle fallback defaults to 60 seconds with a 5-second sweep. The plugin's idleTtlMinutes setting defaults to one minute and accepts 0 to 240. Local transports own a process
group so disposal kills their descendants; initialize retains the owning process watchdog for
runner death. TypeScript disables its separate syntax server and uses a 1024 MiB old-space allowance,
not an RSS hard limit. The plugin sets no option that disables automatic typing acquisition for JavaScript projects.