Docs: Clarified purpose of LSP server in specification

This commit is contained in:
2026-02-20 18:27:03 -05:00
parent 571d476493
commit a3f4fba659
9 changed files with 2083 additions and 98 deletions
+3 -1
View File
@@ -22,7 +22,7 @@ The strict dependency rule is: **outer layers depend on inner layers, never the
### Layer Definitions
**Presentation Layer** — The outermost layer, responsible for user interaction and external API surfaces. Contains the Typer CLI, Textual TUI, Textual Web interface, IDE plugin (Language Server), and REST API (FastAPI/uvicorn). All presentation components call into the Application Layer through service facades. No business logic resides here.
**Presentation Layer** — The outermost layer, responsible for user interaction and external API surfaces. Contains the Typer CLI, Textual TUI, Textual Web interface, IDE plugin (embedded TUI), and REST API (FastAPI/uvicorn). All presentation components call into the Application Layer through service facades. No business logic resides here.
**Application Layer** — Orchestrates use cases by coordinating domain objects and infrastructure services. Contains:
@@ -44,6 +44,7 @@ The strict dependency rule is: **outer layers depend on inner layers, never the
- **Sandbox**: Git worktree, filesystem copy, transaction rollback, no-op strategies.
- **LLM/AI Runtime**: LangChain, LangGraph `StateGraph`, provider registry, RxPY bridge, MCP SDK.
- **External Integrations**: MCP servers, Agent Skills Standard, REST/gRPC clients.
- **LSP Runtime**: Language Server Registry, `LSPToolAdapter`, language server lifecycle management. LSP servers are attached to actors to provide language intelligence (diagnostics, type info, completions, references) — see ADR-027.
- **File System/OS**: Watchdog, file I/O, Git CLI, subprocess management.
### Hexagonal Architecture (Ports and Adapters)
@@ -109,6 +110,7 @@ None — specification-driven requirement. The four-layer hexagonal architecture
| [ADR-005](ADR-005-technical-stack.md) | Technical Stack | Defines the concrete technologies used in each architectural layer |
| [ADR-019](ADR-019-storage-and-persistence.md) | Storage and Persistence | Implements the Infrastructure Layer's persistence adapters |
| [ADR-022](ADR-022-langchain-langgraph-integration.md) | LangChain/LangGraph Integration | Implements the Infrastructure Layer's LLM runtime adapters |
| [ADR-026](ADR-026-agent-client-protocol.md) | Agent Client Protocol (ACP) | Defines the versioned protocol boundary between the Presentation and Application layers; all client-to-backend communication flows through ACP |
## Acceptance
@@ -29,6 +29,7 @@ Actors are defined in YAML configuration files and managed via `agents actor` CL
- **`context_view_policy`**: ACMS settings — preferred strategies, default breadth/depth, depth gradient, skeleton budget ratio, temporal scope, auto-refresh.
- **`limits`**: Token limits, tool call limits per step, retry policies.
- **`cost_policy`**: Budget constraints and cost tracking.
- **`lsp`**: LSP server binding — explicit server names, language-based resolution, or auto-discovery from project resources. Provides language intelligence (diagnostics, type info, completions, references) to actor nodes.
### Actor as Graph
@@ -127,6 +128,7 @@ Agent behavior is configurable without code changes: prompt templates, tool sets
| [ADR-031](ADR-031-actor-abstraction-definition.md) | Actor Abstraction Definition | Formalizes the canonical definition of what an actor is |
| [ADR-032](ADR-032-jinja2-yaml-template-preprocessing.md) | Jinja2 YAML Template Preprocessing | Defines the two-phase Jinja2 + env var preprocessing pipeline for actor YAML files |
| [ADR-033](ADR-033-decision-recording-protocol.md) | Decision Recording Protocol | Actors call `record_decision` tool during Strategize and Execute to record decisions in the tree |
| [ADR-027](ADR-027-language-server-protocol.md) | Language Server Protocol (LSP) Integration | Actors acquire language intelligence through LSP servers bound via the `lsp` configuration field |
## Acceptance
+1 -1
View File
@@ -94,7 +94,7 @@ The architecture supports additional presentation surfaces without modifying the
- **Textual TUI**: Reactive terminal UI using the same Rich primitives.
- **Textual Web**: TUI served as a web application.
- **IDE Plugin**: Language Server Protocol integration.
- **IDE Plugin**: Embedded TUI integration.
- **REST API**: FastAPI/uvicorn for server mode.
All presentation surfaces consume the same Application Layer service facades.
+1 -1
View File
@@ -127,7 +127,7 @@ The key architectural enabler is the hexagonal pattern (ADR-001):
| [ADR-002](ADR-002-namespace-system.md) | Namespace System | Server mode introduces server-prefixed namespaces for multi-server entity resolution |
| [ADR-005](ADR-005-technical-stack.md) | Technical Stack | FastAPI, uvicorn, and Helm/Kubernetes are the server mode stack choices |
| [ADR-019](ADR-019-storage-and-persistence.md) | Storage and Persistence | Server mode uses PostgreSQL instead of SQLite for multi-user persistence |
| [ADR-026](ADR-026-agent-client-protocol.md) | Agent Client Protocol (ACP) | ACP is the client-server contract implemented by the REST API in server mode |
| [ADR-026](ADR-026-agent-client-protocol.md) | Agent Client Protocol (ACP) | ACP is the client-server contract implemented by the REST API in server mode; its transport duality (in-process facade for local, HTTPS + JSON for server) is the mechanism that keeps both modes behaviorally identical |
## Acceptance
+113 -19
View File
@@ -16,22 +16,31 @@ CleverAgents adopts the **Agent Client Protocol (ACP)** as the versioned contrac
## Design
### Architectural Role
ACP defines the **fundamental boundary between the Presentation and Application layers** in the CleverAgents layered architecture (ADR-001). Every arrow from a Presentation-layer component (CLI, TUI, Web, IDE plugin, REST API) to the Application-layer Service Facade represents an ACP operation. No Presentation-layer module is permitted to bypass ACP and access Domain or Infrastructure services directly.
This boundary role makes ACP the single most architecturally significant protocol in the system — MCP and Agent Skills plug into the Infrastructure and Domain layers respectively, LSP provides actor-attached language intelligence in the Infrastructure layer (see ADR-027), but ACP is the protocol surface through which *every* client operation flows.
### Protocol Scope
ACP covers five operation groups:
| Group | Operations |
|-------|-----------|
| **Session lifecycle** | create, resume, list, show, delete, export, import, tell |
| **Plan lifecycle** | use, execute, apply, cancel, status, tree, explain, correct, diff, artifacts, prompt, rollback |
| **Registries** | list / show / add / update / remove for actors, skills, tools, validations, resources, resource types, projects, actions, automation profiles, invariants |
| **Context operations** | context show, context inspect, context simulate, context set |
| **Event streaming** | plan state changes, tool execution events, validation results, structured log events |
| Group | Operations | Service(s) |
|-------|-----------|------------|
| **Session lifecycle** | create, resume, list, show, delete, export, import, tell | `SessionWorkflow` |
| **Plan lifecycle** | use, execute, apply, cancel, status, tree, explain, correct, diff, artifacts, prompt, rollback | `PlanService`, `PlanLifecycle`, `CorrectionFlow` |
| **Registries** | list / show / add / update / remove for actors, skills, tools, validations, resources, resource types, projects, actions, automation profiles, invariants | `ActorService`, `ToolService`, `SkillService`, `ResourceService`, `ProjectService` |
| **Context operations** | context show, context inspect, context simulate, context set | `ContextService` |
| **Event streaming** | plan state changes, tool execution events, validation results, structured log events | `EventEmitter` (via SSE or in-process subscription) |
Every CLI command maps 1:1 to an ACP operation. When a user runs `agents plan status <ID>`, the CLI constructs an ACP `plan.status` request, sends it through the active transport, and renders the response.
### Request / Response Envelope
Every ACP operation uses a canonical JSON envelope:
**Request:**
```json
{
"acp_version": "1.0",
@@ -42,29 +51,111 @@ Every ACP operation uses a canonical JSON envelope:
}
```
Responses follow a matching envelope with `status`, `data`, and `error` fields. Error objects carry `code`, `message`, and optional `details`.
**Successful response:**
```json
{
"acp_version": "1.0",
"request_id": "<ULID>",
"status": "ok",
"data": { "plan_id": "01HXRCF1...", "phase": "execute", "state": "running" },
"error": null,
"timing": { "duration_ms": 42 }
}
```
### Transport
**Error response:**
```json
{
"acp_version": "1.0",
"request_id": "<ULID>",
"status": "error",
"data": null,
"error": { "code": "PLAN_NOT_FOUND", "message": "No plan with ID 01HXRCF1...", "details": {} }
}
```
- ACP is **transport-agnostic** at the specification level.
- The default server implementation uses **HTTPS + JSON** via the FastAPI REST API.
- Streaming updates use **Server-Sent Events (SSE)** in server mode with structured event payloads.
- Local mode resolves operations to direct Python method calls on the Service Facade, bypassing serialization entirely.
Error objects carry `code` (machine-readable), `message` (human-readable), and optional `details` (structured context).
### Transport Duality
ACP is **transport-agnostic** at the specification level. The system ships with two transport implementations:
**Local Mode — In-Process Calls**
```
CLI ──→ ACPLocalTransport ──→ ServiceFacade.method() ──→ Domain / Infrastructure
```
The `ACPLocalTransport` translates ACP operation names to direct Python method calls on the Service Facade. No serialization, no network, no authentication. The adapter enforces the ACP contract boundary — clients cannot call internal services directly.
**Server Mode — HTTPS + JSON**
```
CLI ──→ ACPHTTPTransport ──→ HTTPS POST ──→ FastAPI Router ──→ ServiceFacade.method()
```
The `ACPHTTPTransport` serializes each operation into the canonical JSON request envelope, sends it to the REST API, and deserializes the JSON response envelope. The FastAPI router maps URL paths to Service Facade methods using the same operation-to-service mapping.
**Architectural enforcement of the duality:**
- The same ACP test suite runs in both modes (contract tests).
- No Presentation-layer module imports from Infrastructure (import-linter CI checks).
- The Service Facade is the sole Application-layer entry point from Presentation.
### Event Streaming
ACP provides real-time event channels for long-running operations:
- **Server mode**: Server-Sent Events (SSE) over a persistent HTTPS connection at `/api/v1/events/stream`. Clients filter by `plan_id` and/or `event_type`.
- **Local mode**: In-process subscription via RxPY observables or callback registration.
**Event types:**
| Event Type | Emitted When |
|------------|-------------|
| `plan.created` | Plan instantiated from action |
| `plan.phase_changed` | Phase transition (Strategize, Execute, Apply) |
| `plan.state_changed` | State change within a phase |
| `plan.decision_made` | Decision recorded in decision tree |
| `tool.invoked` / `tool.completed` | Tool execution start/finish |
| `validation.completed` | Validation finishes (pass/fail) |
| `plan.apply_completed` | Apply phase finishes |
| `plan.error` | Unrecoverable error |
| `session.message` | Orchestrator produces a message |
**Causal ordering guarantee**: Events within a single plan lifecycle are delivered in causal order. A `plan.phase_changed` for Execute always arrives after the corresponding Strategize completion event.
### Error Taxonomy
ACP defines structured error codes for programmatic handling:
| Error Code | HTTP Status | Meaning |
|------------|-------------|---------|
| `AUTH_REQUIRED` | 401 | Missing or expired token |
| `AUTH_FORBIDDEN` | 403 | Insufficient namespace access |
| `NOT_FOUND` | 404 | Entity does not exist |
| `ALREADY_EXISTS` | 409 | Duplicate entity |
| `INVALID_PARAMS` | 422 | Parameter validation failure |
| `INVALID_STATE` | 409 | Operation not valid in current state |
| `BUDGET_EXCEEDED` | 429 | Cost budget exceeded |
| `VERSION_MISMATCH` | 400 | Unsupported ACP version |
| `INTERNAL_ERROR` | 500 | Unexpected server failure |
### Versioning and Compatibility
- Each client advertises `acp_version` during connection.
- Servers must support the current minor version plus one prior minor version.
- Backward-compatible additions (new optional fields, new operations) are permitted within a major version; breaking changes require a major version bump.
- Multiple major versions may be served simultaneously via URL path versioning (`/api/v1/`, `/api/v2/`).
### Authentication and Authorization
- **Server mode**: Token-based authentication (`server.token`) required for all ACP requests. Authorization is enforced per namespace.
- **Server mode**: Token-based authentication (`server.token`) required for all ACP requests. The token maps to the `Authorization: Bearer <token>` header in HTTPS. Authorization is enforced per namespace.
- **Local mode**: Authentication is bypassed but the ACP operation surface remains identical.
### Service Facade Mapping
Each ACP operation group maps to an Application-layer service:
Each ACP operation group maps to an Application-layer service. The complete operation-to-endpoint-to-service routing is documented in the specification's [ACP Integration Architecture](../specification.md#acp-integration-architecture) section.
| ACP Group | Service |
|-----------|---------|
@@ -117,10 +208,13 @@ Each ACP operation group maps to an Application-layer service:
| ADR | Title | Relationship |
|-----|-------|-------------|
| [ADR-001](ADR-001-layered-architecture.md) | Layered Architecture | ACP sits at the Presentation-Application boundary defined by the layered architecture |
| [ADR-020](ADR-020-session-model.md) | Session Model | ACP defines the session lifecycle operations exposed to clients |
| [ADR-023](ADR-023-server-mode.md) | Server Mode | ACP is the client-server contract for remote execution in server mode |
| [ADR-027](ADR-027-language-server-protocol.md) | Language Server Protocol (LSP) Integration | The LSP server communicates with the backend exclusively through ACP |
| [ADR-001](ADR-001-layered-architecture.md) | Layered Architecture | ACP defines the fundamental boundary between the Presentation and Application layers in the four-layer architecture |
| [ADR-006](ADR-006-plan-lifecycle.md) | Plan Lifecycle | ACP's plan operation group exposes the full plan lifecycle (use, execute, apply, cancel, correct, rollback) to all clients |
| [ADR-020](ADR-020-session-model.md) | Session Model | ACP defines the session lifecycle operations (create, tell, export, import) exposed to clients |
| [ADR-021](ADR-021-cli-and-output-rendering.md) | CLI and Output Rendering | Every CLI command maps 1:1 to an ACP operation; the CLI is a thin rendering layer over ACP |
| [ADR-023](ADR-023-server-mode.md) | Server Mode | ACP is the client-server contract implemented by the REST API in server mode; the transport duality (local facade vs HTTPS) is the mechanism that keeps both modes behaviorally identical |
| [ADR-025](ADR-025-observability-and-logging.md) | Observability and Logging | ACP event streaming delivers structured log events and plan state changes to subscribed clients |
| [ADR-027](ADR-027-language-server-protocol.md) | Language Server Protocol (LSP) Integration | LSP servers are Infrastructure-layer components attached to actors; the IDE plugin (Presentation layer) communicates through ACP independently of LSP |
## Acceptance
+353 -53
View File
@@ -1,105 +1,405 @@
# ADR-027: Language Server Protocol (LSP) Integration
**Status:** Accepted
**Date:** 2026-02-17
**Date:** 2026-02-20
**Supersedes:** None
**Author(s):** Jeffrey Phillips Freeman <Jeffrey.Freeman@CleverThis.com>
**Approver(s):** Jeffrey Phillips Freeman <Jeffrey.Freeman@CleverThis.com>
## Context
CleverAgents needs deep IDE integration for plan-aware workflows: validation feedback displayed inline, context exploration on hover, and code actions that trigger plan operations. Building bespoke plugins for each editor (VS Code, JetBrains, Neovim, Emacs, etc.) would be expensive and prone to divergence. The architecture requires a single, standard, editor-agnostic interface that connects to the CleverAgents backend and behaves identically across local and server modes.
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
CleverAgents implements the IDE plugin as a **Language Server Protocol (LSP)** server. The LSP server surfaces plan-aware code actions, diagnostics, and context lookups, and communicates with the backend exclusively through ACP (ADR-026).
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 Server Role
### LSP Registry
The LSP server is a Presentation-layer adapter. It translates LSP requests into ACP calls and maps ACP responses back to LSP messages.
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.
```
Editor ←— LSP JSON-RPC —→ LSP Server ←— ACP —→ Service Facade
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"
```
### Feature Mapping
Key configuration fields:
| LSP Feature | CleverAgents Source | ACP Operation |
|-------------|--------------------|--------------|
| **Diagnostics** | Validation results (required + informational) | `validation.list_results` |
| **Code actions** | Run validation, propose fix, apply plan, cancel plan | `plan.execute`, `plan.apply`, `validation.run` |
| **Hover** | Context snippets, decision provenance, UKO entity summaries | `context.inspect` |
| **Workspace symbols** | Registered resources, active plans, attached validations | `resource.list`, `plan.list` |
| 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`. |
### Workspace and Resource Resolution
### Resource Language Discovery
- LSP workspace roots are matched to registered resource paths in the Resource Registry.
- File URI paths are resolved through the Resource Registry so that sandbox-resident files map correctly to their logical resources.
- If a workspace root does not correspond to a registered resource, plan-aware features degrade gracefully (standard editing still works).
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:
### Diagnostic Severity Mapping
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.
| Validation Mode | LSP Severity |
|----------------|-------------|
| `required` — failed | `Error` |
| `required` — passed | (suppressed) |
| `informational` — failed | `Warning` |
| `informational` — passed | `Hint` |
Language discovery results are cached per resource and invalidated when the resource content changes (detected via file watchers or resource refresh).
### Lifecycle
### Actor LSP Binding
The LSP server is a long-lived process launched by the editor. It:
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:
1. Initializes an ACP client (in-process for local mode, HTTPS for server mode).
2. Subscribes to plan and validation events via ACP streaming.
3. Pushes diagnostics to the editor as events arrive.
4. Handles editor-initiated requests (code actions, hover, completion) synchronously via ACP.
#### 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
- The LSP server must not access storage, database, or domain services directly — all operations go through ACP.
- IDE features must remain strictly editor-agnostic; no editor-specific APIs outside the LSP specification.
- LSP diagnostics must reflect validation results without modifying any resources (read-only at the presentation boundary).
- The LSP server must function in both local mode (in-process ACP) and server mode (remote ACP over HTTPS).
- 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
- One integration serves all LSP-compatible editors (VS Code, JetBrains via LSP bridge, Neovim, Emacs, Helix, etc.).
- IDE features stay exactly aligned with CLI/TUI/Web behavior because they share the same ACP contract.
- Reduced maintenance burden compared to N bespoke editor plugins.
- Diagnostics arrive in real-time via ACP event streaming, keeping editors up to date during long-running plan execution.
- 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
- LSP's fixed feature set may limit richer custom UI experiences that specific editors could offer (e.g., custom webview panels in VS Code).
- ACP round-trips add latency to interactive IDE operations compared to direct in-process access.
- 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
- Future LSP specification changes could require compatibility work in the server.
- High-frequency IDE events (typing, hover) could place excessive load on ACP endpoints if not debounced.
- 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
**Editor-specific plugins** — More tailored UX per editor but requires separate implementations and ongoing maintenance for each editor. Rejected as the primary strategy (may be revisited for value-add features on top of the LSP baseline).
**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.
**Custom IDE protocol** — Maximum flexibility but zero ecosystem adoption, higher learning curve for contributors, and no editor-side library support. 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 ACP. Using LSP for IDE integration would create an unnecessary protocol layer between the IDE and ACP when ACP already provides the needed surface. Rejected — the IDE plugin is an embedded TUI communicating through ACP; 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 request/response message formats and declared capabilities against the LSP specification.
- **ACP integration tests**: Verify that every LSP feature correctly maps to the corresponding ACP operation and produces the expected editor-visible result.
- **Diagnostics tests**: Ensure validation results translate to LSP diagnostics with the correct severity mapping and no resource side effects.
- **Degradation tests**: Verify graceful fallback when workspace roots do not map to registered resources.
- **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.
## Related ADRs
| ADR | Title | Relationship |
|-----|-------|-------------|
| [ADR-001](ADR-001-layered-architecture.md) | Layered Architecture | The LSP server is a Presentation-layer adapter |
| [ADR-013](ADR-013-validation-abstraction.md) | Validation Abstraction | Validation results are the primary source for LSP diagnostics |
| [ADR-026](ADR-026-agent-client-protocol.md) | Agent Client Protocol (ACP) | The LSP server communicates with the backend exclusively through ACP |
| [ADR-001](ADR-001-layered-architecture.md) | Layered Architecture | The LSP Runtime resides in the Infrastructure layer; LSP tools are exposed to actors through the Domain layer's tool abstractions |
| [ADR-008](ADR-008-resource-system.md) | Resource System | Resources provide the workspace roots for LSP servers and the language discovery inputs for auto-binding |
| [ADR-010](ADR-010-actor-and-agent-architecture.md) | Actor and Agent Architecture | Actors declare LSP bindings in their YAML configuration; LSP tools participate as capabilities in actor graph nodes |
| [ADR-011](ADR-011-tool-system.md) | Tool System | LSP capabilities are exposed as tools through the `LSPToolAdapter`, following the same adapter pattern as MCP |
| [ADR-012](ADR-012-skill-system.md) | Skill System | LSP tools complement skill-provided tools in the actor's merged tool surface |
| [ADR-014](ADR-014-context-management-acms.md) | Context Management (ACMS) | LSP context enrichment injects diagnostics and type information into the ACMS hot context |
| [ADR-029](ADR-029-model-context-protocol.md) | Model Context Protocol (MCP) Adoption | MCP and LSP follow analogous adapter patterns; both bridge standard protocol servers into the tool system |
| [ADR-031](ADR-031-actor-abstraction-definition.md) | Actor Abstraction Definition | Actors acquire language intelligence through LSP bindings alongside tool capabilities through skills |
| [ADR-032](ADR-032-jinja2-yaml-template-preprocessing.md) | Jinja2 YAML Template Preprocessing | Jinja2 templates enable dynamic LSP binding resolution in actor YAML files |
## Acceptance
@@ -107,7 +407,7 @@ The LSP server is a long-lived process launched by the editor. It:
| Voter | Comment |
|-------|---------|
| Jeffrey Phillips Freeman <Jeffrey.Freeman@CleverThis.com> | LSP integration gives IDE users real-time access to CleverAgents without leaving their editor |
| Jeffrey Phillips Freeman <Jeffrey.Freeman@CleverThis.com> | 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 |
**Total: 1**
@@ -114,7 +114,9 @@ Actors provide intelligence; tools provide capability. Both participate as first
### Capability Acquisition
Actors acquire capabilities (tools) exclusively through skills:
Actors acquire capabilities through two complementary mechanisms:
**Tool Capabilities (via Skills)**
1. An actor's YAML configuration lists skill references in its `skills:` section.
2. At activation, the runtime resolves each skill to its flattened tool set.
@@ -122,6 +124,14 @@ Actors acquire capabilities (tools) exclusively through skills:
There is no mechanism for an actor to gain tool capabilities outside the skill system.
**Language Intelligence (via LSP)**
1. An actor's YAML configuration declares LSP server bindings in its `lsp:` section — explicit server names, language-based resolution, or auto-discovery from project resources.
2. At activation, the runtime resolves each binding to registered LSP servers from the LSP Registry and starts the required language server processes.
3. Language intelligence (diagnostics, type information, completions, symbol references, definition lookups) is exposed to the actor both as tools (via `LSPToolAdapter`, analogous to `MCPToolAdapter`) and as ACMS context enrichment (auto-injected diagnostics and type annotations in code context windows).
This dual acquisition model — tools through skills, language intelligence through LSP — gives actors the same software development capabilities that IDEs give human developers, without coupling the actor system to any IDE or presentation layer. See ADR-027 for full details.
## Constraints
- Every custom actor must be defined in a YAML configuration file with a namespaced name.
@@ -180,6 +190,7 @@ There is no mechanism for an actor to gain tool capabilities outside the skill s
| [ADR-029](ADR-029-model-context-protocol.md) | Model Context Protocol (MCP) Adoption | MCP tools appear as tool nodes in actor graphs via skill composition |
| [ADR-030](ADR-030-skill-abstraction-definition.md) | Skill Abstraction Definition | Skills are the unit of capability assignment to actors; the skill-actor binding model |
| [ADR-032](ADR-032-jinja2-yaml-template-preprocessing.md) | Jinja2 YAML Template Preprocessing | Defines how actor YAML files are preprocessed with Jinja2 templates and environment variable interpolation before parsing |
| [ADR-027](ADR-027-language-server-protocol.md) | Language Server Protocol (LSP) Integration | Actors acquire language intelligence through LSP servers; the `lsp` config field is the second capability acquisition mechanism alongside skills |
## Acceptance
+1 -1
View File
@@ -116,7 +116,7 @@ These ADRs address external integrations, operational interfaces, and deployment
| [ADR-024](ADR-024-configuration-system.md) | Configuration System | TOML global config with dot-path keys, YAML entity config, resolution chain. |
| [ADR-025](ADR-025-observability-and-logging.md) | Observability and Logging | structlog JSON output with context binding, optional LangSmith tracing. |
| [ADR-026](ADR-026-agent-client-protocol.md) | Agent Client Protocol (ACP) | Versioned client-server contract for sessions, plans, registries, and event streaming. |
| [ADR-027](ADR-027-language-server-protocol.md) | Language Server Protocol (LSP) Integration | IDE plugin as LSP server using ACP for backend operations. |
| [ADR-027](ADR-027-language-server-protocol.md) | Language Server Protocol (LSP) Integration | Actor-attached language intelligence via LSP servers registered in the LSP Registry. |
| [ADR-028](ADR-028-agent-skills-standard.md) | Agent Skills Standard (AgentSkills.io) | SKILL.md-based progressive disclosure skills integrated as tool sources and actor graph nodes. |
| [ADR-029](ADR-029-model-context-protocol.md) | Model Context Protocol (MCP) Adoption | MCP tool discovery, registry integration, skill composition, and actor-graph usage. |
| [ADR-030](ADR-030-skill-abstraction-definition.md) | Skill Abstraction Definition | Canonical definition of a skill as a composable collection of tools from four sources (MCP, Agent Skills, built-in, custom). |
+1597 -21
View File
File diff suppressed because it is too large Load Diff