core/agent: suppress end-of-turn todo nag on retry when turn-start pair survived

This commit is contained in:
2026-07-28 23:52:06 +08:00
parent a83c67ba6d
commit 0ce9ed84a0
3 changed files with 288 additions and 6 deletions
+7
View File
@@ -437,6 +437,12 @@ class ChatAgent:
# have survived rollback (it looks like a committed tool batch).
# Skip fresh injection — refresh_synthetic_todo_nags already updated
# the content of the surviving pair above.
_turn_nag_skipped = (
self.todo_stack is not None
and not self.todo_stack.is_empty()
and _turn_nag_strategy == "synthetic_tool"
and _synthetic_todo_pair_after(self.messages, user_index)
)
if (
self.todo_stack is not None
and not self.todo_stack.is_empty()
@@ -467,6 +473,7 @@ class ChatAgent:
max_context_tokens=self.max_context_tokens,
todo_stack=self.todo_stack,
todo_nag_strategy=_turn_nag_strategy,
turn_start_nag_skipped=_turn_nag_skipped,
directive_reminder_tokens=self.directive_reminder_tokens,
directive_reminder_text=self.directive_reminder_text,
reminder_last_band=self.reminder_last_band,
+14 -6
View File
@@ -360,6 +360,7 @@ async def run_chat_loop( # noqa: C901, PLR0912 — multi-phase tool loop
max_context_tokens: int = DEFAULT_CONTEXT_MAX_TOKENS,
todo_stack: TodoStack | None = None,
todo_nag_strategy: TodoNagStrategy = DEFAULT_TODO_NAG_STRATEGY,
turn_start_nag_skipped: bool = False,
directive_reminder_tokens: int = DEFAULT_DIRECTIVE_REMINDER_TOKENS,
directive_reminder_text: str | None = None,
reminder_last_band: int = 0,
@@ -453,12 +454,19 @@ async def run_chat_loop( # noqa: C901, PLR0912 — multi-phase tool loop
if not has_tools:
if todo_stack is not None and todo_stack.needs_review() and not todo_review_injected:
todo_review_injected = True
injected, nag_events = inject_todo_nag_for_stack_with_events(
messages,
todo_stack,
kind="end_of_turn",
strategy=todo_nag_strategy,
)
# On retry, the turn-start nag was skipped (pair survived
# rollback). The model already has the stack reminder
# in-history — skip the end-of-turn duplicate.
if turn_start_nag_skipped:
injected = False
nag_events: list[AgentEvent] = []
else:
injected, nag_events = inject_todo_nag_for_stack_with_events(
messages,
todo_stack,
kind="end_of_turn",
strategy=todo_nag_strategy,
)
for nag_event in nag_events:
yield nag_event
if injected: