Iris has remembered my conversations for a while now. What it could not do was remember my documents. If I dropped a PDF into a chat, it could read that one file for that one message and then forget it existed. Ask about it a week later and it had nothing. I wanted the opposite: drop a file in once, then ask about it whenever, from wherever.
So I built document search. Upload a file, Iris reads it, and from then on it can pull the relevant bits into any answer.
The pipeline
The idea is simple and the work is all in the plumbing. When a file comes in, four things happen:
- Extract the text. PDFs, Word docs, spreadsheets, plain text. Each format needs its own reader, but the output is the same: clean text.
- Split it into passages. You do not search a whole forty-page document as one lump. You break it into small overlapping chunks. Think of it like tearing the relevant pages out of a book instead of photocopying the whole thing. The overlap means a sentence that straddles a boundary is still findable from either side.
- Embed each passage. Every chunk gets turned into a vector, a list of numbers that captures its meaning, so passages about similar things sit near each other.
- Store it. Chunks, vectors, and a keyword index all land in Postgres next to everything else.
Here is the whole thing as a picture. The reading and embedding happen once, in the background, right after you upload:
INGEST (once, in the background)
your file
│
▼
extract text ──► split into overlapping chunks
│
┌─────────────┼─────────────┐
▼ ▼ ▼
chunk 1 chunk 2 chunk 3 ...
│ │ │
▼ ▼ ▼
embed embed embed (text ──► vector)
└─────────────┼─────────────┘
▼
store in Postgres
text + vector + keyword index
Then a search_documents tool lets the agent look things up. Every time you ask a question, only the relevant slice of your document gets pulled back in:
ASK (every question)
"what did the contract say about renewal?"
│
▼
search your chunks (by meaning + by keyword)
│
▼
the few passages that actually mention renewal
│
▼
the model answers from just those passages
So the model never reads the whole forty pages. It reads the handful of chunks that matter, which is faster, cheaper, and more accurate than stuffing the entire file into the prompt.
Why not just use a provider's file search
The AI SDK now ships a hosted file search: upload your documents to the model provider, and they do the storing and searching for you. It is genuinely convenient. I did not use it for the main feature, for two reasons.
First, it only works on some providers (Gemini, OpenAI). Iris talks to many providers, and I switch between them constantly. A feature that only works when I happen to be on Gemini is not a feature I can rely on.
Second, and more important, it means uploading my documents to a third party. Iris is meant to hold things that matter to me. Keeping the documents in my own database, searched by my own code, means they never leave the box. This is the same instinct that made me build Iris instead of using something off the shelf: own the layer that holds the sensitive stuff.
So document search runs on my own retrieval, which has a nice side effect. It works no matter which model I am chatting with, because the search happens before the model is even involved.
Off the request
Embedding a long document is slow. It is many calls to an embedding model, and it can take a few seconds. You never make someone wait at the upload button for that.
So upload is instant. The file is saved, a row is created with the status "queued", and the actual reading, chunking, and embedding happens on a background worker. The UI shows the status moving from queued to processing to ready, and polls until it lands. If the embedding step fails for some reason, the document is still there and still searchable by keyword, so a hiccup degrades to "slightly worse", not "broken".
Everywhere, not just the web
The whole point of Iris is that it meets me where I am. So document search is not a web-only thing.
The upload page on the web is the obvious entry point: drag a file in, watch it turn green, delete it when you are done. But you can also just send a file to the Telegram bot. Send a PDF on its own and Iris saves it and confirms. Send it with a question attached and it saves it and answers the question. The actual create, list, and delete logic lives in the API, so the web page and Telegram both call the same thing, and a future Apple client will too. One brain, many doors.
What I reused
The best part is how little of this was new. Iris already had a retrieval engine for memories, the same "embed the query, find the nearest passages" machinery. Documents are just another thing to embed and search. The chunking and the storage are new, but the search itself is the memory pipeline pointed at a different table. Building the boring plumbing well the first time meant the second feature came cheap.
The next post is about the search itself, and why meaning-based search on its own was quietly letting me down.