Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions factory/cli/_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,8 @@ def _cmd_plugins(args: argparse.Namespace) -> int:
print(f"\nRegistered commands: {', '.join(sorted(registry.commands))}")
if registry.modes:
print(f"Registered modes: {', '.join(registry.modes)}")
if registry.agent_roles:
print(f"Registered agent roles: {', '.join(registry.agent_roles)}")

return 0

Expand Down
12 changes: 7 additions & 5 deletions factory/cli/_parser_groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@

import argparse

BUILTIN_AGENT_ROLES: frozenset[str] = frozenset({
"researcher", "strategist", "builder",
"health_checker", "code_reviewer", "adversarial_tester",
"archivist", "ceo", "failure_analyst", "refiner",
})


def add_project_setup_parsers(sub: argparse._SubParsersAction) -> None: # type: ignore[type-arg]
Expand Down Expand Up @@ -334,11 +339,8 @@ def add_validation_recovery_parsers(sub: argparse._SubParsersAction) -> None: #

def add_entry_point_parsers(sub: argparse._SubParsersAction) -> None: # type: ignore[type-arg]
p = sub.add_parser("agent", help="Invoke a specialist agent with a task")
p.add_argument("role", choices=["researcher", "strategist", "builder",
"health_checker", "code_reviewer", "adversarial_tester",
"archivist", "ceo",
"failure_analyst", "refiner"],
help="Agent role to invoke")
p.add_argument("role",
help="Agent role to invoke (built-in or plugin-registered)")
p.add_argument("--task", required=True, help="Task description for the agent")
p.add_argument("--project", required=True, help="Path to the project")
p.add_argument("--timeout", type=float, default=600.0,
Expand Down
12 changes: 12 additions & 0 deletions factory/cli/agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,12 +158,24 @@ def cmd_agent(args: argparse.Namespace) -> int:
"""Invoke a specialist agent with the given task."""
from factory.agents.plugin import load_agent_config
from factory.agents.runner import invoke_agent
from factory.cli._parser_groups import BUILTIN_AGENT_ROLES
from factory.plugins import get_registry
from factory.user_config import load_config

profile = getattr(args, "profile", None)
load_config(profile=profile)

role = args.role
plugin_roles = set(get_registry().agent_roles)
valid_roles = BUILTIN_AGENT_ROLES | plugin_roles
if role not in valid_roles:
print(
f"Error: unknown agent role '{role}'. "
f"Valid roles: {', '.join(sorted(valid_roles))}",
file=sys.stderr,
)
return 1

task = args.task
project_path = Path(args.project).resolve()
timeout = getattr(args, "timeout", 600.0)
Expand Down
19 changes: 1 addition & 18 deletions factory/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -491,24 +491,7 @@ class CycleState(BaseModel):

cycle_id: str
started_at: datetime
mode: Literal[
"build",
"create",
"deep-qa",
"deep-research",
"design",
"discover",
"founder",
"improve",
"meta",
"parallel-improve",
"qa",
"refine",
"research",
"review",
"study",
"swebench",
]
mode: str
initial_prompt: str = ""
respawns: int = 0
runner_name: str | None = None
Expand Down
13 changes: 13 additions & 0 deletions factory/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class PluginLoadResult:
class PluginRegistry:
commands: dict[str, CommandSpec] = field(default_factory=dict)
modes: list[str] = field(default_factory=list)
agent_roles: list[str] = field(default_factory=list)
ceo_pre_hooks: list[Callable[..., Any]] = field(default_factory=list)
workflow_search_paths: list[str] = field(default_factory=list)
parser_extensions: dict[str, list[Callable[[argparse.ArgumentParser], None]]] = field(
Expand Down Expand Up @@ -77,6 +78,18 @@ def add_modes(self, modes: list[str]) -> None:
continue
self.modes.append(mode)

def add_agent_roles(self, roles: list[str]) -> None:
from factory.cli._parser_groups import BUILTIN_AGENT_ROLES

for role in roles:
if role in BUILTIN_AGENT_ROLES:
log.warning("plugin_agent_role_collision_builtin", role=role, action="skipped")
continue
if role in self.agent_roles:
log.warning("plugin_agent_role_collision", role=role, action="keeping_first")
continue
self.agent_roles.append(role)

def add_ceo_pre_hook(self, hook: Callable[..., Any]) -> None:
self.ceo_pre_hooks.append(hook)

Expand Down
Loading