fix(stabilization): harden attachment lifecycle and agent guard signals (#5420)

* fix: harden stabilization attachment and agent guards

* fix(uploads): preserve durable references during cleanup

* fix(uploads): close cleanup and compaction races
This commit is contained in:
falabellamichael
2026-07-11 10:14:14 -04:00
committed by GitHub
parent 6b8f84553c
commit d16b849c3e
29 changed files with 2718 additions and 189 deletions
+41 -2
View File
@@ -3828,9 +3828,8 @@ async def stream_agent_loop(
and _intent_match is not None
and len(_intent_text) < 400
and "```" not in _intent_text
and _intent_nudge_count < _MAX_INTENT_NUDGES
)
if _looks_like_promise:
if _looks_like_promise and _intent_nudge_count < _MAX_INTENT_NUDGES:
_intent_nudge_count += 1
_matched_phrase = _intent_match.group(0).strip()
logger.info(f"[agent] intent-without-action nudge #{_intent_nudge_count} on round {round_num}: {_matched_phrase!r}")
@@ -3859,6 +3858,31 @@ async def stream_agent_loop(
# Visible signal in the stream so the user knows we caught it.
yield f'data: {json.dumps({"type": "agent_step", "round": round_num + 1})}\n\n'
continue
if _looks_like_promise:
_matched_phrase = _intent_match.group(0).strip()
_guard_message = (
"The agent stopped because it repeatedly announced a tool "
"action without making the tool call."
)
logger.warning(
"[agent] intent-without-action guard exhausted on round %d after %d nudges: %r",
round_num,
_intent_nudge_count,
_matched_phrase,
)
yield (
"data: "
+ json.dumps({
"type": "intent_nudge_exhausted",
"reason": "intent_without_action_nudge_cap",
"message": _guard_message,
"round": round_num,
"nudges": _intent_nudge_count,
"matched": _matched_phrase,
})
+ "\n\n"
)
break
break # no tools — done
# ── Loop-breaker (Terminus-style stall detector) ──────────────
@@ -3895,6 +3919,21 @@ async def stream_agent_loop(
reason = (f"calling {_runaway} with identical arguments over and over" if _runaway
else "repeating the same tool calls without new progress")
logger.warning(f"[agent] loop-breaker tripped on round {round_num} ({reason}); sig={_sig[:80]!r}")
yield (
"data: "
+ json.dumps({
"type": "loop_breaker_triggered",
"reason": "loop_breaker_stall",
"message": (
"The loop-breaker detected repeated tool calls without "
"new progress, so the agent is being forced to stop "
"using tools and give its best final answer."
),
"round": round_num,
"detail": reason,
})
+ "\n\n"
)
# The model has been executing tools, so its results are already
# in context. Force ONE tool-free round to converge: write the
# answer from what it has, or state plainly what's blocking it.