mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-02 14:28:59 +02:00
Every silent-stop fix so far was a detector for a shape somebody had already found in the field, which is why the class kept coming back wearing a new hat. turn_spoke.py moves the question down a tier: at the one exit every terminal path passes through, ask whether anything readable appeared since the user last spoke, and if not, say the honest line. Cause no longer has to be enumerated for the user to be answered. proactive_prune.py is the hermes trigger we were missing. Their own tests say our bug out loud: on a large window, a percentage-of-window compaction check almost never fires, so aged tool output rides in history and is re-sent verbatim every turn. Measured here, our shaping cut 0.0% at every session size; with a fixed 60K-token cost trigger it cuts 88% at 12 turns, 93% at 30, 94% at 60. The prompt-cache contract is load-bearing rather than optional, because our prune is a rebuild: it commits only when it reclaims enough to pay for the busted prefix, then disarms until history has regrown a full runway. Also: lane preflight now treats only 401/403 as a dead credential, since testStatus=="unavailable" conflated a throttled lane with a revoked one and told users to reconnect a merely rate-limited Gemini; and awaiting_reconnect is cleared when the retry budget is spent, so a stale flag can no longer muzzle the floor and end an ask in total silence. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014wtspwSFzZmjCx9UNPAorQ
92 lines
4.0 KiB
Python
92 lines
4.0 KiB
Python
"""The floor: a finished turn always left the user something to read.
|
|
|
|
Every earlier silent-stop fix was a detector for one shape found in the field, which means the
|
|
NEXT shape nobody has found yet still ships as silence. This asks the user's own question at the
|
|
one exit every terminal path passes through, so the state stops being representable rather than
|
|
being enumerated.
|
|
"""
|
|
|
|
from backend.apps.agents.core.models import AgentSession, Message
|
|
from backend.apps.agents.manager.run.empty_finish import EXHAUSTED_NOTE
|
|
from backend.apps.agents.manager.run.turn_spoke import ensure_turn_spoke, turn_left_the_user_with_nothing
|
|
|
|
|
|
def p_session(*msgs, status="completed") -> AgentSession:
|
|
s = AgentSession(name="t", model="sonnet", dashboard_id="d")
|
|
s.status = status
|
|
for role, content in msgs:
|
|
s.messages.append(Message(role=role, content=content, branch_id=s.active_branch_id))
|
|
return s
|
|
|
|
|
|
def p_cards(s):
|
|
return [m for m in s.messages if m.role == "system"]
|
|
|
|
|
|
def test_a_shape_no_detector_knows_about_still_gets_a_line():
|
|
"""The whole point: this must fire for causes nobody has enumerated. Here the turn ended with
|
|
only a tool call, which no existing detector claimed."""
|
|
s = p_session(("user", "audit the repo"), ("tool_call", {"tool": "Bash"}))
|
|
assert turn_left_the_user_with_nothing(s) is True
|
|
assert ensure_turn_spoke(s, "sid") is True
|
|
assert [m.content for m in p_cards(s)] == [EXHAUSTED_NOTE]
|
|
|
|
|
|
def test_it_never_speaks_over_a_turn_that_answered():
|
|
s = p_session(("user", "hi"), ("assistant", "here is your answer"))
|
|
assert ensure_turn_spoke(s, "sid") is False
|
|
assert p_cards(s) == []
|
|
|
|
|
|
def test_it_never_doubles_up_on_a_detector_that_already_spoke():
|
|
"""Upstream detectors write BETTER messages because they know why; the floor must stay quiet
|
|
whenever one of them already did the job."""
|
|
s = p_session(("user", "go"), ("tool_call", {"tool": "Read"}),
|
|
("system", "Your ChatGPT subscription needs reconnecting."))
|
|
assert ensure_turn_spoke(s, "sid") is False
|
|
assert len(p_cards(s)) == 1
|
|
|
|
|
|
def test_a_rendered_widget_counts_as_an_answer():
|
|
s = p_session(("user", "pick one"), ("tool_call", {"tool": "mcp__openswarm-ui__AskUI"}))
|
|
assert ensure_turn_spoke(s, "sid") is False
|
|
|
|
|
|
def test_a_still_running_turn_is_left_alone():
|
|
s = p_session(("user", "go"), ("tool_call", {"tool": "Read"}), status="running")
|
|
assert ensure_turn_spoke(s, "sid") is False
|
|
|
|
|
|
def test_a_parked_turn_is_not_over_so_it_stays_quiet():
|
|
"""Speaking over a turn that is about to resume would be the lie this exists to prevent."""
|
|
s = p_session(("user", "go"), ("tool_call", {"tool": "Read"}))
|
|
s.awaiting_reconnect = True
|
|
assert ensure_turn_spoke(s, "sid") is False
|
|
s.awaiting_reconnect = False
|
|
s.pending_continuation = True
|
|
assert ensure_turn_spoke(s, "sid") is False
|
|
|
|
|
|
def test_a_user_stop_needs_no_explanation():
|
|
"""They pressed stop; telling them it stopped is noise, not honesty."""
|
|
s = p_session(("user", "go"), ("tool_call", {"tool": "Read"}), status="stopped")
|
|
assert ensure_turn_spoke(s, "sid") is False
|
|
|
|
|
|
def test_only_work_since_the_LAST_ask_counts():
|
|
"""A reply to the previous question must not excuse silence on the current one."""
|
|
s = p_session(("user", "first"), ("assistant", "answered the first"),
|
|
("user", "second"), ("tool_call", {"tool": "Read"}))
|
|
assert turn_left_the_user_with_nothing(s) is True
|
|
|
|
|
|
def test_hidden_machinery_is_invisible_to_the_check():
|
|
"""Our own hidden continuations are not the user speaking, and not us answering."""
|
|
s = AgentSession(name="t", model="sonnet", dashboard_id="d")
|
|
s.status = "completed"
|
|
s.messages.append(Message(role="user", content="go", branch_id=s.active_branch_id))
|
|
s.messages.append(Message(role="tool_call", content={"tool": "Read"}, branch_id=s.active_branch_id))
|
|
s.messages.append(Message(role="user", content="[Automated] continue",
|
|
branch_id=s.active_branch_id, hidden=True))
|
|
assert turn_left_the_user_with_nothing(s) is True
|