Try this first

Take Rover’s read_file and change the return to this:

def read_file(path):
    with open(path) as f:
        return {"ok": True, "data": f.read(), "bytes": 42}

Run it. It will fail, and the error will point at the API call rather than at this function.

What a tool result must be

The content of a tool_result is text. Not a dict, not an object.

{
    "id": call.id,
    "content": "Buy milk. Call the plumber.",
}

If your function produces structured data, you serialise it yourself:

import json

return json.dumps({"size": 4096, "modified": "2026-08-10"})

The model reads JSON perfectly well. It just has to arrive as text.

The result is the model’s only view of the world

This is the point of the lesson. What you put in content is what happened, as far as the
model is concerned. It cannot check. It cannot look at the file itself. Your string is the
entire truth.

That has three consequences worth internalising now.

Say what happened, not just the data. A tool that writes a file and returns "" leaves
the model guessing whether it worked. Return "Wrote 412 bytes to summary.txt." The model
then knows, and — importantly — so does anyone reading the transcript later.

Do not dump everything. A tool that returns a 40,000-line log costs you on every
subsequent turn, because the whole transcript is resent each time. Return the useful part and
say what you trimmed: "Last 50 lines of 12,043 (showing the end):".

Give it a next step when there is one. If a search finds nothing, "No matches" is
correct but unhelpful. "No matches for 'plumber' in 12 files searched. Try a shorter pattern
or check the folder."
gets a better next turn.

Big results have a safety net

If a tool result is very large, the platform can offload it to a file in the sandbox and give
the model a preview plus a path, rather than pushing the whole thing into context.

That is a backstop, not a design. Deciding what is worth returning is your job, and doing it
well is one of the biggest levers you have on both cost and quality.

The tool result is the model’s only view of what happened. Write it for a reader who cannot
see anything else.

Try this before the next lesson

Change read_file to return the file contents prefixed with a line saying how many lines it
read. Ask Rover to summarise a file.

Then look at the transcript. That prefix is now part of the conversation forever, resent on
every later turn. Was it worth its size? That question is the whole of Module 6.