OAKENMAIL
Pre-alpha

How Oaken counts token savings

Oaken saves tokens in two different ways and reports them separately. This page explains each one: what is counted, the arithmetic, who it is credited to, and the limits of an estimate.

1

Two savings, kept apart

Cache savings Context savings
What was saved A call to a local model that Oaken did not make Mail text a client did not have to read
Whose tokens Oaken's own models (embedder, enrichers, summaries) The model that is calling Oaken, for example Claude over MCP
What it is worth Compute and time with a local model Billed tokens when that client is hosted
Where it is recorded ai_usage, rows marked cached context_savings

They answer different questions, so Oaken never adds them together. Where a table shows both, they are separate columns.

2

Cache savings

Before Oaken runs a model on a message it looks for an answer it already has for the same content hash, model and prompt version. If there is one, the call is skipped and a cached row is written instead. For a cached row the token counts are what the call would have cost, input plus output, taken from the answer that filled the cache.

tokens_saved is the sum of those cached rows. tokens_in and tokens_out are the sum of the calls that really ran.

A large number here is normal after a rebuild, a re-enrich or a second sync: the work repeats and the cache answers. With a local model this saves time and energy, not money.

3

Context savings

A client that reads mail through Oaken gets snippets, summaries and cited evidence, not every body. For each read operation that returns mail content, Oaken records two numbers:

The saving for a call is full - returned, never less than zero. A column of percentages is saved / full for the rows it covers.

Which operations are measured

list_messages, search, search_keyword, ask, get_message, get_thread, get_enrichment and summarize_message.

Operations that change state, such as mark_read, are not measured. They name messages but return almost nothing, so they would look like large savings without saving anything.

An example

A search returns 5 hits as snippets. The reply is 842 tokens. The five messages in full would be 2,067 tokens. The saving is 1,225 tokens, 59 percent.

get_message is the honest case: it returns the whole cleaned text, so it saves little or nothing. The web UI's get_message rows show exactly that.

4

Who is credited

Every operation goes through one client that knows which door the call came in through: cli, ui, mcp or internal (work Oaken does on its own, such as the ingest pipeline). That door is recorded with each row, so oaken usage can show the savings by caller.

Rows written before callers were tracked appear as unknown.

5

Reading oaken usage

oaken usage --json returns the same data. rows holds the usage table and savings holds the other two. The savings_summary operation returns them too, and is available over MCP.

6

What these numbers do not say

7

Where this lives

The recording is in src/oaken/core/operations/savings.py and the client that sets the caller is src/oaken/core/operations/client.py. The tables are created by the 0016_savings migration, and the totals are read by the savings_summary operation.