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.
The existing lsp control adds shutdownSession(sessionId). BrainService invokes it synchronously
through the already-loaded registry on the final run settlement (agent_settled, which calls onRunSettled in src/brain/persistence.ts) and from LiveSessionRegistry's disposal
callback. agent_end and brain.run.beforeSettle do 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.