From a1ffb27ef8417cd5a870d31d1b984e5ba562d2d7 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Sat, 5 Sep 2026 02:01:04 -0700 Subject: [PATCH 1/2] feat(file_tools): write_file says when it just re-sent a large file that was already on disk In the 1,393-agent refactor run write_file carried 92.8M chars of content (23.2M output tokens, ~$580, 20% of all output tokens). 661 of those calls were read -> whole-file rewrite of an existing file >20k chars (24.9M chars, ~$155): 130k-char agent/model_metadata.py, 109k model_setup_flows.py, 107k google_chat/adapter.py, re-sent in full to change a few lines. Meanwhile patch succeeded 4,602/4,617 (99.7%) at 1.3k chars per call. The tool result is the only place the model reads, so the cost signal goes there. When an overwrite targets an existing file and both old and new content are >=20k chars, write_file diffs them by line (difflib, autojunk off) and, if >=80% of lines are unchanged, adds a `hint` naming the unchanged count, the changed count, the size, and "use patch". The write itself is unchanged and still happens; new files, small files and genuine rewrites get no hint. 300 ms on the 119k-char model_metadata.py. Tests (2): a 40k-char file with 2 lines changed gets the hint and is written; a new file, a small file and a wholly rewritten file get none. --- tests/tools/test_write_file_rewrite_hint.py | 34 +++++++++++++++++++ tools/file_tools.py | 37 +++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 tests/tools/test_write_file_rewrite_hint.py diff --git a/tests/tools/test_write_file_rewrite_hint.py b/tests/tools/test_write_file_rewrite_hint.py new file mode 100644 index 0000000000..5ed14c3b60 --- /dev/null +++ b/tests/tools/test_write_file_rewrite_hint.py @@ -0,0 +1,34 @@ +"""write_file tells the caller when it just re-sent a large file that was already on disk. + +In one 1,393-agent run 661 read->whole-file-rewrites of >20k-char files cost ~25M output chars (~$155) +while `patch` (1.3k chars/call) succeeded 99.7% of the time; the tool result is the only place to say so. +""" +import json + +from tools.file_tools import write_file_tool + + +def _big(n_lines=800): + return "\n".join(f"line {i}: " + "x" * 40 for i in range(n_lines)) + "\n" # ~40k chars + + +def test_rewrite_of_a_large_file_with_few_changes_gets_a_patch_hint(tmp_path): + f = tmp_path / "mod.py" + old = _big() + f.write_text(old, encoding="utf-8") + new = old.replace("line 400:", "line 400 (edited):").replace("line 401:", "line 401 (edited):") + r = json.loads(write_file_tool(str(f), new, task_id="t")) + assert r.get("error") is None and f.read_text(encoding="utf-8") == new # the write still happens + assert "use patch" in r["hint"] and "798 of 800 lines were already on disk" in r["hint"] + + +def test_new_files_small_files_and_real_rewrites_get_no_hint(tmp_path): + new_file = tmp_path / "new.py" + assert "hint" not in json.loads(write_file_tool(str(new_file), _big(), task_id="t")) + small = tmp_path / "small.py" + small.write_text("a\nb\n", encoding="utf-8") + assert "hint" not in json.loads(write_file_tool(str(small), "a\nc\n", task_id="t")) + big = tmp_path / "big.py" + big.write_text(_big(), encoding="utf-8") + rewritten = "\n".join(f"other {i}: " + "y" * 40 for i in range(800)) + "\n" + assert "hint" not in json.loads(write_file_tool(str(big), rewritten, task_id="t")) diff --git a/tools/file_tools.py b/tools/file_tools.py index 280f896fcc..ac1c06e22c 100644 --- a/tools/file_tools.py +++ b/tools/file_tools.py @@ -709,6 +709,40 @@ def _note_edited(task_id: str, paths: list[str], path_to_resolved: dict, session file_state.note_write(task_id, path_to_resolved[p]) +# Whole-file rewrite hint: an overwrite of an existing file this large whose new content keeps at least +# this fraction of the old lines is a patch written the expensive way. In one 1,393-agent run 661 such +# rewrites of >20k-char files cost ~25M output chars (~$155) where `patch` averaged 1.3k chars/call. +_REWRITE_HINT_MIN_CHARS = 20_000 +_REWRITE_HINT_MIN_UNCHANGED = 0.80 + + +def _whole_file_rewrite_hint(resolved: str | None, new_content: str) -> str | None: + """Return a hint when ``new_content`` mostly re-sends what is already on disk at ``resolved``.""" + if not resolved or len(new_content) < _REWRITE_HINT_MIN_CHARS: + return None + try: + old = Path(resolved).read_text(encoding="utf-8", errors="replace") + except OSError: + return None + if len(old) < _REWRITE_HINT_MIN_CHARS: + return None + old_lines, new_lines = old.splitlines(), new_content.splitlines() + if not old_lines: + return None + import difflib + matcher = difflib.SequenceMatcher(None, old_lines, new_lines, autojunk=False) + unchanged = sum(size for _, _, size in matcher.get_matching_blocks()) + ratio = unchanged / max(len(old_lines), len(new_lines)) + if ratio < _REWRITE_HINT_MIN_UNCHANGED: + return None + changed = max(len(old_lines), len(new_lines)) - unchanged + return ( + f"{unchanged:,} of {len(new_lines):,} lines were already on disk ({ratio:.0%} unchanged); ~{changed:,} " + f"line(s) actually changed. Re-sending a {len(new_content):,}-char file costs output tokens for every " + "unchanged line; for edits like this use patch (old_string/new_string), which sends only the changed region." + ) + + def write_file_tool(path: str, content: str, task_id: str = "default", cross_profile: bool = False, session_id: str | None = None) -> str: @@ -741,9 +775,12 @@ def write_file_tool(path: str, content: str, task_id: str = "default", # subagents; different paths stay fully parallel. _lock.enter_context(file_state.lock_path(_resolved)) warnings = _edit_warnings([path], path_to_resolved, task_id) + rewrite_hint = _whole_file_rewrite_hint(_resolved, content) result_dict = _get_file_ops(task_id).write_file(_resolved or path, content).to_dict() if warnings: result_dict["_warning"] = warnings[0] + if rewrite_hint and not result_dict.get("error"): + result_dict["hint"] = rewrite_hint if _resolved: # Always report the ABSOLUTE path written so a wrong-cwd mismatch # is visible in the response instead of silently landing elsewhere. From 08a3c49812d708b6d86e791f7707c96ef1f9d42f Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Sat, 5 Sep 2026 06:32:30 -0700 Subject: [PATCH 2/2] fix(file_tools): the rewrite hint reads through the task's backend, skips non-regular and oversized files, and compares in linear time Independent review: the hint read the HOST path even for remote/sandbox backends (a different file), blocked forever on a host FIFO while holding the write lock, and used SequenceMatcher, which is quadratic on repeated lines (a 460 KB same-line file took ~22 s). It now reads the old content via the task's file ops read_file_raw (regular files only, correct backend), skips files above 400 K chars, and compares line multisets (Counter intersection, linear). Reviewer's own probe on this head: 460 KB repeated-line file 22.3 s -> 0.0 s (skipped), 230 KB 46 ms, remote backend gets no host-derived hint, FIFO write returns at once. --- tools/file_tools.py | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/tools/file_tools.py b/tools/file_tools.py index ac1c06e22c..0db7d67cae 100644 --- a/tools/file_tools.py +++ b/tools/file_tools.py @@ -716,22 +716,34 @@ _REWRITE_HINT_MIN_CHARS = 20_000 _REWRITE_HINT_MIN_UNCHANGED = 0.80 -def _whole_file_rewrite_hint(resolved: str | None, new_content: str) -> str | None: - """Return a hint when ``new_content`` mostly re-sends what is already on disk at ``resolved``.""" - if not resolved or len(new_content) < _REWRITE_HINT_MIN_CHARS: +# Above this the line diff is skipped: SequenceMatcher on pathological repeated-line files is +# quadratic (a 460 KB same-line file took ~22 s under the write lock). +_REWRITE_HINT_MAX_CHARS = 400_000 + + +def _whole_file_rewrite_hint(task_id: str, resolved: str | None, new_content: str) -> str | None: + """Return a hint when ``new_content`` mostly re-sends what is already at ``resolved``. + + Reads the OLD content through the task's own file ops (``read_file_raw``, the sandbox/remote + backend the write targets), never the host path: on a remote backend the host file is a + different file, and a host FIFO at that path would block the write lock forever. Bounded size + and a line multiset comparison (linear) instead of a sequence diff (quadratic on repeated lines).""" + if not resolved or not (_REWRITE_HINT_MIN_CHARS <= len(new_content) <= _REWRITE_HINT_MAX_CHARS): return None try: - old = Path(resolved).read_text(encoding="utf-8", errors="replace") - except OSError: + result = _get_file_ops(task_id).read_file_raw(resolved) + old = getattr(result, "content", None) + if getattr(result, "error", None) or not isinstance(old, str): + return None + except Exception: return None - if len(old) < _REWRITE_HINT_MIN_CHARS: + if not (_REWRITE_HINT_MIN_CHARS <= len(old) <= _REWRITE_HINT_MAX_CHARS): return None old_lines, new_lines = old.splitlines(), new_content.splitlines() if not old_lines: return None - import difflib - matcher = difflib.SequenceMatcher(None, old_lines, new_lines, autojunk=False) - unchanged = sum(size for _, _, size in matcher.get_matching_blocks()) + from collections import Counter + unchanged = sum((Counter(old_lines) & Counter(new_lines)).values()) ratio = unchanged / max(len(old_lines), len(new_lines)) if ratio < _REWRITE_HINT_MIN_UNCHANGED: return None @@ -775,7 +787,7 @@ def write_file_tool(path: str, content: str, task_id: str = "default", # subagents; different paths stay fully parallel. _lock.enter_context(file_state.lock_path(_resolved)) warnings = _edit_warnings([path], path_to_resolved, task_id) - rewrite_hint = _whole_file_rewrite_hint(_resolved, content) + rewrite_hint = _whole_file_rewrite_hint(task_id, _resolved, content) result_dict = _get_file_ops(task_id).write_file(_resolved or path, content).to_dict() if warnings: result_dict["_warning"] = warnings[0]