UAT: ActorService._normalize_name() rejects spec-required server:namespace/name format — enforces exactly one / separator #4550

Open
opened 2026-04-08 14:23:59 +00:00 by HAL9000 · 0 comments
Owner

Bug Report

Feature Area: Actor system — actor namespacing
Severity: Medium — spec-required naming format is rejected by the service layer
Discovered by: UAT tester (uat-tester-actor-system)


Summary

The spec defines actors as namespaced using the format [[server:]namespace/]name (e.g., prod:myorg/my-actor). However, ActorService._normalize_name() enforces exactly one / separator, which means any actor name containing a server qualifier (e.g., prod:myorg/my-actor) would be rejected with a ValidationError.


Expected Behavior (from spec)

The spec (docs/specification.md, line 154–155) defines:

Actor: A YAML-configured conversational unit — either a single LLM/agent or a composed LangGraph of actors and tool nodes. Namespaced as [[server:]namespace/]name.

And the Namespace definition (line 173–174):

Namespace: The scoping segment in the name format [[server:]namespace/]name. Defaults to local/ when omitted. local/ is reserved for local-only items. Non-local/ namespaces with server omitted assume the default configured server.

The NamespacedName.parse() method in domain/models/core/plan.py correctly handles the server:namespace/name format:

# "prod:myorg/my-action" -> server="prod", namespace="myorg", name="my-action"
if ":" in name:
    server, name = name.split(":", 1)
if "/" in name:
    namespace, name = name.split("/", 1)

Actual Behavior

Code location: src/cleveragents/application/services/actor_service.py, lines 41–57

def _normalize_name(self, name: str, *, allow_built_in: bool = False) -> str:
    normalized = name.strip()
    if not normalized:
        raise ValidationError("Actor name cannot be empty")
    if "/" not in normalized:
        normalized = f"local/{normalized}"
    if normalized.count("/") != 1:
        raise ValidationError("Actor names must include exactly one '/' separator")
    # ...

The check normalized.count("/") != 1 rejects names like prod:myorg/my-actor because after stripping the server prefix (prod:), the remaining string myorg/my-actor has exactly one / — but the original string prod:myorg/my-actor also has exactly one /. Wait — actually the issue is different:

The check happens BEFORE stripping the server prefix. For prod:myorg/my-actor:

  • "/" not in normalized → False (there IS a /)
  • normalized.count("/") != 1 → False (there is exactly 1 /)

So the server-qualified name prod:myorg/my-actor would actually pass the count check. However, the subsequent check:

prefix, identifier = normalized.split("/", 1)
if prefix != "local" and not allow_built_in:
    raise ValidationError("Custom actors must use the 'local/<id>' naming pattern")

This check rejects prod:myorg/my-actor because prefix = "prod:myorg" which is not "local" and allow_built_in=False for custom actors.

The real problem: Custom actors are restricted to local/<id> naming only. The spec allows any [[server:]namespace/]name format, but the service enforces local/<id> for all custom actors.


Steps to Reproduce

from cleveragents.application.services.actor_service import ActorService
# Attempt to register an actor with a non-local namespace
service.upsert_actor(
    name="myorg/my-actor",  # Non-local namespace
    provider="openai",
    model="gpt-4",
    ...
)
# Raises: ValidationError("Custom actors must use the 'local/<id>' naming pattern")

Impact

  1. Users cannot register actors in custom namespaces (e.g., myorg/my-actor)
  2. The server:namespace/name format from the spec is completely unsupported
  3. Only local/<id> naming works for custom actors, which is more restrictive than the spec

Fix Required

Update _normalize_name() to allow non-local namespaces for custom actors, and add support for the server:namespace/name format:

def _normalize_name(self, name: str, *, allow_built_in: bool = False) -> str:
    normalized = name.strip()
    if not normalized:
        raise ValidationError("Actor name cannot be empty")
    
    # Strip server qualifier if present (e.g., "prod:myorg/my-actor" -> "myorg/my-actor")
    if ":" in normalized:
        _, normalized = normalized.split(":", 1)
    
    if "/" not in normalized:
        normalized = f"local/{normalized}"
    
    if normalized.count("/") != 1:
        raise ValidationError("Actor names must include exactly one 'namespace/name' segment")
    
    prefix, identifier = normalized.split("/", 1)
    if not prefix or not identifier:
        raise ValidationError("Actor names must include both namespace and identifier")
    
    return name.strip()  # Return original (with server qualifier preserved)

Metadata

Commit Message: fix(actor): allow non-local namespaces and server-qualified names in ActorService
Branch Name: fix/actor-service-namespace-validation

Subtasks

  • Update _normalize_name() to allow non-local namespaces for custom actors
  • Add support for server:namespace/name format parsing
  • Update ActorRegistry._ensure_namespaced() to handle server-qualified names
  • Add Behave tests for non-local namespace actor registration
  • Verify all nox stages pass

Definition of Done

  • agents actor add local/my-actor still works (backward compatible)
  • agents actor add myorg/my-actor works (non-local namespace)
  • agents actor add prod:myorg/my-actor works (server-qualified)
  • All existing tests pass

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

## Bug Report **Feature Area:** Actor system — actor namespacing **Severity:** Medium — spec-required naming format is rejected by the service layer **Discovered by:** UAT tester (uat-tester-actor-system) --- ## Summary The spec defines actors as namespaced using the format `[[server:]namespace/]name` (e.g., `prod:myorg/my-actor`). However, `ActorService._normalize_name()` enforces exactly one `/` separator, which means any actor name containing a server qualifier (e.g., `prod:myorg/my-actor`) would be rejected with a `ValidationError`. --- ## Expected Behavior (from spec) The spec (`docs/specification.md`, line 154–155) defines: > **Actor**: A YAML-configured conversational unit — either a single LLM/agent or a composed LangGraph of actors and tool nodes. Namespaced as `[[server:]namespace/]name`. And the Namespace definition (line 173–174): > **Namespace**: The scoping segment in the name format `[[server:]namespace/]name`. Defaults to `local/` when omitted. `local/` is reserved for local-only items. Non-`local/` namespaces with server omitted assume the default configured server. The `NamespacedName.parse()` method in `domain/models/core/plan.py` correctly handles the `server:namespace/name` format: ```python # "prod:myorg/my-action" -> server="prod", namespace="myorg", name="my-action" if ":" in name: server, name = name.split(":", 1) if "/" in name: namespace, name = name.split("/", 1) ``` --- ## Actual Behavior **Code location:** `src/cleveragents/application/services/actor_service.py`, lines 41–57 ```python def _normalize_name(self, name: str, *, allow_built_in: bool = False) -> str: normalized = name.strip() if not normalized: raise ValidationError("Actor name cannot be empty") if "/" not in normalized: normalized = f"local/{normalized}" if normalized.count("/") != 1: raise ValidationError("Actor names must include exactly one '/' separator") # ... ``` The check `normalized.count("/") != 1` rejects names like `prod:myorg/my-actor` because after stripping the server prefix (`prod:`), the remaining string `myorg/my-actor` has exactly one `/` — but the original string `prod:myorg/my-actor` also has exactly one `/`. Wait — actually the issue is different: The check happens BEFORE stripping the server prefix. For `prod:myorg/my-actor`: - `"/" not in normalized` → False (there IS a `/`) - `normalized.count("/") != 1` → False (there is exactly 1 `/`) So the server-qualified name `prod:myorg/my-actor` would actually pass the count check. However, the subsequent check: ```python prefix, identifier = normalized.split("/", 1) if prefix != "local" and not allow_built_in: raise ValidationError("Custom actors must use the 'local/<id>' naming pattern") ``` This check rejects `prod:myorg/my-actor` because `prefix = "prod:myorg"` which is not `"local"` and `allow_built_in=False` for custom actors. **The real problem:** Custom actors are restricted to `local/<id>` naming only. The spec allows any `[[server:]namespace/]name` format, but the service enforces `local/<id>` for all custom actors. --- ## Steps to Reproduce ```python from cleveragents.application.services.actor_service import ActorService # Attempt to register an actor with a non-local namespace service.upsert_actor( name="myorg/my-actor", # Non-local namespace provider="openai", model="gpt-4", ... ) # Raises: ValidationError("Custom actors must use the 'local/<id>' naming pattern") ``` --- ## Impact 1. Users cannot register actors in custom namespaces (e.g., `myorg/my-actor`) 2. The `server:namespace/name` format from the spec is completely unsupported 3. Only `local/<id>` naming works for custom actors, which is more restrictive than the spec --- ## Fix Required Update `_normalize_name()` to allow non-`local` namespaces for custom actors, and add support for the `server:namespace/name` format: ```python def _normalize_name(self, name: str, *, allow_built_in: bool = False) -> str: normalized = name.strip() if not normalized: raise ValidationError("Actor name cannot be empty") # Strip server qualifier if present (e.g., "prod:myorg/my-actor" -> "myorg/my-actor") if ":" in normalized: _, normalized = normalized.split(":", 1) if "/" not in normalized: normalized = f"local/{normalized}" if normalized.count("/") != 1: raise ValidationError("Actor names must include exactly one 'namespace/name' segment") prefix, identifier = normalized.split("/", 1) if not prefix or not identifier: raise ValidationError("Actor names must include both namespace and identifier") return name.strip() # Return original (with server qualifier preserved) ``` --- ## Metadata **Commit Message:** `fix(actor): allow non-local namespaces and server-qualified names in ActorService` **Branch Name:** `fix/actor-service-namespace-validation` ### Subtasks - [ ] Update `_normalize_name()` to allow non-`local` namespaces for custom actors - [ ] Add support for `server:namespace/name` format parsing - [ ] Update `ActorRegistry._ensure_namespaced()` to handle server-qualified names - [ ] Add Behave tests for non-local namespace actor registration - [ ] Verify all nox stages pass ### Definition of Done - [ ] `agents actor add local/my-actor` still works (backward compatible) - [ ] `agents actor add myorg/my-actor` works (non-local namespace) - [ ] `agents actor add prod:myorg/my-actor` works (server-qualified) - [ ] All existing tests pass --- **Automated by CleverAgents Bot** Supervisor: UAT Testing | Agent: uat-tester
HAL9000 added this to the v3.5.0 milestone 2026-04-08 17:41:42 +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#4550
No description provided.