An empty search result is a perfectly valid tool response.
“Therefore, it doesn’t exist” is a completely different claim.
The bug happens when an agent silently converts one into the other—and then uses that conclusion to recommend what you should do next.
Here is an illustrative example, not a recorded incident:
User:
Do we already have a migration guide for this service?
Tool:
Search completed. 0 results.
Agent:
There is no migration guide.
You should write one before starting the migration.
The recommendation sounds responsible.
The premise is unproven.
Maybe the guide uses the old service name. Maybe it lives in another repository. Maybe the agent cannot access the relevant folder.
The tool reported a retrieval outcome. The agent promoted it into a statement about reality.
That promotion is the bug.
An empty array is not a proof
Consider this interface:
async function searchDocs(
query: string
): Promise<Document[]>
It tells the caller which documents were returned.
It does not tell the caller:
- Whether every relevant source was searched.
- Whether permissions excluded relevant documents.
- Whether the query matched the terminology in use.
- Whether an index was current.
- Whether the search completed successfully everywhere.
Yet the result is easy to consume as a binary:
const docs = await searchDocs(query);
if (docs.length === 0) {
return "The document does not exist.";
}
We can see the problem immediately in code.
In natural language, the same invalid branch can hide inside a fluent paragraph.
The missing state is “unknown”
The wrong model is:
Found → Exists
Not found → Does not exist
A more useful model is:
Found supporting evidence → Supported
Found sufficient evidence against → Refuted
Neither → Insufficient evidence
Research on completeness-sensitive negative reasoning examines precisely this distinction: when absence warrants a negative conclusion, and when the answer should remain unknown. It reports that models do not reliably distinguish those situations across the tested model families. arxiv
This does not mean an empty result is always useless.
If you inspect every entry in a complete, authoritative registry, absence can justify a scoped negative conclusion.
If you search one document index, the same conclusion may be unjustified.
The important question is not just:
Did the search return anything?
It is:
If the thing existed, would this search have been expected to find it?
Give retrieval results a contract
My proposed fix starts at the tool boundary.
Instead of returning only documents, return evidence about the retrieval operation:
type RetrievalResult<T> = {
items: T[];
scope: {
requestedSources: string[];
searchedSources: string[];
};
access: "complete" | "restricted" | "unknown";
freshness: "verified" | "stale" | "unknown";
completion: "complete" | "partial" | "failed";
errors: Array<{
source: string;
message: string;
}>;
};
These fields are not magic. They must come from instrumentation, not from asking the model to guess whether its search was complete.
And even a technically complete search is not necessarily semantically exhaustive. Searching every configured source does not prove that the query could retrieve every relevant document.
But this contract makes an important distinction visible:
{
"items": [],
"scope": {
"requestedSources": ["docs", "github"],
"searchedSources": ["docs"]
},
"access": "restricted",
"freshness": "unknown",
"completion": "partial",
"errors": [
{
"source": "github",
"message": "Permission denied"
}
]
}
This is not “no migration guide exists.”
It is “the search found nothing in the accessible documentation index, and GitHub was not searched.”
Those sentences support different decisions.
Separate the claim from the recommendation
The next boundary is between evidence and action.
Compare:
No guide exists.
Write a new one.
With:
I found no guide in the documentation index.
The repository search failed because of permissions.
Before creating a new guide, check the repository
or ask the service owner.
The second answer is not merely more cautious. It exposes a concrete unresolved dependency.
A simple proposed representation:
type Claim = {
text: string;
status: "supported" | "refuted" | "unresolved";
evidenceIds: string[];
limitations: string[];
};
type Recommendation = {
action: string;
dependsOn: string[];
conditional: boolean;
};
The design rule is straightforward:
An unresolved premise must not silently become an unconditional recommendation.
GroundEval makes a related distinction when it identifies “invalid absence” as an evaluation failure—for example, claiming that a report is absent after searching only Jira. A correct-looking final answer is not enough if the evidence path does not justify it. arxiv
Then comes the second bug: asking the agent why
Suppose you catch the mistake.
You ask:
Why did you say the guide didn't exist?
The agent replies:
I prioritized giving you a complete answer
and treated the search results as exhaustive.
That explanation sounds plausible.
But you have not yet established that it is true.
Research on entity-resolution tasks has found that model-generated explanations can be unstable and poorly faithful to the evidence underlying the decision. That does not prove every explanation is false; it does mean explanatory fluency is not sufficient validation. arxiv
Treat the agent’s explanation as a debugging hypothesis.
Then check the trace:
Which queries were issued?
Which sources were actually searched?
What did each tool return?
Were there access failures?
Was conflicting evidence available?
What information reached the final answer step?
Telemetry is evidence about the execution. The model’s retrospective story is something to test against it.
More searching is not automatically the fix
It is tempting to solve this by adding another search loop.
Sometimes that will help.
But the system also needs a stopping rule and a way to preserve uncertainty. Research on over-searching reports a trade-off: search generally improved accuracy on answerable questions while weakening abstention on unanswerable ones in the tested settings. arxiv
A longer search trace is not, by itself, a stronger justification.
The question is whether the additional search changes the evidence relevant to the claim.
A small evaluation suite
Before calling this fixed, I would test the proposed design against controlled cases:
| Case | What the agent should do |
|---|---|
| A document exists but uses an unexpected name | Avoid turning retrieval failure into global absence |
| A document exists in an inaccessible source | Report the access limitation |
| No document exists in a complete test collection | Make a negative claim scoped to that collection |
| Two sources disagree | Surface the disagreement |
| The search tool fails | Distinguish execution failure from no matches |
I would measure more than false negatives:
- Unsupported absence claims.
- Omitted source conflicts.
- Unconditional recommendations built on unresolved claims.
- Unnecessary abstentions when evidence is sufficient.
- Search cost and task completion.
Otherwise, a system that always says “I don’t know” could look artificially safe.
This is a proposed evaluation design, not a claim that the interface above has already solved the problem.
The engineering takeaway
An agent should not need to sound uncertain about everything.
It should know which uncertainty belongs to which claim.
A search tool can report zero matches.
A system can establish absence within a defined, adequately covered scope.
Those are different operations—and they deserve different contracts.
Don’t let an empty array make the decision for you.
created by :https://almodarresieh.github.io/
Top comments (0)