UAT: Actor compiler ignores NodeDefinition.lsp_binding — per-node LSP bindings silently dropped #4636

Open
opened 2026-04-08 17:40:34 +00:00 by HAL9000 · 0 comments
Owner

Bug Report

Feature Area: Actor graph compilation — LSP binding in actor config
Severity: High — per-node LSP bindings defined in YAML are silently ignored
Found by: UAT worker uat-worker-actor-system-001


What Was Tested

The actor YAML schema (src/cleveragents/actor/schema.py) defines a NodeDefinition.lsp_binding field of type NodeLspBinding for per-node LSP server binding configuration. The compiler (src/cleveragents/actor/compiler.py) is responsible for translating this schema into a CompiledActor.


Expected Behavior (from spec and schema)

When a YAML actor config specifies lsp_binding on a graph node, e.g.:

nodes:
  - id: code_analyzer
    type: agent
    name: Code Analyzer
    description: Analyzes code with LSP support
    config:
      model: gpt-4
      prompt: "Analyze this code"
    lsp_binding:
      server: local/pyright
      languages: [python]
      auto: false

The compiler should read node.lsp_binding (the validated NodeLspBinding object) and include it in CompilationMetadata.lsp_bindings.


Actual Behavior

The _extract_lsp_bindings() function in compiler.py reads from node.config.get("lsp_bindings", []) — the raw unvalidated config dict — instead of node.lsp_binding (the validated NodeLspBinding field):

# compiler.py line 158-172
def _extract_lsp_bindings(node: NodeDefinition) -> list[LspBinding]:
    """Extract LSP bindings from a node config block."""
    bindings: list[LspBinding] = []
    raw_bindings = node.config.get("lsp_bindings", [])  # ← reads from config dict!
    if not isinstance(raw_bindings, list):
        return bindings
    for entry in raw_bindings:
        ...

But the schema defines lsp_binding as a top-level field on NodeDefinition, not inside config:

# schema.py line 445-448
lsp_binding: NodeLspBinding | None = Field(
    default=None, description="Per-node LSP binding"
)

Result: Any lsp_binding specified in YAML is parsed and validated by Pydantic into node.lsp_binding, but the compiler never reads that field. The compiled actor's metadata.lsp_bindings will always be empty for nodes using the schema-defined lsp_binding field.


Steps to Reproduce

from cleveragents.actor.schema import ActorConfigSchema
from cleveragents.actor.compiler import compile_actor
import yaml

config_yaml = """
name: test/lsp-actor
type: graph
description: Test LSP binding
model: gpt-4
route:
  nodes:
    - id: analyzer
      type: agent
      name: Analyzer
      description: Analyzes code
      config:
        model: gpt-4
        prompt: Analyze this
      lsp_binding:
        server: local/pyright
        languages: [python]
  edges: []
  entry_node: analyzer
  exit_nodes: [analyzer]
"""

config = ActorConfigSchema.model_validate(yaml.safe_load(config_yaml))
assert config.route.nodes[0].lsp_binding is not None  # ✓ schema parses it

compiled = compile_actor(config)
# BUG: lsp_bindings is empty despite lsp_binding being set on the node
assert len(compiled.metadata.lsp_bindings) == 0  # ← this passes (bug confirmed)

Code Location

  • File: src/cleveragents/actor/compiler.py
  • Function: _extract_lsp_bindings() (line 158)
  • Bug: Reads node.config.get("lsp_bindings", []) instead of node.lsp_binding
  • Schema field: src/cleveragents/actor/schema.py line 445 — NodeDefinition.lsp_binding

Fix Direction

_extract_lsp_bindings() should be updated to read from node.lsp_binding (the validated NodeLspBinding object) and convert it to a LspBinding instance:

def _extract_lsp_bindings(node: NodeDefinition) -> list[LspBinding]:
    if node.lsp_binding is None:
        return []
    binding = node.lsp_binding
    server = binding.server or ""
    if not server:
        return []
    return [LspBinding(
        node_name=node.id,
        lsp_server_name=server,
        languages=binding.languages,
        auto_detect=binding.auto,
    )]

Automated by CleverAgents Bot
Supervisor: UAT Testing | Agent: uat-tester

## Bug Report **Feature Area:** Actor graph compilation — LSP binding in actor config **Severity:** High — per-node LSP bindings defined in YAML are silently ignored **Found by:** UAT worker uat-worker-actor-system-001 --- ## What Was Tested The actor YAML schema (`src/cleveragents/actor/schema.py`) defines a `NodeDefinition.lsp_binding` field of type `NodeLspBinding` for per-node LSP server binding configuration. The compiler (`src/cleveragents/actor/compiler.py`) is responsible for translating this schema into a `CompiledActor`. --- ## Expected Behavior (from spec and schema) When a YAML actor config specifies `lsp_binding` on a graph node, e.g.: ```yaml nodes: - id: code_analyzer type: agent name: Code Analyzer description: Analyzes code with LSP support config: model: gpt-4 prompt: "Analyze this code" lsp_binding: server: local/pyright languages: [python] auto: false ``` The compiler should read `node.lsp_binding` (the validated `NodeLspBinding` object) and include it in `CompilationMetadata.lsp_bindings`. --- ## Actual Behavior The `_extract_lsp_bindings()` function in `compiler.py` reads from `node.config.get("lsp_bindings", [])` — the **raw unvalidated config dict** — instead of `node.lsp_binding` (the validated `NodeLspBinding` field): ```python # compiler.py line 158-172 def _extract_lsp_bindings(node: NodeDefinition) -> list[LspBinding]: """Extract LSP bindings from a node config block.""" bindings: list[LspBinding] = [] raw_bindings = node.config.get("lsp_bindings", []) # ← reads from config dict! if not isinstance(raw_bindings, list): return bindings for entry in raw_bindings: ... ``` But the schema defines `lsp_binding` as a **top-level field on `NodeDefinition`**, not inside `config`: ```python # schema.py line 445-448 lsp_binding: NodeLspBinding | None = Field( default=None, description="Per-node LSP binding" ) ``` **Result:** Any `lsp_binding` specified in YAML is parsed and validated by Pydantic into `node.lsp_binding`, but the compiler never reads that field. The compiled actor's `metadata.lsp_bindings` will always be empty for nodes using the schema-defined `lsp_binding` field. --- ## Steps to Reproduce ```python from cleveragents.actor.schema import ActorConfigSchema from cleveragents.actor.compiler import compile_actor import yaml config_yaml = """ name: test/lsp-actor type: graph description: Test LSP binding model: gpt-4 route: nodes: - id: analyzer type: agent name: Analyzer description: Analyzes code config: model: gpt-4 prompt: Analyze this lsp_binding: server: local/pyright languages: [python] edges: [] entry_node: analyzer exit_nodes: [analyzer] """ config = ActorConfigSchema.model_validate(yaml.safe_load(config_yaml)) assert config.route.nodes[0].lsp_binding is not None # ✓ schema parses it compiled = compile_actor(config) # BUG: lsp_bindings is empty despite lsp_binding being set on the node assert len(compiled.metadata.lsp_bindings) == 0 # ← this passes (bug confirmed) ``` --- ## Code Location - **File:** `src/cleveragents/actor/compiler.py` - **Function:** `_extract_lsp_bindings()` (line 158) - **Bug:** Reads `node.config.get("lsp_bindings", [])` instead of `node.lsp_binding` - **Schema field:** `src/cleveragents/actor/schema.py` line 445 — `NodeDefinition.lsp_binding` --- ## Fix Direction `_extract_lsp_bindings()` should be updated to read from `node.lsp_binding` (the validated `NodeLspBinding` object) and convert it to a `LspBinding` instance: ```python def _extract_lsp_bindings(node: NodeDefinition) -> list[LspBinding]: if node.lsp_binding is None: return [] binding = node.lsp_binding server = binding.server or "" if not server: return [] return [LspBinding( node_name=node.id, lsp_server_name=server, languages=binding.languages, auto_detect=binding.auto, )] ``` --- **Automated by CleverAgents Bot** Supervisor: UAT Testing | Agent: uat-tester
HAL9000 added this to the v3.5.0 milestone 2026-04-08 17:41:08 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
cleveragents/cleveragents-core#4636
No description provided.