Files
openswarm/backend/tests/test_effect_disposition.py
T

112 lines
5.6 KiB
Python

"""A failed browser dispatch must resolve to a state the caller can branch on.
Haik, production 1.7.9, on a Spotify playlist edit: "Zero-confidence outcomes on a write path is
the worst possible spot to leave an agent in: I'm forced to choose between doing nothing and
risking a double-write." The honesty gate was right to refuse to claim the send happened. What it
returned was prose, so a parent agent could not tell "definitely did not happen" from "cannot
tell", and a naive retry duplicates tracks (ENG-402).
"""
from backend.apps.agents.browser.browser_loop import STATE_CHANGING_TOOLS, READ_ONLY_TOOLS
from backend.apps.agents.browser.effect_disposition import (
CONFIRMED, MAY_HAVE_HAPPENED, NOTHING_HAPPENED, disposition_line, effect_disposition,
)
def p_log(*tools):
return [{"tool": t, "ok": True} for t in tools]
def test_a_run_that_only_looked_around_changed_nothing():
assert effect_disposition([]) == NOTHING_HAPPENED
assert effect_disposition(p_log(*sorted(READ_ONLY_TOOLS))) == NOTHING_HAPPENED
def test_an_attempted_write_with_no_proof_is_unknown_not_failed():
# This is the Spotify case: every click returned ok and the send was never confirmed.
assert effect_disposition(p_log("BrowserType", "BrowserClick")) == MAY_HAVE_HAPPENED
def test_ok_on_a_click_is_not_proof_the_write_landed():
# A click reporting success is how a user got told "Done, I sent it for you" on a post that
# never arrived. Only the run's own confirmation signal earns "applied".
assert effect_disposition([{"tool": "BrowserClick", "ok": True}]) != CONFIRMED
assert effect_disposition([{"tool": "BrowserClick", "ok": True}], send_confirmed=True) == CONFIRMED
def test_an_unrecognised_tool_is_assumed_to_have_changed_something():
# The read-only set is an ALLOWLIST. A browser tool added tomorrow must read as UNKNOWN, not as
# harmless: ENG-297 shipped seven state-changing tools that no classifier knew about.
assert effect_disposition(p_log("BrowserSomethingShippedNextWeek")) == MAY_HAVE_HAPPENED
for tool in STATE_CHANGING_TOOLS:
assert effect_disposition(p_log(tool)) == MAY_HAVE_HAPPENED, tool
def test_every_disposition_tells_the_caller_what_to_do_next():
assert "safe to retry" in disposition_line(NOTHING_HAPPENED)
assert "do not repeat" in disposition_line(CONFIRMED)
unknown = disposition_line(MAY_HAVE_HAPPENED)
assert "check before retrying" in unknown and "twice" in unknown
assert "may or may not" in unknown, "the ambiguity has to be stated, not implied"
def test_the_failed_run_carries_the_disposition_home():
src = open("backend/apps/agents/browser/browser_agent.py").read()
i = src.index("I was not able to complete this task")
body = src[i - 400:i + 300]
assert "effect_disposition(action_log" in body
assert "disposition_line(p_effect)" in body, "the sentence must reach the caller's summary"
# ---------------------------------------------- the READ side of the same problem (ENG-404)
def test_a_run_whose_every_read_failed_says_its_specifics_are_unverified():
from backend.apps.agents.browser.effect_disposition import unverified_reads_line
# Haik's playlist run: the clicks reported ok, every read failed, and the agent handed the user
# "6 confirmed tracks" with titles and artists. The gate's "only looked around" check never
# fires once an action succeeds, so nothing caught it.
log = [{"tool": "BrowserClick", "ok": True},
{"tool": "BrowserGetText", "ok": False},
{"tool": "BrowserGetElements", "ok": False}]
line = unverified_reads_line(log)
assert "unverified" in line and "must not be treated as confirmed" in line
def test_a_read_that_returned_nothing_counts_as_failed():
from backend.apps.agents.browser.effect_disposition import unverified_reads_line
assert unverified_reads_line([{"tool": "BrowserGetText", "ok": True, "result_summary": " "}])
def test_a_run_that_really_read_the_page_is_left_alone():
from backend.apps.agents.browser.effect_disposition import unverified_reads_line
assert unverified_reads_line(
[{"tool": "BrowserGetText", "ok": True, "result_summary": "Track 1 - Artist"}]) == ""
def test_a_pure_write_run_is_not_labelled():
# It never tried to read, so there is nothing unverified to warn about; a note on every write
# would be noise, and noise is how a real warning stops being read.
from backend.apps.agents.browser.effect_disposition import unverified_reads_line
assert unverified_reads_line([{"tool": "BrowserType", "ok": True}]) == ""
assert unverified_reads_line([]) == ""
def test_the_label_rides_the_SUCCESSFUL_path_where_the_fabrication_travelled():
src = open("backend/apps/agents/browser/browser_agent.py").read()
i_ghost = src.index("completion gate caught a ghost")
i_label = src.index("unverified_reads_line(action_log)")
assert i_label > i_ghost, "it belongs on the else branch: the run the gate let through"
assert "summary = f\"{summary}\\n\\n{p_unverified}\"" in src
def test_it_labels_rather_than_rejects():
# A click that lands while the verification read fails is an honest partial. Flipping that to an
# error would delete real work to punish a word.
src = open("backend/apps/agents/browser/browser_agent.py").read()
i = src.index("unverified_reads_line(action_log)")
# Up to (not including) the status assignment that closes the branch: nothing in here may
# change the verdict, only the text.
body = src[i:src.index("session.status = final_status", i)]
assert "final_status =" not in body and "honest = False" not in body
assert "summary = " in body, "the only thing it touches is the wording"