fix(cli): derive actor name from config file instead of positional argument #1189

Merged
freemo merged 1 commits from fix/actor-add-name-from-config into master 2026-03-30 18:47:40 +00:00
7 changed files with 44 additions and 33 deletions
+2 -1
View File
@@ -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:
+23 -19
View File
@@ -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),
],
-3
View File
@@ -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",
+2 -1
View File
@@ -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}
+2 -1
View File
@@ -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
+1 -1
View File
@@ -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",
+14 -7
View File
@@ -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) <FILE> [--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,