Skip to main content

Language Server Protocol (LSP)

VibeOS runs full language servers — pyright, gopls, rust-analyzer, typescript-language-server, clangd, and ~20 more — as background subprocesses and feeds their semantic diagnostics into the post-write lint check used by write_file and patch. When the agent edits a file, it sees exactly the errors that edit introduced — not just syntax errors, but type errors, undefined names, missing imports, and project-wide semantic issues the language server detects.

This is the same architecture top-tier coding agents use. VibeOS ships it self-contained: no editor host required, no plugins to install, no separate daemon to manage.

When LSP runs​

LSP is gated on git workspace detection. When the agent's working directory (or the file being edited) is inside a git repository, LSP runs against that workspace. When neither is in a git repo, LSP stays dormant — useful for messaging gateways where the cwd is the user's home directory and there's no project to diagnose.

The check is layered: in-process syntax check first (microseconds), then LSP diagnostics second when syntax is clean. A flaky or missing language server can never break a write — every LSP failure path falls back silently to the syntax-only result.

Concretely, on every successful write_file or patch:

  1. VibeOS captures a baseline of current diagnostics for the file.
  2. Performs the write.
  3. Re-queries the language server, filters out diagnostics that were already in the baseline, and surfaces only the new ones.

The agent sees output like:

{
"bytes_written": 42,
"dirs_created": false,
"lint": {"status": "ok", "output": ""},
"lsp_diagnostics": "LSP diagnostics introduced by this edit:\n<diagnostics file=\"/path/to/foo.py\">\nERROR [42:5] Cannot find name 'foo' [reportUndefinedVariable] (Pyright)\nERROR [50:1] Argument of type \"str\" is not assignable to \"int\" [reportArgumentType] (Pyright)\n</diagnostics>"
}

The lint field carries the syntax-check result (microsecond in-process parse via ast.parse, json.loads, etc.); the lsp_diagnostics field carries the semantic diagnostics from the real language server. Two channels, independent signals — the agent sees a syntax-clean file with semantic problems as lint: ok plus a populated lsp_diagnostics.

Supported languages​

LanguageServerAuto-install
Pythonpyright-langservernpm
TypeScript / JavaScript / JSX / TSXtypescript-language-servernpm
Vue@vue/language-servernpm
Sveltesvelte-language-servernpm
Astro@astrojs/language-servernpm
Gogoplsgo install
Rustrust-analyzermanual (rustup)
C / C++clangdmanual (LLVM)
Bash / Zshbash-language-servernpm
YAMLyaml-language-servernpm
Lualua-language-servermanual (GitHub releases)
PHPintelephensenpm
OCamlocaml-lspmanual (opam)
Dockerfiledockerfile-language-server-nodejsnpm
Terraformterraform-lsmanual
Dartdart language-servermanual (dart sdk)
Haskellhaskell-language-servermanual (ghcup)
Juliajulia + LanguageServer.jlmanual
Clojureclojure-lspmanual
Nixnixdmanual
Zigzlsmanual
Gleamgleam lspmanual (gleam install)
Elixirelixir-lsmanual
Prismaprisma language-servermanual
Kotlinkotlin-language-servermanual
Javajdtlsmanual

For "manual" entries, install the server through whatever toolchain manager makes sense for that language (rustup, ghcup, opam, brew, …). VibeOS auto-detects the binary on PATH or in <VIBEOS_HOME>/lsp/bin/.

A few servers are installed alongside a peer dependency that npm won't auto-pull. The current case is typescript-language-server, which requires the typescript SDK importable from the same node_modules tree — VibeOS installs both packages together when you run vibeos lsp install typescript or auto-install fires on first use.

CLI​

vibeos lsp status          # service state + per-server install status
vibeos lsp list # registry, optionally --installed-only
vibeos lsp install <id> # eagerly install one server
vibeos lsp install-all # try every server with a known recipe
vibeos lsp restart # tear down running clients
vibeos lsp which <id> # print resolved binary path

vibeos lsp status is the best starting point — it shows which languages will get semantic diagnostics today and which need a binary installed.

Configuration​

The defaults work for typical setups; nothing to set if the binaries are on PATH.

# config.yaml
lsp:
# Master toggle. Disabling skips the entire subsystem — no servers
# spawn, no background event loop runs.
enabled: true

# How long to wait for diagnostics after each write.
wait_mode: document # "document" or "full"
wait_timeout: 5.0

# How to handle missing server binaries.
# auto — install via npm/pip/go install into <VIBEOS_HOME>/lsp/bin
# manual — only use binaries already on PATH
install_strategy: auto

# Per-server overrides (all optional).
servers:
pyright:
disabled: false
command: ["/abs/path/to/pyright-langserver", "--stdio"]
env: { PYRIGHT_LOG_LEVEL: "info" }
initialization_options:
python:
analysis:
typeCheckingMode: "strict"
typescript:
disabled: true # skip TS even when its extensions match

Per-server keys​

  • disabled: true — skip this server entirely even when its extensions match a file.
  • command: [bin, ...args] — pin a custom binary path. Bypasses auto-install.
  • env: {KEY: value} — extra env vars passed to the spawned process.
  • initialization_options: {...} — merged into the LSP initializationOptions payload sent in the initialize handshake. Server-specific; consult the language server's docs.

Installation locations​

When install_strategy: auto, VibeOS installs binaries into <VIBEOS_HOME>/lsp/bin/. NPM packages land in <VIBEOS_HOME>/lsp/node_modules/ with bin symlinks one level up. Go binaries come from go install with GOBIN pointed at the staging dir.

Nothing is ever installed to /usr/local/, ~/.local/, or any other shared location — the staging dir is fully VibeOS-owned and is removed when you reset the profile.

Performance characteristics​

LSP servers are lazy-spawned on first use. Editing a Python file in a project that's never seen .py traffic spawns pyright; the spawn takes 1-3 seconds for most servers (rust-analyzer can take 10+ on a cold project). Subsequent edits in the same workspace re-use the running server.

The LSP layer adds a few milliseconds to clean writes when no diagnostics are emitted. When diagnostics are emitted, the wait budget is wait_timeout seconds — typically the server responds in tens of milliseconds for pyright/tsserver and a few seconds for rust-analyzer mid-indexing.

Servers are kept alive for the life of the VibeOS process. There's no idle-timeout reaper — the cost of restarting the server's index on every write would be far higher than holding the daemon.

Disabling​

Set lsp.enabled: false in config.yaml to disable the entire subsystem. The post-write check falls back to the in-process syntax check (ast.parse for Python, json.loads for JSON, etc.) which ships unchanged from earlier versions.

To disable a single language without disabling the whole layer:

lsp:
servers:
rust-analyzer:
disabled: true

Troubleshooting​

vibeos lsp status shows a server as "missing"

The binary isn't on PATH and isn't in <VIBEOS_HOME>/lsp/bin/. Run vibeos lsp install <server_id> to attempt an auto-install, or install the binary manually through the language's normal toolchain.

Backend warnings section in vibeos lsp status

Some servers ship as thin wrappers around an external CLI for actual diagnostics — they spawn cleanly and accept requests but never emit errors when the sidecar binary is missing. The most common case is bash-language-server, which delegates diagnostics to shellcheck. When vibeos lsp status shows a Backend warnings section, install the named tool through your OS package manager:

apt install shellcheck      # Debian / Ubuntu
brew install shellcheck # macOS
scoop install shellcheck # Windows

The same warning is logged once at server spawn time in ~/.vibeos/logs/agent.log.

Server starts but never returns diagnostics

Check ~/.vibeos/logs/agent.log for [agent.lsp.client] entries — both stderr from the language server and protocol errors land there. Some servers (rust-analyzer especially) need to finish a project-wide index before they emit per-file diagnostics; the first edit after server start may complete with no diagnostics, with subsequent edits picking them up.

Server crashed

A crashed server is added to the broken-set and won't be retried for the rest of the session. Run vibeos lsp restart to clear the set; the next edit re-spawns.

Editing a file outside any git repo

By design, LSP only runs inside a git repository. If the project isn't yet initialized, run git init to enable LSP diagnostics. Otherwise the in-process syntax-only fallback applies.