From aaa0d306dbdd590589230b794104ef7a3cefbc60 Mon Sep 17 00:00:00 2001 From: ciregenz Date: Fri, 5 Jun 2026 15:19:36 -0700 Subject: [PATCH] [eric] browser: fast path failure detection fail-closed, card-gone summaries now trigger recovery --- backend/apps/agents/browser/browser_fast_path.py | 6 +++++- backend/tests/test_browser_fast_path.py | 8 +++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/backend/apps/agents/browser/browser_fast_path.py b/backend/apps/agents/browser/browser_fast_path.py index 8c2ae0fc..e49e6268 100644 --- a/backend/apps/agents/browser/browser_fast_path.py +++ b/backend/apps/agents/browser/browser_fast_path.py @@ -117,8 +117,12 @@ def compose_task(prompt: str, brief: str) -> str: def dispatch_failed(summary: str) -> bool: + """Fail-closed: only an explicit DONE line or a skill replay counts as + success; framework failures ('I was not able to complete this task (the + browser became unresponsive...)') carry no OUTCOME line at all, and the + recovery task's verify-first wording makes a rare redundant retry safe.""" s = (summary or "").strip() - return not s or s.startswith("Error:") or "OUTCOME: NOT DONE" in s.upper() + return not ("OUTCOME: DONE" in s.upper() or "learned skill replay" in s) def recovery_task(prompt: str, first_report: str) -> str: diff --git a/backend/tests/test_browser_fast_path.py b/backend/tests/test_browser_fast_path.py index 8b2d28d5..a7592d88 100644 --- a/backend/tests/test_browser_fast_path.py +++ b/backend/tests/test_browser_fast_path.py @@ -61,11 +61,17 @@ def test_compose_task_keeps_user_words_first(): assert composed.endswith("ENTRY: https://news.ycombinator.com") -def test_dispatch_failure_detection(): +def test_dispatch_failure_detection_is_fail_closed(): assert dispatch_failed("") assert dispatch_failed("Error: browser card was deleted") assert dispatch_failed("Could not find the thread. OUTCOME: NOT DONE - login wall") + assert dispatch_failed( + "I was not able to complete this task (the browser became unresponsive " + "(the tab hung or was closed); it needs a fresh browser to continue)." + ) + assert dispatch_failed("Found the page and clicked around a bit.") assert not dispatch_failed("Sent it. OUTCOME: DONE - bubble visible at 12:05 PM") + assert not dispatch_failed("Completed via learned skill replay (3 steps, no LLM).") def test_recovery_task_verifies_before_repeating():