diff --git a/GramAddict/core/goap.py b/GramAddict/core/goap.py
index 54a3590..4edadb7 100644
--- a/GramAddict/core/goap.py
+++ b/GramAddict/core/goap.py
@@ -496,8 +496,12 @@ class GoalExecutor:
return False
else:
# action_success is None (INCONCLUSIVE)
- # We decay the memory so it unlearns if it repeatedly fails to produce a definitive success.
+ logger.warning(f"⚠️ [GOAP Execute] Applying AGGRESSIVE PENALTY for inconclusive action '{action}'.")
engine.decay_click(action)
+ # Double penalty to burn ambiguous paths faster (outer loop adds +1, so total +2 = instantly hits MAX_RETRIES)
+ self.action_failures[(pre_action_screen_type, action)] = (
+ self.action_failures.get((pre_action_screen_type, action), 0) + 1
+ )
return False
def _execute_recalled_path(self, steps: List[Dict], goal: str) -> bool:
diff --git a/GramAddict/core/perception/action_memory.py b/GramAddict/core/perception/action_memory.py
index 99a5986..0e19570 100644
--- a/GramAddict/core/perception/action_memory.py
+++ b/GramAddict/core/perception/action_memory.py
@@ -167,7 +167,8 @@ class ActionMemory:
and "row_feed_button_like" not in post_xml_lower
and "clips_viewer" not in post_xml_lower
):
- return None # Still on grid, inconclusive
+ logger.warning(f"⚠️ [ActionMemory] Still on grid after trying to '{intent}'. Verification FAIL.")
+ return False # Still on grid, definitely failed
state_toggles = ["like", "save", "follow", "heart"]
is_toggle = any(t in intent_lower for t in state_toggles)
@@ -269,7 +270,10 @@ class ActionMemory:
if diff > 0:
logger.debug(f"🧠 [ActionMemory] Structural delta detected for toggle '{intent}'. Verification PASS.")
return True
- else:
+ logger.warning(
+ f"⚠️ [ActionMemory] Zero structural shift (diff={diff}) for state-toggle '{intent}'. Verification FAIL."
+ )
+ return False
# If the intent is an abstract goal (like "find customers"), diff > 50 is NOT enough.
# We must force visual VLM confirmation because clicking the wrong thing (like "Create highlight")
# also produces a large diff but achieves the wrong goal.
@@ -317,6 +321,12 @@ class ActionMemory:
logger.warning(f"⚠️ [ActionMemory] Cannot visually verify abstract intent '{intent}'. Failing safe.")
return False
+ # If diff <= 50 for non-toggle
+ logger.warning(
+ f"⚠️ [ActionMemory] Insufficient structural change (diff={diff}) for non-toggle '{intent}'. Verification FAIL."
+ )
+ return False
+
def _intent_matches_node(intent: str, semantic_string: str) -> bool:
"""Checks if the clicked element semantically matches the toggle intent.
diff --git a/tests/e2e/test_goap_loop_prevention.py b/tests/e2e/test_goap_loop_prevention.py
index 8009b27..9290f83 100644
--- a/tests/e2e/test_goap_loop_prevention.py
+++ b/tests/e2e/test_goap_loop_prevention.py
@@ -54,9 +54,9 @@ def test_goap_planner_avoids_infinite_loop_on_masked_edge():
action_failures=action_failures,
)
- # The HD Map should fail, and because the planner is trapped, it forces a restart
+ # The HD Map should fail, and because the planner is trapped, it falls back to back-tracking
assert (
- action_avoided == "force start instagram"
+ action_avoided == "press back" or action_avoided == "force start instagram"
), "Planner routed BLIND into the dead end despite the edge being masked!"
diff --git a/tests/e2e/test_system_llm_perception.py b/tests/e2e/test_system_llm_perception.py
index f75d8de..f0d3a5b 100644
--- a/tests/e2e/test_system_llm_perception.py
+++ b/tests/e2e/test_system_llm_perception.py
@@ -855,14 +855,13 @@ class TestVerifySuccessStructuralDelta:
result = memory.verify_success("like", pre_click_xml=pre_xml, post_click_xml=post_xml)
assert result is True
- def test_toggle_zero_diff_is_none(self):
- """Zero XML change for a toggle = inconclusive (not confirmed)."""
+ def test_toggle_zero_diff_is_false(self):
+ """Zero XML change for a toggle = fail."""
memory, _ = self._make_memory()
same_xml = ''
result = memory.verify_success("like", pre_click_xml=same_xml, post_click_xml=same_xml)
- # Zero diff, no markers → None (inconclusive) not True
- assert result is None or result is False
+ assert result is False
def test_follow_success_with_requested_marker(self):
"""
@@ -888,7 +887,7 @@ class TestVerifySuccessStructuralDelta:
result = memory.verify_success("view a post", pre_click_xml="", post_click_xml=post_xml)
assert result is True
- def test_view_post_still_on_grid_is_inconclusive(self):
+ def test_view_post_still_on_grid_is_false(self):
"""
If after 'view a post' the XML still shows explore_action_bar
WITHOUT post detail markers, navigation failed.
@@ -896,7 +895,7 @@ class TestVerifySuccessStructuralDelta:
memory, _ = self._make_memory()
post_xml = ''
result = memory.verify_success("grid item", pre_click_xml="", post_click_xml=post_xml)
- assert result is None # Inconclusive
+ assert result is False
def test_semantic_gate_blocks_wrong_toggle_element(self):
"""