mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-10 11:47:43 +02:00
Revert "[eric] agents: the policy ratchet narrows the declared recap mode, not whatever the turn happened to render"
This reverts commit 729dc968bc.
This commit is contained in:
@@ -31,7 +31,6 @@ from backend.apps.agents.core.error_classify import (
|
||||
from backend.apps.agents.core.is_router_unavailable_error import is_router_unavailable_error
|
||||
from backend.apps.agents.core.extract_reset_hint import extract_reset_hint
|
||||
from backend.apps.agents.core.redact_for_telemetry import redact_for_telemetry
|
||||
from backend.apps.agents.manager.run.RunOptions import PREFIX_NARROWNESS
|
||||
from backend.apps.agents.core import flight_recorder
|
||||
from backend.apps.agents.manager.run.empty_finish import count_tool_calls
|
||||
from backend.apps.agents.session_credential import api_key_twin_model
|
||||
@@ -377,23 +376,14 @@ async def handle_run_error(e: Exception, session: AgentSession, session_id: str,
|
||||
# The provider's abuse classifier declined the REQUEST, and on the subscription lane what it reads as "duplicating model outputs" is OUR recap of the chat on a fresh CLI session (192 blocks in 14 days, 0 on API keys); deterministic, so the one retry that can pass carries no history at all, the session stays that way, and every block is reported with the shape it sent.
|
||||
p_sent = session.history_prefix_sent
|
||||
p_report_model_error(f"policy_block:{p_sent}", session_id, session, turn, e, p_stderr_tail)
|
||||
# Ratchet on the DECLARED mode, never on what happened to be rendered. `history_prefix_sent`
|
||||
# defaults to "none" on every turn and is only overwritten when a fresh-session rebuild
|
||||
# actually attaches a recap, so on a RESUMED turn (the common case) it reads "none" while the
|
||||
# mode is still "minimal". Keying the ladder on it meant a block on a resumed turn skipped the
|
||||
# ratchet entirely, went straight to "nothing left to strip", and left the mode untouched, so
|
||||
# the next rebuild sent a recap again and blocked again. Live: one chat blocked 3x with
|
||||
# sent going none -> minimal -> none, which ENG-399 says can never happen (2026-08-30).
|
||||
p_mode = session.history_prefix_mode
|
||||
if p_mode != "none":
|
||||
p_next = PREFIX_NARROWNESS[PREFIX_NARROWNESS.index(p_mode) + 1]
|
||||
session.history_prefix_mode = p_next
|
||||
if p_sent != "none":
|
||||
session.history_prefix_mode = "none"
|
||||
session.needs_fresh_session = True
|
||||
session.pending_continuation = True
|
||||
session.pending_continuation_prompt = (
|
||||
"Continue the task exactly where you left off; the session summary was reduced "
|
||||
"this turn, rely on the visible conversation.")
|
||||
logger.warning(f"Agent {session_id}: provider content-policy block (sent={p_sent}); narrowing the recap {p_mode} -> {session.history_prefix_mode} and retrying")
|
||||
logger.warning(f"Agent {session_id}: provider content-policy block on a turn carrying a {p_sent} history prefix; retrying with {session.history_prefix_mode}")
|
||||
return
|
||||
# The subscription lane declined and nothing is left to strip; fleet data says the same request passes on an API key (0 of 328 vs 4.4%), so a user who connected their own Anthropic key continues there, told in one line, instead of losing the ask (ENG-383).
|
||||
p_twin = api_key_twin_model(session.model or "", load_settings())
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
"""A blocked chat retried, blocked, retried, blocked (Ken, 2026-08-31; envelopes 2026-08-30).
|
||||
|
||||
The ratchet keyed on `history_prefix_sent`, which is an OUTCOME: RunOptions sets it to "none" at the
|
||||
top of every turn and only overwrites it when a fresh-session rebuild actually attaches a recap. On a
|
||||
RESUMED turn nothing is attached, so it reads "none" while the declared mode is still "minimal".
|
||||
|
||||
The handler read that "none" as "nothing left to strip", skipped the ladder, and went straight to the
|
||||
terminal card WITHOUT narrowing anything. The next rebuild therefore sent a recap again and blocked
|
||||
again. The fleet shows the impossible sequence that proves it: one session, three blocks, sent going
|
||||
none -> minimal -> none, when ENG-399 says a session ratcheted to none is never widened back.
|
||||
|
||||
Rule this restores: never key a guard on an incidental fact; use the DECLARED signal.
|
||||
"""
|
||||
import pytest
|
||||
|
||||
from backend.apps.agents.core.models import AgentSession
|
||||
from backend.apps.agents.manager.run.RunOptions import PREFIX_NARROWNESS
|
||||
|
||||
|
||||
def p_session(mode, sent):
|
||||
s = AgentSession(name="t", model="sonnet", dashboard_id="d")
|
||||
s.history_prefix_mode = mode
|
||||
s.history_prefix_sent = sent
|
||||
return s
|
||||
|
||||
|
||||
def test_sent_is_an_outcome_not_the_state():
|
||||
"""The field the old code trusted defaults to 'none' on a turn that simply resumed."""
|
||||
import inspect
|
||||
from backend.apps.agents.manager.run import RunOptions
|
||||
src = inspect.getsource(RunOptions)
|
||||
assert 'session.history_prefix_sent = "none"' in src, (
|
||||
"if this default ever goes away, the bug's premise changes and this file should be re-read"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("mode,expected", [("minimal", "summary"), ("summary", "none")])
|
||||
async def test_a_block_on_a_RESUMED_turn_still_narrows(monkeypatch, mode, expected):
|
||||
"""sent='none' (nothing attached) while the mode is wider: the ladder MUST still step."""
|
||||
from backend.apps.agents.manager.run import handle_run_error as mod
|
||||
from backend.apps.agents.manager.streaming.state import TurnState
|
||||
|
||||
async def p_send(*a, **k):
|
||||
return None
|
||||
monkeypatch.setattr(mod.ws_manager, "send_to_session", p_send)
|
||||
monkeypatch.setattr(mod, "p_report_model_error", lambda *a, **k: None)
|
||||
|
||||
s = p_session(mode, "none")
|
||||
err = RuntimeError(
|
||||
'API Error: 400 {"error":{"message":"Output blocked as it seems to violate our '
|
||||
'Acceptable Use Policy (legal/aup): reverse engineering or duplicating model outputs"}}'
|
||||
)
|
||||
await mod.handle_run_error(err, s, s.id, TurnState(), [])
|
||||
|
||||
assert s.history_prefix_mode == expected, "the declared mode must narrow one step"
|
||||
assert s.pending_continuation is True, "and the narrowed retry must actually be armed"
|
||||
assert [m for m in s.messages if m.role == "system"] == [], "no terminal card while the ladder has room"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_only_an_EXHAUSTED_ladder_reaches_the_terminal_card(monkeypatch):
|
||||
from backend.apps.agents.manager.run import handle_run_error as mod
|
||||
from backend.apps.agents.manager.streaming.state import TurnState
|
||||
|
||||
async def p_send(*a, **k):
|
||||
return None
|
||||
monkeypatch.setattr(mod.ws_manager, "send_to_session", p_send)
|
||||
monkeypatch.setattr(mod, "p_report_model_error", lambda *a, **k: None)
|
||||
monkeypatch.setattr(mod, "api_key_twin_model", lambda *a, **k: None) # no own key, like the field
|
||||
|
||||
s = p_session("none", "none")
|
||||
await mod.handle_run_error(RuntimeError("blocked ... legal/aup ... duplicating model outputs"),
|
||||
s, s.id, TurnState(), [])
|
||||
cards = [m for m in s.messages if m.role == "system"]
|
||||
assert cards, "with nothing left to strip the user is owed the honest card"
|
||||
assert "start a fresh chat" in cards[-1].content.lower()
|
||||
assert s.pending_continuation is False, "and it must NOT keep retrying a request that cannot pass"
|
||||
|
||||
|
||||
def test_the_ladder_only_ever_narrows():
|
||||
assert PREFIX_NARROWNESS == ("minimal", "summary", "none")
|
||||
for i, mode in enumerate(PREFIX_NARROWNESS[:-1]):
|
||||
assert PREFIX_NARROWNESS.index(PREFIX_NARROWNESS[i + 1]) > PREFIX_NARROWNESS.index(mode)
|
||||
Reference in New Issue
Block a user