diff --git a/benchmarks/actor_cli_bench.py b/benchmarks/actor_cli_bench.py index 0e30e5d4c..fc55b0134 100644 --- a/benchmarks/actor_cli_bench.py +++ b/benchmarks/actor_cli_bench.py @@ -34,6 +34,7 @@ from cleveragents.cli.commands.actor import app as actor_app # noqa: E402 from cleveragents.domain.models.core.actor import Actor # noqa: E402 _VALID_CONFIG = { + "name": "local/bench", "provider": "openai", "model": "gpt-4", "temperature": 0.5, @@ -84,7 +85,7 @@ class ActorCLIAddSuite: def time_add_from_config(self) -> None: """Benchmark add --config end-to-end.""" - _runner.invoke(actor_app, ["add", "local/bench", "--config", self._path]) + _runner.invoke(actor_app, ["add", "--config", self._path]) class ActorCLIListSuite: diff --git a/features/steps/actor_cli_steps.py b/features/steps/actor_cli_steps.py index a6d53301c..ec098370f 100644 --- a/features/steps/actor_cli_steps.py +++ b/features/steps/actor_cli_steps.py @@ -59,6 +59,7 @@ def step_impl(context): @given("I have an actor JSON config file") def step_impl(context): context.actor_config_data = { + "name": "local/test-actor", "provider": "openai", "model": "gpt-4o-mini", "temperature": 0.5, @@ -76,6 +77,7 @@ def step_impl(context): @given("I have an actor JSON config file with options") def step_impl(context): context.actor_config_data = { + "name": "local/test-actor", "provider": "openai", "model": "gpt-4o", "options": {"temperature": 0.4, "enabled": True}, @@ -104,11 +106,15 @@ def step_impl(context): @given("I have an actor YAML config file returning empty data") def step_impl(context): context.actor_config_data = { + "name": "local/existing-actor", "provider": "existing-provider", "model": "existing-model", } with tempfile.NamedTemporaryFile(delete=False, suffix=".yaml") as handle: - handle.write(b"provider: existing-provider\nmodel: existing-model\n") + handle.write( + b"name: local/existing-actor\n" + b"provider: existing-provider\nmodel: existing-model\n" + ) handle.flush() context.actor_config_path = Path(handle.name) _register_cleanup(context, context.actor_config_path) @@ -117,6 +123,7 @@ def step_impl(context): @given("I have an actor YAML-only config file") def step_impl(context): context.actor_config_data = { + "name": "local/test-actor", "provider": "openai", "model": "gpt-4o-mini", "yaml_only": True, @@ -124,7 +131,10 @@ def step_impl(context): with tempfile.NamedTemporaryFile( delete=False, suffix=".yaml", mode="w", encoding="utf-8" ) as handle: - handle.write("provider: openai\nmodel: gpt-4o-mini\nyaml_only: true\n") + handle.write( + "name: local/test-actor\nprovider: openai\n" + "model: gpt-4o-mini\nyaml_only: true\n" + ) handle.flush() context.actor_config_path = Path(handle.name) _register_cleanup(context, context.actor_config_path) @@ -133,6 +143,7 @@ def step_impl(context): @given("I have an actor graph config file") def step_impl(context): context.actor_config_data = { + "name": "service/graph-actor", "provider": "graph-provider", "model": "graph-model", "graph": {"nodes": ["start"], "edges": []}, @@ -149,7 +160,7 @@ def step_impl(context): @given("I have an empty actor YAML config file") def step_impl(context): context.actor_config_data = {} - context.expected_error = "provider is required" + context.expected_error = "Config file must contain a 'name' field" with tempfile.NamedTemporaryFile( delete=False, suffix=".yaml", mode="w", encoding="utf-8" ) as handle: @@ -162,6 +173,7 @@ def step_impl(context): @given("I have an unsafe actor JSON config file") def step_impl(context): context.actor_config_data = { + "name": "local/test-actor", "provider": "openai", "model": "gpt-4o-mini", "unsafe": True, @@ -181,7 +193,7 @@ def step_impl(context): mock_actor_service = MagicMock() mock_actor_registry = MagicMock() mock_get_services.return_value = (mock_actor_service, mock_actor_registry) - context.result = context.runner.invoke(actor_app, ["add", "test-actor"]) + context.result = context.runner.invoke(actor_app, ["add"]) context.mock_actor_registry = mock_actor_registry context.expected_error = "Config file is required" @@ -216,7 +228,6 @@ def step_impl(context): actor_app, [ "add", - "test-actor", "--config", str(context.actor_config_path), ], @@ -232,8 +243,8 @@ def step_impl(context): context.expected_allow_unsafe = False if not isinstance(context.actor_config_data, dict): context.expected_error = "Config must be a JSON/YAML object" - elif context.actor_config_data == {}: - context.expected_error = "provider is required" + elif not context.actor_config_data.get("name"): + context.expected_error = "Config file must contain a 'name' field" elif isinstance( context.actor_config_data, dict ) and context.actor_config_data.get("unsafe"): @@ -273,7 +284,6 @@ def step_impl(context): actor_app, [ "add", - "test-actor", "--config", str(context.actor_config_path), *option_args, @@ -315,7 +325,6 @@ def step_impl(context): actor_app, [ "add", - "test-actor", "--config", str(context.actor_config_path), *option_args, @@ -357,7 +366,6 @@ def step_impl(context): actor_app, [ "add", - "test-actor", "--config", str(context.actor_config_path), *option_args, @@ -381,7 +389,6 @@ def step_impl(context): actor_app, [ "add", - "test-actor", "--config", str(context.actor_config_path), "--option", @@ -399,7 +406,6 @@ def step_impl(context): actor_app, [ "add", - "test-actor", "--config", str(context.actor_config_path), "--option", @@ -439,7 +445,6 @@ def step_impl(context): actor_app, [ "add", - "test-actor", "--config", str(context.actor_config_path), "--unsafe", @@ -480,7 +485,6 @@ def step_impl(context): actor_app, [ "add", - "service/graph-actor", "--config", str(context.actor_config_path), ], @@ -499,7 +503,6 @@ def step_impl(context): actor_app, [ "add", - "test-actor", "--config", str(missing_path), ], @@ -509,7 +512,11 @@ def step_impl(context): @when("I run actor add with business rule violation") def step_impl(context): - config_data = {"provider": "openai", "model": "gpt-4o"} + config_data = { + "name": "local/test-actor", + "provider": "openai", + "model": "gpt-4o", + } with tempfile.NamedTemporaryFile( delete=False, suffix=".json", mode="w", encoding="utf-8" ) as handle: @@ -528,7 +535,6 @@ def step_impl(context): actor_app, [ "add", - "test-actor", "--config", str(config_path), ], @@ -546,7 +552,6 @@ def step_impl(context): actor_app, [ "add", - "service/unsafe", "--config", str(context.actor_config_path), ], @@ -585,7 +590,6 @@ def step_impl(context): actor_app, [ "add", - "service/unsafe", "--config", str(context.actor_config_path), ], diff --git a/features/steps/actor_cli_yaml_steps.py b/features/steps/actor_cli_yaml_steps.py index 8daa9eb6c..0463bd13a 100644 --- a/features/steps/actor_cli_yaml_steps.py +++ b/features/steps/actor_cli_yaml_steps.py @@ -209,7 +209,6 @@ def step_add_update(context: Any) -> None: actor_app, [ "add", - "test-actor", "--config", str(context.actor_config_path), "--update", @@ -246,7 +245,6 @@ def step_add_format_json(context: Any) -> None: actor_app, [ "add", - "test-actor", "--config", str(context.actor_config_path), "--format", @@ -268,7 +266,6 @@ def step_add_format_yaml(context: Any) -> None: actor_app, [ "add", - "test-actor", "--config", str(context.actor_config_path), "--format", diff --git a/robot/e2e/m2_acceptance.robot b/robot/e2e/m2_acceptance.robot index 6d3f0561b..104521bca 100644 --- a/robot/e2e/m2_acceptance.robot +++ b/robot/e2e/m2_acceptance.robot @@ -48,6 +48,7 @@ M2 Full Actor Compiler And LLM Integration Create File ${actor_yaml_path} ${actor_yaml} ${actor_config}= Catenate SEPARATOR=\n ... { + ... "name": "${ACTOR_NAME}", ... "provider": "openai", ... "model": "gpt-4", ... "options": {"temperature": 0.2} @@ -55,7 +56,7 @@ M2 Full Actor Compiler And LLM Integration ${config_path}= Set Variable ${SUITE_HOME}${/}actor_config.json Create File ${config_path} ${actor_config} ${r_actor}= Run CleverAgents Command - ... actor add ${ACTOR_NAME} --config ${config_path} --format plain + ... actor add --config ${config_path} --format plain Should Not Contain ${r_actor.stdout}${r_actor.stderr} Traceback Log Actor registration: ${r_actor.stdout} diff --git a/robot/e2e/wf14_server_mode.robot b/robot/e2e/wf14_server_mode.robot index 1204bc4a3..4e51e4158 100644 --- a/robot/e2e/wf14_server_mode.robot +++ b/robot/e2e/wf14_server_mode.robot @@ -104,12 +104,13 @@ WF14 E2E Actor Add For Namespace ... the current ActorService validation. ${actor_name}= Set Variable local/team-rev-${RUN_SUFFIX} ${yaml_content}= Catenate SEPARATOR=\n + ... name: ${actor_name} ... provider: openai ... model: gpt-4 ... description: Team code reviewer actor for collaborative reviews ${yaml_file}= Set Variable ${SUITE_HOME}${/}team-reviewer-actor.yaml Create File ${yaml_file} ${yaml_content} - ${add_result}= Run CleverAgents Command actor add ${actor_name} --config ${yaml_file} + ${add_result}= Run CleverAgents Command actor add --config ${yaml_file} Output Should Contain ${add_result} team-rev # Use --format plain to avoid table truncation ${list_result}= Run CleverAgents Command actor list --format plain diff --git a/robot/helper_m2_e2e_verification.py b/robot/helper_m2_e2e_verification.py index 7abb810ef..19e8fcfd2 100644 --- a/robot/helper_m2_e2e_verification.py +++ b/robot/helper_m2_e2e_verification.py @@ -167,6 +167,7 @@ def actor_add_config_cli() -> None: with open(config_path, "w") as f: json.dump( { + "name": "local/m2-cli-actor", "provider": "openai", "model": "gpt-4", "options": {"temperature": 0.5}, @@ -177,7 +178,6 @@ def actor_add_config_cli() -> None: result = run_cli( "actor", "add", - "local/m2-cli-actor", "--config", config_path, "--format", diff --git a/src/cleveragents/cli/commands/actor.py b/src/cleveragents/cli/commands/actor.py index 7ae0582e0..1268ed747 100644 --- a/src/cleveragents/cli/commands/actor.py +++ b/src/cleveragents/cli/commands/actor.py @@ -367,7 +367,6 @@ def _print_role_warnings(config_blob: dict[str, Any]) -> None: @app.command() def add( - name: Annotated[str, typer.Argument(help="Name for the actor")], config: Annotated[ Path | None, typer.Option("--config", "-c", help="Path to JSON/YAML actor config"), @@ -397,14 +396,16 @@ def add( ) -> None: """Add a new actor configuration. - Actors are defined via YAML configuration files (``--config``). - The actor name follows the ``[[server:]namespace/]name`` format; - names without a namespace default to ``local/``. + The actor is fully defined by the YAML configuration file specified + with ``--config``, including the ``name`` field which determines the + actor's registered name. + + Signature: ``agents actor add (--config|-c) [--update]`` Examples: - agents actor add local/my-actor --config ./actors/my-actor.yaml - agents actor add local/my-actor --config ./actors/my-actor.yaml --update - agents actor add local/my-actor --config actor.yaml --format json + agents actor add --config ./actors/my-actor.yaml + agents actor add --config ./actors/my-actor.yaml --update + agents actor add --config actor.yaml --format json """ service, registry = _get_services() config_blob = _load_config(config) @@ -415,6 +416,12 @@ def add( if config_blob is None: raise typer.BadParameter("Config file is required for actor add") + name = config_blob.get("name") + if not name or not isinstance(name, str): + raise typer.BadParameter( + "Config file must contain a 'name' field for the actor name" + ) + resolved, canonical_blob, requires_confirmation = _canonicalize_actor_config( name=name, config_blob=config_blob,