---
adr_number: 27
title: Language Server Protocol (LSP) Integration
status_history:
- - '2026-02-20'
  - Proposed
  - Jeffrey Phillips Freeman
- - '2026-02-20'
  - Accepted
  - Jeffrey Phillips Freeman
tier: 4
authors:
- Jeffrey Phillips Freeman
superseded_by: null
related_adrs:
- number: 1
  title: Layered Architecture
  relationship: The LSP Runtime resides in the Infrastructure layer; LSP tools are exposed to actors through the Domain layer's tool abstractions
- number: 8
  title: Resource System
  relationship: Resources provide the workspace roots for LSP servers and the language discovery inputs for auto-binding
- number: 10
  title: Actor and Agent Architecture
  relationship: Actors declare LSP bindings in their YAML configuration; LSP tools participate as capabilities in actor graph nodes
- number: 11
  title: Tool System
  relationship: LSP capabilities are exposed as tools through the `LSPToolAdapter`, following the same adapter pattern as MCP
- number: 12
  title: Skill System
  relationship: LSP tools complement skill-provided tools in the actor's merged tool surface
- number: 14
  title: Context Management (ACMS)
  relationship: LSP context enrichment injects diagnostics and type information into the ACMS hot context
- number: 29
  title: Model Context Protocol (MCP) Adoption
  relationship: MCP and LSP follow analogous adapter patterns; both bridge standard protocol servers into the tool system
- number: 31
  title: Actor Abstraction Definition
  relationship: Actors acquire language intelligence through LSP bindings alongside tool capabilities through skills
- number: 32
  title: Jinja2 YAML Template Preprocessing
  relationship: Jinja2 templates enable dynamic LSP binding resolution in actor YAML files
- number: 40
  title: LSP Resource Types
  relationship: ADR-027 defines CleverAgents as an LSP server; ADR-040 defines external LSP servers as resources in the DAG
acceptance:
  votes_for:
  - voter: Jeffrey Phillips Freeman <Jeffrey.Freeman@CleverThis.com>
    comment: Giving actors the same language intelligence that human developers use in IDEs is a force multiplier for code quality — and leveraging the LSP ecosystem avoids reinventing mature tooling
  votes_against: []
  abstentions: []
---
## Context

Actors in CleverAgents that perform software development tasks — writing code, refactoring, reviewing, debugging — operate on source code as raw text. Without semantic understanding of the code they manipulate, actors lack the ability to resolve types, navigate symbol definitions, discover references, identify compilation errors, or understand the structural relationships within a codebase. Human developers gain these capabilities through IDE features powered by language servers. Actors need the same language intelligence to work effectively.

Building bespoke code analysis for each programming language would be prohibitively expensive and would perpetually lag behind the state of the art in language tooling. The **Language Server Protocol (LSP)** is an open standard that decouples language intelligence from any specific editor or tool — it defines a JSON-RPC interface through which any client can request diagnostics, type information, symbol navigation, completions, and more from a language-specific server process. The ecosystem already provides production-quality language servers for virtually every mainstream language (Pyright for Python, typescript-language-server for TypeScript/JavaScript, clangd for C/C++, rust-analyzer for Rust, gopls for Go, etc.).

CleverAgents should leverage this ecosystem directly, giving actors the same rich language intelligence that human developers enjoy — but integrated into the agent's reasoning loop and tool-calling pipeline rather than into an IDE.

## Decision Drivers

- Actors performing software development tasks need semantic code understanding (type resolution, symbol navigation, diagnostics) beyond raw text manipulation
- Building bespoke code analysis per language is prohibitively expensive; the LSP ecosystem already provides production-quality servers for virtually every mainstream language
- Language intelligence must integrate into the actor's reasoning loop and tool-calling pipeline, not into an IDE presentation layer
- Actors must be able to work across multiple programming languages without per-language configuration when possible
- LSP capabilities must be available both as explicit tools (on-demand queries) and as automatic context enrichment (injected diagnostics and type information)

## Decision

CleverAgents adopts LSP as the standard protocol for attaching **language intelligence capabilities** to actors and agents. LSP servers are registered in a global **LSP Registry** (namespaced like all other entities), and bound to specific actor graph nodes via YAML configuration. LSP capabilities are exposed to actors as **tools** (via an `LSPToolAdapter`) and as **context enrichment** (automatic injection of diagnostics and type information into the actor's ACMS context). This integration lives in the **Infrastructure layer**, not the Presentation layer — LSP serves the AI agents, not human users in editors.

## Design

### LSP Registry

Language servers are registered as first-class entities in a global **LSP Registry**, following the same patterns as the Tool Registry, Skill Registry, and Actor Registry.

Each LSP entry is:

- **Namespaced** using the standard `[[server:]namespace/]name` format (e.g., `local/pyright`, `local/clangd`, `cleverthis/rust-analyzer`).
- **Defined via YAML configuration** and registered via `agents lsp add --config <file>`.
- **Independently versioned and managed** — language servers can be added, updated, listed, shown, and removed like any other registered entity.

#### LSP Configuration Schema

```yaml
name: local/pyright
description: "Pyright language server for Python type checking and intelligence"

command: pyright-langserver
args: ["--stdio"]
transport: stdio                       # stdio (default) or tcp

languages:                             # Languages this server covers
  - python

capabilities:                          # LSP capabilities this server provides
  - diagnostics                        # textDocument/publishDiagnostics
  - hover                              # textDocument/hover
  - completions                        # textDocument/completion
  - definitions                        # textDocument/definition
  - references                         # textDocument/references
  - rename                             # textDocument/rename
  - code_actions                       # textDocument/codeAction
  - formatting                         # textDocument/formatting
  - signature_help                     # textDocument/signatureHelp
  - document_symbols                   # textDocument/documentSymbol
  - workspace_symbols                  # workspace/symbol

initialization:                        # LSP initialization options
  python:
    pythonPath: "${PYTHON_PATH:/usr/bin/python3}"
    venvPath: "${VENV_PATH:}"
    analysis:
      typeCheckingMode: "basic"
      autoSearchPaths: true

workspace_settings:                    # Per-workspace LSP settings
  python.analysis.diagnosticMode: "workspace"
```

Key configuration fields:

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | string | Yes | Namespaced identifier (`<namespace>/<name>`). |
| `description` | string | No | Human-readable description. |
| `command` | string | Yes | Executable command to launch the language server. |
| `args` | list[string] | No | Command-line arguments for the server process. |
| `transport` | string | No | Communication transport: `stdio` (default) or `tcp`. |
| `languages` | list[string] | Yes | Programming languages this server provides intelligence for. |
| `capabilities` | list[string] | No | Explicit list of LSP capabilities. When omitted, capabilities are discovered from the server's `initialize` response. |
| `initialization` | object | No | LSP `initializationOptions` passed during the `initialize` handshake. Supports `${ENV_VAR}` interpolation. |
| `workspace_settings` | object | No | LSP workspace configuration settings sent via `workspace/didChangeConfiguration`. |

### Resource Language Discovery

Resources attached to a project represent codebases, file trees, and repositories that contain source code in one or more programming languages. The system determines what languages a resource contains through a multi-layered discovery process:

1. **File extension analysis** — The most common and fastest method. File extensions (`.py`, `.ts`, `.rs`, `.go`, `.cpp`, etc.) are mapped to language identifiers using a built-in extension-to-language table.
2. **Content analysis** — For files without extensions or with ambiguous extensions, content markers are inspected: shebang lines (`#!/usr/bin/env python3`), magic comments, file headers, and structural patterns.
3. **UKO classification** — The Universal Knowledge Ontology (ACMS) classifies resources at the technology-specific layer (Python, TypeScript, Rust, etc.), providing semantic language identification that persists across sessions.
4. **Explicit project-level declaration** — Projects may explicitly declare the languages they contain via configuration, overriding or supplementing automatic discovery.

Language discovery results are cached per resource and invalidated when the resource content changes (detected via file watchers or resource refresh).

### Actor LSP Binding

Actors declare their LSP dependencies in their YAML configuration via the `lsp:` field. The binding model supports three levels of specificity, from fully explicit to fully automatic:

#### Explicit Binding (by server name)

The actor lists specific registered LSP servers by their namespaced names:

```yaml
actors:
  code_analyzer:
    type: llm
    config:
      actor: anthropic/claude-3-opus
      system_prompt: |
        You are a Python code analyst. Use LSP tools to understand
        type information and identify issues in the codebase.
    skills:
      - local/file-ops
    lsp:
      - local/pyright
      - local/ruff-lsp
```

#### Language-Based Binding

The actor specifies languages and the runtime resolves the appropriate LSP servers from the registry:

```yaml
actors:
  polyglot_reviewer:
    type: llm
    config:
      actor: openai/gpt-4
      system_prompt: |
        Review code for correctness and style issues.
    lsp:
      languages:
        - python
        - typescript
        - rust
```

At activation, the runtime queries the LSP Registry for servers covering each declared language. If multiple servers cover the same language, the first registered match is used (or the user can disambiguate with explicit names).

#### Resource-Auto Binding

The actor requests automatic LSP resolution based on the languages present in the resources it is processing:

```yaml
actors:
  universal_developer:
    type: llm
    config:
      actor: anthropic/claude-3-opus
      system_prompt: |
        You are a software developer. Use LSP capabilities to understand
        the codebase you are working in.
    lsp:
      auto: true
```

When `auto: true` is set, the runtime inspects the project's bound resources via language discovery, determines which languages are present, and binds the appropriate LSP servers automatically. This is the most reusable pattern — an actor configured with `lsp: { auto: true }` works correctly regardless of the language of the project it is assigned to.

#### Jinja2 Dynamic Binding

For advanced use cases, Jinja2 templates can dynamically resolve LSP bindings at preprocessing time:

```yaml
actors:
  dynamic_analyst:
    type: llm
    config:
      actor: openai/gpt-4
    lsp:
      {% for lang in context.project_languages %}
      - {{ lsp_registry.for_language(lang) | first }}
      {% endfor %}
```

Template variables available during LSP resolution:

| Variable | Type | Description |
|----------|------|-------------|
| `lsp_registry` | object | The LSP Registry, supporting `.for_language(lang)` and `.all()` queries. |
| `context.project_languages` | list[string] | Languages detected in the current project's resources. |
| `context.resource_languages` | list[string] | Languages detected in the specific resource being processed. |

#### Per-Node Binding in Actor Graphs

Different nodes in an actor's graph can have different LSP configurations. This allows fine-grained control — for example, giving a strategy actor read-only diagnostics while giving an execution actor full LSP capabilities:

```yaml
actors:
  strategy_planner:
    type: llm
    config:
      actor: openai/gpt-4
      system_prompt: |
        Plan the implementation strategy.
    lsp:
      - local/pyright
    lsp_capabilities:              # Restrict to read-only capabilities
      - diagnostics
      - hover
      - definitions
      - references

  code_implementer:
    type: llm
    config:
      actor: anthropic/claude-3-opus
      system_prompt: |
        Implement code changes according to the strategy.
    lsp:
      auto: true
    lsp_capabilities: all          # Full capabilities (default)

routes:
  dev_workflow:
    type: graph
    entry_point: plan
    nodes:
      - name: plan
        type: agent
        agent: strategy_planner
      - name: implement
        type: agent
        agent: code_implementer
    edges:
      - source: plan
        target: implement
      - source: implement
        target: end
```

The `lsp_capabilities` field controls which LSP features are exposed to the actor as tools. When omitted or set to `all`, every capability declared by the bound LSP servers is available.

| Capability | LSP Method | Tool Name | Description |
|-----------|-----------|-----------|-------------|
| `diagnostics` | `textDocument/publishDiagnostics` | `lsp/diagnostics` | Retrieve compilation errors, warnings, and hints for a file |
| `hover` | `textDocument/hover` | `lsp/hover` | Get type information and documentation at a position |
| `completions` | `textDocument/completion` | `lsp/completions` | Get code completion suggestions at a position |
| `definitions` | `textDocument/definition` | `lsp/definition` | Go to the definition of a symbol |
| `references` | `textDocument/references` | `lsp/references` | Find all references to a symbol |
| `rename` | `textDocument/rename` | `lsp/rename` | Compute a rename refactoring across files |
| `code_actions` | `textDocument/codeAction` | `lsp/code-actions` | Get available code actions (quick fixes, refactors) |
| `formatting` | `textDocument/formatting` | `lsp/format` | Format a document according to language conventions |
| `signature_help` | `textDocument/signatureHelp` | `lsp/signature` | Get function signature information at a call site |
| `document_symbols` | `textDocument/documentSymbol` | `lsp/symbols` | List all symbols (classes, functions, variables) in a file |
| `workspace_symbols` | `workspace/symbol` | `lsp/workspace-symbols` | Search for symbols across the entire workspace |

### LSP Capability Exposure

LSP capabilities reach actors through two complementary mechanisms:

#### As Tools (via LSPToolAdapter)

The `LSPToolAdapter` is an Infrastructure-layer adapter (analogous to `MCPToolAdapter` for MCP) that translates registered LSP server capabilities into CleverAgents tools. When an actor with LSP bindings is activated:

1. The runtime resolves the actor's `lsp:` configuration to concrete LSP server entries from the LSP Registry.
2. For each bound server, the `LSPToolAdapter` generates tool definitions for each exposed capability.
3. These tools are injected into the actor's available tool surface alongside skill-provided tools.
4. The actor can call LSP tools during its reasoning loop (e.g., `lsp/diagnostics` to check for errors before committing a change, `lsp/definition` to understand a symbol before modifying it).

Tool signatures follow a consistent pattern:

```
lsp/diagnostics(file_path: str) -> list[Diagnostic]
lsp/hover(file_path: str, line: int, column: int) -> HoverResult
lsp/definition(file_path: str, line: int, column: int) -> list[Location]
lsp/references(file_path: str, line: int, column: int) -> list[Location]
lsp/completions(file_path: str, line: int, column: int) -> list[CompletionItem]
lsp/rename(file_path: str, line: int, column: int, new_name: str) -> WorkspaceEdit
lsp/code-actions(file_path: str, start_line: int, start_col: int, end_line: int, end_col: int) -> list[CodeAction]
lsp/format(file_path: str) -> FormattedText
lsp/symbols(file_path: str) -> list[DocumentSymbol]
lsp/workspace-symbols(query: str) -> list[SymbolInformation]
lsp/signature(file_path: str, line: int, column: int) -> SignatureHelp
```

When multiple LSP servers are bound to an actor (e.g., `local/pyright` for Python and `local/typescript-lsp` for TypeScript), the tool adapter routes requests to the appropriate server based on the file's detected language. The tool names remain the same — the routing is transparent to the actor.

#### As Context Enrichment

In addition to explicit tool calls, LSP servers can automatically enrich the actor's context when code files are loaded into the ACMS hot context:

- **Diagnostic injection**: When a source file enters the actor's hot context, the LSP server's diagnostics for that file are automatically appended as structured annotations. This means the actor "sees" type errors, unused imports, and other issues without needing to explicitly call `lsp/diagnostics`.
- **Type annotation overlay**: Hover information for key symbols (function signatures, class hierarchies, variable types) can be pre-fetched and included as context metadata, giving the actor type-level understanding of the code it is examining.

Context enrichment is controlled by the actor's `lsp_context_enrichment` configuration:

```yaml
actors:
  enriched_reviewer:
    type: llm
    config:
      actor: openai/gpt-4
    lsp:
      auto: true
    lsp_context_enrichment:
      diagnostics: true              # Auto-inject diagnostics (default: true)
      type_annotations: false        # Auto-inject type info (default: false, can be expensive)
      max_diagnostics_per_file: 50   # Limit to avoid context bloat
```

### LSP Server Lifecycle

LSP servers are long-lived processes managed by the **LSP Runtime** in the Infrastructure layer:

1. **Startup**: When an actor with LSP bindings is activated (either for a plan phase or a direct `agents actor run` invocation), the LSP Runtime starts the required language server processes. Each server is initialized with the `initialize` LSP handshake, passing `initializationOptions` from the registry entry and workspace root paths mapped from the actor's bound resources.

2. **Workspace Mapping**: LSP workspace roots are mapped to the physical paths of registered resources. When the actor operates within a sandbox (Execute phase), the sandbox's working directory is used as the workspace root, ensuring the language server analyzes the sandboxed code — not the original.

3. **File Synchronization**: As the actor reads and modifies files (via tools), the LSP Runtime sends `textDocument/didOpen`, `textDocument/didChange`, and `textDocument/didClose` notifications to the language server, keeping the server's view of the codebase synchronized with the actor's mutations.

4. **Shared Instances**: If multiple actors in the same plan or session require the same language server for the same workspace, the LSP Runtime shares a single server instance. Reference counting ensures the server stays alive as long as at least one actor needs it.

5. **Shutdown**: When the actor deactivates (plan phase completes, session ends, or direct run finishes), the LSP Runtime sends the `shutdown` and `exit` lifecycle messages. Shared instances are shut down only when the last referencing actor deactivates.

### LSP in the Plan Lifecycle

LSP integration enhances each phase of the plan lifecycle:

| Phase | LSP Role | Example Use |
|-------|---------|-------------|
| **Strategize** | Read-only intelligence — diagnostics, type information, and symbol navigation inform strategy decisions | Strategy actor calls `lsp/diagnostics` to assess codebase health, `lsp/symbols` to understand module structure, `lsp/references` to gauge impact of proposed changes |
| **Execute** | Full intelligence — all capabilities available for code generation and modification | Execution actor calls `lsp/definition` to understand APIs before using them, `lsp/diagnostics` to verify changes compile, `lsp/rename` to perform safe refactorings |
| **Apply** | Validation intelligence — diagnostics confirm the final changeset is clean | Apply-phase validations invoke `lsp/diagnostics` on all modified files to ensure no regressions before merge |

### Comparison with MCP

LSP and MCP are both standard protocols for external capability servers, but they serve different purposes:

| Aspect | MCP (Model Context Protocol) | LSP (Language Server Protocol) |
|--------|------------------------------|-------------------------------|
| **Purpose** | General-purpose tool discovery and invocation | Language-specific code intelligence |
| **Scope** | Any callable operation (file I/O, API calls, data queries) | Code analysis capabilities (diagnostics, navigation, completions) |
| **Registry** | Tool Registry (via `MCPToolAdapter`) | LSP Registry (via `LSPToolAdapter`) |
| **Binding** | Skills compose MCP tools; actors acquire via skill references | Actors bind LSP servers directly via `lsp:` configuration |
| **Tool generation** | Tools pre-defined by MCP server, registered at discovery | Tools generated dynamically from LSP server capabilities at activation |
| **Lifecycle** | Managed by MCP SDK connection lifecycle | Managed by LSP Runtime with workspace mapping and file synchronization |

Both adapters follow the same architectural pattern: a standard protocol server in the Infrastructure layer is bridged into the actor's tool surface through a typed adapter. The key difference is that MCP tools are general-purpose and explicitly authored, while LSP tools are language-specific and automatically derived from the protocol's capability model.

## Constraints

- LSP servers must be registered in the LSP Registry before they can be referenced by actors. There is no inline LSP server definition.
- Actor LSP bindings are resolved at actor activation time — unresolvable bindings (missing registry entry, no server for a requested language) produce a clear error.
- LSP tools must not modify resources outside the sandbox boundary during Execute phase. Mutation operations (rename, code actions that apply edits) must route through the sandbox.
- The LSP Runtime must handle language server crashes gracefully — restarting the server and re-synchronizing file state without disrupting the actor's execution.
- LSP servers must operate within the same security boundary as the actor. Server processes run with the same permissions as the CleverAgents process.
- Context enrichment (automatic diagnostic/type injection) must respect the actor's context budget — diagnostic results are truncated if they would exceed the hot context token limit.

## Consequences

### Positive
- Actors gain the same rich language intelligence that human developers use in IDEs, dramatically improving code quality in generated output.
- The LSP ecosystem provides production-quality language servers for virtually every mainstream language — CleverAgents benefits from years of community investment without building custom analyzers.
- Language-agnostic actor definitions (using `lsp: { auto: true }`) work correctly across any project language, maximizing actor reusability.
- Per-node LSP binding gives fine-grained control over which actors in a graph receive which capabilities, enabling cost-conscious configurations (e.g., diagnostics-only for strategy, full capabilities for execution).
- The adapter pattern (`LSPToolAdapter`) mirrors the established `MCPToolAdapter` pattern, maintaining architectural consistency.

### Negative
- Language servers are resource-intensive processes (memory, CPU). Running multiple servers simultaneously (e.g., Pyright + typescript-language-server + clangd for a polyglot project) increases system resource consumption.
- LSP startup time adds latency to actor activation. Large workspaces may take several seconds to index before diagnostics become available.
- File synchronization overhead — every file read/write by the actor triggers LSP notifications, adding I/O overhead proportional to the actor's file activity.
- LSP protocol complexity — the protocol has many capabilities and edge cases. The `LSPToolAdapter` must handle server-specific quirks and partial capability support.

### Risks
- Language server stability varies. Some servers may crash under heavy load, produce incorrect diagnostics, or fail to handle large files. The LSP Runtime must be robust to these failure modes.
- LSP diagnostic latency may not keep up with rapid file modifications during Execute phase, leading to stale diagnostics if the actor reads diagnostics immediately after writing a file.
- The `auto: true` binding mode depends on accurate language discovery. Misidentified languages would bind incorrect servers, producing confusing tool results.
- Deep Jinja2 templating for LSP resolution may produce configurations that are difficult to debug when binding fails.

## Alternatives Considered

**Custom language analysis per language** — Build bespoke code analysis (AST parsing, type checking) for each supported language. Maximum control but enormous development cost, perpetual maintenance burden, and inevitable lag behind the state of the art in language tooling. The LSP ecosystem already provides what CleverAgents needs. Rejected.

**Tree-sitter only** — Use Tree-sitter for syntax-level analysis across all languages. Tree-sitter provides fast, incremental parsing and syntax highlighting but lacks semantic understanding (no type checking, no cross-file references, no completion intelligence). Tree-sitter may be used as a complementary lightweight layer for syntax-only tasks, but it cannot replace LSP for semantic intelligence. Rejected as the primary strategy.

**LSP as IDE integration (Presentation layer)** — Use LSP as a protocol for IDE plugin integration, mapping editor features to plan operations. This conflates two concerns: LSP is designed for language intelligence, not for application-level UI integration. CleverAgents' Presentation-layer clients (CLI, TUI, Web, IDE plugin) all communicate through A2A. Using LSP for IDE integration would create an unnecessary protocol layer between the IDE and A2A when A2A already provides the needed surface. Rejected — the IDE plugin is an embedded TUI communicating through A2A; LSP serves the Infrastructure layer.

**LSP as part of the Skill system** — Package LSP capabilities as skills rather than as a separate registry and binding mechanism. While architecturally possible, this conflates two distinct concerns: skills organize tools into reusable collections, while LSP binding requires workspace-aware server lifecycle management, file synchronization, and language-based routing that skills are not designed to handle. A dedicated LSP Registry and Runtime provide clearer separation of concerns. Rejected.

## Compliance

- **LSP conformance tests**: Validate that the LSP Runtime correctly implements the LSP initialize/shutdown lifecycle, file synchronization notifications, and capability negotiation for each registered server.
- **Tool adapter tests**: Verify that `LSPToolAdapter` generates correct tool definitions from LSP server capabilities and routes tool invocations to the correct server based on file language.
- **Binding resolution tests**: Test all three binding modes (explicit, language-based, resource-auto) including edge cases: missing servers, ambiguous language detection, multiple servers for the same language.
- **Lifecycle tests**: Verify server startup/shutdown, shared instance reference counting, crash recovery and re-synchronization, and sandbox workspace mapping.
- **Context enrichment tests**: Confirm that diagnostic injection respects context budgets, handles empty diagnostics, and correctly annotates files in hot context.
- **Integration tests**: End-to-end tests where an actor with LSP bindings performs code analysis tasks (find errors, navigate definitions, complete code) and produces correct results using LSP tools.
