mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-30 13:34:50 +02:00
[eric] agents: silent-quit re-nudges are earned by new tool work, capped at 3, stalls surface honestly (ENG-121 family)
This commit is contained in:
@@ -135,6 +135,8 @@ class AgentSession(BaseModel):
|
||||
pending_continuation_prompt: Optional[str] = None
|
||||
# Silent-quit nudges spent since the user's last real message; capped at 1 so an agent that keeps ending empty can't loop.
|
||||
empty_finish_nudges: int = 0
|
||||
# Tool-call count at the last nudge: a re-nudge is only earned by NEW tool work since then.
|
||||
empty_finish_progress_mark: int = 0
|
||||
# Sanitized server names model has explicitly activated this session; _build_mcp_servers intersects connected MCPs with this. Non-bypassable; dispatch-layer gate.
|
||||
active_mcps: list[str] = Field(default_factory=list)
|
||||
# Heuristic preamble tokens (preset + tool defs + MCP descs + composed prompt); subtracted from displayed input.
|
||||
|
||||
@@ -136,6 +136,7 @@ class Messaging(AgentManagerProtocol):
|
||||
# A real user message opens a fresh silent-quit budget; the cap only guards within one ask.
|
||||
if not hidden:
|
||||
session.empty_finish_nudges = 0
|
||||
session.empty_finish_progress_mark = 0
|
||||
# Fire a background aux LLM call to generate a 3-6 word verb-phrase describing this turn ("Auditing the pull request", "Drafting your email"). The narrator pill swaps from its heuristic verb to this label as soon as it lands, usually ~500ms-1s into the turn, which is exactly when "Thinking…" starts feeling generic. Provider-agnostic via resolve_aux_model. Non-blocking; failure is silent and the heuristic stays.
|
||||
if not hidden and prompt:
|
||||
try:
|
||||
|
||||
@@ -20,14 +20,23 @@ NUDGE_PROMPT = (
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
NUDGE_HARD_CAP = 3
|
||||
|
||||
|
||||
@typechecked
|
||||
def maybe_nudge_empty_finish(session: AgentSession, session_id: str) -> bool:
|
||||
"""Arm one hidden continue nudge when the finished turn quit silently; the loop's existing
|
||||
auto-continuation block dispatches it. Capped per user ask; never loops."""
|
||||
if getattr(session, "pending_continuation", False) or session.empty_finish_nudges >= 1:
|
||||
"""Arm a hidden continue nudge when the finished turn quit silently; the loop's existing
|
||||
auto-continuation block dispatches it. A re-nudge must be EARNED by new tool work since the
|
||||
last one (the model is visibly still working, just mute); a stalled continuation surfaces
|
||||
honestly, so this can never ping-pong a model that has nothing left to do."""
|
||||
if getattr(session, "pending_continuation", False) or session.empty_finish_nudges >= NUDGE_HARD_CAP:
|
||||
return False
|
||||
if not turn_finished_empty(session):
|
||||
return False
|
||||
p_tool_calls = p_count_tool_calls(session)
|
||||
if session.empty_finish_nudges >= 1 and p_tool_calls <= session.empty_finish_progress_mark:
|
||||
return False
|
||||
session.empty_finish_progress_mark = p_tool_calls
|
||||
session.empty_finish_nudges += 1
|
||||
session.pending_continuation = True
|
||||
session.pending_continuation_prompt = NUDGE_PROMPT
|
||||
@@ -43,6 +52,11 @@ def maybe_nudge_empty_finish(session: AgentSession, session_id: str) -> bool:
|
||||
P_ANSWER_TOOL_MARKERS = ("openswarm-ui", "ShowUI", "AskUI", "AskUserQuestion")
|
||||
|
||||
|
||||
@typechecked
|
||||
def p_count_tool_calls(session: AgentSession) -> int:
|
||||
return sum(1 for m in get_branch_messages(session) if getattr(m, "role", "") == "tool_call")
|
||||
|
||||
|
||||
def p_tool_name_of(msg: object) -> str:
|
||||
content = getattr(msg, "content", None)
|
||||
if isinstance(content, dict):
|
||||
|
||||
Reference in New Issue
Block a user