OAKENMAIL
Pre-alpha

Retrieval and ask

Search runs the keyword index and the vector store side by side and merges them. Ask builds a small context from the best hits and holds the model to it: every claim must quote the message it came from, and a quote that is not really there is dropped.

2

Ask

ask(question, filters) retrieves, builds a context, and asks the model to answer in claims. Each claim has the excerpt it rests on and the exact words it quotes.

  1. A question has its stop words removed for the keyword side, which then matches messages containing at least half of what is left; the vector side gets the question as written.
  2. The context builder spends ai.ask_context_tokens (3,000 by default) in tiers, across all hits in rank order before going deeper into any: a header line and the stored summary for each hit; the matched chunks for the first ask_chunk_hits; the neighbouring chunks and the summaries of the rest of the thread for the first ask_neighbor_hits.
  3. Near-identical chunks are sent once, and words a neighbouring chunk already shows are cut. The total is counted again at the end and trimmed, so the budget is a limit and not a target.
  4. The mail goes to the model as UntrustedText, one block per message, behind the usual data rules (see AI safety).
  5. Core keeps a claim only if its excerpt was in the context and its quote really is in that chunk's stored text (whitespace may differ, nothing else may). Claims that fail are dropped and counted. If any were dropped, confidence is low: a model that invented something is not trusted to rate itself. If none are left, the answer is insufficient_evidence, and the text says so instead of guessing.

The result has answer, citations (message, chunk ordinal, the quote as it stands in the message, and its offsets), confidence, insufficient_evidence, dropped_claims, degraded and the token figures: evidence_tokens and full_body_tokens, the cost of sending every hit whole, so the saving is visible on every call. When nothing is found there is no model call at all.

Mail from more than one account is never put in one prompt, because an account decides which workspace, and so which models, may see it. A question that finds mail in several accounts is refused with a request to name one. ask does not mark anything read.

ask is off over MCP by default. It would let an agent read mail by asking questions, so the user turns it on, like any operation (see Operations).

3

How it is measured

uv run python -m oaken.evals retrieval builds a seeded synthetic mailbox of about a thousand messages with planted facts and questions about them, ingests it through the real pipeline and scores each retriever. Evaluation results shows the latest run. The corpus has four kinds of answerable question and one kind that the mail does not answer, chosen so that keyword search and vector search each fail on one of them. That is why hybrid wins by a wide margin offline, and why the numbers say little about real mail.

Offline, the embedder and the reader are stand-ins: a small concept embedder and an extractive reader that quotes the sentence closest to the question. The run is deterministic, and a smaller run is a test, which asserts that hybrid is at least as good as each single retriever. A run with a real embedder and model (--live) is manual and is not part of CI.

4

What it does not do

No reranking model, no cross-account search, no search inside attachments, and no query rewriting unless llm_rewrite is on.