Skip to content

Commit a53668a

Browse files
committed
fix(autonomous-loop): cap read_file and run_bash output
grep_tool capped its output at 64 KB; read_file_tool returned f.read() whole and run_bash_tool returned the complete stdout and stderr. A 2 MB log or a failing test suite therefore entered the transcript in full, and the loop re-serializes its history into every later prompt, so one oversized result is paid for on every remaining call of the run. Lift grep's cap into a shared truncate_tool_output and route all three tools through it. The marker now reports how much was cut, so the model can narrow its query rather than guessing that output was complete. read_file's memory note still reports the file's real length, not the truncated one. Closes kyegomez#1971
1 parent 8db8662 commit a53668a

2 files changed

Lines changed: 39 additions & 11 deletions

File tree

swarms/structs/autonomous_loop_utils.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,21 @@
4545
MAX_SUBTASK_LOOPS = 20
4646
MAX_CONSECUTIVE_THINKS = 2
4747

48+
# The loop re-serializes its history into every later prompt, so one oversized
49+
# tool result is paid for on every remaining call of the run.
50+
MAX_TOOL_OUTPUT_CHARS = 65536
51+
52+
53+
def truncate_tool_output(text: str) -> str:
54+
"""Cap a tool result, telling the model how much it is not seeing."""
55+
if len(text) <= MAX_TOOL_OUTPUT_CHARS:
56+
return text
57+
return (
58+
text[:MAX_TOOL_OUTPUT_CHARS]
59+
+ "\n... (output truncated: showing the first "
60+
+ f"{MAX_TOOL_OUTPUT_CHARS} of {len(text)} characters)"
61+
)
62+
4863

4964
# Prompts.
5065

@@ -784,12 +799,13 @@ def read_file_tool(agent: Any, file_path: str, **kwargs) -> str:
784799

785800
# Read file
786801
with open(full_path, "r", encoding="utf-8") as f:
787-
content = f.read()
802+
raw = f.read()
803+
content = truncate_tool_output(raw)
788804

789805
# Add to memory
790806
agent.short_memory.add(
791807
role="File Operations",
792-
content=f"Read file: {full_path} ({len(content)} characters)",
808+
content=f"Read file: {full_path} ({len(raw)} characters)",
793809
)
794810

795811
if agent.verbose:
@@ -1080,7 +1096,7 @@ def run_bash_tool(
10801096
if agent.verbose:
10811097
logger.info(f"Executed bash command: {command[:80]}...")
10821098

1083-
return result_msg.strip()
1099+
return truncate_tool_output(result_msg.strip())
10841100
except subprocess.TimeoutExpired:
10851101
error_msg = f"Error: Command timed out after {timeout_seconds} seconds"
10861102
logger.error(error_msg)
@@ -1099,9 +1115,6 @@ def run_bash_tool(
10991115
return error_msg
11001116

11011117

1102-
_GREP_MAX_BYTES = 65536 # cap output at 64 KB
1103-
1104-
11051118
def grep_tool(
11061119
agent: Any,
11071120
pattern: str,
@@ -1174,10 +1187,7 @@ def grep_tool(
11741187
stderr = result.stderr or ""
11751188

11761189
# Truncate oversized output
1177-
if len(stdout) > _GREP_MAX_BYTES:
1178-
stdout = (
1179-
stdout[:_GREP_MAX_BYTES] + "\n... (output truncated)"
1180-
)
1190+
stdout = truncate_tool_output(stdout)
11811191

11821192
if result.returncode == 0:
11831193
output = stdout.strip() or "(no matches)"

tests/agents/test_autonomous_loop.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@
3333

3434
from swarms import Agent
3535
from swarms.agents.autonomous_loop import AutonomousAgentLoop
36-
from swarms.structs.autonomous_loop_utils import MAX_SUBTASK_LOOPS
36+
from swarms.structs.autonomous_loop_utils import (
37+
MAX_SUBTASK_LOOPS,
38+
MAX_TOOL_OUTPUT_CHARS,
39+
read_file_tool,
40+
)
3741

3842

3943
# --------------------------------------------------------------------------
@@ -224,6 +228,20 @@ def test_skipped_counts_as_terminal(self):
224228
# --------------------------------------------------------------------------
225229

226230

231+
class TestToolOutputIsCapped:
232+
"""One oversized read is re-sent on every later call of the run."""
233+
234+
def test_read_file_truncates_a_large_file(self, tmp_path):
235+
agent = build_agent()
236+
big = tmp_path / "big.txt"
237+
big.write_text("x" * (MAX_TOOL_OUTPUT_CHARS + 5000))
238+
239+
output = read_file_tool(agent, str(big))
240+
241+
assert len(output) < MAX_TOOL_OUTPUT_CHARS + 200
242+
assert "output truncated" in output
243+
244+
227245
class TestBatchedToolCalls:
228246
"""Every tool call in a response runs, whatever its position."""
229247

0 commit comments

Comments
 (0)