Files
cleveragents-core/robot/helper_actor_loading.py
T
aditya e476d2de0e feat(actor): extend hierarchical actor YAML schema and loader
Extend actor YAML schema to support hierarchical graphs with explicit
node types (agent, tool, conditional, subgraph), per-node LSP bindings
(lsp_binding with server, languages, auto, capabilities), and tool-source
references (skills, mcp_servers, agent_skills).

Add schema validation for namespaced actor references, duplicate node IDs,
edge target existence, and graph reachability — all nodes must be reachable
from entry_node via explicit edges or conditional node routing targets.

Update loader to report YAML parse errors with precise line/column positions
and schema validation errors with dotted field paths and remediation hints
pointing to docs/reference/actor_config.md.

Add docs/reference/actor_config.md as the practical configuration reference
covering hierarchical graph examples, node type table, topology rules, and
common error cases with fix guidance.

Refresh examples/actors/graph_workflow.yaml to replace deprecated actor_path
with actor_ref. Add benchmarks/actor_yaml_bench.py for schema load overhead.

Tests: 95 Behave scenarios, 10 Robot smoke tests (including hierarchical
loader smoke test), security scan clean, coverage 99% (threshold 97%).

ISSUES CLOSED: #157
2026-02-25 10:30:42 +00:00

117 lines
3.5 KiB
Python

"""Robot Framework helper for actor loader smoke tests.
Provides a CLI-style interface for Robot to invoke actor discovery
and loading. Exit code 0 = success, 1 = failure.
Usage:
python robot/helper_actor_loading.py discover <directory>
python robot/helper_actor_loading.py discover-examples
python robot/helper_actor_loading.py discover-hierarchical
python robot/helper_actor_loading.py discover-invalid <directory>
python robot/helper_actor_loading.py namespace-default <directory>
"""
from __future__ import annotations
import sys
import tempfile
from pathlib import Path
_SRC = str(Path(__file__).resolve().parents[1] / "src")
if _SRC not in sys.path:
sys.path.insert(0, _SRC)
from cleveragents.actor.loader import ActorLoader # noqa: E402
def main() -> int:
if len(sys.argv) < 2:
print("Usage: helper_actor_loading.py <command> [args...]")
return 1
command = sys.argv[1]
if command == "discover":
directory = sys.argv[2]
return _discover(Path(directory))
if command == "discover-examples":
project_root = Path(__file__).resolve().parents[1]
return _discover(project_root / "examples" / "actors")
if command == "discover-hierarchical":
project_root = Path(__file__).resolve().parents[1]
return _discover_hierarchical(project_root / "examples" / "actors")
if command == "discover-invalid":
directory = sys.argv[2]
return _discover_invalid(Path(directory))
if command == "namespace-default":
return _namespace_default()
print(f"Unknown command: {command}")
return 1
def _discover(directory: Path) -> int:
loader = ActorLoader(search_roots=[directory])
actors = loader.discover()
for actor in actors:
print(f"actor-loaded: {actor.name} type={actor.type.value}")
print(f"actor-count: {len(actors)}")
return 0
def _discover_hierarchical(examples_dir: Path) -> int:
"""Load the hierarchical workflow example via ActorLoader and print key stats."""
loader = ActorLoader(search_roots=[examples_dir])
actors = loader.discover()
target = next((a for a in actors if a.name == "workflows/dev-workflow"), None)
if target is None:
print("hierarchical-actor-not-found")
return 1
print(f"actor-loaded: {target.name}")
print(f"actor-type: {target.type.value}")
route = target.route
if route is not None:
print(f"node-count: {len(route.nodes)}")
print(f"edge-count: {len(route.edges)}")
lsp_configured = bool(getattr(target, "lsp", None))
print(f"lsp-configured: {lsp_configured}")
return 0
def _discover_invalid(directory: Path) -> int:
loader = ActorLoader(search_roots=[directory])
try:
loader.discover()
print("actor-loading-unexpected-success")
return 1
except Exception as exc:
print(f"actor-loading-expected-fail: {exc}")
return 0
def _namespace_default() -> int:
tmp = Path(tempfile.mkdtemp(prefix="actor_ns_"))
yaml_content = (
"name: bare-name\n"
"type: llm\n"
"description: Test namespace defaulting\n"
'version: "1.0"\n'
"model: gpt-4\n"
)
(tmp / "bare.yaml").write_text(yaml_content)
loader = ActorLoader(search_roots=[tmp])
actors = loader.discover()
if len(actors) == 1 and actors[0].name == "local/bare-name":
print("namespace-default-ok: local/bare-name")
return 0
print(f"namespace-default-fail: {[a.name for a in actors]}")
return 1
if __name__ == "__main__":
sys.exit(main())