v10.13.8: hard tool-strip on budget cap, compaction output stripping, null-tool loop detection

This commit is contained in:
Roman | RyzenAdvanced
2026-05-27 17:08:21 +04:00
Unverified
parent da43297953
commit 6861700c0d
3 changed files with 81 additions and 55 deletions

View File

@@ -27,21 +27,14 @@ model_catalog_json = ""
""" """
CHANGELOG = [ CHANGELOG = [
("10.13.7", "2026-05-27", [ ("10.13.8", "2026-05-27", [
"Fix: compaction summary strips raw tool outputs (was re-triggering read loops)",
"Fix: budget cap now strips tools from request (model literally cannot call tools)",
"Fix: detect get_goal/completion_budget null-tool loops (3+ consecutive → force finalize)",
"Fix: post-compaction write directive when 10+ reads with 0 writes",
"Fix: strip timestamps from loop hash (<current_date> broke cross-session tracker)", "Fix: strip timestamps from loop hash (<current_date> broke cross-session tracker)",
"Fix: strip base64 image data from tool outputs in normalizer (multi-MB payload bloat)", "Fix: strip base64 image data from tool outputs in normalizer",
"Fix: thread-safe file tracker (was unprotected dict in ThreadingHTTPServer)", "Fix: thread-safe file tracker, response logging for finalize/budget paths",
"Fix: response logging for finalize and budget-cap paths",
"Anti-loop: cross-session tracker, tool-call budget (150), file read-loop detection",
"Auto 401 token refresh with retry (v2 + OA compat)",
"Model-aware idle timeout: flash models 120s, pro 300s",
"Smart compaction: directive summary on read-loops",
"Default provider policy for unrecognized backends",
"Anti-stall fix: no longer kills own parent/process group",
"Codex Desktop Updater: check/install/rollback/manual rebuild",
"Fix Codex CLI 0.134.0 profiles: separate .config.toml files",
"E2E test suite: bash test-antigravity.sh [--task]",
"Merge cobra91 PR #17: MSIX Desktop launch, button state",
]), ]),
("3.12.1", "2026-05-27", [ ("3.12.1", "2026-05-27", [
"Fix Antigravity adapter (PR #15): simplified model resolution", "Fix Antigravity adapter (PR #15): simplified model resolution",

View File

@@ -83,21 +83,14 @@ model_catalog_json = ""
""" """
CHANGELOG = [ CHANGELOG = [
("10.13.7", "2026-05-27", [ ("10.13.8", "2026-05-27", [
"Fix: compaction summary strips raw tool outputs (was re-triggering read loops)",
"Fix: budget cap now strips tools from request (model literally cannot call tools)",
"Fix: detect get_goal/completion_budget null-tool loops (3+ consecutive → force finalize)",
"Fix: post-compaction write directive when 10+ reads with 0 writes",
"Fix: strip timestamps from loop hash (<current_date> broke cross-session tracker)", "Fix: strip timestamps from loop hash (<current_date> broke cross-session tracker)",
"Fix: strip base64 image data from tool outputs in normalizer (multi-MB payload bloat)", "Fix: strip base64 image data from tool outputs in normalizer",
"Fix: thread-safe file tracker (was unprotected dict in ThreadingHTTPServer)", "Fix: thread-safe file tracker, response logging for finalize/budget paths",
"Fix: response logging for finalize and budget-cap paths",
"Anti-loop: cross-session tracker, tool-call budget (150), file read-loop detection",
"Auto 401 token refresh with retry",
"Model-aware idle timeout: flash 120s, pro 300s",
"Smart compaction: directive summary on read-loops",
"Default provider policy for unrecognized backends",
"Anti-stall fix: no longer kills own parent/process group",
"Codex Desktop Updater: check/install/rollback/manual rebuild",
"Fix Codex CLI 0.134.0 profiles: separate .config.toml files",
"E2E test suite: bash test-antigravity.sh [--task]",
"Merge cobra91 PR #17: MSIX Desktop launch, button state",
]), ]),
("3.12.1", "2026-05-27", [ ("3.12.1", "2026-05-27", [
"Fix Antigravity adapter (PR #15): simplify model resolution", "Fix Antigravity adapter (PR #15): simplify model resolution",

View File

@@ -1879,7 +1879,7 @@ def _crof_compact_for_retry(input_data, model, aggression=0):
return head + tail return head + tail
summary_lines = [f"[Auto-compacted: {len(body)} turns removed (adaptive limit={limit})]"] summary_lines = [f"[Auto-compacted: {len(body)} turns removed (adaptive limit={limit})]"]
for item in body[-5:]: for item in body[-3:]:
summary_lines.append(_item_summary(item, max_len=120)) summary_lines.append(_item_summary(item, max_len=120))
summary_msg = {"type": "message", "role": "user", "content": [{"type": "input_text", "text": "\n".join(summary_lines)}]} summary_msg = {"type": "message", "role": "user", "content": [{"type": "input_text", "text": "\n".join(summary_lines)}]}
@@ -1908,9 +1908,13 @@ def _item_summary(item, max_len=200):
return f"[tool call] {name}({args[:max_len]})" return f"[tool call] {name}({args[:max_len]})"
elif t == "function_call_output": elif t == "function_call_output":
output = item.get("output", "") output = item.get("output", "")
if len(output) > max_len: out_len = len(output) if isinstance(output, str) else len(str(output))
return f"[tool result] {output[:max_len]}..." exit_match = re.search(r'Process exited with code (\d+)', output if isinstance(output, str) else str(output))
return f"[tool result] {output}" exit_code = exit_match.group(1) if exit_match else "?"
tok_match = re.search(r'Original token count: (\d+)', output if isinstance(output, str) else str(output))
tok_count = tok_match.group(1) if tok_match else ""
tok_info = f" ({tok_count} tokens)" if tok_count else ""
return f"[tool result] exit={exit_code}{tok_info} {out_len} chars"
return f"[{t}]" return f"[{t}]"
def _extract_files(items): def _extract_files(items):
@@ -1998,6 +2002,10 @@ def _compact_input(input_data):
tool_summaries.append(_item_summary(item, max_len=150)) tool_summaries.append(_item_summary(item, max_len=150))
files = _extract_files(body) files = _extract_files(body)
n_read_tools = sum(1 for it in body if it.get("type") == "function_call"
and any(k in it.get("arguments", "") for k in ["cat ", "head ", "tail ", "sed -n", "grep ", "less ", "more ", "python3 -c", ".read()"]))
n_write_tools = sum(1 for it in body if it.get("type") == "function_call"
and any(k in it.get("arguments", "") for k in ["write(", ".write", " > ", " >> ", "sed -i", "patch "]))
summary_lines = [f"[Auto-compacted: {len(body)} earlier turns summarized to preserve context]"] summary_lines = [f"[Auto-compacted: {len(body)} earlier turns summarized to preserve context]"]
if user_queries: if user_queries:
@@ -2005,11 +2013,13 @@ def _compact_input(input_data):
if assistant_msgs: if assistant_msgs:
summary_lines.append(f"Assistant responses: {'; '.join(assistant_msgs[-3:])}") summary_lines.append(f"Assistant responses: {'; '.join(assistant_msgs[-3:])}")
if tool_summaries: if tool_summaries:
summary_lines.append(f"Actions taken ({len(tool_summaries)} steps):") summary_lines.append(f"Actions taken ({len(tool_summaries)} steps, {n_read_tools} reads, {n_write_tools} writes):")
for ts in tool_summaries[-15:]: for ts in tool_summaries[-5:]:
summary_lines.append(f" {ts}") summary_lines.append(f" {ts}")
if files: if files:
summary_lines.append(f"Files touched: {', '.join(sorted(files)[-10:])}") summary_lines.append(f"Files touched: {', '.join(sorted(files)[-10:])}")
if n_read_tools > 10 and n_write_tools == 0:
summary_lines.append("⚠ You have already read these files extensively but made NO edits. You MUST write your changes NOW. Do NOT read any more files.")
summary_text = "\n".join(summary_lines) summary_text = "\n".join(summary_lines)
summary_msg = { summary_msg = {
@@ -2135,7 +2145,7 @@ def _adaptive_compact(input_data, model, policy=None):
if not body: if not body:
return head + tail, True return head + tail, True
summary_lines = [f"[Auto-compacted: {len(body)} turns removed (budget={input_budget}tok, model={model})]"] summary_lines = [f"[Auto-compacted: {len(body)} turns removed (budget={input_budget}tok, model={model})]"]
for item in body[-5:]: for item in body[-3:]:
summary_lines.append(_item_summary(item, max_len=120)) summary_lines.append(_item_summary(item, max_len=120))
summary_msg = {"type": "message", "role": "user", summary_msg = {"type": "message", "role": "user",
"content": [{"type": "input_text", "text": "\n".join(summary_lines)}]} "content": [{"type": "input_text", "text": "\n".join(summary_lines)}]}
@@ -5907,6 +5917,18 @@ class Handler(http.server.BaseHTTPRequestHandler):
file=sys.stderr) file=sys.stderr)
break break
null_tool_names = {"get_goal", "get_remaining_tokens", "get_completion_budget", "status"}
consecutive_null = 0
for item in reversed(input_data):
if isinstance(item, dict):
if item.get("type") == "function_call" and item.get("name") in null_tool_names:
consecutive_null += 1
elif item.get("type") == "function_call":
break
if consecutive_null >= 3:
ag_state["force_finalize"] = True
print(f"[{getattr(self, '_session_id', '?')}] [antigravity-loop] NULL-TOOL LOOP: {consecutive_null} consecutive {null_tool_names} calls, forcing finalize", file=sys.stderr)
last_tool_key = None last_tool_key = None
for item in reversed(input_data): for item in reversed(input_data):
if isinstance(item, dict) and item.get("type") == "function_call": if isinstance(item, dict) and item.get("type") == "function_call":
@@ -5940,9 +5962,12 @@ class Handler(http.server.BaseHTTPRequestHandler):
request_body["systemInstruction"] = {"role": "user", "parts": system_parts} request_body["systemInstruction"] = {"role": "user", "parts": system_parts}
if gen_config: if gen_config:
request_body["generationConfig"] = gen_config request_body["generationConfig"] = gen_config
if gemini_tools: _budget_exceeded = ag_state.get("total_tool_calls", 0) > _ANTIGRAVITY_MAX_TOOL_CALLS_PER_TASK
if gemini_tools and not _budget_exceeded and not ag_state.get("force_finalize"):
request_body["tools"] = gemini_tools request_body["tools"] = gemini_tools
if _is_claude_model and gemini_tools: elif _budget_exceeded or ag_state.get("force_finalize"):
print(f"[{getattr(self, '_session_id', '?')}] [antigravity-budget] TOOLS STRIPPED from request (budget exceeded or force_finalize)", file=sys.stderr)
if _is_claude_model and "tools" in request_body:
request_body["toolConfig"] = {"functionCallingConfig": {"mode": "VALIDATED"}} request_body["toolConfig"] = {"functionCallingConfig": {"mode": "VALIDATED"}}
import platform as _plat import platform as _plat
@@ -6720,6 +6745,18 @@ class Handler(http.server.BaseHTTPRequestHandler):
f"{ft['path_counts'][dp]}x, total={ft['total_reads']}", file=sys.stderr) f"{ft['path_counts'][dp]}x, total={ft['total_reads']}", file=sys.stderr)
break break
null_tool_names = {"get_goal", "get_remaining_tokens", "get_completion_budget", "status"}
consecutive_null = 0
for item in reversed(input_data):
if isinstance(item, dict):
if item.get("type") == "function_call" and item.get("name") in null_tool_names:
consecutive_null += 1
elif item.get("type") == "function_call":
break
if consecutive_null >= 3:
ag_state["force_finalize"] = True
print(f"[{getattr(self, '_session_id', '?')}] [antigravity-loop] NULL-TOOL LOOP: {consecutive_null} consecutive {null_tool_names} calls, forcing finalize", file=sys.stderr)
last_tool_key = None last_tool_key = None
for item in reversed(input_data): for item in reversed(input_data):
if isinstance(item, dict) and item.get("type") == "function_call": if isinstance(item, dict) and item.get("type") == "function_call":
@@ -6789,10 +6826,13 @@ class Handler(http.server.BaseHTTPRequestHandler):
request_body["systemInstruction"] = {"parts": system_parts} request_body["systemInstruction"] = {"parts": system_parts}
if gen_config: if gen_config:
request_body["generationConfig"] = gen_config request_body["generationConfig"] = gen_config
if gemini_tools: _budget_exceeded_oa = ag_state.get("total_tool_calls", 0) > _ANTIGRAVITY_MAX_TOOL_CALLS_PER_TASK
if gemini_tools and not _budget_exceeded_oa and not ag_state.get("force_finalize"):
request_body["tools"] = gemini_tools request_body["tools"] = gemini_tools
elif _budget_exceeded_oa or ag_state.get("force_finalize"):
print(f"[antigravity-budget] TOOLS STRIPPED from OA request (budget exceeded or force_finalize)", file=sys.stderr)
if OAUTH_PROVIDER == "google-antigravity" and _is_claude_model and gemini_tools: if OAUTH_PROVIDER == "google-antigravity" and _is_claude_model and "tools" in request_body:
request_body["toolConfig"] = {"functionCallingConfig": {"mode": "VALIDATED"}} request_body["toolConfig"] = {"functionCallingConfig": {"mode": "VALIDATED"}}
if _is_claude_thinking: if _is_claude_thinking:
print(f"[antigravity-claude] applied VALIDATED toolConfig for thinking model", file=sys.stderr) print(f"[antigravity-claude] applied VALIDATED toolConfig for thinking model", file=sys.stderr)