fix: prevent unchanged execution retry loops

This commit is contained in:
wuyang
2026-07-26 13:49:30 +08:00
parent 8a31a0edb6
commit f149379cf7
2 changed files with 170 additions and 3 deletions
+70 -1
View File
@@ -70,6 +70,7 @@ EXECUTION_REQUEST = re.compile(
MUTATION_TOOLS = {"write_file", "apply_patch"}
VERIFICATION_TOOLS = {"read_file", "git_status", "git_diff", "exec", "poll_process"}
EXECUTION_TOOLS = {"exec", "start_process", "poll_process"}
REPEAT_GUARDED_EXECUTION_TOOLS = {"exec", "start_process"}
MAX_CONSECUTIVE_CHECKPOINT_REJECTIONS = 3
@@ -133,6 +134,28 @@ def recovery_message(failure: str) -> str:
)
def execution_recovery_message(failures: list[str]) -> str:
details = "\n".join(f"- {failure}" for failure in failures)
return (
"Execution recovery is required before you may finish the task.\n"
f"{details}\n"
"Do not repeat an unchanged failed command. Text in assistant content does not edit a file. "
"Use read_file or search_files to inspect the actual source, then use write_file or apply_patch "
"to repair it (or use a materially different diagnostic command), and finally execute the relevant "
"check successfully. Keep benchmark inputs small enough for quadratic algorithms to finish."
)
def execution_call_key(name: str, arguments: dict[str, Any]) -> str | None:
if name not in REPEAT_GUARDED_EXECUTION_TOOLS:
return None
command = " ".join(str(arguments.get("command", "")).split())
if not command:
return None
cwd = str(arguments.get("cwd", ".")).strip() or "."
return f"{name}\0{cwd}\0{command}"
@dataclass(slots=True)
class RunRecorder:
store: RuntimeStore
@@ -246,6 +269,7 @@ class AgentLoop:
successful_tools = 0
successful_executions = 0
unresolved_execution_failure: str | None = None
failed_execution_revisions: dict[str, int] = {}
consecutive_checkpoint_rejections = 0
for iteration in range(max_iterations):
await recorder.emit(
@@ -321,6 +345,20 @@ class AgentLoop:
"tool_calls": tool_calls,
}
messages.append(assistant_message)
blocked_call_ids: set[str] = set()
mutation_planned = False
for call in tool_calls:
function = call.get("function") or {}
name = str(function.get("name", ""))
try:
arguments = json.loads(function.get("arguments") or "{}")
except json.JSONDecodeError:
arguments = {}
if name in MUTATION_TOOLS:
mutation_planned = True
key = execution_call_key(name, arguments) if isinstance(arguments, dict) else None
if key and not mutation_planned and failed_execution_revisions.get(key) == mutation_count:
blocked_call_ids.add(str(call.get("id") or ""))
results = await self._execute_calls(
calls=tool_calls,
spec=spec,
@@ -328,9 +366,16 @@ class AgentLoop:
context=tool_context,
depth=depth,
read_only=read_only,
blocked_call_ids=blocked_call_ids,
)
execution_failures: list[str] = []
for call, result in zip(tool_calls, results, strict=True):
name = str((call.get("function") or {}).get("name", ""))
function = call.get("function") or {}
name = str(function.get("name", ""))
try:
arguments = json.loads(function.get("arguments") or "{}")
except json.JSONDecodeError:
arguments = {}
if result.get("ok", False):
successful_tools += 1
if name in MUTATION_TOOLS:
@@ -342,6 +387,10 @@ class AgentLoop:
unresolved_execution_failure = None
elif name in EXECUTION_TOOLS:
unresolved_execution_failure = f"{name}: {public_tool_summary(result, 1000)}"
execution_failures.append(unresolved_execution_failure)
key = execution_call_key(name, arguments) if isinstance(arguments, dict) else None
if key:
failed_execution_revisions[key] = mutation_count
messages.append(
{
"role": "tool",
@@ -349,6 +398,13 @@ class AgentLoop:
"content": tool_result_text(result, self.max_tool_output_chars),
}
)
if execution_failures:
messages.append(
{
"role": "user",
"content": execution_recovery_message(execution_failures),
}
)
messages.append(
{
@@ -388,7 +444,9 @@ class AgentLoop:
context: ToolContext,
depth: int,
read_only: bool,
blocked_call_ids: set[str] | None = None,
) -> list[dict[str, Any]]:
blocked_call_ids = blocked_call_ids or set()
parsed: list[tuple[int, dict[str, Any], str, dict[str, Any], bool]] = []
for index, call in enumerate(calls):
function = call.get("function") or {}
@@ -419,6 +477,7 @@ class AgentLoop:
context=context,
depth=depth,
read_only=read_only,
blocked_repeat=str(call.get("id") or "") in blocked_call_ids,
)
index = 0
@@ -445,6 +504,7 @@ class AgentLoop:
context: ToolContext,
depth: int,
read_only: bool,
blocked_repeat: bool = False,
) -> dict[str, Any]:
call_id = str(call.get("id") or uuid.uuid4().hex)
public_args = {
@@ -456,6 +516,15 @@ class AgentLoop:
)
if "__parse_error__" in arguments:
result = {"ok": False, "error": arguments["__parse_error__"]}
elif blocked_repeat:
result = {
"ok": False,
"error": (
"Execution policy blocked this unchanged retry because the same command already failed "
"and no repair was applied afterward. Inspect and edit the underlying files first, or run "
"a materially different diagnostic command."
),
}
elif name not in TOOL_METADATA:
result = {"ok": False, "error": f"Unknown tool: {name}"}
elif read_only and name not in READ_ONLY_TOOL_NAMES: