Skip to content
Open
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
1 change: 1 addition & 0 deletions src/guardloop/ace/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# This file is intentionally left blank.
25 changes: 25 additions & 0 deletions src/guardloop/ace/adapter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""The ACE adapter."""

from typing import List

from guardloop.ace.models import Action, Goal, Plan


class ACEAdapter:
"""The ACE adapter."""

def __init__(self):
"""Initialize the ACE adapter."""
pass

def get_goals(self) -> List[Goal]:
"""Get the ACE's goals."""
return []

def get_plan(self, goal: Goal) -> Plan:
"""Get the ACE's plan for a goal."""
return Plan(goal=goal)

def get_next_action(self, plan: Plan) -> Action:
"""Get the ACE's next action."""
return Action(name="finish")
26 changes: 26 additions & 0 deletions src/guardloop/ace/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""Pydantic models for the ACE lifecycle."""

from typing import List, Optional

from pydantic import BaseModel, Field


class Goal(BaseModel):
"""An ACE's goal."""

description: str = Field(description="The goal's description.")
sub_goals: List["Goal"] = Field(default_factory=list, description="A list of sub-goals.")


class Plan(BaseModel):
"""An ACE's plan to achieve a goal."""

goal: Goal = Field(description="The goal this plan is for.")
steps: List[str] = Field(default_factory=list, description="The steps in the plan.")


class Action(BaseModel):
"""An action an ACE can take."""

name: str = Field(description="The action's name.")
args: Optional[dict] = Field(default=None, description="The action's arguments.")
155 changes: 59 additions & 96 deletions src/guardloop/agents/chain_optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,105 +24,92 @@ class AgentChainOptimizer:
TASK_AGENT_CHAINS = {
# Simple tasks - single agent
"fix_typo": ["standards_oracle"],
"update_docs": ["documentation_codifier"],
"update_docs": ["documentation"],
"format_code": ["standards_oracle"],
# Medium tasks - focused chain
"implement_function": ["cold_blooded_architect", "ruthless_coder", "ruthless_tester"],
"add_tests": ["ruthless_tester"],
"fix_bug": ["support_debug_hunter", "ruthless_tester"],
"refactor": ["cold_blooded_architect", "ruthless_coder", "ruthless_tester"],
"implement_function": ["architect", "coder", "tester"],
"add_tests": ["tester"],
"fix_bug": ["debug_hunter", "tester"],
"refactor": ["architect", "coder", "tester"],
# Complex tasks - extended chain
"implement_feature": [
"business_analyst",
"cold_blooded_architect",
"ruthless_coder",
"ruthless_tester",
"merciless_evaluator",
"architect",
"coder",
"tester",
"evaluator",
],
"implement_auth": [
"cold_blooded_architect",
"secops_engineer",
"ruthless_coder",
"ruthless_tester",
"merciless_evaluator",
"architect",
"secops",
"coder",
"tester",
"evaluator",
],
"database_design": [
"cold_blooded_architect",
"architect",
"dba",
"ruthless_coder",
"ruthless_tester",
"coder",
"tester",
],
# Critical tasks - full chain + compliance
"build_auth_system": [
"business_analyst",
"cold_blooded_architect",
"secops_engineer",
"architect",
"secops",
"dba",
"ruthless_coder",
"ruthless_tester",
"sre_ops",
"coder",
"tester",
"sre",
"standards_oracle",
"merciless_evaluator",
"evaluator",
],
"implement_payment": [
"business_analyst",
"cold_blooded_architect",
"secops_engineer",
"architect",
"secops",
"dba",
"ruthless_coder",
"ruthless_tester",
"coder",
"tester",
"standards_oracle",
"sre_ops",
"merciless_evaluator",
"sre",
"evaluator",
],
"compliance_feature": [
"business_analyst",
"cold_blooded_architect",
"secops_engineer",
"ruthless_coder",
"ruthless_tester",
"architect",
"secops",
"coder",
"tester",
"standards_oracle",
"merciless_evaluator",
"documentation_codifier",
"evaluator",
"documentation",
],
# UI/UX tasks
"implement_ui": [
"ux_ui_designer",
"ruthless_coder",
"ruthless_tester",
"ux_designer",
"coder",
"tester",
],
"improve_accessibility": [
"ux_ui_designer",
"ruthless_coder",
"ruthless_tester",
"ux_designer",
"coder",
"tester",
],
# API tasks
"implement_api": [
"cold_blooded_architect",
"ruthless_coder",
"ruthless_tester",
"architect",
"coder",
"tester",
],
"api_security": [
"cold_blooded_architect",
"secops_engineer",
"ruthless_coder",
"ruthless_tester",
"architect",
"secops",
"coder",
"tester",
],
}

# Agent name normalization mapping (old → new)
AGENT_NAME_MAP = {
"architect": "cold_blooded_architect",
"coder": "ruthless_coder",
"tester": "ruthless_tester",
"debug_hunter": "support_debug_hunter",
"secops": "secops_engineer",
"sre": "sre_ops",
"evaluator": "merciless_evaluator",
"documentation": "documentation_codifier",
"ux_designer": "ux_ui_designer",
}

def __init__(self):
"""Initialize chain optimizer"""
logger.debug("AgentChainOptimizer initialized")
Expand All @@ -145,28 +132,22 @@ def select_chain(
"""
# User explicitly chose an agent
if user_specified_agent:
# Normalize agent name
normalized_agent = self._normalize_agent_name(user_specified_agent)
logger.info(
"Using user-specified agent",
original=user_specified_agent,
normalized=normalized_agent,
agent=user_specified_agent,
)
return [normalized_agent]
return [user_specified_agent]

# Get base chain for task
chain = self.TASK_AGENT_CHAINS.get(
task_type,
["cold_blooded_architect", "ruthless_coder", "ruthless_tester"], # Default medium chain
["architect", "coder", "tester"], # Default medium chain
)

# Strict mode: add compliance agents
if mode == "strict":
chain = self._add_strict_agents(chain, task_type)

# Normalize all agent names
chain = [self._normalize_agent_name(agent) for agent in chain]

# Remove duplicates while preserving order
seen = set()
unique_chain = []
Expand Down Expand Up @@ -198,25 +179,25 @@ def _add_strict_agents(self, chain: List[str], task_type: str) -> List[str]:
strict_chain = chain.copy()

# Always add security check in strict mode
if "secops_engineer" not in strict_chain and "secops" not in strict_chain:
if "secops" not in strict_chain:
# Insert after architect, before coder
insert_pos = next(
(
i
for i, agent in enumerate(strict_chain)
if agent in ["ruthless_coder", "coder", "ruthless_tester", "tester"]
if agent in ["coder", "tester"]
),
len(strict_chain),
)
strict_chain.insert(insert_pos, "secops_engineer")
strict_chain.insert(insert_pos, "secops")

# Always add standards check
if "standards_oracle" not in strict_chain:
strict_chain.append("standards_oracle")

# Always add final evaluation
if "merciless_evaluator" not in strict_chain and "evaluator" not in strict_chain:
strict_chain.append("merciless_evaluator")
if "evaluator" not in strict_chain:
strict_chain.append("evaluator")

logger.debug(
"Strict mode agents added",
Expand All @@ -238,13 +219,13 @@ def get_complexity(self, task_type: str) -> TaskComplexity:
"""
chain_length = len(
self.TASK_AGENT_CHAINS.get(
task_type, ["cold_blooded_architect", "ruthless_coder", "ruthless_tester"]
task_type, ["architect", "coder", "tester"]
)
)

if chain_length <= 2:
complexity = TaskComplexity.SIMPLE
elif chain_length <= 5:
elif chain_length <= 4:
complexity = TaskComplexity.MEDIUM
elif chain_length <= 8:
complexity = TaskComplexity.COMPLEX
Expand All @@ -260,24 +241,6 @@ def get_complexity(self, task_type: str) -> TaskComplexity:

return complexity

def _normalize_agent_name(self, agent_name: str) -> str:
"""Normalize agent name to standard format.

Args:
agent_name: Original agent name

Returns:
Normalized agent name
"""
# Remove hyphens and underscores, convert to lowercase
normalized = agent_name.lower().replace("-", "_")

# Apply mapping if exists
if normalized in self.AGENT_NAME_MAP:
return self.AGENT_NAME_MAP[normalized]

return normalized

def get_task_types(self) -> List[str]:
"""Get all supported task types.

Expand Down Expand Up @@ -306,4 +269,4 @@ def estimate_execution_time(self, task_type: str, mode: str = "standard") -> flo
if mode == "strict":
base_time *= 1.3

return base_time
return base_time
40 changes: 35 additions & 5 deletions src/guardloop/agents/orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import structlog

from guardloop.ace.adapter import ACEAdapter
from guardloop.agents.base import AgentContext, AgentDecision, BaseAgent
from guardloop.agents.chain_optimizer import AgentChainOptimizer
from guardloop.utils.config import Config
Expand All @@ -24,6 +25,7 @@ def __init__(self, config: Config):
self.config = config
self.agents: Dict[str, BaseAgent] = {}
self.chain_optimizer = AgentChainOptimizer()
self.ace_adapter = ACEAdapter()

def register_agent(self, name: str, agent: BaseAgent) -> None:
"""Register an agent with the orchestrator
Expand Down Expand Up @@ -230,13 +232,12 @@ async def orchestrate(
decision = await agent.evaluate(context)
decisions.append(decision)

# Stop if agent blocks
if not decision.approved:
logger.warning(f"Chain stopped by {agent_name}", reason=decision.reason)
# Stop if agent blocks in strict mode
if not decision.approved and context.mode == "strict":
logger.warning(f"Chain stopped by {agent_name} in strict mode", reason=decision.reason)
break

# Update context with findings
context.violations.extend(decision.violations)
# The context is updated by the agents themselves, so no action is needed here.

return decisions

Expand Down Expand Up @@ -302,3 +303,32 @@ def get_stats(self) -> Dict:
"registered_agents": len(self.agents),
"agent_names": list(self.agents.keys()),
}

async def run_ace(self, context: AgentContext) -> AgentDecision:
"""Run the ACE lifecycle.

Args:
context: Agent context

Returns:
Agent decision
"""
goals = self.ace_adapter.get_goals()
if not goals:
return AgentDecision(
agent_name=self.name,
approved=True,
reason="No goals for ACE to accomplish.",
)

plan = self.ace_adapter.get_plan(goals[0])
next_action = self.ace_adapter.get_next_action(plan)

# This is where the guardrails would be applied to the plan and actions.
# For now, we'll just return a decision.
return AgentDecision(
agent_name=self.name,
approved=True,
reason=f"ACE is executing plan for goal: {plan.goal.description}",
suggestions=[f"Next action: {next_action.name}"],
)
Loading
Loading