mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-02 23:48:45 +02:00
112 lines
5.6 KiB
Python
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"
|