Compare commits

...

5 Commits

Author SHA1 Message Date
freemo 450e118b1a fix(resources): remove unsupported executable resource type
The executable resource type was not defined in the specification
(docs/specification.md lines 10567-10575). The supported resource types
are: git-checkout, git, fs-mount, fs-directory, fs-file,
container-instance, and devcontainer-instance.

Additionally, the agents resource list output included a Location column
that is not part of the spec-defined columns (Name, ID, Type, Phys/Virt,
Children, Projects per line 11051).

Changes:
- Remove executable type registration from _resource_registry_lsp.py
- Remove executable from BUILTIN_TYPE_NAMES in _resource_type_validation.py
- Update resource list table columns to match spec (Name, ID, Type,
  Phys/Virt, Children, Projects) removing Location and Description
- Remove all executable-related test scenarios from
  features/resource_type_lsp.feature
- Update robot/helper_resource_type_lsp.py to reflect 3 LSP types
  (not 4) and add assertions that executable is absent

ISSUES CLOSED: #3077
2026-05-07 10:53:53 +00:00
HAL9000 f2d1f4efe7 docs: fix quickstart plan/apply command signatures
CI / push-validation (push) Successful in 38s
CI / helm (push) Successful in 47s
CI / build (push) Successful in 1m2s
CI / lint (push) Successful in 1m14s
CI / quality (push) Successful in 1m23s
CI / typecheck (push) Successful in 1m47s
CI / security (push) Successful in 1m59s
CI / integration_tests (push) Successful in 4m2s
CI / e2e_tests (push) Successful in 4m35s
CI / unit_tests (push) Failing after 6m19s
CI / coverage (push) Has been skipped
CI / docker (push) Has been skipped
CI / status-check (push) Failing after 3s
CI / benchmark-regression (push) Has been skipped
CI / benchmark-publish (push) Successful in 1h17m32s
CI / benchmark-publish (pull_request) Has been skipped
CI / push-validation (pull_request) Successful in 43s
CI / helm (pull_request) Successful in 47s
CI / lint (pull_request) Successful in 1m21s
CI / quality (pull_request) Successful in 1m32s
CI / build (pull_request) Successful in 1m18s
CI / security (pull_request) Successful in 1m39s
CI / benchmark-regression (pull_request) Failing after 1m23s
CI / typecheck (pull_request) Successful in 2m14s
CI / integration_tests (pull_request) Successful in 4m16s
CI / unit_tests (pull_request) Failing after 4m59s
CI / coverage (pull_request) Has been skipped
CI / docker (pull_request) Has been skipped
CI / e2e_tests (pull_request) Successful in 4m52s
CI / status-check (pull_request) Failing after 3s
Update docs/quickstart.md to use correct CLI command signatures:
- Replace 'cleveragents plan --project' with 'cleveragents plan use <action> --project'
- Replace 'cleveragents apply --project' with 'cleveragents plan list' + 'cleveragents plan apply <plan-id>'

Addresses reviewer non-blocking suggestion to verify CLI commands match
actual signatures before merging.
2026-05-06 04:29:40 +00:00
HAL9000 54fef4768c docs: add quick start guide (PR #9245)
Add docs/quickstart.md with end-to-end quick start guide covering
prerequisites, installation, project creation, resource registration,
plan/apply workflow, and troubleshooting.

Update mkdocs.yml navigation to include Quick Start page and rename
ADR-028 title to Skill/Tool Abstraction Definition.

Update CHANGELOG.md with quick start guide entry.

Closes #9245
2026-05-06 04:29:40 +00:00
HAL9000 0461f8e51f fix(actor): remove type: ignore from subgraph cycle detection steps
CI / benchmark-publish (push) Has started running
CI / benchmark-regression (push) Has been skipped
CI / push-validation (push) Successful in 47s
CI / helm (push) Successful in 58s
CI / build (push) Successful in 59s
CI / lint (push) Successful in 1m28s
CI / typecheck (push) Successful in 1m37s
CI / security (push) Successful in 1m37s
CI / quality (push) Successful in 1m44s
CI / e2e_tests (push) Failing after 4m41s
CI / integration_tests (push) Failing after 7m36s
CI / unit_tests (push) Failing after 9m48s
CI / coverage (push) Has been skipped
CI / docker (push) Has been skipped
CI / status-check (push) Failing after 4s
Remove # type: ignore[import-untyped] suppression comments from
features/steps/actor_subgraph_cycle_detection_steps.py per
CONTRIBUTING.md zero-tolerance policy on inline type suppressions.
Pyright does not check the features/ directory so these suppressions
were unnecessary and violated project policy.
2026-05-06 04:08:25 +00:00
HAL9000 5db663cb63 fix(actor): read actor_ref from NodeDefinition field in _detect_subgraph_cycles
Fix cross-actor subgraph cycle detection in the actor compiler by reading
actor_ref from the top-level NodeDefinition field instead of the config dict.

The _detect_subgraph_cycles(), _map_node(), and compile_actor() functions
were all reading node.config.get("actor_ref", "") instead of node.actor_ref.
Because actor_ref is a typed, validated Pydantic field on NodeDefinition (not
a key inside the untyped config dict), the old code always returned an empty
string, causing cross-actor cycle detection to silently fail and leaving the
system vulnerable to infinite recursion at runtime.

Changes:
- Fix all three call sites in src/cleveragents/actor/compiler.py
- Add Behave regression tests (features/actor_subgraph_cycle_detection.feature)
  with @tdd_issue and @tdd_issue_1431 tags on all scenarios
- Add Robot Framework integration test (robot/actor_compiler.robot)
- Fix existing test helpers to use actor_ref as top-level field
- Add CHANGELOG entry

ISSUES CLOSED: #1431
2026-05-06 04:08:25 +00:00
14 changed files with 427 additions and 97 deletions
+14
View File
@@ -5,6 +5,19 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [Unreleased]
### Fixed
- **Cross-actor subgraph cycle detection reads actor_ref field** (#1431): Fixed
`_detect_subgraph_cycles()`, `_map_node()`, and the `compile_actor()` main loop
in `src/cleveragents/actor/compiler.py` to read `actor_ref` from the top-level
`NodeDefinition.actor_ref` field instead of `node.config.get("actor_ref", "")`.
Because `actor_ref` is a typed, validated Pydantic field (not a key inside the
untyped `config` dict), the old code always returned an empty string, causing
cross-actor cycle detection to silently fail and leaving the system vulnerable to
infinite recursion at runtime. Added Behave regression tests
(`features/actor_subgraph_cycle_detection.feature`) and a Robot Framework
integration test (`robot/actor_compiler.robot`) to prevent regressions.
### Changed
- **`agents session list` now displays full 26-character session ULIDs** (#10970): The Rich table
@@ -69,6 +82,7 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- `agents actor context clear` command to reset actor message history and
state while preserving the underlying context directory via `ContextManager`
(#6370).
- **Quick Start Guide** (PR #9245): Added `docs/quickstart.md` with an end-to-end quick start guide covering prerequisites, installation, project creation, resource registration, plan/apply workflow, and troubleshooting. Updated `mkdocs.yml` navigation to include the Quick Start page.
- **Plan checkpoint management CLI commands** (#8683): Added `agents plan checkpoint-list <plan-id>` and `agents plan checkpoint-delete <checkpoint-id>` commands. Listing output now highlights checkpoint ID, type, created timestamp, reason, phase, and decision linkage with a concise field summary footer across rich/table/json/yaml formats. Deletion supports batch IDs, interactive confirmation (skip with `--yes`), and structured JSON/YAML responses for automation-friendly scripting.
- **Invariant Remove CLI Command** (#8530): Implemented `agents invariant remove <id>` command that soft-deletes an invariant by ID. The command displays a confirmation prompt before removal (bypassable with `--yes`/`-y`), outputs the removed invariant ID on success, and shows a clear error message when the invariant ID does not exist. Supports `--format` flag for JSON and YAML output. Full BDD test coverage and Robot Framework integration tests included.
+45
View File
@@ -0,0 +1,45 @@
# Quick Start Guide
This quick start guide will walk you through creating a new project, registering a resource, running a plan, and applying changes with CleverAgents.
## Prerequisites
- Python 3.11+ and virtualenv
- Git
- A working CleverAgents installation (see development/testing.md for details)
## Install (local development)
```bash
python -m venv .venv
source .venv/bin/activate
pip install -e .[dev]
```
## Create a new project
```bash
# Create a new directory for your project
mkdir my-project && cd my-project
# Initialize a CleverAgents project (example command)
cleveragents init --name my-project
```
## Register a resource
Create a resource file under `resources/` (example YAML) and register it with the CLI or API.
## Plan and apply
```bash
# Create a plan using an action on your project
cleveragents plan use <action-name> --project my-project
# List plans to find the plan ID
cleveragents plan list
# Review the plan, then apply it by plan ID
cleveragents plan apply <plan-id>
```
## Troubleshooting
If you encounter issues running the examples above, consult `docs/development/testing.md` and the project README for local development tips.
@@ -0,0 +1,38 @@
Feature: Cross-actor subgraph cycle detection reads actor_ref field
As a CleverAgents developer
I want the actor compiler to correctly detect cross-actor subgraph cycles
So that mutually-referencing actors raise SubgraphCycleError at compile time
Background:
Given the actor compiler is available
# ────────────────────────────────────────────────────────────
# Bug fix: actor_ref is a top-level NodeDefinition field
# ────────────────────────────────────────────────────────────
@tdd_issue @tdd_issue_1431
Scenario: Mutually-referencing actors raise SubgraphCycleError
Given actor "test/actor-a" has a subgraph node with actor_ref "test/actor-b"
And actor "test/actor-b" has a subgraph node with actor_ref "test/actor-a"
And a registry containing both actors
When I compile actor "test/actor-a" with the registry resolver
Then the compilation should raise SubgraphCycleError
And the cycle error message should mention "cycle"
@tdd_issue @tdd_issue_1431
Scenario: Non-cyclic subgraph reference compiles successfully
Given actor "test/actor-x" has a subgraph node with actor_ref "test/actor-y"
And actor "test/actor-y" has no subgraph nodes
And a registry containing both actors
When I compile actor "test/actor-x" with the registry resolver
Then the actor compilation should succeed
And the actor subgraph_refs should map "sub" to "test/actor-y"
@tdd_issue @tdd_issue_1431
Scenario: Subgraph node actor_ref is reflected in compiled metadata
Given actor "test/actor-p" has a subgraph node with actor_ref "test/actor-q"
And actor "test/actor-q" has no subgraph nodes
And a registry containing both actors
When I compile actor "test/actor-p" with the registry resolver
Then the actor compilation should succeed
And the compiled node "sub" should have subgraph set to "test/actor-q"
+1 -23
View File
@@ -16,18 +16,12 @@ Feature: LSP Resource Types
Examples:
| type_name |
| executable |
| lsp-server |
| lsp-workspace |
| lsp-document |
# ── User-addable flags ────────────────────────────────────────
Scenario: executable is user-addable for lsp_rt
Given the built-in executable YAML file for lsp_rt
When I load the YAML via schema for lsp_rt
Then the lsp_rt schema user_addable should be true
Scenario: lsp-server is user-addable for lsp_rt
Given the built-in lsp-server YAML file for lsp_rt
When I load the YAML via schema for lsp_rt
@@ -51,12 +45,6 @@ Feature: LSP Resource Types
Then the lsp_rt capability read should be true
And the lsp_rt capability write should be true
Scenario: executable has read-only capabilities for lsp_rt
Given the built-in executable YAML file for lsp_rt
When I load the YAML via schema for lsp_rt
Then the lsp_rt capability read should be true
And the lsp_rt capability write should be false
# ── Parent/child hierarchy ────────────────────────────────────
Scenario: lsp-server has lsp-workspace as child type for lsp_rt
@@ -81,12 +69,6 @@ Feature: LSP Resource Types
# ── Auto-discovery ────────────────────────────────────────────
Scenario: executable has auto-discovery from container-exec-env for lsp_rt
Given the built-in executable YAML file for lsp_rt
When I load the YAML via schema for lsp_rt
Then the lsp_rt auto_discovery should be present
And the lsp_rt auto_discovery trigger_types should contain "container-exec-env"
Scenario: lsp-workspace has auto-discovery from lsp-server for lsp_rt
Given the built-in lsp-workspace YAML file for lsp_rt
When I load the YAML via schema for lsp_rt
@@ -97,8 +79,7 @@ Feature: LSP Resource Types
Scenario: BUILTIN_NAMES includes all LSP types for lsp_rt
Given the ResourceTypeSpec BUILTIN_NAMES set for lsp_rt
Then BUILTIN_NAMES should contain "executable" for lsp_rt
And BUILTIN_NAMES should contain "lsp-server" for lsp_rt
Then BUILTIN_NAMES should contain "lsp-server" for lsp_rt
And BUILTIN_NAMES should contain "lsp-workspace" for lsp_rt
And BUILTIN_NAMES should contain "lsp-document" for lsp_rt
@@ -106,9 +87,6 @@ Feature: LSP Resource Types
Scenario: LSP types survive DB roundtrip after bootstrap for lsp_rt
Given a lsp_rt fresh in-memory resource registry with bootstrap
When I query the lsp_rt registry for type "executable"
Then the lsp_rt db type "executable" should exist
And the lsp_rt db type "executable" should have kind "physical"
When I query the lsp_rt registry for type "lsp-server"
Then the lsp_rt db type "lsp-server" should exist
When I query the lsp_rt registry for type "lsp-workspace"
@@ -135,7 +135,8 @@ def _subgraph_node(node_id: str, actor_ref: str = "") -> NodeDefinition:
type=NodeType.SUBGRAPH,
name=node_id.title(),
description=f"Subgraph {node_id}",
config={"actor_ref": actor_ref},
config={},
actor_ref=actor_ref if actor_ref else None,
)
+6 -3
View File
@@ -186,7 +186,8 @@ def step_given_subgraph_ref(context: Context, ref_name: str) -> None:
type=NodeType.SUBGRAPH,
name="Sub",
description="Subgraph",
config={"actor_ref": ref_name},
config={},
actor_ref=ref_name,
),
]
edges = [EdgeDefinition(from_node="main", to_node="sub")]
@@ -241,7 +242,8 @@ def step_given_outer_referencing_inner(
type=NodeType.SUBGRAPH,
name="Sub",
description="Subgraph",
config={"actor_ref": inner_name},
config={},
actor_ref=inner_name,
),
]
edges = [EdgeDefinition(from_node="main", to_node="sub")]
@@ -260,7 +262,8 @@ def step_given_resolver_cycle(context: Context, inner_name: str, back_ref: str)
type=NodeType.SUBGRAPH,
name="Child",
description="Back-ref",
config={"actor_ref": back_ref},
config={},
actor_ref=back_ref,
),
]
inner = _build_graph_config(inner_name, inner_nodes, [], "child", ["child"])
@@ -0,0 +1,201 @@
"""Step definitions for cross-actor subgraph cycle detection tests.
Tests for features/actor_subgraph_cycle_detection.feature — validates that
the actor compiler correctly reads actor_ref from the top-level
NodeDefinition field (not from node.config) when detecting cross-actor
subgraph cycles.
This is the regression test for issue #1431: _detect_subgraph_cycles()
was reading node.config.get("actor_ref", "") instead of node.actor_ref,
causing cycle detection to always return an empty string and never detect
cross-actor cycles.
"""
from __future__ import annotations
from behave import given, then, when
from behave.runner import Context
from cleveragents.actor.compiler import (
SubgraphCycleError,
compile_actor,
)
from cleveragents.actor.schema import (
ActorConfigSchema,
ActorType,
EdgeDefinition,
NodeDefinition,
NodeType,
RouteDefinition,
)
# ────────────────────────────────────────────────────────────
# Helpers
# ────────────────────────────────────────────────────────────
def _make_graph_actor(
name: str,
nodes: list[NodeDefinition],
edges: list[EdgeDefinition],
entry_node: str,
exit_nodes: list[str],
) -> ActorConfigSchema:
"""Build a minimal valid GRAPH actor for testing."""
route = RouteDefinition(
nodes=nodes,
edges=edges,
entry_node=entry_node,
exit_nodes=exit_nodes,
)
return ActorConfigSchema(
name=name,
type=ActorType.GRAPH,
description=f"Test actor {name}",
provider="openai",
model="gpt-4",
route=route,
)
def _make_agent_node(node_id: str) -> NodeDefinition:
"""Build a minimal AGENT node."""
return NodeDefinition(
id=node_id,
type=NodeType.AGENT,
name=node_id.title(),
description=f"Agent {node_id}",
config={"agent": "default"},
)
def _make_subgraph_node(node_id: str, actor_ref: str) -> NodeDefinition:
"""Build a SUBGRAPH node using the top-level actor_ref field.
The actor_ref is set as a first-class field on NodeDefinition, NOT
inside config. This is the correct way to reference a subgraph actor.
"""
return NodeDefinition(
id=node_id,
type=NodeType.SUBGRAPH,
name=node_id.title(),
description=f"Subgraph {node_id}",
config={},
actor_ref=actor_ref,
)
# ────────────────────────────────────────────────────────────
# Given steps
# ────────────────────────────────────────────────────────────
@given("the actor compiler is available")
def step_compiler_available(context: Context) -> None:
"""Ensure the actor compiler module is importable."""
context.actor_registry: dict[str, ActorConfigSchema] = {}
@given('actor "{name}" has a subgraph node with actor_ref "{ref}"')
def step_actor_has_subgraph_ref(context: Context, name: str, ref: str) -> None:
"""Create a GRAPH actor with one agent node and one subgraph node."""
agent = _make_agent_node("start")
sub = _make_subgraph_node("sub", actor_ref=ref)
actor = _make_graph_actor(
name,
nodes=[agent, sub],
edges=[EdgeDefinition(from_node="start", to_node="sub")],
entry_node="start",
exit_nodes=["sub"],
)
context.actor_registry[name] = actor
context.actor_to_compile = name
@given('actor "{name}" has no subgraph nodes')
def step_actor_has_no_subgraph(context: Context, name: str) -> None:
"""Create a GRAPH actor with only an agent node (no subgraph references)."""
agent = _make_agent_node("leaf")
actor = _make_graph_actor(
name,
nodes=[agent],
edges=[],
entry_node="leaf",
exit_nodes=["leaf"],
)
context.actor_registry[name] = actor
@given("a registry containing both actors")
def step_registry_contains_both(context: Context) -> None:
"""Set up the resolver from the accumulated registry."""
registry = context.actor_registry
def resolver(actor_name: str) -> ActorConfigSchema | None:
return registry.get(actor_name)
context.resolver = resolver
# ────────────────────────────────────────────────────────────
# When steps
# ────────────────────────────────────────────────────────────
@when('I compile actor "{name}" with the registry resolver')
def step_compile_actor(context: Context, name: str) -> None:
"""Compile the named actor using the registry resolver."""
context.compile_error = None
context.compiled = None
actor = context.actor_registry[name]
try:
context.compiled = compile_actor(actor, actor_resolver=context.resolver)
except Exception as exc:
context.compile_error = exc
# ────────────────────────────────────────────────────────────
# Then steps
# ────────────────────────────────────────────────────────────
@then("the compilation should raise SubgraphCycleError")
def step_raises_cycle_error(context: Context) -> None:
"""Assert that compilation raised SubgraphCycleError."""
assert context.compile_error is not None, (
"Expected SubgraphCycleError but compilation succeeded"
)
assert isinstance(context.compile_error, SubgraphCycleError), (
f"Expected SubgraphCycleError, got {type(context.compile_error).__name__}: "
f"{context.compile_error}"
)
@then("the actor compilation should succeed")
def step_actor_compilation_succeeds(context: Context) -> None:
"""Assert that compilation succeeded without errors."""
assert context.compile_error is None, (
f"Expected compilation to succeed but got: {context.compile_error}"
)
assert context.compiled is not None
@then('the cycle error message should mention "{text}"')
def step_cycle_error_mentions(context: Context, text: str) -> None:
"""Assert that the cycle error message contains the given text."""
assert context.compile_error is not None
msg = str(context.compile_error)
assert text.lower() in msg.lower(), (
f"Expected '{text}' in error message, got: {msg}"
)
@then('the actor subgraph_refs should map "{node}" to "{ref}"')
def step_actor_subgraph_refs_map(context: Context, node: str, ref: str) -> None:
"""Assert that the compiled metadata maps the node to the expected actor ref."""
assert context.compiled is not None
refs = context.compiled.metadata.subgraph_refs
assert refs.get(node) == ref, (
f"Expected subgraph_refs[{node!r}] == {ref!r}, got {refs}"
)
+2 -1
View File
@@ -61,6 +61,7 @@ nav:
- Reference/Command Input & Sessions: tui/input-and-sessions.md
- Configuration, Key Bindings & Integration: tui/configuration-and-integration.md
- FAQ: faq.md
- Quick Start: quickstart.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- Reference: reference/
@@ -93,7 +94,7 @@ nav:
- ADR-025 Observability & Logging: adr/ADR-025-observability-and-logging.md
- ADR-026 Agent-to-Agent Protocol (A2A): adr/ADR-026-agent-client-protocol.md
- ADR-027 Language Server Protocol (LSP) Integration: adr/ADR-027-language-server-protocol.md
- ADR-028 Agent Skills Standard (AgentSkills.io): adr/ADR-028-agent-skills-standard.md
- ADR-028 Skill/Tool Abstraction Definition: adr/ADR-028-agent-skills-standard.md
- ADR-029 Model Context Protocol (MCP) Adoption: adr/ADR-029-model-context-protocol.md
- ADR-030 Skill Abstraction Definition: adr/ADR-030-skill-abstraction-definition.md
- ADR-031 Actor Abstraction Definition: adr/ADR-031-actor-abstraction-definition.md
+12
View File
@@ -42,3 +42,15 @@ Reject LLM Actor Compilation
Should Be Equal As Integers ${result.rc} 0
Should Contain ${result.stdout} actor-compiler-expected-fail
Should Contain ${result.stdout} GRAPH
Detect Cross-Actor Subgraph Cycle Via actor_ref Field
[Documentation] Verify that mutually-referencing actors raise SubgraphCycleError.
... This is the regression test for issue #1431: _detect_subgraph_cycles()
... was reading node.config.get("actor_ref") instead of node.actor_ref,
... causing cycle detection to silently fail.
[Tags] slow
${result}= Run Process ${PYTHON} ${HELPER} cycle-detect dummy cwd=${WORKSPACE}
Log ${result.stdout}
Log ${result.stderr}
Should Be Equal As Integers ${result.rc} 0
Should Contain ${result.stdout} actor-compiler-cycle-detected
+76
View File
@@ -66,6 +66,82 @@ def main() -> int:
print(f"actor-compiler-fail: {exc}")
return 1
if command == "cycle-detect":
# Test cross-actor cycle detection using actor_ref top-level field.
# Creates two mutually-referencing actors and verifies SubgraphCycleError
# is raised, confirming the fix for issue #1431.
from cleveragents.actor.compiler import SubgraphCycleError
from cleveragents.actor.schema import (
ActorType,
EdgeDefinition,
NodeDefinition,
NodeType,
RouteDefinition,
)
def _make_actor(
name: str, subgraph_ref: str | None = None
) -> ActorConfigSchema:
if subgraph_ref is not None:
nodes = [
NodeDefinition(
id="start",
type=NodeType.AGENT,
name="Start",
description="Start",
config={"agent": "default"},
),
NodeDefinition(
id="sub",
type=NodeType.SUBGRAPH,
name="Sub",
description="Subgraph",
config={},
actor_ref=subgraph_ref,
),
]
edges = [EdgeDefinition(from_node="start", to_node="sub")]
entry, exits = "start", ["sub"]
else:
nodes = [
NodeDefinition(
id="leaf",
type=NodeType.AGENT,
name="Leaf",
description="Leaf",
config={"agent": "default"},
)
]
edges = []
entry, exits = "leaf", ["leaf"]
route = RouteDefinition(
nodes=nodes, edges=edges, entry_node=entry, exit_nodes=exits
)
return ActorConfigSchema(
name=name,
type=ActorType.GRAPH,
description=f"Test actor {name}",
model="gpt-4",
route=route,
)
actor_a = _make_actor("test/actor-a", subgraph_ref="test/actor-b")
actor_b = _make_actor("test/actor-b", subgraph_ref="test/actor-a")
registry = {"test/actor-a": actor_a, "test/actor-b": actor_b}
try:
compile_actor(actor_a, actor_resolver=registry.get)
print(
"actor-compiler-cycle-not-detected: FAIL — expected SubgraphCycleError"
)
return 1
except SubgraphCycleError as exc:
print(f"actor-compiler-cycle-detected: {exc}")
return 0
except Exception as exc:
print(f"actor-compiler-unexpected-error: {exc}")
return 1
print(f"Unknown command: {command}")
return 1
+18 -19
View File
@@ -15,26 +15,30 @@ def _import_lsp_types() -> None:
LSP_RESOURCE_TYPES,
)
assert len(LSP_RESOURCE_TYPES) == 4, f"Expected 4, got {len(LSP_RESOURCE_TYPES)}"
assert len(LSP_RESOURCE_TYPES) == 3, f"Expected 3, got {len(LSP_RESOURCE_TYPES)}"
names = [t["name"] for t in LSP_RESOURCE_TYPES]
for expected in ("executable", "lsp-server", "lsp-workspace", "lsp-document"):
for expected in ("lsp-server", "lsp-workspace", "lsp-document"):
assert expected in names, f"{expected} not in LSP_RESOURCE_TYPES: {names}"
print("import-lsp-types-ok")
def _check_builtin_names() -> None:
"""Verify all 4 LSP types are in BUILTIN_NAMES."""
"""Verify all 3 LSP types are in BUILTIN_NAMES."""
from cleveragents.domain.models.core.resource_type import ResourceTypeSpec
for name in ("executable", "lsp-server", "lsp-workspace", "lsp-document"):
for name in ("lsp-server", "lsp-workspace", "lsp-document"):
assert name in ResourceTypeSpec.BUILTIN_NAMES, f"{name} not in BUILTIN_NAMES"
assert "executable" not in ResourceTypeSpec.BUILTIN_NAMES, (
"executable should not be in BUILTIN_NAMES (not a spec-defined type)"
)
print("check-builtin-names-ok")
def _db_roundtrip() -> None:
"""Bootstrap in-memory DB and verify all 4 LSP types are retrievable."""
"""Bootstrap in-memory DB and verify all 3 LSP types are retrievable."""
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
@@ -48,7 +52,7 @@ def _db_roundtrip() -> None:
factory = sessionmaker(bind=engine, expire_on_commit=False)
svc = ResourceRegistryService(session_factory=factory)
for name in ("executable", "lsp-server", "lsp-workspace", "lsp-document"):
for name in ("lsp-server", "lsp-workspace", "lsp-document"):
spec = svc.show_type(name)
assert spec is not None, f"{name} not found after bootstrap"
assert str(spec.resource_kind) == "physical", (
@@ -85,32 +89,27 @@ def _check_hierarchy() -> None:
assert "lsp-workspace" in types["lsp-document"]["parent_types"], (
"lsp-document missing lsp-workspace parent"
)
# executable has no children
assert types["executable"]["child_types"] == [], (
"executable should have no children"
)
print("check-hierarchy-ok")
def _check_auto_discovery() -> None:
"""Verify executable has auto_discovery with trigger_types."""
"""Verify lsp-workspace has auto_discovery with trigger_types."""
from cleveragents.application.services._resource_registry_lsp import (
LSP_RESOURCE_TYPES,
)
types = {t["name"]: t for t in LSP_RESOURCE_TYPES}
types = {t["name"] for t in LSP_RESOURCE_TYPES}
# executable has auto_discovery
ad = types["executable"].get("auto_discovery")
assert ad is not None, "executable missing auto_discovery"
assert "container-exec-env" in ad.get("trigger_types", []), (
"executable missing container-exec-env trigger"
# executable should not be present
assert "executable" not in types, (
"executable should not be in LSP_RESOURCE_TYPES (not a spec-defined type)"
)
assert ad.get("lazy") is True, "executable auto_discovery should be lazy"
lsp_types = {t["name"]: t for t in LSP_RESOURCE_TYPES}
# lsp-workspace has auto_discovery from lsp-server
ad_ws = types["lsp-workspace"].get("auto_discovery")
ad_ws = lsp_types["lsp-workspace"].get("auto_discovery")
assert ad_ws is not None, "lsp-workspace missing auto_discovery"
assert "lsp-server" in ad_ws.get("trigger_types", []), (
"lsp-workspace missing lsp-server trigger"
+8 -3
View File
@@ -137,7 +137,7 @@ def _map_node(node: NodeDefinition) -> lg_nodes.NodeConfig:
config.get("function") if node.type == NodeType.CONDITIONAL else None
),
tools=config.get("tools", []) if node.type == NodeType.TOOL else [],
subgraph=(config.get("actor_ref") if node.type == NodeType.SUBGRAPH else None),
subgraph=(node.actor_ref if node.type == NodeType.SUBGRAPH else None),
metadata=dict(config),
)
@@ -187,6 +187,11 @@ def _detect_subgraph_cycles(
"""Detect cycles in subgraph references across actors.
Returns list of actor names forming a cycle, or empty list.
The actor reference is read from the top-level actor_ref field on
NodeDefinition, not from node.config. Reading from config
would always return an empty string because actor_ref is stored as a
first-class field on the schema model.
"""
if resolver is None:
return []
@@ -194,7 +199,7 @@ def _detect_subgraph_cycles(
for node in route_nodes:
if node.type != NodeType.SUBGRAPH:
continue
ref_name = node.config.get("actor_ref", "")
ref_name = node.actor_ref or ""
if not ref_name:
continue
if ref_name in visited:
@@ -290,7 +295,7 @@ def compile_actor(
all_lsp_bindings.extend(bindings)
if node_def.type == NodeType.SUBGRAPH:
ref = node_def.config.get("actor_ref", "")
ref = node_def.actor_ref or ""
if ref:
subgraph_refs[node_def.id] = ref
@@ -1,15 +1,14 @@
"""LSP resource type definitions for the Resource Registry.
Contains the built-in LSP-related type entries (``executable``,
``lsp-server``, ``lsp-workspace``, ``lsp-document``) that are appended
to ``BUILTIN_TYPES`` in ``_resource_registry_data``.
Contains the built-in LSP-related type entries (``lsp-server``,
``lsp-workspace``, ``lsp-document``) that are appended to
``BUILTIN_TYPES`` in ``_resource_registry_data``.
These types bridge the resource system with the LSP runtime, allowing
actors to discover available language tooling and the LSP system to know
which resources it operates on.
Spec reference: docs/adr/ADR-039-container-resource-types.md (executable),
docs/adr/ADR-040-lsp-resource-types.md (lsp-server,
Spec reference: docs/adr/ADR-040-lsp-resource-types.md (lsp-server,
lsp-workspace, lsp-document)
.. note::
@@ -28,52 +27,11 @@ __all__ = ["LSP_RESOURCE_TYPES"]
# === LSP Resource Types (#832) ===
#
# These types model language tooling infrastructure in the resource DAG.
# - executable: system binary / interpreter / LSP server binary
# - lsp-server: a configured or running LSP server instance
# - lsp-workspace: workspace root tracked by an LSP server
# - lsp-document: text document tracked by an LSP server
LSP_RESOURCE_TYPES: list[dict[str, Any]] = [
# ── executable ───────────────────────────────────────────────
{
"name": "executable",
"description": (
"System executable (language runtime, compiler, LSP server "
"binary). Discovered from container or host PATH."
),
"resource_kind": "physical",
"sandbox_strategy": "none",
"user_addable": True,
"built_in": True,
"cli_args": [
{
"name": "path",
"type": "string",
"required": True,
"description": "Absolute path to the executable",
},
{
"name": "version",
"type": "string",
"required": False,
"description": "Executable version if detectable",
},
],
"parent_types": ["container-exec-env", "fs-directory"],
"child_types": [],
"handler": "cleveragents.resource.handlers.executable:ExecutableHandler",
"auto_discovery": {
"trigger_types": ["container-exec-env", "fs-directory"],
"scan_paths": [],
"lazy": True,
},
"capabilities": {
"read": True,
"write": False,
"sandbox": False,
"checkpoint": False,
},
},
# ── lsp-server ───────────────────────────────────────────────
{
"name": "lsp-server",
@@ -125,7 +125,6 @@ BUILTIN_TYPE_NAMES: frozenset[str] = frozenset(
"submodule",
"symlink",
# LSP resource types (#832)
"executable",
"lsp-server",
"lsp-workspace",
"lsp-document",