mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-02 23:08:46 +02:00
75 lines
3.5 KiB
Python
75 lines
3.5 KiB
Python
"""Did the run change anything out there? Answered as a value the caller can branch on.
|
|
|
|
Haik, production 1.7.9, on a 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." Our honesty gate was right to refuse to claim the send happened; what it gave back
|
|
was prose, so a parent agent could not tell "definitely did not happen" from "cannot tell" and had
|
|
no basis to decide between stalling and retrying (ENG-402).
|
|
|
|
Fail-safe by construction: the read-only set is an ALLOWLIST, and anything not on it is assumed to
|
|
have changed something. A browser tool added tomorrow reads as UNKNOWN rather than as harmless.
|
|
"""
|
|
|
|
from typing import Dict, List, Literal
|
|
|
|
from typeguard import typechecked
|
|
|
|
from backend.apps.agents.browser.browser_loop import READ_ONLY_TOOLS
|
|
|
|
Disposition = Literal["applied", "none", "unknown"]
|
|
|
|
NOTHING_HAPPENED = "none"
|
|
MAY_HAVE_HAPPENED = "unknown"
|
|
CONFIRMED = "applied"
|
|
|
|
|
|
@typechecked
|
|
def effect_disposition(action_log: List[Dict], send_confirmed: bool = False) -> Disposition:
|
|
"""Whether anything left this machine, given what the run actually called.
|
|
|
|
"applied" needs positive proof, not a tool that returned ok: a click reporting success is not
|
|
evidence the write landed, which is the precise gap that told a user "Done, I sent it for you"
|
|
while the post never arrived.
|
|
"""
|
|
p_changing = [a for a in action_log or [] if str(a.get("tool") or "") not in READ_ONLY_TOOLS]
|
|
if not p_changing:
|
|
return NOTHING_HAPPENED
|
|
if send_confirmed:
|
|
return CONFIRMED
|
|
return MAY_HAVE_HAPPENED
|
|
|
|
|
|
@typechecked
|
|
def disposition_line(disposition: Disposition) -> str:
|
|
"""One sentence telling the caller what it is safe to do next."""
|
|
if disposition == NOTHING_HAPPENED:
|
|
return ("Nothing was changed on the page, so this is safe to retry.")
|
|
if disposition == CONFIRMED:
|
|
return ("The change was confirmed on the page, so do not repeat it.")
|
|
return ("A change was attempted and could not be confirmed, so this may or may not have gone "
|
|
"through. Read the page and check before retrying; repeating it blind risks doing it "
|
|
"twice.")
|
|
|
|
|
|
@typechecked
|
|
def unverified_reads_line(action_log: List[Dict]) -> str:
|
|
"""One line when the run tried to read the page and got nothing back, else "".
|
|
|
|
The honesty gate already refuses a run that only looked around, but that check is skipped the
|
|
moment any state-changing action succeeded. Haik's playlist run took that path: the clicks
|
|
reported ok, every read failed, and the agent handed the user "6 confirmed tracks" with titles
|
|
and artists. In his own words, "the path of least resistance was to paper over the ambiguity
|
|
with a confident-sounding answer" (ENG-404).
|
|
|
|
It labels rather than rejects on purpose. A click that lands while the verification read fails
|
|
is a real, honest partial result, and flipping that to an error would delete work to punish a
|
|
word. What the reader was missing is the distinction between verified and assumed, so say it.
|
|
"""
|
|
p_reads = [a for a in action_log or [] if str(a.get("tool") or "") in READ_ONLY_TOOLS]
|
|
if not p_reads:
|
|
return ""
|
|
if any(a.get("ok") and str(a.get("result_summary") or "").strip() for a in p_reads):
|
|
return ""
|
|
return ("No page content was read back successfully during this run, so any specific values "
|
|
"above are unverified and must not be treated as confirmed.")
|