diff --git a/strix/agents/base_agent.py b/strix/agents/base_agent.py index 0ab3902..32a8b3e 100644 --- a/strix/agents/base_agent.py +++ b/strix/agents/base_agent.py @@ -206,6 +206,26 @@ class BaseAgent(metaclass=AgentMeta): async def _wait_for_input(self) -> None: import asyncio + if self.state.has_waiting_timeout(): + self.state.resume_from_waiting() + self.state.add_message("assistant", "Waiting timeout reached. Resuming execution.") + + from strix.cli.tracer import get_global_tracer + + tracer = get_global_tracer() + if tracer: + tracer.update_agent_status(self.state.agent_id, "running") + + try: + from strix.tools.agents_graph.agents_graph_actions import _agent_graph + + if self.state.agent_id in _agent_graph["nodes"]: + _agent_graph["nodes"][self.state.agent_id]["status"] = "running" + except (ImportError, KeyError): + pass + + return + await asyncio.sleep(0.5) async def _enter_waiting_state( diff --git a/strix/agents/state.py b/strix/agents/state.py index acfa0a5..a351dc7 100644 --- a/strix/agents/state.py +++ b/strix/agents/state.py @@ -24,6 +24,7 @@ class AgentState(BaseModel): stop_requested: bool = False waiting_for_input: bool = False llm_failed: bool = False + waiting_start_time: datetime | None = None final_result: dict[str, Any] | None = None messages: list[dict[str, Any]] = Field(default_factory=list) @@ -88,12 +89,13 @@ class AgentState(BaseModel): def enter_waiting_state(self, llm_failed: bool = False) -> None: self.waiting_for_input = True - self.stop_requested = False + self.waiting_start_time = datetime.now(UTC) self.llm_failed = llm_failed self.last_updated = datetime.now(UTC).isoformat() def resume_from_waiting(self, new_task: str | None = None) -> None: self.waiting_for_input = False + self.waiting_start_time = None self.stop_requested = False self.completed = False self.llm_failed = False @@ -104,6 +106,21 @@ class AgentState(BaseModel): def has_reached_max_iterations(self) -> bool: return self.iteration >= self.max_iterations + def has_waiting_timeout(self) -> bool: + if not self.waiting_for_input or not self.waiting_start_time: + return False + + if ( + self.stop_requested + or self.llm_failed + or self.completed + or self.has_reached_max_iterations() + ): + return False + + elapsed = (datetime.now(UTC) - self.waiting_start_time).total_seconds() + return elapsed > 120 + def has_empty_last_messages(self, count: int = 3) -> bool: if len(self.messages) < count: return False diff --git a/strix/tools/agents_graph/agents_graph_actions.py b/strix/tools/agents_graph/agents_graph_actions.py index 3decd89..1bc7b6b 100644 --- a/strix/tools/agents_graph/agents_graph_actions.py +++ b/strix/tools/agents_graph/agents_graph_actions.py @@ -603,5 +603,6 @@ def wait_for_message( "Message from another agent", "Message from user", "Direct communication", + "Waiting timeout reached", ], } diff --git a/strix/tools/agents_graph/agents_graph_actions_schema.xml b/strix/tools/agents_graph/agents_graph_actions_schema.xml index 9b02e9b..e4fce50 100644 --- a/strix/tools/agents_graph/agents_graph_actions_schema.xml +++ b/strix/tools/agents_graph/agents_graph_actions_schema.xml @@ -198,9 +198,12 @@ NOTE: If you are waiting for an agent that is NOT your subagent, you first tell - Another agent sends it a message via send_message_to_agent - A user sends it a direct message through the CLI - Any other form of inter-agent or user communication occurs + - Waiting timeout is reached The agent will automatically resume from where it left off once a message is received. - This is particularly useful for parent agents waiting for subagent results or for coordination points in multi-agent workflows. + This is particularly useful for parent agents waiting for subagent results or for coordination points in multi-agent workflows. + NOTE: If you finished your task, and you do NOT have any child agents running, you should NEVER use this tool, and just call finish tool instead. + Explanation for why the agent is waiting (for logging and monitoring purposes) @@ -215,11 +218,6 @@ NOTE: If you are waiting for an agent that is NOT your subagent, you first tell Waiting for subdomain enumeration and port scanning subagents to complete their tasks and report findings - # Wait for user input on next steps - - Waiting for user decision on whether to proceed with exploitation of discovered SQL injection vulnerability - - # Coordinate with other agents Waiting for vulnerability assessment agent to share discovered attack vectors before proceeding with exploitation phase