Skip to content

Commit fee17cc

Browse files
committed
Add cart management, reasoning capture, and ADK turn limiting
- Add remove_from_cart and view_cart MCP tools for error recovery tasks - Add cart pre-population for error_recovery task setup - Add RunConfig(max_llm_calls) to properly limit ADK ReAct loop turns - Capture reasoning from agent JSON response and flow via A2A - Replace keyword-based error identification with LLM-as-judge - Add include_memory_tasks config field mapping - Update tests for simplified prompt (MCP discovers tools dynamically)
1 parent eb8a88e commit fee17cc

12 files changed

Lines changed: 895 additions & 858 deletions

File tree

green_agent/src/a2a_executor.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ def _parse_config(metadata: dict[str, Any]) -> AssessmentConfig:
8383
"categories": "task_types",
8484
"timeout_per_task": "timeout_per_task",
8585
"max_steps_per_task": "max_steps_per_task",
86+
"include_memory_tasks": "include_memory_tasks",
8687
}
8788

8889
mapped_config = {}

green_agent/src/agent.py

Lines changed: 80 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,17 @@
2727
AssessmentConfig,
2828
AssessmentResults,
2929
BudgetConstrainedTask,
30+
ErrorRecoveryTask,
3031
EvaluationResult,
3132
NegativeConstraintTask,
3233
PreferenceMemoryTask,
33-
SessionState,
3434
Task,
3535
TaskType,
3636
TaskUpdate,
3737
)
3838
from .purple_client import PurpleAgentClient, TaskResult, PurpleAgentClientError
39-
from .state_manager import StateManager
4039
from .task_generator import TaskGenerator
40+
from .webshop_mcp.session_state import SessionState as MCPSessionState
4141
from .webshop_wrapper import WebShopWrapper
4242

4343
if TYPE_CHECKING:
@@ -100,7 +100,6 @@ def __init__(
100100
self,
101101
config: Optional[AgentConfig] = None,
102102
task_generator: Optional[TaskGenerator] = None,
103-
state_manager: Optional[StateManager] = None,
104103
webshop: Optional[WebShopWrapper] = None,
105104
evaluator: Optional[Evaluator] = None,
106105
llm_client: Optional[LLMClient] = None,
@@ -112,7 +111,6 @@ def __init__(
112111
Args:
113112
config: Agent configuration.
114113
task_generator: Task generator instance (creates default if None).
115-
state_manager: State manager instance (creates default if None).
116114
webshop: WebShop wrapper instance (creates default if None).
117115
evaluator: Evaluator instance (creates default if None).
118116
llm_client: LLM client for evaluation (creates default if None).
@@ -122,7 +120,6 @@ def __init__(
122120

123121
# Components - lazy initialization
124122
self._task_generator = task_generator
125-
self._state_manager = state_manager
126123
self._webshop = webshop
127124
self._evaluator = evaluator
128125
self._llm_client = llm_client
@@ -138,12 +135,6 @@ def task_generator(self) -> TaskGenerator:
138135
self._task_generator = TaskGenerator()
139136
return self._task_generator
140137

141-
@property
142-
def state_manager(self) -> StateManager:
143-
if self._state_manager is None:
144-
self._state_manager = StateManager()
145-
return self._state_manager
146-
147138
@property
148139
def webshop(self) -> WebShopWrapper:
149140
if self._webshop is None:
@@ -575,41 +566,55 @@ async def _dispatch_task_to_purple(
575566
session_id="",
576567
)
577568

578-
# Create session for tracking
579-
session = self.state_manager.create_session(task.task_id, agent_id)
580-
result.session_id = session.session_id
581-
582-
# Get agent memory for preference tasks
583-
memory: Optional[AgentMemory] = None
584-
if isinstance(task, PreferenceMemoryTask):
585-
memory = self.state_manager.get_agent_memory(agent_id)
586-
587-
try:
588-
# Extract task data for kickoff
589-
goal, budget, constraints = self._extract_task_kickoff_data(task)
590-
591-
# Create MCP session if session manager is available
592-
mcp_session_id: Optional[str] = None
593-
mcp_uri: Optional[str] = None
594-
595-
if self._session_manager is not None:
596-
mcp_session_id = str(uuid.uuid4())
597-
await self._session_manager.create_session(
598-
session_id=mcp_session_id,
599-
goal=goal,
600-
budget=budget,
601-
constraints=constraints,
602-
max_turns=self.config.max_actions_per_task,
603-
)
604-
mcp_uri = self._get_mcp_uri(mcp_session_id)
605-
569+
# Extract task data for kickoff
570+
goal, budget, constraints = self._extract_task_kickoff_data(task)
571+
572+
# Create MCP session
573+
mcp_session_id: Optional[str] = None
574+
mcp_uri: Optional[str] = None
575+
mcp_state: Optional[MCPSessionState] = None
576+
577+
if self._session_manager is not None:
578+
mcp_session_id = str(uuid.uuid4())
579+
result.session_id = mcp_session_id # Use MCP session ID as result session ID
580+
581+
# Extract initial cart for error_recovery tasks
582+
initial_cart = None
583+
if isinstance(task, ErrorRecoveryTask) and task.setup.cart_contents:
584+
initial_cart = [
585+
{
586+
"product_id": item.product_id,
587+
"name": item.product_name,
588+
"price": item.price,
589+
"quantity": item.quantity,
590+
"options": item.attributes,
591+
}
592+
for item in task.setup.cart_contents
593+
]
606594
logger.info(
607-
"Created MCP session for task",
595+
"Pre-populating cart for error_recovery task",
608596
task_id=task.task_id,
609-
mcp_session_id=mcp_session_id,
610-
mcp_uri=mcp_uri,
597+
cart_items=len(initial_cart),
611598
)
612599

600+
await self._session_manager.create_session(
601+
session_id=mcp_session_id,
602+
goal=goal,
603+
budget=budget,
604+
constraints=constraints,
605+
max_turns=self.config.max_actions_per_task,
606+
initial_cart=initial_cart,
607+
)
608+
mcp_uri = self._get_mcp_uri(mcp_session_id)
609+
610+
logger.info(
611+
"Created MCP session for task",
612+
task_id=task.task_id,
613+
mcp_session_id=mcp_session_id,
614+
mcp_uri=mcp_uri,
615+
)
616+
617+
try:
613618
# Send task to purple agent via PurpleAgentClient
614619
async with PurpleAgentClient(shopper_endpoint) as client:
615620
task_result = await client.send_task(
@@ -651,43 +656,31 @@ async def _dispatch_task_to_purple(
651656
error=task_result.error,
652657
)
653658

654-
# Get final result from MCP session if available
659+
# Get final MCP session state for evaluation
660+
mcp_state: Optional[MCPSessionState] = None
655661
if mcp_session_id and self._session_manager:
656-
session_completed = self._session_manager.is_session_completed(mcp_session_id)
657-
logger.info(
658-
"Checking MCP session completion",
659-
mcp_session_id=mcp_session_id,
660-
session_completed=session_completed,
661-
task_id=task.task_id,
662-
)
663-
if session_completed:
664-
mcp_result = self._session_manager.get_final_result(mcp_session_id)
662+
mcp_state = await self._session_manager.get_session(mcp_session_id)
663+
664+
if mcp_state:
665665
logger.info(
666-
"Retrieved MCP final result",
667-
mcp_session_id=mcp_session_id,
668-
has_result=mcp_result is not None,
669-
result_keys=list(mcp_result.keys()) if mcp_result else [],
670-
task_id=task.task_id,
671-
)
672-
if mcp_result:
673-
# Merge MCP result into task execution result
674-
if "turns_used" in mcp_result:
675-
logger.info(
676-
"Setting actions_taken from MCP result",
677-
turns_used=mcp_result["turns_used"],
678-
task_id=task.task_id,
679-
)
680-
result.actions_taken = mcp_result["turns_used"]
681-
if "success" in mcp_result:
682-
result.completed = mcp_result["success"]
683-
if "reward" in mcp_result:
684-
result.total_reward = mcp_result["reward"]
685-
else:
686-
logger.warning(
687-
"MCP session not marked as completed",
666+
"Retrieved MCP state for evaluation",
688667
mcp_session_id=mcp_session_id,
668+
history_len=len(mcp_state.history),
669+
cart_len=len(mcp_state.cart),
670+
turns_used=mcp_state.turn_count,
689671
task_id=task.task_id,
690672
)
673+
# Update result with MCP metrics
674+
result.actions_taken = mcp_state.turn_count
675+
676+
# Attach reasoning from purple agent for evaluation
677+
if task_result.result_data and "reasoning_summary" in task_result.result_data:
678+
mcp_state.reasoning_summary = task_result.result_data["reasoning_summary"]
679+
logger.info(
680+
"Attached reasoning_summary to MCP state",
681+
reasoning_len=len(mcp_state.reasoning_summary),
682+
task_id=task.task_id,
683+
)
691684

692685
# Clean up MCP session
693686
await self._session_manager.cleanup_session(mcp_session_id)
@@ -702,7 +695,7 @@ async def _dispatch_task_to_purple(
702695
result.error = f"Task execution error: {str(e)}"
703696
logger.error("MCP task execution error", task_id=task.task_id, error=str(e))
704697

705-
return self._finalize_task(result, session, task, memory)
698+
return self._finalize_task(result, task, mcp_state)
706699

707700
async def _execute_task(
708701
self,
@@ -726,24 +719,20 @@ async def _execute_task(
726719
def _finalize_task(
727720
self,
728721
result: TaskExecutionResult,
729-
session: SessionState,
730722
task: Task,
731-
memory: Optional[AgentMemory],
723+
mcp_state: Optional[MCPSessionState],
732724
) -> TaskExecutionResult:
733-
"""Finalize task execution and evaluate."""
734-
# Update session with actions_taken from result
735-
# (result.actions_taken was set from MCP final result)
736-
session.set_action_count(result.actions_taken)
737-
738-
# Complete session
739-
session_summary = self.state_manager.complete_session(
740-
session.session_id,
741-
task_type=task.task_type.value,
742-
)
743-
744-
# Evaluate
745-
evaluation = self.evaluator.evaluate(session, task, memory)
746-
result.evaluation = evaluation
725+
"""Finalize task execution and evaluate using MCP state."""
726+
# Evaluate using MCP state directly
727+
if mcp_state:
728+
evaluation = self.evaluator.evaluate(mcp_state, task)
729+
result.evaluation = evaluation
730+
else:
731+
logger.warning(
732+
"No MCP state available for evaluation",
733+
task_id=task.task_id,
734+
)
735+
result.error = result.error or "No MCP state available for evaluation"
747736

748737
return result
749738

0 commit comments

Comments
 (0)