← All posts

Tool results too big? Don't feed them all back

Last post was about messages growing forever. The biggest source of that growth is usually one thing:

A tool returns a huge result, and you feed it back into context raw.

A cat of a 200KB log, an ls -R with thousands of lines, a full HTML page from a fetch… all land in messages. The result:

  • Tokens explode → cost and latency rise together;
  • The model drowns → the 3 useful lines are buried in 50,000, so it misses them;
  • A bit bigger → it blows past the context window.

The core idea

A tool is a query port, not a printer.

Every byte that enters messages costs tokens and steals the model's attention. So the right move isn't "dump the output back" — it's "bring back only what's needed, in the smallest form".

tool runs
   ↓
【filter / truncate / summarize】   ← the key step most people skip
   ↓
feed back to the model (small, precise)

Four concrete fixes

1. Safety net: truncate head + tail

The simplest — if too long, keep head and tail, drop the middle.

MAX = 4000  # per-tool-result cap (chars)

def cap(text: str, limit: int = MAX) -> str:
    if len(text) <= limit:
        return text
    half = limit // 2
    return f"{text[:half]}\n…({len(text) - limit} chars omitted)…\n{text[-half:]}"

2. Don't cat — read in pages

Give the model a tool that reads on demand, instead of shoving the whole file at it.

def read_file(path: str, offset: int = 0, limit: int = 100) -> str:
    lines = open(path, encoding="utf-8", errors="ignore").read().splitlines()
    chunk = lines[offset : offset + limit]
    more = len(lines) - (offset + limit)
    out = "\n".join(chunk)
    if more > 0:
        out += f"\n…({more} more lines; continue with offset={offset + limit})"
    return out

3. Give a "search" tool, not a "dump everything" tool

When searching logs, filter inside the tool and return only matching lines.

def search_log(path: str, pattern: str, max_hits: int = 30) -> str:
    hits = [f"{i+1}: {ln}" for i, ln in enumerate(
        open(path, encoding="utf-8", errors="ignore")) if pattern in ln]
    body = "\n".join(hits[:max_hits])
    return body + (f"\n…{len(hits)} matches total" if len(hits) > max_hits else "")

4. Structural results: return the point

When running a command, don't return all of stdout — return exit code + a few key lines + counts:

def run_and_summarize(cmd: str) -> str:
    p = subprocess.run(cmd, shell=True, capture_output=True, text=True)
    out = p.stdout
    return (f"exit={p.returncode}\n"
            f"stdout({len(out)} chars, last 30 lines):\n"
            + "\n".join(out.splitlines()[-30:])
            + (f"\nstderr:\n{p.stderr[-1000:]}" if p.stderr else ""))

One-line rules

You want toWrongRight
Read logscat app.logsearch_log(path, keyword)
Read a filefeed the whole fileread_file(offset, limit)
Run a commandfull stdoutexit code + tail + counts
Fetch a pagewhole HTMLextract text / summarize first

Why this matters even more on the edge

Edge / small models often have just 8K of context. One unfiltered cat can eat the entire window.

In the cloud, "result too big" means slow and expensive; on the edge, it means it won't run at all.

The takeaway

Remember this:

A tool's value isn't how much it can fetch — it's how little it can return.

When you design a tool, ask: does the model really need all of this? Most of the time — no.

Comments 0

No comments yet — be the first!

Log in Log in to comment