-
Notifications
You must be signed in to change notification settings - Fork 5
feat(core): wire StallAction dispatch into ReactAgent and runtime (#401) #425
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,6 +23,7 @@ | |
| from codeframe.core.context import ContextLoader, TaskContext | ||
| from codeframe.core.events import EventType | ||
| from codeframe.core.fix_tracker import EscalationDecision, FixAttemptTracker, FixOutcome | ||
| from codeframe.core.stall_detector import StallAction, StallDetectedError | ||
| from codeframe.core.stall_monitor import StallEvent, StallMonitor | ||
| from codeframe.core.models import AgentPhase, CompletionEvent, ErrorEvent, ProgressEvent | ||
| from codeframe.core.quick_fixes import apply_quick_fix, find_quick_fix | ||
|
|
@@ -110,6 +111,7 @@ def __init__( | |
| max_iterations: int = 30, | ||
| max_verification_retries: int = 5, | ||
| stall_timeout_s: float = 300, | ||
| stall_action: StallAction = StallAction.BLOCKER, | ||
| event_publisher: Optional[EventPublisher] = None, | ||
| dry_run: bool = False, | ||
| verbose: bool = False, | ||
|
|
@@ -123,6 +125,7 @@ def __init__( | |
| self.max_iterations = max_iterations | ||
| self.max_verification_retries = max_verification_retries | ||
| self._stall_timeout_s = stall_timeout_s | ||
| self._stall_action = stall_action | ||
| self.event_publisher = event_publisher | ||
| self.dry_run = dry_run | ||
| self.verbose = verbose | ||
|
|
@@ -205,11 +208,12 @@ def run(self, task_id: str) -> AgentStatus: | |
| try: | ||
| status = self._react_loop(system_prompt) | ||
| if status == AgentStatus.FAILED: | ||
| reason = "stall_detected" if self._stall_triggered.is_set() else "max_iterations_reached" | ||
| self._emit(EventType.AGENT_FAILED, { | ||
| "task_id": task_id, | ||
| "reason": "max_iterations_reached", | ||
| "reason": reason, | ||
| }) | ||
| self._emit_stream_error(task_id, "max_iterations_reached") | ||
| self._emit_stream_error(task_id, reason) | ||
| return status | ||
|
|
||
| if status == AgentStatus.BLOCKED: | ||
|
|
@@ -244,6 +248,9 @@ def run(self, task_id: str) -> AgentStatus: | |
| return AgentStatus.FAILED | ||
| finally: | ||
| self._stall_monitor.stop() | ||
| except StallDetectedError: | ||
| self._stall_monitor.stop() | ||
| raise # Let runtime handle retry | ||
| except Exception: | ||
| logger.exception("ReactAgent.run() failed for task %s", task_id) | ||
| self._emit(EventType.AGENT_FAILED, { | ||
|
|
@@ -283,16 +290,30 @@ def _react_loop(self, system_prompt: str) -> AgentStatus: | |
| # Check for stall before each iteration | ||
| if self._stall_triggered.is_set(): | ||
| stall_ctx = "" | ||
| elapsed_s = 0.0 | ||
| if self._stall_event: | ||
| elapsed_s = self._stall_event.elapsed_s | ||
| stall_ctx = ( | ||
| f"Agent stalled: no tool call for {self._stall_event.elapsed_s:.0f}s " | ||
| f"Agent stalled: no tool call for {elapsed_s:.0f}s " | ||
| f"(timeout: {self._stall_event.stall_timeout_s}s)" | ||
| ) | ||
| self._create_text_blocker( | ||
| stall_ctx or "Agent stalled with no tool activity", | ||
| "stall_detected", | ||
| ) | ||
| return AgentStatus.BLOCKED | ||
|
|
||
| if self._stall_action == StallAction.RETRY: | ||
| raise StallDetectedError( | ||
| elapsed_s=elapsed_s, | ||
| iterations=iterations, | ||
| last_tool=recent_tool_signatures[-1][0] if recent_tool_signatures else "", | ||
| ) | ||
| elif self._stall_action == StallAction.FAIL: | ||
| self._verbose_print(f"[ReactAgent] Stall → FAILED: {stall_ctx}") | ||
| return AgentStatus.FAILED | ||
| else: | ||
| # StallAction.BLOCKER (default) | ||
| self._create_text_blocker( | ||
| stall_ctx or "Agent stalled with no tool activity", | ||
| "stall_detected", | ||
| ) | ||
| return AgentStatus.BLOCKED | ||
|
Comment on lines
+293
to
+316
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Keep stall failures distinct from max-iteration failures. The 🤖 Prompt for AI Agents |
||
|
|
||
| self._verbose_print(f"[ReactAgent] Iteration {iterations + 1}/{self.max_iterations}") | ||
| self._emit(EventType.AGENT_ITERATION_STARTED, { | ||
|
|
@@ -475,6 +496,13 @@ def _run_final_verification( | |
|
|
||
| for attempt in range(1 + self.max_verification_retries): | ||
| if self._stall_triggered.is_set(): | ||
| if self._stall_action == StallAction.RETRY: | ||
| raise StallDetectedError( | ||
| elapsed_s=self._stall_event.elapsed_s if self._stall_event else 0, | ||
| iterations=0, | ||
| ) | ||
| elif self._stall_action == StallAction.FAIL: | ||
| return (False, "stall_failed") | ||
| return (False, "stall_detected") | ||
|
|
||
| self._verbose_print("[ReactAgent] Running final verification...") | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: frankbria/codeframe
Length of output: 293
🏁 Script executed:
Repository: frankbria/codeframe
Length of output: 745
🏁 Script executed:
Repository: frankbria/codeframe
Length of output: 293
🏁 Script executed:
Repository: frankbria/codeframe
Length of output: 95
🏁 Script executed:
Repository: frankbria/codeframe
Length of output: 923
🏁 Script executed:
Repository: frankbria/codeframe
Length of output: 507
🏁 Script executed:
Repository: frankbria/codeframe
Length of output: 2386
🏁 Script executed:
Repository: frankbria/codeframe
Length of output: 8462
🏁 Script executed:
Repository: frankbria/codeframe
Length of output: 3079
🏁 Script executed:
Repository: frankbria/codeframe
Length of output: 6119
🏁 Script executed:
Repository: frankbria/codeframe
Length of output: 333
🏁 Script executed:
Repository: frankbria/codeframe
Length of output: 685
🏁 Script executed:
Repository: frankbria/codeframe
Length of output: 4100
🏁 Script executed:
Repository: frankbria/codeframe
Length of output: 45
🏁 Script executed:
Repository: frankbria/codeframe
Length of output: 258
🏁 Script executed:
Repository: frankbria/codeframe
Length of output: 504
🏁 Script executed:
Repository: frankbria/codeframe
Length of output: 800
Use
StallActionenum type for parse-time validation of--stall-actionoption.The CLI currently accepts arbitrary strings and validates them only during
execute_agent()viaStallAction(stall_action)conversion. Inwork_start(), this creates a timing gap: the run is created at line 2055 before validation occurs, so an invalid--stall-actionvalue leaves behind a dangling run inIN_PROGRESSstate.Suggested change
Apply to both
work_start()andbatch_run().📝 Committable suggestion
🤖 Prompt for AI Agents