search_knowledge answers a broken embedder with the sentence "the knowledge base may be empty". That sentence is a fabricated explanation of an infrastructure failure, and it is the one the model reads.
The path (all verified)
embed() swallows everything:
// workers/api/src/agent-storage/vectors.ts:304
protected async embed(text: string): Promise<number[] | null> {
if (!this.ai) return null;
try { ... } catch { return null; } // :320-322
}
grep -in "log\|error\|console" workers/api/src/agent-storage/vectors.ts returns 0 lines inside embed — a Workers-AI outage, a rate-limit, a 500 from the model gateway all become null with nothing written to the error log, the trace, or the console.
vectorSearch turns that null into "no matches":
// vectors.ts:106-109
if (!this.vectorize || !this.ai) return [];
const embedding = await this.embed(query);
if (!embedding) return [];
Two callers consume that empty array, and both state it as a fact about the user's data:
-
The tool. workers/api/src/lib/storage-tools.ts:262-267 —
if (results.length === 0) return ok(call.name, "No relevant results found. The knowledge base may be empty or the query didn't match any stored content.")
Note ok(...), not fail(...). The model is told the search succeeded and found nothing.
-
The prompt. workers/api/src/agent-storage/context.ts:71-72 —
const results = await this.vectorSearch(query, 8); if (results.length > 0) { ... }
On an empty result the entire ## Relevant Knowledge block is omitted. The turn proceeds ungrounded, and nothing in the prompt, the trace or list_errors says retrieval ran at all.
Why this is the read path's version of a bug the write path already fixed
indexingEnabled exists precisely so this cannot happen on ingest:
// vectors.ts:26-31
* ... so callers MUST check this before reporting a KB add / repo index as "indexed" —
* otherwise ingest looks green while RAG is dead (#22).
get indexingEnabled(): boolean { return !!(this.vectorize && this.ai); }
grep -rn "indexingEnabled" workers/api/src — the read path never consults it. #22 stopped ingest lying about indexing; the symmetric lie on retrieval was never closed.
Blast radius (live, read over MCP)
Repo Chat (267286d5-6877-4f2a-8b5c-3c40b3e3be85) — vector_stats: 315 sources, 3,979 chunks, 1,823,670 chars. Its declared tools are exactly ["search_knowledge","list_knowledge","read_knowledge"], so search_knowledge is its only route to any of it.
- Every RAG agent shares
buildRAGContext: Doc Chat, Local Repo Chat, Google Drive Doc Chat, the apply agent's KB.
An agent told "the knowledge base may be empty" will report that to the user, and the user will believe their ingest failed.
What "fixed" looks like
Distinguish the search returned nothing from the search did not run. embed() should report its failure (a distinguishable return, plus one logError so it is visible in list_errors), vectorSearch should propagate it rather than flattening to [], search_knowledge should fail(...) with "retrieval is unavailable right now" instead of ok(...) with a claim about the corpus, and buildRAGContext should say retrieval was attempted and failed rather than silently omitting the block.
Verified vs inferred
- Verified: every line and grep above; the
Repo Chat numbers are a live vector_stats read on 2026-08-16.
- Inferred: the frequency of embed failures in production. Not measurable today — that is the point of the issue: there is no record of one ever having happened.
search_knowledgeanswers a broken embedder with the sentence "the knowledge base may be empty". That sentence is a fabricated explanation of an infrastructure failure, and it is the one the model reads.The path (all verified)
embed()swallows everything:grep -in "log\|error\|console" workers/api/src/agent-storage/vectors.tsreturns 0 lines insideembed— a Workers-AI outage, a rate-limit, a 500 from the model gateway all becomenullwith nothing written to the error log, the trace, or the console.vectorSearchturns thatnullinto "no matches":Two callers consume that empty array, and both state it as a fact about the user's data:
The tool.
workers/api/src/lib/storage-tools.ts:262-267—if (results.length === 0) return ok(call.name, "No relevant results found. The knowledge base may be empty or the query didn't match any stored content.")Note
ok(...), notfail(...). The model is told the search succeeded and found nothing.The prompt.
workers/api/src/agent-storage/context.ts:71-72—const results = await this.vectorSearch(query, 8); if (results.length > 0) { ... }On an empty result the entire
## Relevant Knowledgeblock is omitted. The turn proceeds ungrounded, and nothing in the prompt, the trace orlist_errorssays retrieval ran at all.Why this is the read path's version of a bug the write path already fixed
indexingEnabledexists precisely so this cannot happen on ingest:grep -rn "indexingEnabled" workers/api/src— the read path never consults it. #22 stopped ingest lying about indexing; the symmetric lie on retrieval was never closed.Blast radius (live, read over MCP)
Repo Chat(267286d5-6877-4f2a-8b5c-3c40b3e3be85) —vector_stats: 315 sources, 3,979 chunks, 1,823,670 chars. Its declared tools are exactly["search_knowledge","list_knowledge","read_knowledge"], sosearch_knowledgeis its only route to any of it.buildRAGContext: Doc Chat, Local Repo Chat, Google Drive Doc Chat, the apply agent's KB.An agent told "the knowledge base may be empty" will report that to the user, and the user will believe their ingest failed.
What "fixed" looks like
Distinguish the search returned nothing from the search did not run.
embed()should report its failure (a distinguishable return, plus onelogErrorso it is visible inlist_errors),vectorSearchshould propagate it rather than flattening to[],search_knowledgeshouldfail(...)with "retrieval is unavailable right now" instead ofok(...)with a claim about the corpus, andbuildRAGContextshould say retrieval was attempted and failed rather than silently omitting the block.Verified vs inferred
Repo Chatnumbers are a livevector_statsread on 2026-08-16.