The Inbox Agent Said Nobody Had Replied. The Search Tool Had Returned Page One of Four.
An agent can't tell "there's nothing here" from "I only saw the first page" unless the tool tells it. Here is how to make a paginated tool announce its own incompleteness, and why claims of absence need a stricter rule than claims of presence.
A personal assistant agent is asked a simple question: "Did anyone from the venue reply about the booking?" It calls search_email(query="venue booking"), reads the results, and answers with confidence: nobody has replied yet, want me to send a follow-up? The reply was there. It was result 27, and the tool returned 20 results per page.
Nothing crashed. The tool worked exactly as designed, the model read exactly what it was given, and the answer was wrong. This failure lives in the gap between a paginated API and a model that has no way to feel the edge of a page.
Why the agent can't see the missing pages
A human scrolling an inbox sees a scrollbar, a "1–20 of 83" counter, a "next" arrow. The truncation is visible in the interface. An agent sees a JSON blob. Here is what a typical wrapper around an email API hands back:
{
"messages": [ ...20 items... ],
"next_page_token": "CiAKGjBpNDZ..."
}
To an engineer, next_page_token screams "there's more." To the model, it's an opaque string at the bottom of a long payload, after 20 messages of subject lines and snippets. Nothing in the result says the list is incomplete. The model has to infer it from a field name, and under a task framing of "find the reply," it often doesn't. It scans what's there, finds nothing, and reports what it saw as if it were everything.
This gets worse in three common situations:
- Relevance ordering. Search results are ranked by relevance, not date. The newest message, often the one that matters, can sit on page three.
- Long payloads. The more text each item carries, the further the pagination hint sinks from the model's attention.
- Negative questions. "Did anyone reply?" "Is there an open ticket for this?" "Have we paid this invoice?" All three are answered by the absence of a match, and absence is exactly what a partial page fakes.
Presence and absence are not symmetric
This is the core idea, and it applies well beyond email.
A claim of presence is grounded by one item. If the agent says "yes, the venue replied on Tuesday," it can point at the message. Page one is enough if the item is on page one.
A claim of absence is only grounded by an exhaustive search. "Nobody replied" is a statement about every message that matches, including the ones the agent never received. A partial result can support a "yes." It can never support a "no."
Most tool designs treat these the same. The fix is to make the tool and the agent treat them differently.
🔍 Fix 1: Make the tool say "incomplete" in words
Don't rely on the model inferring meaning from a cursor field. Put the completeness status in plain language, near the top, where it will be read before the items:
{
"status": "PARTIAL: showing 20 of 83 matching messages. Results are sorted by relevance, not date. Call again with page_token to see more, or narrow the query.",
"total_matches": 83,
"complete": false,
"page_token": "CiAKGjBpNDZ...",
"messages": [ ...20 items... ]
}
Three things changed. The status line is a sentence the model reads as instruction, not a field it has to interpret. The total count makes the gap concrete: 20 of 83 is obviously not the whole picture. And the sort order is stated, so the model knows the newest message might not be here.
If the underlying API can't give a total count, say so honestly: "PARTIAL: at least 21 matches exist; only the first 20 are shown." An honest lower bound beats silence.
Fix 2: Give the agent a tool shaped for the question
A lot of pagination pain disappears when the tool matches the question being asked. If the common task is "did X reply," a generic search is the wrong primitive. Offer narrower tools:
search_email(query, from=None, after=None, sort="date_desc"), where filters and date sorting let the agent shrink 83 matches to 2.count_email(query, from=None, after=None), a cheap existence check that returns a number, not a page.
A count of zero from count_email(from="bookings@venue.example", after="2026-09-01") is a grounded "no." A page of twenty relevance-ranked results that happen to lack the reply isn't.
Filtering is usually cheaper than paging, too. Fetching four pages of full message bodies to answer a yes/no question burns context for nothing.
Fix 3: A rule for absence claims
Tool design catches most cases. A short instruction in the system prompt catches the rest:
Before stating that something does not exist, has not happened, or was not found, confirm the search was complete. If a tool result says PARTIAL or
complete: false, either narrow the query until the result is complete, fetch the remaining pages, or tell the user that the search was partial and how many results were checked.
The last clause matters. Sometimes exhausting the search is too expensive, and the right move is to be honest about coverage: "I checked the 20 most relevant of 83 matching messages and didn't see a reply from the venue. Want me to check the rest, or search only messages from their address?" That answer is slightly less satisfying and a lot more useful than a confident wrong one.
Before and after
Before:
search_email("venue booking")→ 20 messages + an opaque token- Model scans 20 messages, finds no reply
- "Nobody from the venue has replied. Shall I send a follow-up?"
- User sends a follow-up to someone who already confirmed, looking disorganized
After:
search_email("venue booking")→ "PARTIAL: showing 20 of 83..."- Model sees the partial status, narrows:
search_email("venue booking", from="bookings@venue.example", sort="date_desc") - Result: "COMPLETE: 2 matching messages"
- "Yes, they replied on Tuesday confirming the 14th. They asked you to confirm headcount by Friday."
Same model, same API. The difference is whether the tool told the truth about what it left out.
How to find this in your own agents
Go through your tool inventory and flag every tool that can return fewer results than exist: search, list, query, log fetch, file listing. For each one, check whether the result says so in words. Then look through your traces for answers containing "no," "none," "not found," "hasn't," or "never," and check whether the preceding tool result was complete. That second check usually turns up a few surprises.
A paginated tool that hides its pagination is a tool that lies by omission. The model can't notice a page it was never shown, so the tool has to tell it. And any time an agent says something isn't there, that claim should need more evidence than any other claim it makes.