Skip to content

feat(core): wire StallAction dispatch into ReactAgent and runtime (#401) - #425

Merged
frankbria merged 3 commits into
mainfrom
feat/401-stall-action-integration
Mar 9, 2026
Merged

feat(core): wire StallAction dispatch into ReactAgent and runtime (#401)#425
frankbria merged 3 commits into
mainfrom
feat/401-stall-action-integration

Conversation

@frankbria

@frankbria frankbria commented Mar 9, 2026

Copy link
Copy Markdown
Owner

Summary

Wires the StallAction enum into the recovery flow so stall detection can trigger configurable recovery actions instead of always creating a blocker.

  • StallDetectedError exception class for the RETRY recovery path
  • stall_action parameter threaded through CLI → runtime → ReactAgent → conductor
  • --stall-action CLI flag on work start and work batch run (default: blocker)
  • Configurable dispatch in react loop: RETRY raises exception (runtime retries once), FAIL transitions directly to FAILED, BLOCKER creates blocker (existing behavior)
  • Retry logic in execute_agent() catches StallDetectedError and retries up to 1 time

Data flow

CLI (--stall-action retry) → execute_agent(stall_action="retry")
  → ReactAgent(stall_action=StallAction.RETRY)
    → stall detected → raise StallDetectedError
  → execute_agent catches → retry agent once

Closes #401

Test plan

  • 24 unit tests covering all new integration points (up from 14)
  • All 22 existing stall_monitor tests pass
  • All 1428 core tests pass
  • Ruff lint clean
  • Backwards compatible: default behavior unchanged (BLOCKER)

Summary by CodeRabbit

  • New Features

    • Configurable stall recovery with modes: blocker (default), retry (one retry), and fail; applies to agents and batch runs
    • New stall-detected exception providing elapsed/iteration/last-tool context
  • User-facing

    • CLI and runtime accept a stall-action option to control stall behavior
    • UI/events updated to surface configurable stall handling
  • Tests

    • Added tests for stall actions, exception details, and defaults

- Add StallDetectedError exception for RETRY stall recovery path
- Add stall_action parameter to ReactAgent (default: BLOCKER)
- Wire configurable StallAction dispatch in react loop stall check:
  RETRY raises StallDetectedError, FAIL returns FAILED, BLOCKER creates blocker
- Catch StallDetectedError in execute_agent() with 1 retry attempt
- Add --stall-action CLI flag to work start and work batch run
- Thread stall_action through conductor and subprocess calls
- 24 tests covering all new integration points
@coderabbitai

coderabbitai Bot commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b6312215-11cd-41c3-968e-d0edc8c1148c

📥 Commits

Reviewing files that changed from the base of the PR and between 5b47653 and c8883ee.

📒 Files selected for processing (1)
  • codeframe/core/react_agent.py

Walkthrough

Adds a StallDetectedError exception and a configurable stall_action (blocker/retry/fail) threaded from CLI through runtime, ReactAgent, and conductor/batch subprocess invocations, with runtime retry handling for RETRY and subprocess flag propagation for batch tasks.

Changes

Cohort / File(s) Summary
CLI Argument Propagation
codeframe/cli/app.py
Added --stall-action option to work_start and batch_run, forwarding the value to runtime/conductor entry points.
Public API Export
codeframe/core/__init__.py
Export list updated to include StallDetectedError alongside StallAction and StallDetector.
Stall Detection Exception
codeframe/core/stall_detector.py
New StallDetectedError(elapsed_s, iterations, last_tool="") exception to carry stall context.
Agent Stall Handling
codeframe/core/react_agent.py
ReactAgent gains stall_action parameter (default BLOCKER); on stall it branches: RETRY raises StallDetectedError, FAIL returns FAILED, BLOCKER creates blocker and returns BLOCKED.
Runtime Retry Logic
codeframe/core/runtime.py
execute_agent accepts stall_action; catches StallDetectedError and implements up-to-one retry loop, logging and marking FAILED if retries exhausted.
Batch Execution Threading
codeframe/core/conductor.py
BatchRun and start_batch include stall_action; threaded through task execution paths and _execute_task_subprocess, adding --stall-action to subprocess invocations.
Tests
tests/core/test_stall_detector.py
Expanded tests for StallDetectedError attributes/message, ReactAgent stall_action behavior (default and FAIL), execute_agent signature, and BatchRun/start_batch stall_action support.

Sequence Diagram(s)

sequenceDiagram
    participant User as User (CLI)
    participant CLI as CLI Parser
    participant Runtime as Runtime
    participant Agent as ReactAgent
    participant Detector as StallDetector
    participant Subproc as Task Subprocess

    User->>CLI: cf work start --stall-action retry
    CLI->>Runtime: execute_agent(..., stall_action="retry")
    Runtime->>Agent: instantiate ReactAgent(stall_action=RETRY)
    Agent->>Detector: start monitor

    loop Agent tool loop
        Agent->>Detector: record_activity()
        Agent->>Detector: is_stalled()?
        alt Stall detected
            Detector->>Agent: stall event (elapsed_s, iterations, last_tool)
            Agent->>Agent: branch on stall_action
            alt RETRY
                Agent->>Runtime: raise StallDetectedError(elapsed_s, iterations, last_tool)
                Runtime->>Runtime: catch & retry (<= max retries)
            else FAIL
                Agent->>Agent: return FAILED
            else BLOCKER
                Agent->>Agent: create blocker & return BLOCKED
            end
        else No stall
            Agent->>Agent: continue iterations
        end
    end

    Note over Runtime,Subproc: For batch tasks, Conductor/Runtime pass --stall-action to Subproc
    Runtime->>Subproc: run task --stall-action retry
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰
I hopped through loops where silent tools lay,
I sniffed the stall and saved the day.
Retry, fail, or hold the gate—
I carry context, hop once straight.
Threaded actions stitched with cheer,
A rabbit sings: "Restart or persevere!"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main objective of the PR—wiring the StallAction dispatch mechanism into ReactAgent and runtime for configurable stall recovery.
Linked Issues check ✅ Passed The PR implements the core coding requirements from #401: StallDetectedError exception, stall_action parameter threading through CLI/runtime/ReactAgent, configurable dispatch logic (RETRY/FAIL/BLOCKER), and retry handling in execute_agent.
Out of Scope Changes check ✅ Passed All changes are within scope of #401 requirements. Additions include StallAction dispatch in ReactAgent, stall_action parameter propagation through runtime and conductor, CLI flag integration, and supporting exception and test changes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/401-stall-action-integration

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude

claude Bot commented Mar 9, 2026

Copy link
Copy Markdown

Code Review: feat(core): wire StallAction dispatch into ReactAgent and runtime

The PR is well-structured and the data flow is clean. The StallAction dispatch logic is clear and backwards-compatible (default stays BLOCKER). A few issues worth addressing:

Bug / Correctness

Redundant stop() call in except StallDetectedError (react_agent.py line 251)

_react_loop() is called inside the inner try/finally block (lines 208-249). When StallDetectedError propagates out of _react_loop, the finally: self._stall_monitor.stop() fires before the exception reaches the outer except StallDetectedError: handler. The second stop() call there is dead code. stop() is idempotent (_thread = None guard in stall_monitor.py:95) so this will not crash, but the comment "Let runtime handle retry" next to a redundant cleanup call is misleading. The extra stop() call should be removed.

Input Validation

StallAction(stall_action) can raise a cryptic ValueError for bad CLI input (runtime.py line 679)

If a user passes --stall-action typo, the raw enum ValueError propagates up through the CLI with no user-friendly message. The call should be wrapped with a try/except that provides a helpful error listing valid values. Alternatively, change the CLI parameter type from str to StallAction so Typer validates it automatically and auto-documents valid values in --help.

Silent Flag / UX

--stall-action is silently ignored when --engine plan is used

The flag is wired into both work start and batch run, but the plan engine path in execute_agent never receives or uses it. A user running cf work start with --engine plan and --stall-action retry will get BLOCKER behavior with no warning. A short check or warning would prevent silent misuse.

Design / Maintainability

max_stall_retries = 1 is hardcoded without explanation (runtime.py line 696)

The retry count is intentionally capped at 1, but nothing documents why. A brief inline comment preserves intent and prevents future contributors from treating it as an oversight.

Test Coverage

Behavioral dispatch is not tested - only structural/signature checks

TestReactAgentStallAction verifies that _stall_action is stored; TestExecuteAgentStallAction uses inspect.signature. Neither confirms that:

  • StallAction.RETRY path actually raises StallDetectedError
  • StallAction.FAIL path actually returns AgentStatus.FAILED
  • The runtime retry loop catches StallDetectedError and retries once before failing

These are the most critical new behaviors. Mock-based unit tests for _react_loop with _stall_triggered pre-set for each action variant would close the gap without requiring a live LLM.

Minor

state could be flagged as potentially unbound by static analysis (runtime.py)

The for-loop in execute_agent always exits via break, so state is always set at runtime. However, mypy/pyright may warn that state could be unbound. A one-liner initialization before the loop eliminates the false positive.


Summary: The BLOCKER -> RETRY/FAIL dispatch logic is correct and the parameter threading is mechanically sound. Addressing the redundant stop(), input validation for bad --stall-action values, and adding behavioral tests for the three dispatch paths would make this production-ready.

@claude

claude Bot commented Mar 9, 2026

Copy link
Copy Markdown

PR Review for feat(core): wire StallAction dispatch into ReactAgent and runtime.

Overall this is a clean, well-scoped change. The three-way dispatch (RETRY to exception, FAIL to status, BLOCKER to existing path) is easy to follow, and threading stall_action through the call chain is done consistently.

ISSUE 1 - Redundant _stall_monitor.stop() in except StallDetectedError (react_agent.py lines 250-252):

The inner try/finally (line 248-249) guarantees _stall_monitor.stop() is called before any exception propagates. StallMonitor.stop() is idempotent (_stop_event.set() is a no-op when already set, and the thread reference is nulled on first call), so this will not crash but the second stop() is dead code. The handler should just be: raise with a comment that finally already stopped the monitor.

ISSUE 2 - No CLI-level validation of --stall-action value:

Both work_start and batch_run accept stall_action as a plain str with no validation. An invalid value like --stall-action kaboom will not be caught until deep inside execute_agent() where StallAction(stall_action) raises a ValueError with a Python traceback instead of a user-friendly message. The Typer enum type approach is cleanest: import StallAction into app.py and use it as the parameter type so Typer shows valid choices automatically in --help. The subprocess path in batch mode makes this doubly important as invalid values will silently fail the subprocess.

ISSUE 3 - max_stall_retries = 1 is a magic number (runtime.py line 696):

Mentioned in the PR description but not obvious from the code. A named constant or clarifying comment would help future readers.

ISSUE 4 - Tests are structural, not behavioral:

The new test classes verify parameter existence and defaults (valuable for API stability) but the actual dispatch logic is not covered: that RETRY raises StallDetectedError, that FAIL returns AgentStatus.FAILED, that BLOCKER calls _create_text_blocker. These are testable by pre-setting _stall_triggered and _stall_event on an agent instance and calling _react_loop() directly. Worth tracking as a follow-up.

Non-blocking notes: the _build_react_agent() closure captures outer variables cleanly; the retry loop logic is correct (verified); default stall_action=blocker preserves all existing behavior; init.py exports are consistent with the existing pattern.

Summary: Items 1 and 2 are most important to address before merge. Items 3 and 4 are lower priority. The overall approach and data flow design are solid.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
codeframe/core/react_agent.py (1)

114-128: ⚠️ Potential issue | 🟠 Major

Honor stall_action during verification stalls too.

This new field is only consulted in _react_loop(). If the monitor fires during final verification, Line 497 still returns "stall_detected" and Lines 234-240 always turn that into BLOCKED, so --stall-action fail|retry is ignored for long or stuck gate runs.

Also applies to: 292-315

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@codeframe/core/react_agent.py` around lines 114 - 128, The monitor
"stall_detected" result is currently always mapped to BLOCKED in the
reaction/verification flow, ignoring the configured stall_action; update the
handling in _react_loop (and the similar handling at the other verification
branch around the 292-315 region) to check self._stall_action (or stall_action)
and act accordingly—i.e., when a verification monitor returns "stall_detected"
choose between BLOCKED, FAIL, or RETRY per self._stall_action instead of
hard-coding BLOCKED; ensure both the main loop path (_react_loop) and the
separate verification path use the same decision logic so --stall-action
fail|retry is honored.
codeframe/core/conductor.py (1)

461-475: ⚠️ Potential issue | 🟠 Major

Persist stall_action across BatchRun reloads.

This only stores the new field on the in-memory object. The SQLite mapping in _save_batch(), get_batch(), list_batches(), and _row_to_batch() still round-trips only through engine, so any batch reloaded from the DB falls back to "blocker"/300. In practice, resume_batch() will ignore a non-default stall policy on the next attempt.

🗄️ Minimal shape of the fix
- SELECT ..., results, engine
+ SELECT ..., results, engine, stall_timeout_s, stall_action

- (id, workspace_id, task_ids, status, strategy, max_parallel, on_failure,
-  started_at, completed_at, results, engine)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ (id, workspace_id, task_ids, status, strategy, max_parallel, on_failure,
+  started_at, completed_at, results, engine, stall_timeout_s, stall_action)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)

 return BatchRun(
     ...
     engine=row[10] if len(row) > 10 and row[10] else "plan",
+    stall_timeout_s=row[11] if len(row) > 11 and row[11] is not None else 300,
+    stall_action=row[12] if len(row) > 12 and row[12] else "blocker",
 )

This also needs the matching batch_runs schema migration.

Also applies to: 523-523

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@codeframe/core/conductor.py` around lines 461 - 475, The BatchRun's
stall_action (and stall_timeout_s) are only set on the in-memory object but not
persisted or reloaded; update the SQLite persistence to include these fields:
add columns to the batch_runs schema (migration), include stall_action and
stall_timeout_s in _save_batch() INSERT/UPDATE, include them in SELECT
projections used by get_batch() and list_batches(), and populate them in
_row_to_batch() so reloaded BatchRun instances (and resume_batch()) retain
non-default stall policies; ensure you keep sensible defaults when columns are
absent for backward compatibility.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@codeframe/cli/app.py`:
- Around line 2006-2010: The CLI option currently defined as stall_action: str
should use the StallAction enum for parse-time validation: change the
typer.Option parameter type to StallAction (e.g., stall_action: StallAction =
typer.Option(StallAction.blocker, "--stall-action", help=...)) in both
work_start() and batch_run(), and remove the later runtime conversion
StallAction(stall_action) in execute_agent() so invalid values are rejected at
parsing and no IN_PROGRESS run is created; keep the same help text but set the
default to StallAction.blocker and update any downstream usage sites that expect
a string to accept the StallAction value (or call .value where a string is
required).

In `@codeframe/core/react_agent.py`:
- Around line 292-315: The FAIL branch currently returns AgentStatus.FAILED
which causes callers to report max_iterations_reached; change the FAIL branch to
raise StallDetectedError (same shape as the RETRY branch) with elapsed_s,
iterations, and last_tool so stalled runs are distinguishable; also ensure the
BLOCKER branch preserves diagnostics by passing iterations, last_tool and
elapsed_s into the blocker (e.g., include them in the stall_ctx passed to
_create_text_blocker or otherwise attach them) so iteration/last-tool/token
diagnostics are not lost (refer to symbols: StallAction.FAIL,
StallAction.BLOCKER, StallDetectedError, _create_text_blocker, and
recent_tool_signatures).

In `@tests/core/test_stall_detector.py`:
- Around line 14-17: Remove the unused import by deleting the `threading` import
statement from the top of the `tests/core/test_stall_detector.py` file (the line
that reads `import threading`) so the file only imports the used symbols
(`inspect`, `time`, and `MagicMock`), which resolves the Ruff F401 unused-import
error.

---

Outside diff comments:
In `@codeframe/core/conductor.py`:
- Around line 461-475: The BatchRun's stall_action (and stall_timeout_s) are
only set on the in-memory object but not persisted or reloaded; update the
SQLite persistence to include these fields: add columns to the batch_runs schema
(migration), include stall_action and stall_timeout_s in _save_batch()
INSERT/UPDATE, include them in SELECT projections used by get_batch() and
list_batches(), and populate them in _row_to_batch() so reloaded BatchRun
instances (and resume_batch()) retain non-default stall policies; ensure you
keep sensible defaults when columns are absent for backward compatibility.

In `@codeframe/core/react_agent.py`:
- Around line 114-128: The monitor "stall_detected" result is currently always
mapped to BLOCKED in the reaction/verification flow, ignoring the configured
stall_action; update the handling in _react_loop (and the similar handling at
the other verification branch around the 292-315 region) to check
self._stall_action (or stall_action) and act accordingly—i.e., when a
verification monitor returns "stall_detected" choose between BLOCKED, FAIL, or
RETRY per self._stall_action instead of hard-coding BLOCKED; ensure both the
main loop path (_react_loop) and the separate verification path use the same
decision logic so --stall-action fail|retry is honored.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 04783770-6f44-4b3d-a3ad-f5ef12eaa7ac

📥 Commits

Reviewing files that changed from the base of the PR and between d5e9fff and 08242b7.

📒 Files selected for processing (7)
  • codeframe/cli/app.py
  • codeframe/core/__init__.py
  • codeframe/core/conductor.py
  • codeframe/core/react_agent.py
  • codeframe/core/runtime.py
  • codeframe/core/stall_detector.py
  • tests/core/test_stall_detector.py

Comment thread codeframe/cli/app.py
Comment on lines +2006 to +2010
stall_action: str = typer.Option(
"blocker",
"--stall-action",
help="Recovery action on stall: 'blocker' (default), 'retry', or 'fail'",
),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's locate and inspect the relevant lines in codeframe/cli/app.py
fd -n "app.py" | grep "cli"

Repository: frankbria/codeframe

Length of output: 293


🏁 Script executed:

# Get the actual code at lines 2006-2010 and surrounding context
sed -n '2000,2020p' codeframe/cli/app.py

Repository: frankbria/codeframe

Length of output: 745


🏁 Script executed:

# Check if StallAction exists in codeframe/core/stall_detector
fd -n "stall_detector.py" codeframe/

Repository: frankbria/codeframe

Length of output: 293


🏁 Script executed:

# Find stall_detector.py
find codeframe -name "stall_detector.py" -type f

Repository: frankbria/codeframe

Length of output: 95


🏁 Script executed:

# Check batch_run() at lines 2883-2887
sed -n '2880,2900p' codeframe/cli/app.py

Repository: frankbria/codeframe

Length of output: 923


🏁 Script executed:

# Check work_start() function to see the line 2055 context and state mutation concern
sed -n '2050,2060p' codeframe/cli/app.py

Repository: frankbria/codeframe

Length of output: 507


🏁 Script executed:

# Examine the StallAction enum in stall_detector.py
cat codeframe/core/stall_detector.py | head -100

Repository: frankbria/codeframe

Length of output: 2386


🏁 Script executed:

# Find where stall_action is validated/used in the codebase
rg "stall_action" -A 2 -B 2 codeframe/

Repository: frankbria/codeframe

Length of output: 8462


🏁 Script executed:

# Check the full work_start() function signature and usage of stall_action
sed -n '1990,2070p' codeframe/cli/app.py

Repository: frankbria/codeframe

Length of output: 3079


🏁 Script executed:

# Check if there are existing Typer+Enum patterns in the codebase
rg "typer.Option" -A 3 codeframe/cli/app.py | grep -E "(Enum|enum|\[.*\])" -B 3

Repository: frankbria/codeframe

Length of output: 6119


🏁 Script executed:

# Check what's imported at top of codeframe/cli/app.py
head -60 codeframe/cli/app.py | grep -E "^import|^from"

Repository: frankbria/codeframe

Length of output: 333


🏁 Script executed:

# Verify: does work_start() call execute_agent with stall_action and what happens to it?
sed -n '2065,2075p' codeframe/cli/app.py

Repository: frankbria/codeframe

Length of output: 685


🏁 Script executed:

# Check what batch_run() does with stall_action - does it create state before validation?
sed -n '2850,2950p' codeframe/cli/app.py | head -100

Repository: frankbria/codeframe

Length of output: 4100


🏁 Script executed:

# Check if Typer supports Enum type hints by searching for documentation patterns
rg "from enum import" codeframe/cli/

Repository: frankbria/codeframe

Length of output: 45


🏁 Script executed:

# Final check: verify the exact conversion logic in runtime.execute_agent()
rg "StallAction\(" -B 2 -A 2 codeframe/core/runtime.py

Repository: frankbria/codeframe

Length of output: 258


🏁 Script executed:

# Check if conductor.start_batch() creates persistent state immediately
rg "def start_batch" -A 15 codeframe/core/conductor.py | head -30

Repository: frankbria/codeframe

Length of output: 504


🏁 Script executed:

# Verify: is BatchRun a database model that gets persisted on creation?
rg "class BatchRun" -A 10 codeframe/core/

Repository: frankbria/codeframe

Length of output: 800


Use StallAction enum type for parse-time validation of --stall-action option.

The CLI currently accepts arbitrary strings and validates them only during execute_agent() via StallAction(stall_action) conversion. In work_start(), this creates a timing gap: the run is created at line 2055 before validation occurs, so an invalid --stall-action value leaves behind a dangling run in IN_PROGRESS state.

Suggested change
+from codeframe.core.stall_detector import StallAction
@@
-    stall_action: str = typer.Option(
-        "blocker",
+    stall_action: StallAction = typer.Option(
+        StallAction.BLOCKER,
         "--stall-action",
         help="Recovery action on stall: 'blocker' (default), 'retry', or 'fail'",
     ),

Apply to both work_start() and batch_run().

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
stall_action: str = typer.Option(
"blocker",
"--stall-action",
help="Recovery action on stall: 'blocker' (default), 'retry', or 'fail'",
),
from codeframe.core.stall_detector import StallAction
stall_action: StallAction = typer.Option(
StallAction.BLOCKER,
"--stall-action",
help="Recovery action on stall: 'blocker' (default), 'retry', or 'fail'",
),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@codeframe/cli/app.py` around lines 2006 - 2010, The CLI option currently
defined as stall_action: str should use the StallAction enum for parse-time
validation: change the typer.Option parameter type to StallAction (e.g.,
stall_action: StallAction = typer.Option(StallAction.blocker, "--stall-action",
help=...)) in both work_start() and batch_run(), and remove the later runtime
conversion StallAction(stall_action) in execute_agent() so invalid values are
rejected at parsing and no IN_PROGRESS run is created; keep the same help text
but set the default to StallAction.blocker and update any downstream usage sites
that expect a string to accept the StallAction value (or call .value where a
string is required).

Comment on lines +292 to +315
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Keep stall failures distinct from max-iteration failures.

The FAIL branch returns only AgentStatus.FAILED, but Line 211 later emits reason="max_iterations_reached" for any failed _react_loop() result. That misreports stalled runs, and the BLOCKER branch also drops the iteration / last-tool / token diagnostics this feature is supposed to preserve.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@codeframe/core/react_agent.py` around lines 292 - 315, The FAIL branch
currently returns AgentStatus.FAILED which causes callers to report
max_iterations_reached; change the FAIL branch to raise StallDetectedError (same
shape as the RETRY branch) with elapsed_s, iterations, and last_tool so stalled
runs are distinguishable; also ensure the BLOCKER branch preserves diagnostics
by passing iterations, last_tool and elapsed_s into the blocker (e.g., include
them in the stall_ctx passed to _create_text_blocker or otherwise attach them)
so iteration/last-tool/token diagnostics are not lost (refer to symbols:
StallAction.FAIL, StallAction.BLOCKER, StallDetectedError, _create_text_blocker,
and recent_tool_signatures).

Comment thread tests/core/test_stall_detector.py
…reason

- Verification stall check now dispatches via StallAction (RETRY/FAIL/BLOCKER)
- FAILED status from react loop correctly reports stall_detected vs max_iterations
@claude

claude Bot commented Mar 9, 2026

Copy link
Copy Markdown

Follow-up Review after commit c8883ee. The third commit addresses two genuine gaps. Fixed: _run_final_verification now dispatches RETRY/FAIL/BLOCKER correctly on stall, and AGENT_FAILED events now emit stall_detected vs max_iterations_reached accurately. Both correct. Still open from prior reviews: (1) Redundant _stall_monitor.stop() in the except StallDetectedError block in react_agent.py line 250 - the inner try/finally already calls stop() before exception reaches there. (2) No CLI validation for --stall-action - both work_start and batch_run take stall_action as plain str, invalid values raise raw ValueError in runtime.py; use StallAction as the Typer type for free validation and --help documentation. (3) max_stall_retries equals 1 is undocumented in runtime.py line 696 - add a comment explaining the intentional cap. (4) Tests remain structural not behavioral - no coverage that RETRY raises StallDetectedError, FAIL returns AgentStatus.FAILED, or BLOCKER calls _create_text_blocker. Items 1-3 are small and addressable here; item 4 can be a follow-up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Phase 2.5] Stall Detection: ReAct Agent and Runtime Integration

1 participant