From aa07ff07283e13084ac64d692e218f727ebafc5d Mon Sep 17 00:00:00 2001 From: ciregenz Date: Mon, 3 Aug 2026 15:48:33 -0700 Subject: [PATCH] [eric] browser: tell a user their DELETION was unconfirmed, not their send --- backend/apps/agents/browser/browser_agent.py | 2 +- backend/apps/agents/browser/browser_loop.py | 8 ++++++++ .../tests/test_removal_never_claims_a_send.py | 20 +++++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/backend/apps/agents/browser/browser_agent.py b/backend/apps/agents/browser/browser_agent.py index effae089..97603da1 100644 --- a/backend/apps/agents/browser/browser_agent.py +++ b/backend/apps/agents/browser/browser_agent.py @@ -3105,7 +3105,7 @@ async def run_browser_agent( else: honest, dishonest_reason = completion_is_honest( action_log, publish_task=is_publish_task(skill_key_task), - send_confirmed=send_confirmed) + send_confirmed=send_confirmed, removal_task=p_task_is_removal) final_status = "completed" if honest else "error" if not honest: summary = f"I was not able to complete this task ({dishonest_reason})." diff --git a/backend/apps/agents/browser/browser_loop.py b/backend/apps/agents/browser/browser_loop.py index 33d8e063..50f4534e 100644 --- a/backend/apps/agents/browser/browser_loop.py +++ b/backend/apps/agents/browser/browser_loop.py @@ -364,6 +364,7 @@ def deliverable_is_informational(summary: str, task: str = "") -> bool: def completion_is_honest( action_log: list[dict], publish_task: bool = False, send_confirmed: bool = False, + removal_task: bool = False, ) -> tuple[bool, str]: """Reality-check a run the model declared done. Returns (honest, reason). @@ -383,6 +384,13 @@ def completion_is_honest( # click runs (a resend guard, not proof), so this can still let a bad send through, but it can # never newly flag a run that had any send signal at all. if publish_task and not send_confirmed: + # "delete my post" carries the noun `post`, so is_publish_task calls a DELETE a publish and + # this gate then judged it against a send it was never meant to make. Flagging is right (the + # run proved nothing either way), but the sentence has to describe the task the user gave. + # Telling someone their deletion "may not have gone out" sends them to check the wrong thing. + if removal_task: + return False, ("the deletion was never confirmed, so the item may still be there; " + "check the page before trusting this") return False, ("the send was never confirmed, so it may not have gone out; " "check the page before trusting this") if not action_log: diff --git a/backend/tests/test_removal_never_claims_a_send.py b/backend/tests/test_removal_never_claims_a_send.py index eed5339c..d661fdfb 100644 --- a/backend/tests/test_removal_never_claims_a_send.py +++ b/backend/tests/test_removal_never_claims_a_send.py @@ -72,3 +72,23 @@ def test_the_post_send_backstop_refuses_to_claim_a_send_on_a_removal(): removal_code = [ln for ln in branch.split("elif")[0].splitlines() if "done_message" in ln and not ln.strip().startswith("#")] assert removal_code and all("sent" not in ln for ln in removal_code), removal_code + + +def test_the_completion_gate_describes_the_task_the_user_actually_gave(): + """The fourth site of the same root cause, found while cleaning up after the third. + + `is_publish_task` matches the NOUN in "delete my post", so a delete run reaches the publish + branch of the completion gate. Flagging it is right (the run proved nothing either way), but + the sentence was "the send was never confirmed, so it may not have gone out", which points the + user at the wrong page to check: they deleted something, and nothing was ever meant to go out. + """ + from backend.apps.agents.browser.browser_loop import completion_is_honest + + honest, why = completion_is_honest([], publish_task=True, send_confirmed=False, + removal_task=True) + assert not honest + assert "deletion" in why and "may still be there" in why + assert "gone out" not in why, "a delete must not be described as a send" + + honest, why = completion_is_honest([], publish_task=True, send_confirmed=False) + assert not honest and "gone out" in why, "a real send keeps its own wording"