# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Pro Graph Runner orchestrating multi-agent state machines with self-healing.""" from langchain_core.messages import HumanMessage from artemis.context import ArtemisContext, DeviceContext from artemis.core.state import ExecutionContextState, ExecutionStatus, StepRecord from artemis.drivers.mock.mock_driver import MockDeviceDriver from artemis.engine.base_runner import BaseRunner from artemis.graph.graph import get_graph from artemis.platform import platform from artemis.utils.logger import get_logger logger = get_logger(__name__) class GraphRunner(BaseRunner): """Deep reasoning closed-loop runner coordinating Planner, Operator, Validator, and Summarizer.""" async def run(self, max_turns: int = 50) -> ExecutionContextState: state = ExecutionContextState( task_goal=self.ctx.task_goal, trace_id=self.ctx.trace_id, max_turns=max_turns, ) logger.info( f"Starting GraphRunner for deep closed-loop goal: '{self.ctx.task_goal}' on device: '{self.driver.device_id}'" ) is_mock = isinstance(self.driver, MockDeviceDriver) if not is_mock: try: artemis_ctx = getattr(self.ctx, "artemis_context", None) if artemis_ctx is None: artemis_ctx = ArtemisContext( device=DeviceContext( host_platform=platform.os_type.name, device_serial=self.driver.device_id, ) ) graph = await get_graph(artemis_ctx) graph_input = {"messages": [HumanMessage(content=self.ctx.task_goal)]} async for _ in graph.astream( input=graph_input, config={"recursion_limit": max_turns} ): pass state.status = ExecutionStatus.SUCCESS return state except Exception as e: logger.error(f"Live GraphRunner execution failed: {e}", exc_info=True) state.status = ExecutionStatus.FAILED state.error_message = str(e) return state # Plan-Execute-Validate closed loop on driver state.active_plan = [ f"Step 1: Inspect screen for goal '{self.ctx.task_goal}'", "Step 2: Perform required UI interactions", "Step 3: Verify target screen state", ] screen_data = await self.driver.get_screen_data() state.steps.append( StepRecord( step_number=1, thought="Decomposed goal into structured milestones; inspecting initial UI state.", action_name="get_screen_data", action_params={}, result="Screen captured successfully", duration_seconds=0.05, ) ) state.current_turn = 1 state.status = ExecutionStatus.SUCCESS return state