agent.yaml, and give an agent a tool that searches it.
Reach for a knowledge base when the agent should quote your own documents
instead of guessing, especially when a caller’s words and your document’s
words differ, or a caller wants an exact string off a price list or a policy.
An API you already have is a webhook instead: a search
service is a network hop and a service to run, not a folder of documents.
On this page:
- Which agent sees which documents - the only access rule
- Every key a base takes - the full shape
- The
knowledge:block on a tool - what the tool file holds - How the search works - meaning, keyword, hybrid
- What happens, and when - compile, startup, lookup
- Advanced - sizing, scores, embedding models
- Troubleshooting - the warnings and the failures
agent.yaml
tools/look_up_policy.yaml
agent.yaml
Which agent sees which documents
An agent reaches a knowledge base by being given its tool. There is no allow list and nothing else to configure. So a tool on the wrong agent is a real leak, not an untidiness: an agent givenlook_up_policy can quote refund policy to anyone it talks to. Two tools over one
base is fine and normal; the same tool on every agent means every agent can quote
that folder.
Every key a base takes
Every field is per base, because different documents want different treatment. A folder of prose and a folder of price rows can sit in the same package with different settings.map key
required
3 to 64 characters of
[a-z0-9_]. It becomes the search collection’s name, and
the folder name inside the build.string
required
Path to a folder inside the package containing
.txt, .md, or .pdf documents. No
folder is inferred.string
An embedding service. Omitted means
openai.
Keyword mode makes no embedding call.string
Accepts
meaning, keyword, or hybrid. Omitted means hybrid.integer
Passage size in tokens, from 1 to 2048. Omitted means
90.integer
Tokens shared by neighboring passages, from 0 through
chunk_size. Omitted means 20.integer
Maximum passages returned by a lookup, from 1 to 20. Omitted means
3.number
Minimum accepted result score, from 0 to 1. Omit for no score filtering. Scores are
similarities, not probabilities.
The knowledge: block on a tool
string
required
The name of a base declared under
knowledge in agent.yaml. No base is inferred.description, announce, interruption.
Refused beside it: input, output, inject, effect. The tool owns both sides
of its contract, it takes one string, the caller’s question, and returns passages,
so there is nothing for those to describe.
Write a real description. It is the only thing that tells the model when to
look something up instead of answering from memory. Say what is in the folder and
when to check it. Write an announce too: a lookup takes a moment, and silence
sounds like a dropped call.
How the search works
Three modes, and they answer different kinds of question.mode: meaning
Vector search. Each passage and each caller question is turned into a vector by an
embedding model, and the passages closest to the question win.
What it is for: the caller’s words and the document’s words are different.
Someone asks “when will the money land back in my account” and the document says
“five to seven working days”. They share no distinctive word, so nothing that
matches on words can find it. This is the case vector search exists for.
Where it struggles: an exact string. A reference code, a surname, a part number, a
price read off a letter. It also gets weaker as a corpus grows, because more
passages of similar prose means more near neighbours competing with the right one.
Needs an embedding model, so it needs a credential and one network call per lookup.
mode: keyword
BM25, a ranking function over the words themselves. A word the caller says is
matched against the words in each passage, with rarer words counting for more.
Stemming is included, so a question about “closing” still matches a passage that
says “closed”.
What it is for: the caller says the exact thing. Codes, names, model numbers,
prices. It also holds up as a corpus grows, because a rare word stays rare.
Where it struggles: paraphrase. Nothing that matches on words can bridge two ways
of saying the same thing.
keyword needs nothing. No embedding model, no credential in secrets:, no
network call, and the emitted image installs no embeddings package. A lookup is
local memory access rather than a round trip, and the index builds in a fraction
of the time an embedded one takes. If your documents cannot be sent to a third
party, this is the mode that answers that.
keyword base produces no relevance scores. BM25 scores sit on a different
scale from vector similarities, and giving the model two incomparable numbers in
one field would be worse than giving it none. min_score on a keyword base is
refused at compile rather than silently doing nothing.
mode: hybrid (the default)
Both, run separately and interleaved, so each half gets slots the other cannot
take. A question that only paraphrase can answer and a question that only an exact
term can answer both work, without you deciding in advance which kind your callers
will ask.
This is the default because it is rarely the wrong answer. It needs an embedding
model, the same as meaning.
Choosing
What happens, and when
Content is fixed until the next compile. Editing a PDF in your package changes
nothing in a running agent, or in a deployed one, until you compile and deploy
again.
Indexing happens before the agent takes calls, so a caller never waits for it. It
runs once per worker process, so every worker indexes the whole corpus at start:
more workers means more startup work, with the same content in each. That is what
baking removes.
What a lookup gives the model
At mosttop_k passages, each about chunk_size tokens, with the source file name
and, on the modes that embed, a relevance score where a higher number is a closer
match. Results are ordered best first. With the defaults that is 3 passages of
about 90 tokens.
Nothing is filtered by score unless you set min_score, because a score that looks
low can still be the right answer. The tool tells the model in words that the
results may not answer the question, so it says it does not know rather than
offering the closest thing it found.
Advanced
Everything here is tuning a first agent does not need. The defaults are a working knowledge base.Sizing the passages
A document is split into passages before anything is searched, and a lookup returns whole passages. Three fields control that.chunk_size is how big a passage is, in tokens. Small passages are precise and
can cut a fact in half. Large passages keep a fact whole and dilute it with
neighbouring text.
chunk_overlap is how much two neighbouring passages share, so a sentence cut
across a boundary is still whole in one of them. Roughly a fifth of chunk_size
is a reasonable starting point. 0 is legal and means no overlap at all.
top_k is how many passages come back.
The list case is the one that bites. At a narrow
chunk_size a table of prices
splits mid row, so the name of a service lands in one passage and its price in the
next, and a question about the price ranks something else above it. Widening the
window keeps each row with its own value:
top_k times chunk_size is what reaches the model on every lookup. That is
tokens on the way in and latency on every call, during a live conversation. Above
about 1500 tokens the compiler warns:
Filtering by score
min_score drops results scoring below it. It is absent by default, so nothing
is filtered unless you ask for it.
These are similarity scores, not probabilities. That is the whole difficulty
with the field. They look like confidence values, so 0.9 reads like “only very
good matches”, when in practice scores land well below 1 and a cutoff that high
returns nothing at all. A genuine answer and an off-topic question are separated by
a much smaller gap than the 0 to 1 range suggests.
Above 0.25 the compiler warns:
- An exact-term hit survives any cutoff, because it has no score to compare. It was found because the caller said a rare word verbatim, which is better evidence than a similarity number.
- Every drop is logged, with a count and never the question. Without that, a cutoff set too high makes the agent say it does not know on every question with nothing anywhere to explain why.
chunk_size does not move it the way you would expect: fewer, longer
passages is fewer chances to match, so the weakest genuine score can stay where it
is while an answer disappears entirely.
Even with a cutoff, the tool still tells the model that results may not answer the
question. A surviving result is not the same as an answer.
Embedding models
embed: chooses the model that turns documents and questions into vectors. It
applies to meaning and hybrid; keyword embeds nothing and ignores it.
Switching is one line, and it is per base, so two bases in the same package can use
different services:
agent.yaml
embed: value outside that table is
refused with the supported list. A service that needs a credential and does not
find it in secrets: is refused too, at compile, rather than failing when the
agent starts.
bedrock is the one that does not name a variable. It authenticates through the
AWS credential chain, so boto3 resolves an access key pair, a profile, a role or
an instance identity, and a region, in its own order. Nothing has to be listed in
secrets: for it, and nothing is checked at startup, because there is no single
check that would be right for all of those paths.
huggingface is the hosted Inference API, not a model running inside your image.
All four services are network calls.
Documents are sent to the chosen service once, when the agent starts, and each
caller question is sent on each lookup. If that is not allowed for your documents,
use mode: keyword, which sends nothing anywhere.
Skip the startup embedding
By default every worker process embeds the documents when it starts. That is correct, and it is per process rather than per deployment, so it is paid again on every scale-up. On a large corpus it is the difference between a process that is ready immediately and one that spends seconds indexing first. Building withKNOWLEDGE_BAKE=1 does the embedding once, at image build time, and
every process then loads the finished index from disk:
README.md prints the exact command for your package, with the credential your
embedding service needs.
The credential arrives as a build secret, so it is never written into an image
layer. Both flags are needed: the build argument is what puts the decision in
Docker’s cache key, and without it a build that once ran without the secret would
keep reusing that layer and silently skip the bake.
Document formats
Anything else in the folder is ignored without comment, so a
.DS_Store or a
stray image is not an error.
Troubleshooting
The compile warns that a base is never searched
A base that no tool searches is a warning at compile, not an error, because it is read and indexed at every start and never queried. Fix: attach aknowledge: tool that names it to an agent, or drop the base
from agent.yaml.
The deployment stops because a base yields no text
A base where no document yields any text stops the deployment, the same as a missing credential. A PDF with no text layer is the usual cause, and it is named and skipped at startup rather than at compile time. Fix: check the folder against the document formats above, and replace a scanned PDF with one that carries a text layer.A lookup fails in the middle of a call
A lookup that fails mid-call does not end the call. The agent is told the lookup is unavailable, and says so. The reason goes to the process log, not into the model’s context: a model handed a provider error can read it aloud. Fix: read the process log for the reason. An embedding service that needs a credential and a network call is the common one, andmode: keyword needs
neither.
Where to go next
Orchestration
Split work between prompts when one agent stops being enough.
Tools
Return to all eight execution blocks.