← All articles

Your Data Should Answer You

The BunnyQuery bunny, magnifying glass in hand

You do not have a storage problem. Storage is solved, and it is nearly free. What you have is a retrieval problem, and it looks like this: the answer you need is definitely somewhere in your files, and getting it out will cost you an afternoon.

So you keep organizing. Folders inside folders, naming conventions nobody follows twice, a spreadsheet that indexes the other spreadsheets. All of that effort is spent making data findable by a machine that could not read it.

That machine can read now. Which means the filing cabinet was never the goal, it was the workaround.

BunnyQuery is AI indexed data storage. You put your data in as it is, and you ask it questions in plain language. No folder structure, no naming discipline, no schema designed six months before you knew what you would need. You store, and then you ask.

This article is about why we think that is what storage looks like next, and how it actually works underneath.


From a filing cabinet to an oracle

Here is the shape of every storage product built in the last forty years:

   You  -->  remember where you put it  -->  open the file  -->  read it  -->  answer
              (this step is your job)

Every part of that chain that involves knowing where something is, is work you do on the machine's behalf. Your files are stored by location. Your questions are about meaning. Nothing bridges the two except your memory.

Here is the shape we are building instead:

   You  -->  ask in plain language  -->  answer, with its sources
                                          (the finding is the machine's job)

No path. No filename. No folder. You ask how much did we spend with this supplier last year, and you get a number and the rows it came from. Somebody's data is an oracle you consult, not a cabinet you rummage through.

The word oracle matters here, and so does its opposite. An oracle that makes things up is a fortune teller. The rest of this article is about the difference.


Why we do not train a model on your data

The obvious way to build this, and the way many products do build it, is to feed your documents into a language model until it knows them. Fine tune on the corpus, ship the model, ask it things.

We think that is the wrong foundation, for reasons that have nothing to do with cost.

You never learn what it knows. A trained model has no inventory. There is no query that returns "here is everything you taught me about this supplier," and no way to confirm a document made it in. Your data becomes weights. Weights are not auditable. You are trusting a system that cannot describe its own contents.

It cannot tell knowing from guessing. A model producing an invoice total from training is doing the same thing it does when it invents one. Both are the model predicting plausible text. It has no internal signal that separates the two, which is exactly why hallucination is so hard to catch: wrong answers arrive with the same confidence and the same fluency as right ones.

Deleting is not possible. A customer asks you to erase their record. From a database that is one call. From a trained model it is a retrain, and you still cannot prove the information is gone.

Updating is not possible either. Your price list changed on Tuesday. The model learned the old one. Now it holds both, with no notion of which is current, and no way for you to tell it that one has expired.

Permissions do not exist. Weights have no access control. If a model was trained on the salary file, then any user who can talk to it can potentially get salary information out of it, and the only thing standing in the way is a prompt asking it nicely.

Put simply: training teaches a model to recite your data. We want it to consult your data.

Train on your data BunnyQuery
Where the data lives inside the model weights in a database you own
Can you list what it holds no yes, query it
Wrong answers indistinguishable from right ones traceable to a record
Deleting one fact retrain, unprovable one call
Updating one fact retrain one write
Permissions none enforced by the database
Citations invented real record IDs

The model in BunnyQuery is not a memory. It is a reader. It knows how to interpret your question, how to search, how to read what comes back, and how to notice when something is missing. What it never does is answer from recall. The answer always comes from a lookup that happened moments ago, in a database that is the single source of truth.

That is the whole design philosophy: a library, not a memorized book. A librarian who has read nothing but knows exactly where everything is will beat a scholar working from memory every time you need to be right.

(We take a similar position on vector databases, and for related reasons. That argument is in Why BunnyQuery is not built on a vector database.)


How your Bunny indexes a file

Indexing is the expensive half, and it is where the reliability is won or lost.

   1. UPLOAD          your file, as it is, no renaming or sorting
         |
   2. WINDOW          the file is read in overlapping windows, start to finish
         |            (a 200 MB spreadsheet does not fit in any context)
         |
   3. READ            an AI model reads each window and writes down what is there
         |
   4. STRUCTURE       output becomes typed records: real fields, real numbers,
         |            real dates, tagged and linked back to the source file
         |
   5. RECONCILE       count what was built against what landed.
         |            a hole FAILS the pass. it does not report success
         v
      records in your database, queryable forever

Step five is the one nobody else talks about, so let us be concrete. This is the actual check in our pipeline:

def reconcile(expected, saved, duplicates=0):
    """Did every record we built actually land?

    ... Anything else missing is a hole, and a hole must fail the pass rather
    than let a partial index be reported as complete.
    """

A file that half indexed because of a transient storage error is worse than a file that failed loudly, because a half indexed file answers questions confidently and wrongly forever. So a pass that cannot account for every record it built is a failed pass, and it gets retried.

What a record actually looks like

Say you drag in a card statement. Your Bunny does not store "a spreadsheet." It stores one record per row. Here is a genuine record, produced by the real pipeline:

{
  "table_name": "spreadsheet_rows",
  "access_group": "authorized",
  "unique_id": "row::db:acme/statements/q1-card.xlsx#Sheet1-949ba259#R3",
  "reference": "src::db:acme/statements/q1-card.xlsx",
  "tags": ["q1-card.xlsx", "Sheet1", "detail", "Travel", "Lufthansa 220-441"],
  "data": {
    "_sheet": "Sheet1",
    "_row": 3,
    "Date": "2026-01-16",
    "Description": "Lufthansa 220-441",
    "Category": "Travel",
    "Amount": 1180.0
  },
  "index": { "name": "Amount", "value": 1180.0 }
}

Four details in there carry the entire design:

  • data holds real types. 1180.0 is a number, not the characters "1180.00" inside a sentence. That is what makes sum, average, over 500 and sort by amount into arithmetic instead of estimation.
  • unique_id is deterministic, derived from the file path, sheet and row number. Re-index the same file and the same rows land in the same place, updating rather than duplicating. Before we made ids deterministic, one measured file produced 13,036 records covering 7,017 real rows. Now it cannot.
  • reference points at the source file. Every record knows which document it came from. One lookup on that reference pulls back an entire file's worth of rows, across every table it got split into.
  • tags and index are server-side filters. These are what let the database narrow to the right rows before any result exists, rather than making the model sift a pile.

That last point about tags has a story behind it. On one real workbook, a filtered sum returned 243,987.64 where the file itself stated 85,608.40. The cause was that total rows, VAT lines and rate lines were indistinguishable from ordinary settlement lines to any query the database could run. So rows now carry their type as a tag, and a sum over detail rows stops swallowing the totals. That class of bug is invisible in a demo and fatal in your accounts, and hunting it is most of what indexing quality actually means.


How your Bunny answers a question

Retrieval is a conversation between the model and the database, not a single search:

   your question: "what did we spend on cloud in Q1?"
         |
   1. ORIENT      what tables exist? what tags? what index columns?
         |
   2. QUERY       fetch records: table + access group, filtered by tag,
         |        ranged on an index value
         |
   3. READ        the model reads the returned rows
         |
   4. NOTICE      is this complete? is has_more still true?
         |        is something obviously absent?
         |
   5. QUERY AGAIN as many times as needed, until the set is closed
         |
         v
   answer, plus the records it was computed from

Two mechanics make this trustworthy rather than merely plausible.

A page is a sample, and the system says so. Our query tool tells the model, in the tool description itself: ONE page per call unless fetch_all is true, and a page is a SAMPLE: never count, total, or call something absent from it. When a question needs a total or a count, the agent reads to the end and knows when it has got there. The database can answer "that is all of them," which is the single thing similarity search can never say.

Results are shaped by permissions before they exist. Access rules are enforced by the database at query time, not by trimming a list afterwards and not by asking the model to be discreet. A record you are not allowed to see is not a record the model was shown and told to ignore. It is a record that never entered the result set.

And because the answer is a query rather than a recollection, it comes with its receipts. Every row carries its reference, so "where did this number come from" resolves to a file, a sheet and a row you can open.


Why this holds up as a source of truth

Reliability here is not one feature, it is the sum of several boring guarantees:

  • The model never answers from memory. Nothing is trained on your data. Every answer is downstream of a lookup performed seconds ago.
  • Every record traces to a source. File, sheet, row. You can go and look.
  • You can audit the index. Open your Files page and read the records made from a document. Compare row 412 against the original. Nobody can inspect a set of weights or an embedding and tell whether it is going to be right.
  • Corrections are writes, not retrains. A row came out wrong, you fix it, and the next question uses the fixed value. There is no derived artifact to rebuild.
  • Completeness is checked, not assumed. Partial indexes fail rather than pretend.
  • Deletion is real deletion. The record is gone, because it was only ever a record.

None of these are impressive individually. Together they are the difference between a tool you demo and a tool you run your quarter on.


How this was possible

The interesting part is that most of this is not AI work. It is database architecture that happens to be unusually well suited to an AI agent standing in front of it.

   You, in plain language
         |
   BunnyQuery client   (chat, uploads, indexing status, collaborators)
         |
   MCP server          (the standard tool interface: OAuth, verified tokens,
         |              a narrow set of database tools)
         |
   Skapi API           (serverless, no idle servers, client based security)
         |
   Your project database

Skapi is serverless with client based security. This is the load bearing piece. In a conventional backend, the rules about who may read what live in server code that somebody writes per endpoint. In Skapi, they are enforced by the database layer itself, in terms of record ownership and access groups.

That distinction is what makes it safe to let an agent structure your database. An AI agent deciding table names, choosing index columns and writing thousands of records is a genuinely alarming idea if your security depends on the code path taken to get there. It is unremarkable when the database enforces ownership and access on every single operation, regardless of who is asking or how. The agent gets to be creative about structure precisely because it has no ability to be creative about permissions.

That is also why there is no idle server in the path, and why a project can sit at zero cost until somebody asks something. It is the same architecture that lets a plain HTML file be a full application, described in how Skapi saves you on AI tokens.

MCP is the interface, and it is a standard one. Your Bunny reaches your data through the Model Context Protocol, over a deliberately small tool surface: list the tables, list the tags, list the index columns, fetch records, query a grid, read a file, export to a file. Because it is a standard rather than something we invented, the same project is reachable from Claude or any other MCP compatible client. Your data is not locked inside our chat window.

And your files never go to the AI wholesale. Each file is read once, at index time, to write down what is in it. After that, answering a question fetches the matching records and nothing else. Storing ten thousand files does not mean shipping ten thousand files to a model. Exports skip the model entirely: ask for a CSV and the query runs on our servers, with rows going straight into the file.


The trade we chose: accuracy over speed

We should be straight about the cost, because it is real and you will feel it.

Reading every file properly is far more expensive than skimming it, so indexing takes minutes rather than seconds. And an agent that orients, queries, checks and queries again is slower than one that fires a single search and starts typing. On a question that needs a complete answer, your Bunny will take longer than a chatbot that guesses.

We chose that deliberately, and the reasoning is simple: speed is an engineering problem, and wrongness is a design problem. We can make a slow correct system faster, and we do, most releases. Nobody has ever made a fast wrong system correct without rebuilding its foundation.

There is also an asymmetry worth naming. You upload a file once and ask questions about it for a year. Paying at upload to be right for a year is a better deal than paying nothing at upload and being quietly wrong forever. Quietly is the operative word: a wrong total does not announce itself. It goes in a report, and you find out in a meeting.

If you need an approximate answer instantly, there are faster tools. If you need an answer you will put your name on, this is the trade we made for you.


What people are building

The same primitive, data in and questions out, turns into wildly different products depending on what you feed it.

A digital twin of yourself. Give your Bunny your own life context: journals, notes, correspondence, project history, the documents that explain how you think and what you have done. What you get is not a chatbot playing a character, it is something that answers from your actual record, and can tell you where each answer came from. A version of you that has not forgotten anything.

A customer service agent that is actually correct. Point it at your product documentation, past tickets, price lists and policies. It answers from the current documents, not from a memory of them, so the day you change your refund window the answers change with it. And when a question falls outside what it holds, that is a lookup returning nothing, which is a much better failure than a confident invention.

A team archive with no folder etiquette. This is the one that changes daily life fastest. Nobody has to know which file is in which folder, or which quarter's naming convention was in force. A new hire on their first day asks the archive the same question a five year veteran would ask, and gets the same answer. Institutional memory stops being a person who might be on holiday.

Your finances and taxes. Drag in the mess: bank statements, PDF invoices, photographed receipts, a card export in a format nobody chose. Your Bunny extracts them into rows, and you ask for a clean categorized spreadsheet back. This is the case where extraction into real records earns its keep most obviously, because the output is arithmetic and arithmetic has a right answer.

The pattern under all four is the same. Data that used to be inert becomes something you can hold a conversation with, without you doing the work of organizing it first.


The end of files and folders

Folders, filenames and forms with fixed boxes are all the same invention, and they exist for one reason: computers could not read. A machine that cannot read a page can still match a filename or add a column somebody defined in advance, so somebody always had to translate reality into a shape the machine could handle. That somebody was you, and it has been you for forty years.

That constraint is gone. A machine can read a page now, in any language, and it reads the four thousandth file as carefully as the first.

The organizing still has to happen. A total needs your numbers to still be numbers, your dates to still be dates, and your part numbers to be exactly the characters they are. That work is real and it is most of what your Bunny does when it reads. What ends is you doing it.

Storage that only stores is a solved problem and a boring one. Storage that answers is the interesting one, and it needs to be right, every time, from a source you can check. That is what we are building: not a model that has read your files, but a database your model can never lie about.

Go and find your messiest folder. Upload it without renaming a thing, and ask it something you have been putting off.

Enjoy your Bunny!

Baksa, Creator of BunnyQuery