OAKENMAIL
Beta

Settings, storage and the event log

Everything here is a command on the running core. It edits one table of config.toml and leaves your comments alone, tells you what a change needs before it takes effect, and never lets an environment variable quietly win.

1

Core settings

oaken settings                       # every section: value, default, where it came from, what a change costs
oaken settings ai                    # one section
oaken settings check ai chunk_tokens=128   # test a change; nothing is written
oaken settings set ai ask_context_tokens=2500
oaken settings set ai --unset ask_context_tokens   # back to the default
oaken settings set ui mark_read_delay_ms=500 --reload   # save, then reload core on it

Only some of core's settings are editable here. Anything that changes where data lives, which address or port core listens on, or touches security ([api], [storage], token files, the model and backend choices) is not, and settings says so (by hand in the list, a refusal if you try). Plugin settings, including the models, are separate: see Plugins.

Section Table in config.toml What it holds
index [index] Threading by subject; limits on a search query.
retrieval [search] How keyword and vector results are merged; how much an answer reads.
read_state [read_state] Automatic marking as read; audit batching.
ui [ui] How long a message is open before it is marked read.
sync [sync] Default interval, concurrency, backoff and batch size. The minimum interval is a floor you set by hand.
ai [ai] Token thrift: how much of a message a model reads, and how chunks are cut.
log [log] The log level and format.

What a change costs

A saved change is in config.toml at once, and reaches the running core when it reloads. The answer to check and set says what is needed:

Safe by construction

2

Storage

oaken storage          # the two databases, their write-ahead logs, and the quarantine folder
oaken vacuum --wait    # give back the space deleted mail left; shows the sizes before and after

Sizes are read from the files, not by scanning mail. vacuum rewrites raw.db and index.db, one at a time, and refuses while an index rebuild runs or if the disk does not have about the larger database's size free. While it works each database is write-locked: ingest waits and reads carry on.

3

The enrichment cache

oaken cache                      # entries, size, prompt versions, and how often it saved a model call
oaken cache clear --task triage  # one task's answers; no --task clears all

The cache holds model answers keyed by what produced them, so clearing it costs only the calls it had saved. Mail, the index and the usage record are untouched. Without a terminal, clear needs --yes.

4

Watching a plugin or a job: the trace

oaken trace on                       # start recording (it holds mail text: off until asked for)
oaken trace -f plugin.triager.ai     # follow one plugin, pushed as each event happens
oaken trace -f plugin.enricher.summary
oaken trace plugin.llm_backend.ollama -n 20
oaken trace --job 0192a7...          # one job instance, and nothing else
oaken trace --message 0192a8...      # everything that happened to one message
oaken trace off

The trace records, with the full text: every call into a plugin (its arguments and what it returned, for every kind: providers, embedders, LLM backends, vector stores, caches, triagers and enrichers); every model call core makes (the system text, each user part fenced as mail, the schema, the response, the thinking a model returned, the backend's own token counts and timings, and the tokens a cache hit saved); the jobs (queued, started, done, failed) and what each stored; and the agent door (what an agent was handed on a claim, its heartbeats, the answer it committed, and any thinking or tool calls it chose to report). Every event carries the plugin (plugin_kind, plugin), the job (job_id, job_kind) and the message it happened under, so each of the filters above is exact.

It is off by default, because it holds mail text. [log] trace = true turns it on at start-up; oaken trace on|off changes it at run time. It goes to trace.jsonl in the data directory (owner-only, rotated at trace_max_bytes, keeping trace_backups), is read only through the authenticated API, is never shown on the console's token-free pages and is never offered over MCP. A single text longer than trace_event_cap is cut and says so.

A plugin author gets the same view of their own code: every public async method on a plugin built on a base class is traced under its kind and name, and self.trace("my.event", key=value) adds an event of your own.

5

What happened: events and the audit log

oaken events --tail 20           # recent events, including from before core's last restart
oaken events --kind JobStateChanged --since 2026-10-24T09:00:00
oaken events --follow            # the history, then the live stream
oaken audit --limit 20           # recent changes: who changed what, and whether it can be undone
oaken audit --all --since 2026-10-01

Core writes every event to events.jsonl in the data directory ([log] events_file; empty turns it off), one JSON line each, owner-only, rotated at events_max_bytes and keeping events_backups older files. Events hold ids, states and numbers, never mail text. audit pages with a cursor: the last line of each page gives the command for the next, and --all follows them all.

6

Is it healthy?

oaken status says whether core is running and, when it answers, adds a line: accounts, messages (and how many are indexed), the queue, healthy plugins, and anything that needs attention, such as a stale index or sync schedules that are paused or failing.