- CHANGELOG: add CI log artifacts (PR #2782), provider benchmarks (PR #3022) to [Unreleased] Added section - CHANGELOG: add LSP restart_server() deadlock fix (PR #3165) to [Unreleased] Fixed section - docs/reference/lsp.md: document LspLifecycleManager 3-phase lock concurrency model for restart_server() thread safety - docs/development/ci-cd.md: document CI log artifact names, download API, and agent usage patterns ISSUES CLOSED: #3377
6.1 KiB
LSP Integration
Language Server Protocol (LSP) servers are registered in a global LSP Registry (namespaced like all other entities) and bound to actor graph nodes via YAML configuration. When an actor activates, the LSP Runtime starts the appropriate language servers. LSP capabilities (diagnostics, type info, symbol navigation, completions, references, rename, code actions) are exposed to the actor as callable tools (via the LspToolAdapter) and as automatic context enrichment.
Local-Mode Constraints
In local mode the LSP subsystem operates as a set of stubs:
| Component | Local-Mode Behavior |
|---|---|
LspRegistry |
Fully functional — register, list, remove servers |
LspRuntime |
Raises LspNotAvailableError on every operation |
LspToolAdapter |
Generates tool specs whose handlers always raise |
The registry is available for configuration and validation purposes even in local mode. The runtime and tool adapter become functional only when server mode is enabled.
Models
LspCapability
Enum of LSP features that can be exposed as tools:
| Value | Description |
|---|---|
DIAGNOSTICS |
Retrieve diagnostics (errors/warnings) |
HOVER |
Get type information and documentation |
COMPLETIONS |
Get code completion suggestions |
DEFINITIONS |
Go to the definition of a symbol |
REFERENCES |
Find all references to a symbol |
RENAME |
Rename a symbol across the workspace |
CODE_ACTIONS |
Retrieve available code actions for a range |
FORMATTING |
Format a document or selection |
SIGNATURE_HELP |
Get function signature information at a call |
DOCUMENT_SYMBOLS |
List all symbols in a file |
WORKSPACE_SYMBOLS |
Search for symbols across the workspace |
LspServerConfig
Registration record for a language server:
| Field | Type | Description |
|---|---|---|
name |
str |
Namespaced server name |
languages |
list[str] |
Programming languages served |
command |
str |
Executable command to start server |
args |
list[str] |
Additional CLI arguments |
env |
dict[str, str] |
Environment variables |
capabilities |
list[LspCapability] |
Capabilities exposed by this server |
LspBinding
Binds an LSP server to an actor graph node:
| Field | Type | Description |
|---|---|---|
node_name |
str |
Actor graph node name |
lsp_server_name |
str |
Namespaced LSP server name |
languages |
list[str] |
Subset of languages (empty = all) |
auto_detect |
bool |
Auto-detect language from file context |
Error Hierarchy
All errors inherit from CleverAgentsError:
CleverAgentsError
└── LspError (base for all LSP errors)
├── LspNotAvailableError (operation attempted in local mode)
└── LspServerNotFoundError (server not in registry)
CLI Commands
| Command | Description |
|---|---|
agents lsp add |
Register server from YAML config |
agents lsp remove |
Remove a registered server |
agents lsp list |
List servers with optional filters |
agents lsp show |
Show full server details |
Examples
# Register a Pyright LSP server
agents lsp add --config ./lsp/pyright.yaml
# List all Python language servers
agents lsp list --language python
# Show details for a specific server
agents lsp show local/pyright
# Remove a server
agents lsp remove local/pyright --yes
YAML Configuration
name: local/pyright
command: pyright-langserver
args: ["--stdio"]
languages: ["python"]
capabilities:
- diagnostics
- completions
- hover
- definitions
- references
- rename
env:
PYTHONPATH: ./src
Thread Safety
The LspRegistry uses a threading.RLock to ensure safe concurrent access. All public methods (register, get, list_servers, remove) acquire the lock before reading or mutating state.
Namespacing
LSP server names follow the project-wide namespacing pattern: [[server:]namespace/]name. For example:
local/pyright— a locally-defined Pyright serverdevops/clangd— a clangd server in the devops namespace
LspLifecycleManager — Concurrency Model
The LspLifecycleManager uses a 3-phase lock pattern for all server lifecycle
operations to prevent deadlocks under concurrent access:
| Phase | Lock held? | What happens |
|---|---|---|
| 1 | ✅ short | Read current state, snapshot fields, mutate _servers dict |
| 2 | ❌ none | All blocking I/O: transport.stop(), transport.start(), client.initialize() |
| 3 | ✅ short | Insert updated _ManagedServer into _servers |
This pattern applies to both start_server() and restart_server(). Releasing
the lock during Phase 2 ensures that concurrent callers of health_check(),
list_running(), or stop_server() are never blocked for the duration of
blocking I/O (which can take up to 60 seconds on timeout).
During a restart_server() call, the server is removed from _servers in
Phase 1 and re-inserted in Phase 3. Concurrent callers will see the server as
absent during the restart window — this is consistent with the start_server()
contract. The ref_count from the original _ManagedServer is carried forward
to the new instance.