OAKENMAIL
Pre-alpha

Prompt safety and workspaces

Mail is data. It enters a prompt only inside a fenced block with a fresh random nonce, the instructions are constants written in this codebase, and the answer is JSON that is checked, never interpreted.

1

The rule

A message can say anything, including "ignore your instructions". So the code never lets mail text near the instructions:

2

Structured answers

Each task is a PromptBuilder: literal instructions, a pydantic answer model and a version. The JSON schema goes in the request, and the reply is validated. A reply that is not valid JSON, does not match, or is longer than ai.max_output_chars gets one repair attempt (the same request plus a plain note naming the fields that were wrong, never the input). If that fails too, the call raises AIBackendError. Tokens spent on the failed attempt are still counted in ai_usage. The builder's version includes a hash of its own text, so editing a prompt without bumping the version cannot return an answer made by the old one.

3

The cache and workspaces

generate_cached() is the only function enrichers call. It looks up llm_cache in raw.db by (workspace, task, content hash, model, prompt version). A hit makes no backend call and is recorded in ai_usage as cached.

A workspace is a named profile with a trust level, an LLM backend, and its own slice of the cache and usage:

Trust Backend must be Meant for
secure local (locality = "local") Mail that must not leave this machine. The default.
insecure anything Mail the user is happy to send to a hosted model.

Because the workspace is part of the cache key, a secure workspace's results are never served to an insecure one, and the reverse. A backend that does not say where it runs counts as remote. An account picks its workspace with config["workspace"] (non-secret); Sieve-based routing comes in a later prompt. Only the language model is partitioned: embeddings use the single configured embedder.

[workspaces.private]
trust = "secure"
llm_backend = "ollama"

[workspaces.cloud]
trust = "insecure"
llm_backend = "ollama-remote"
4

The Ollama plugin

oaken_ollama is the first-party embedder and LLM backend.

[plugins.ollama]
# base_url defaults to this machine
keep_alive = "5m"
batch_size = 32

Model names come from ai.embed_model and ai.llm_model (defaults nomic-embed-text and qwen2.5:3b, both placeholders until tried) and can be overridden per plugin with model.

5

What this does not prove

Delimiters, nonces and a schema make an attack harder and make a success visible; they do not make a model obey. A model can still be talked into a wrong summary, which is why the answers are validated, never executed, and never allowed to trigger an action by themselves. The unit tests check what is sent and what is refused. They cannot show how a particular model behaves when it reads a hostile message; that needs a live run against a real model, which is still to do. The cache may contain text derived from mail, so it is as sensitive as raw.db and is cleared per workspace.