# AI tools

> A local LLM running on your device, or bring your own API key: your choice.

URL: https://wmkeyboard.pages.dev/tools/ai/
Section: Tools

The AI tool runs a fixed set of one-tap writing actions against whatever text is in the field you're typing in: rewrite, summarize, translate, and more. Where they run is up to you. Pick a cloud provider with your own API key, a self-hosted Ollama or LM Studio server on your network, or a model that downloads once and answers entirely on the device.

  tools/ai-panel-idle — The AI panel's action chips, ready to run against the text in the field.

## Using it

Open **AI** from the [toolbar or the toolbox](https://wmkeyboard.pages.dev/tools/overview/). It isn't pinned by default, so the first time you'll probably reach it through the toolbox. Typing its `ai` keyword on the suggestion strip works too. On a hardware keyboard, use the [leader key](https://wmkeyboard.pages.dev/reference/shortcuts/): double-tap Ctrl, then press A.

In the settings app, the **AI** row on the Home screen collects everything AI in one place: this tool and [AI reply](https://wmkeyboard.pages.dev/tools/ai-reply/), each with its switch, then the [chat](https://wmkeyboard.pages.dev/tools/ai-chat/), the settings assistant and the history. Press **AI writing tools** there to reach the provider, key and model.

The panel opens on a row of action chips. Eleven ship with the keyboard, and the list is yours to change: rename them, rewrite their prompts, turn off the ones you never use, reorder them, or add your own. See [Your own actions](#your-own-actions) below.

- **Rewrite**, **Summarize**, **Translate**, **Improve**, **Formal**, **Shorter**, **Friendly**, **Fix grammar**, **Explain**, **Continue**: each needs text to work on and is greyed out on an empty field.
- **Custom**: the one chip that stays live on an empty field. Tapping it opens an instruction box on the keyboard itself. Whatever you type there is the prompt for that run, so Custom can write brand-new text from nothing instead of transforming what's already in the field.

In a password field the chips that read the field are greyed out, and no action runs at all, Custom included. The text in a password box never goes to a provider, the same rule [AI chat](https://wmkeyboard.pages.dev/tools/ai-chat/) follows when it reads a field.

If nothing is configured yet, the panel shows a short explanation in place of the chip row. You get an **Open settings** shortcut when no provider is set up, which is where a fresh install starts, or a **Download a model** shortcut when On-device is selected and nothing has come down yet. A model that's downloading right now takes over that empty state with its progress. Once you're past the empty state, a one-line progress strip sits above the chips, so a download you started in Settings stays visible while you carry on typing.

  tools/ai-need-setup — The AI panel's empty state, before any provider or local model is set up.

**What gets sent:** selected text wins if you've selected any, and otherwise the whole field goes. **Continue** is the exception. It always reads back from the cursor, since continuing needs to know what came before rather than what comes after, and **How much the AI reads back** sets how far: 500 to 32,000 characters, default 4,000. That limit only applies with nothing selected. A selection is always sent as it is. Tap a chip on a truly empty field with no selection and you get "There is nothing to work on. Type some text first." instead of a request.

While a request runs, the panel names the step it's on instead of showing a bare spinner: Preparing, Connecting, Waiting for the model, and Reasoning for a model that thinks before it answers. A request can spend most of its time waiting on a slow connection or a long field, so the step is worth seeing. **Waiting for the model** is the one you'll meet most. It starts the moment your text is on its way and lasts until the first words come back, which for a cloud provider is nearly the whole run.

Once a result streams in, two buttons commit it. **Replace** swaps the field's text for the result. **Insert** drops it in at the cursor instead. Continue is the one exception to Replace: deleting the text you just asked it to continue would defeat the point, so Replace appends there. A **Delete markdown** checkbox, shown only when the result contains any and checked to start with, removes headings, bullets, quotes, code fences, inline code, links, and bold/italic syntax from what gets committed, and writes LaTeX maths out in Unicode, so `$x^2$` lands as `x²`. Uncheck it to keep the raw markdown. Reasoning output between `<think>` tags is filtered out of both Replace and Insert either way, so only the answer itself lands in your field.

If a model stops mid-answer because it hit the length ceiling, the panel says so and points at the setting to raise. That's worth knowing about: a truncated answer otherwise looks exactly like a finished one, and you'd only find out by reading your own half-rewritten text.

### Seeing what changed

  tools/ai-result-changes — A Fix grammar result in the Changes view, with the old spellings struck through.

A finished result gets a **Result** / **Changes** pair of chips. Changes compares the answer against the text the action ran on and marks what the model added and what it took out, the way `git diff` does. It's the one thing the plain result can't show you: after a Fix grammar run, the spelling it corrected is gone from the screen.

Deleted text is struck through, added text is underlined and slightly bolder. Shape rather than colour alone is deliberate, so the view stays readable on a photo-background theme, in a high-contrast theme, and if you're colour-blind. A changed space or paragraph break is drawn with a visible mark, since an empty highlight tells you nothing. Under the text sits a count of how many parts changed. "Parts" because they're words in prose and single characters in a language that doesn't put spaces between words.

Two cases can't be compared, so they don't offer the chips: **Continue**, which only ever read the text before your cursor, and any run that wrote from an empty field, where there's no original to compare against. Very long pairs of texts aren't compared either. The panel shows the plain result and says why. The comparison also waits until the answer has finished streaming, since half an answer reads as "everything after this was deleted".

The chips are on by default. **Open a result on the changes** (off by default) makes the panel start there instead.

  tools/ai-result-ready — A streamed result with Replace, Insert, and the strip-markdown checkbox.

### Reporting a result

Every finished result also carries a **Report** button, at the bottom right of the panel next to the Delete markdown checkbox. Tap it if a model produced something offensive, dangerous, or plainly wrong, and it opens a pre-filled email to the developer with a blank line at the top for what went wrong.

Nothing is uploaded when you tap it. The report is a draft in your own mail app, and you can read every line, edit it, or throw it away before sending. What it fills in for you:

- Your complaint, once you type it in the space at the top.
- Which action ran, which provider and model answered, and (for Custom) the instruction you gave.
- The app version, Android version, and device model.
- The text that went in and the text that came out, each trimmed to the first 4,000 characters.

That last one is worth a second look before you hit send, since it quotes whatever was in the field. The button only appears once a result has finished streaming. Half an answer isn't the thing you'd be reporting anyway.

For bugs in the keyboard itself rather than something a model wrote, use the **Feedback** rows in [About](https://wmkeyboard.pages.dev/reference/settings/about/) instead.

## Choosing where it runs

The services the AI can use are an **AI providers** list at the top of the tool's settings, the same kind of [provider list](https://wmkeyboard.pages.dev/tools/providers/) the translate and search tools keep. **Add a provider** offers Anthropic (Claude), OpenAI, Google Gemini, xAI (Grok), DeepSeek, Brave Answers, Privatemode AI, Ollama, LM Studio, an OpenAI-compatible service, and On your device. Each row holds its own key, address and model, set up in the sheet that opens when you tap it, with a **Test** button that sends the service one word and shows whether it answered.

One row answers at a time. Pick it under **In use**, just below the list, or with the [model picker](#options) on the AI panel; the [chat](https://wmkeyboard.pages.dev/tools/ai-chat/) and the settings assistant can each send to another row without changing it. A service can be on the list more than once: a second Anthropic key for work, or two Ollama servers, one at home and one at the office. Give each a nickname, and the picker shows it.

Switching a row off takes it out of the picker. With nothing on the list, or every row off, the panel says the AI isn't set up yet, even if keys from before are still stored.

  tools/ai-settings-providers — The AI providers list, with a service's key and model in its setup sheet.

### Cloud providers

Claude (Anthropic), OpenAI, Gemini, Grok, and DeepSeek are bring-your-own-key only. Unlike some of the other network tools, WM Keyboard ships no built-in or proxied key for any of them. Add the service and paste your own key into its setup:

- **Claude**: from `console.anthropic.com` → API keys
- **OpenAI**: from `platform.openai.com` → API keys
- **Gemini**: has a free tier at `aistudio.google.com`
- **Grok**: from `console.x.ai` → API keys
- **DeepSeek**: from `platform.deepseek.com` → API keys

Each service's setup also has a **Model** field. Leave it blank and the app falls back to a built-in default, shown right in the field's hint: `claude-sonnet-5-5` for Claude, `gpt-6-luna` for OpenAI, `gemini-3.8-flash` for Gemini, `grok-4.7` for Grok, `deepseek-flash` for DeepSeek. Type a model name here to use something else.

DeepSeek's reasoning models stream their thinking on a separate field. The app folds that into the same `<think>` handling everything else uses, so **Show model reasoning** and the reasoning progress readout behave there exactly as they do elsewhere.

### Brave

**Brave** uses Brave's Answers API, which runs a web search before it writes each answer. That makes it the provider to pick for questions about facts, prices or recent events, and a poor fit for pure writing jobs like Proofread, where the search adds cost and nothing else.

- **API key**: from `api-dashboard.search.brave.com`. The key needs Brave's **Answers** plan. A key that only has the **Search** plan is refused, and the panel shows Brave's own reason.
- **Leave the key blank to reuse the web search key.** If you already gave the [web search tool](https://wmkeyboard.pages.dev/tools/search/) your own Brave key, the AI tool uses that one. The key built into some builds is never used for answers, only for searches. With neither key, the row says it needs one.
- **No Model field.** Brave has one model, so there's nothing to pick.
- **One message per request.** Brave doesn't accept a system prompt or earlier turns, so the app sends the action's instructions, and in a [chat](#chatting-instead-of-transforming) the earlier messages as a transcript, together with your text as one message.
- Brave bills each answer for the search it ran plus the tokens in and out. The usage and citation markers it writes into the stream are removed before you see the answer.

### Privatemode

**Privatemode AI** runs its models inside confidential computing, and its requests are encrypted end to end, so the service operator can't read them. Get a key at `privatemode.ai`, which has a free tier. The row has three fields:

- **API key**: your Privatemode key.
- **Server address**: leave it blank for the keyboard to encrypt each request itself, or give the address of your own `privatemode-proxy`, for example `http://192.168.1.20:8080`. With a proxy the key is optional, since the proxy can hold it.
- **Model**: blank means `openai/gpt-oss-120b`. **List models** fetches the others your key may use.

With no proxy address, the keyboard does what Privatemode's proxy does before a request leaves your phone. It fetches the deployment's published manifest and asks the service to attest itself, and the attested deployment has to run exactly that manifest. It then agrees a secret with Privatemode's secret service, using a fresh post-quantum key pair (ML-KEM-768 with X25519), and checks that the answer is signed by a certificate of the deployment's mesh CA. Each request is encrypted with that secret, and the answer is decrypted as it streams in. An answer that comes back unencrypted, cut off or altered is refused. The secret is renewed every 45 minutes.

The proxy also verifies the hardware report itself, against AMD's and Intel's certificates, and compares the deployment's measurements with the manifest. The keyboard doesn't. So the encryption keeps your text from everything between your phone and the service that holds the attested mesh CA, but if Privatemode's API lied about its attestation, the keyboard wouldn't catch it. That's why the row says **Encrypted on this phone, attestation not checked**, and why nothing on the phone ever says "verified". For the full check, run `privatemode-proxy` on a computer you trust and give its address.

Through a proxy, the row says **Through your Privatemode proxy**, and the request travels from your phone to the proxy as an ordinary OpenAI-style call. Use `https://`, or keep the proxy on a network you trust. In the [chat](https://wmkeyboard.pages.dev/tools/ai-chat/), a lock in the top bar shows while Privatemode answers. Tap it to see which of the two it is.

Pictures aren't sent to Privatemode, and extra headers aren't added to its encrypted requests, because everything it reads outside the encryption is fixed by its protocol.

### Any other OpenAI-compatible service

**OpenAI-compatible service** is the escape hatch, and it reaches far more than the named services do. Anything that speaks OpenAI's chat-completions shape works: OpenRouter, Groq, Together, Mistral, Cerebras, self-hosted vLLM. Add one row for each service you use. Three fields:

- **Server address**: the address up to and including the version segment, for example `https://openrouter.ai/api/v1`. The app adds `/chat/completions` itself. That split exists because services disagree about the prefix (`/v1`, `/openai/v1`, `/api/v1`), so guessing it would break half of them.
- **Model**: required here, unlike the named providers. There's no sensible default to guess for a service the app knows nothing about.
- **API key**: optional. A gateway on your own network often wants none.

An `http://` address works, for a service on your own network, and that traffic is plain HTTP. Keep the server on a network you trust.

### Usage and balance

Each AI service's own screen has a **Usage** group: the requests and tokens this phone sent this month, any per-minute allowance the service reports, and for DeepSeek and OpenRouter a **Check what is left** button that asks for your balance. See [Usage and credit](https://wmkeyboard.pages.dev/tools/providers/#usage-and-credit).

### Extra headers

Some services want more in a request than a key. OpenCode Go, for one, reads a session header and a User-Agent of its own, and a gateway in front of a provider may want its own token. An **Extra headers** group on each service's own screen holds them: press the service's row on the list. The model on your device has none. Tap **Add header**, then fill in a name and a value. Each edit saves as you make it, and the value stays hidden until you tap the eye.

- **Yours are sent last.** A header here replaces the app's own of the same name, whatever its capitals, so a `User-Agent` or `Authorization` you add is the one the service sees.
- **`{session}` stands in for a session id.** Type it anywhere in a value, for example the name `x-opencode-session` with the value `{session}`. One panel action, its lookups included, sends one id, and a [chat](https://wmkeyboard.pages.dev/tools/ai-chat/) sends the same id for every message in that conversation.
- **Each kind of service has its own list.** Switching to another kind shows that kind's headers, not the last one's. Two rows of one kind share theirs.
- A name with a space or other character a header can't hold, or a value with a line break, is left out of the request rather than failing it.
- A backup without secrets leaves the headers out, the same as API keys.

### Self-hosted servers

**Ollama** and **LM Studio** point at a server on your own network instead of a cloud API. No key is needed, only an address like `http://192.168.0.10:11434` (Ollama) or `http://192.168.0.10:1234` (LM Studio). Set `OLLAMA_HOST=0.0.0.0` so Ollama listens beyond localhost. Turn on LM Studio's local-server option. Add one row for each server you run. Ollama defaults its model field to `qwen3` when you leave it blank. LM Studio has no default model to fall back to, so it uses whatever the server currently has loaded.

Traffic to a self-hosted server is plain HTTP, and the app allows cleartext traffic app-wide to support it. Keep the server on a network you trust.

### Local models

google

The **On your device** provider runs a model entirely on the phone, no network involved. It's the only provider that needs a download first, and the one that **Add a provider** doesn't offer at all in the F-Droid build.

  tools/ai-settings-local-catalog — The on-device model catalog, with tier badges and a size/RAM subtitle per model.

The catalog has two lists. The first is eight models in LiteRT-LM's format, the engine the keyboard has always used and still the default. They're ordered best-first rather than smallest-first:

| Model | Params | Size | Tier | Gated |
|---|---|---|---|---|
| Gemma 4 E2B | 2B | 2.59 GB | Recommended | |
| Gemma 4 E4B | 4B | 3.66 GB | Recommended | |
| Gemma 3 1B | 1B | 584 MB | Untested | Yes |
| Qwen 2.5 1.5B | 1.5B | 1.6 GB | Standard | |
| Qwen 3 0.6B | 0.6B | 347 MB | Experimental | |
| Qwen 2.5 0.5B | 0.5B | 546 MB | Experimental | |
| Gemma 3 270M | 270M | 304 MB | Untested | Yes |
| SmolLM2 135M | 135M | 143 MB | Experimental | |

Each row names the licence its weights are published under, and **Licence and model page** under an undownloaded row opens the repository where the text is. Gemma 3 comes under Google's Gemma Terms of Use; Gemma 4, Qwen and SmolLM2 are Apache-2.0. About, Licences lists the same under downloadable data packs.

Two of the eight are gated and need a Hugging Face account. Paste a token into the **Access token** field under **Hugging Face account**, above the catalog. Then accept the model's license on its Hugging Face page, once per model.

#### GGUF models on llama.cpp

Below them sits a second list, **GGUF models (llama.cpp)**. These run on [llama.cpp](https://github.com/ggml-org/llama.cpp), a second engine that loads any GGUF file, the format most open models are published in. Each row says **Runs on llama.cpp**, and its name ends in "(GGUF)", which keeps it apart from a LiteRT-LM model of the same name in the panel's and the chat's model pickers. Picking one of these models is how you pick the engine: the keyboard routes every request to whichever engine runs the model in use. On the F-Droid build this is the only list: it has llama.cpp but not LiteRT-LM.

| Model | Params | Size | Tier | Licence |
|---|---|---|---|---|
| Qwen 2.5 VL 3B | 3B | 2.77 GB | Untested | Qwen Research License |
| Qwen 3 1.7B | 1.7B | 1.83 GB | Untested | Apache-2.0 |
| Llama 3.2 1B | 1B | 808 MB | Untested | Llama 3.2 Community License |
| Gemma 3 1B | 1B | 806 MB | Untested | Gemma Terms of Use |
| Qwen 3 0.6B | 0.6B | 639 MB | Experimental | Apache-2.0 |
| Qwen 2.5 0.5B | 0.5B | 491 MB | Experimental | Apache-2.0 |
| SmolVLM 500M | 500M | 546 MB | Experimental | Apache-2.0 |
| SmolLM2 360M | 360M | 386 MB | Experimental | Apache-2.0 |
| Gemma 3 270M | 270M | 292 MB | Experimental | Gemma Terms of Use |

None of them is gated, so no Hugging Face token is needed, Gemma 3 included. Each one is fetched from a fixed commit of its repository and checked against a SHA-256 before it counts as downloaded. A file that doesn't match is deleted and the row asks you to download it again. The Qwen 3, Qwen 2.5 and SmolLM2 files come from the makers' own repositories, Gemma, Qwen 2.5 VL and SmolVLM from ggml-org (the llama.cpp project), and Llama 3.2 from bartowski's conversion of Meta's model.

Qwen 2.5 VL 3B and SmolVLM 500M read pictures. Each brings a second file, its projector (the `mmproj` file), which the download fetches first; the sizes above count both. Qwen 2.5 VL reads the text in a chat screenshot well, but needs a phone with 8 GB of RAM, and its licence allows research use only. SmolVLM is small enough for most phones, and tells you what a picture shows, but often gets the words in a screenshot wrong.

What's different about them:

- **GPU.** With **Compute** on **GPU**, a GGUF model runs on the phone's GPU: on a Snapdragon chip (an Adreno GPU) through llama.cpp's OpenCL code, which Qualcomm wrote for these GPUs, and on a Mali, Xclipse or PowerVR GPU through llama.cpp's Vulkan code. A phone with neither runs it on the CPU's big cores, whatever that row says. See [Compute](#compute-and-gguf-models) below.
- **Pictures, for the models that read them.** Qwen 2.5 VL and SmolVLM look at a picture themselves: a screenshot in the [reply tool](https://wmkeyboard.pages.dev/tools/ai-reply/), and the pictures in the newest message of a [chat](https://wmkeyboard.pages.dev/tools/ai-chat/#sending-pictures). For every other GGUF model, the reply tool reads a screenshot another way, as it does for any model that can't see.
- **The model's own chat template.** llama.cpp formats each conversation with the template stored in the GGUF file, so a model you import is prompted the way its makers trained it. Text from your field is kept as text: it can't open a turn of its own, even if it contains the template's markers.
- **Stop really stops.** A LiteRT-LM answer runs to its end in the background after you press Stop. A GGUF answer ends at the next word, and what it had written is what the model remembers.
- **Memory is checked first.** Before a model loads, the keyboard compares what it needs (the file, plus room for the context) with the memory free on the phone. If it's short, the context shrinks first. If even that won't fit, you get a message with both figures instead of a crash. A loaded model is unloaded after three minutes with nothing to do, and the keyboard lets go of it earlier when it hides.

llama.cpp itself is an extra part of a few megabytes. The Google Play edition downloads it from Play with the first GGUF model, and the GitHub edition fetches it from the project's own release. Until it's there, the GGUF list shows a banner with a **Download** button. Google Play sometimes wants your OK before it downloads a part like this, for example on mobile data. It can only ask inside the app, not over the keyboard, so the chat says so, and the question appears the next time you open WM Keyboard's settings.

Every model carries an advisory minimum-RAM figure behind the scenes. When your phone's total RAM falls under it, the app appends "May be too large for this device" to that model's subtitle. It never blocks the download and never shows you the raw number. That warning is the only per-device signal the catalog gives you up front. There's no published speed benchmark, because how fast a model answers depends on the phone as much as on the model. What you get instead is a progress readout with elapsed seconds, once a request is actually running.

Tapping **Download** starts a resumable download. Cancel it, lose signal, or background the app, and the next attempt picks the transfer up where it left off rather than restarting. Only one download runs at a time. On a metered connection, a model of 500 MB or more asks for confirmation first.

Don't need the catalog? **Import your own** takes any `.litertlm` or `.task` file, which LiteRT-LM runs, and any GGUF file, which llama.cpp runs. That covers one you converted yourself or downloaded elsewhere in a browser. A GGUF is recognised by its contents, so one that lost its `.gguf` extension on the way still imports and gets the extension back. Anything else is rejected. The same goes for **Import files** under Storage, which also takes a catalog GGUF and checks it against its SHA-256 like a download. An imported GGUF uses the chat template inside it; one with no template is prompted in ChatML, the format most small open models use.

A GGUF vision model you import reads pictures when its projector is imported with it: select the model and its `mmproj` file together, or import one after the other. The projector is not a model, so it gets no row of its own. It pairs with the imported model whose file name it shares, or, when there is only one imported GGUF, with that one. Deleting the model deletes its projector too.

A **Compute** row picks **CPU** or **GPU** for inference, and CPU is the default. GPU is best-effort. If it fails to initialize, runs out of GPU memory partway through an answer, or takes the keyboard down with it, the engine falls back to CPU on its own and keeps that model on the CPU from then on. It tries the GPU again after a system update, an app update or a fresh download of the model. Some GPU drivers keep a failed model's memory after the model is closed, which can leave the whole phone slow. When that happens, the keyboard restarts itself a few seconds after you close it, so the memory is freed. It doesn't do this while a screen of the app is open or a model is downloading.

#### Compute and GGUF models

**GPU** applies to a GGUF model on a phone with an Adreno GPU, the GPU in Snapdragon chips, or with any other GPU whose driver speaks Vulkan 1.2 (Mali, PowerVR, Samsung's Xclipse). On an Adreno it runs llama.cpp's OpenCL code, which Qualcomm tuned for the Snapdragon 8 Gen 1 and newer; older Adreno GPUs that support OpenCL 2.0 run it too, with less gain. On the other GPUs it runs llama.cpp's Vulkan code. How that compares with the CPU depends on the GPU and its driver: public measurements range from much faster on recent Mali and Xclipse GPUs to slower than the CPU on older Mali ones, so try both on your phone. **NPU** runs a GGUF model on the NPU of a Snapdragon 8 Gen 3, 8 Elite or 8 Elite Gen 5 through llama.cpp's Hexagon code, a download of its own of about 6 MB (11 MB on the phone), in place of the GPU build. The NPU works best with models quantized as Q4_0 or Q8_0; the parts of a model it cannot run go to the CPU. It is new in llama.cpp and has not been measured on a phone here, so compare it with **GPU** on yours. On any other phone **NPU** means **GPU** for a GGUF model. Each GPU build of llama.cpp is a download of its own, fetched only on a phone it runs on: the OpenCL build, about 10 MB, on a Snapdragon, and the Vulkan build, about 13 MB to download and 42 MB on the phone because it carries its compiled shaders, on the others. It comes when you pick **GPU** with a GGUF model on the phone, or the first time a GGUF model answers with **GPU** picked. Until it's there, GGUF models run on the CPU. It's loaded the first time a GGUF model runs after the keyboard starts, so if one already ran on the CPU, the GPU takes over after the keyboard restarts, which it does by itself a few seconds after you close it, as above. Failures are handled the same way as for LiteRT-LM models: a model that fails on the GPU moves to the CPU and stays there until the next system or app update, and a GPU driver that takes the keyboard down while starting is not used again until then.

The GPU makes the biggest difference to reading a long prompt, such as a chat screenshot or a long text to rewrite. Writing the answer is limited by memory speed, so it gains less, and for the smallest models the CPU can be as fast. Qualcomm tuned models quantized as Q4_0 first. The catalog's Q4_K_M and Q8_0 files also run on the GPU, and with some Snapdragon 8 Gen 2 and Gen 3 drivers llama.cpp keeps the last layer of a K-quant model on the CPU, where those drivers compile it badly.

On a phone with a Snapdragon 8 Elite (SM8750, the Galaxy S25 series) or 8 Elite Gen 5 (SM8850, the Galaxy S26 Ultra), running Android 12 or later, the row has a third choice, **NPU**, which runs a model on the phone's AI accelerator. The NPU needs a build of the model compiled for that chip, and only Gemma 3 1B and Gemma 3 270M have one. With **NPU** selected, **Download** on those two rows fetches the NPU build (689 MB and 461 MB on an SM8750) instead of the standard file. A model you already have gets a **Get the NPU build** button. Every other model runs as it does with **GPU**. So does a model whose NPU build fails to start, and the app keeps that model off the NPU until the next system or app update. A failure partway through an answer only moves the next try in that session to the GPU, because a prompt longer than the build's window fails the same way. The NPU build runs only on the NPU, so a model whose only file is its NPU build offers **Get the standard build** when Compute is CPU or GPU. The NPU runtime is Qualcomm's QNN. The Google Play edition downloads it from Play the first time you pick **NPU**. The GitHub edition first asks you to accept Qualcomm's licence, then fetches only your chip's files from Qualcomm's own package on Maven Central, about 30 MB. The F-Droid edition has no NPU at all.

A **Context size** row sets how much text the model is loaded with, prompt and answer together, in tokens. It defaults to the model's own value, or 4,096 tokens for a GGUF model, whose own value is often far more than a phone can hold. This is the only length lever on-device. Unlike a cloud API there's no per-response ceiling to set, so the window is what bounds an answer. A larger window costs memory, and changing it reloads the model.

## Your own actions

Tools / AI writing tools / Actions on the panel

The eleven chips on the panel are a list you own. The screen lists them in panel order, with a checkbox to turn one off, an edit button, and a **New action** row at the bottom. Drag the handles in **Order of the actions** to rearrange them. The panel follows that order exactly.

Any of these can also be a button on the [selection actions](https://wmkeyboard.pages.dev/smart/selection-actions/#language) bar: pick them under Advanced, Selection actions, AI buttons, and one tap runs the action on whatever is selected, with the result landing here ready to apply. Formal, Shorter and Friendly were added with that in mind.

An action that ships with the keyboard is never deleted, only changed. Editing one saves your version under the same identity, so **Reset** in its editor drops your version and the original comes straight back. Turning one off leaves it on this screen, greyed on the panel and one checkbox away from returning. Only actions you wrote yourself get a **Delete**.

The editor holds a name, the prompt, and a handful of behaviour switches:

- **Type the prompt at every run**: the keyboard asks you for the prompt instead of using a saved one. This is what makes **Custom** work the way it does.
- **Start from the saved prompt**: only shown under the switch above, and it brings the prompt field back. The instruction box then opens with that text already in it, so you adjust a template rather than retype it. Useful for something you run often with a small variation each time: "reply to this politely, and mention that…". The saved text wins every time the box opens, not only the first, since a template you see once isn't a template. Leave it off and the box reopens on whatever you typed last.
- **Works with an empty field**: with nothing in the field, the action writes new text instead of changing text.
- **Read the text before the cursor**: the action reads only what's in front of the cursor rather than the whole field. A selection always wins over this.
- **Add the result to the end**: Replace appends rather than overwriting. Pair it with the switch above and you have **Continue**. Keeping them separate is what makes "append but read the whole field" something you can build.
- **Answer with the text only**: on for everything except **Explain**, whose answer is prose about your text rather than a replacement for it. Hidden once **Write the whole prompt** is on, since a raw prompt has nothing left for the switch to act on.

### About the prompt field

You write the *task*, not the whole prompt. The app wraps what you write in a fixed frame. That frame says who the model is, and it says your field's text is material to work on and never instructions to obey. Unless you turn it off, it also says to answer with the result and nothing else.

That's on purpose. The middle part is what stops a model from treating text in your field as commands aimed at it, and that text is often an email you're replying to or a message someone sent you. A text box shouldn't be able to delete that rule by accident. **The prompt that the app sends** below the editor shows you the assembled result, so it's visible without being editable.

Write `{target}` anywhere in a prompt and it becomes the translate target language, which is how the shipped **Translate** action works. Because the token is stored rather than the language, changing the target language updates every prompt that mentions it.

**Write the whole prompt** is the deliberate way out for anyone who wants to write the safety wording themselves. The app then sends your text exactly as you typed it.

Prompts you'd already customised carry over automatically, and the app checks its own work. A prompt it recognises is unpicked back into a task. Anything it doesn't recognise is kept verbatim under this switch rather than guessed at.

## Letting the model look things up

Tools / AI writing tools / What the model can do

A model answers from what it was trained on, which is a fixed moment in the past. Turn one of these on and it can stop guessing: before it answers, it asks the keyboard to go and find out.

- **Let the model search the web**: on by default, but the row only appears once you've set up a search service. The model writes its own search (usually a better one than the question you asked), reads the results, and answers from them. Searches go through whatever backend the [search tool](https://wmkeyboard.pages.dev/tools/search/) already uses (your SearXNG instance, your Tavily key, or Brave) and count against that service's quota like any other search.
- **Let the model read a web page**: off by default. The model gives one address and gets the text of that page back. It needs no search service, so it works on its own: paste a link, ask "what does this say?", and the model fetches it.
- **Lookups per answer**: 1 to 5, default 3. How many times the model may go and look something up before it has to answer with what it has. Each round is another request carrying everything found so far, so this is the setting that decides what a curious model costs you.

Both tools work in the panel's actions and in [AI chat](https://wmkeyboard.pages.dev/tools/ai-chat/). While one runs, the progress readout says what it's doing and on what: **Searching the web for "…"** with the model's own query, or **Reading example.com…** with the site it opened.

### Why page reading is the one that's off

**Let the model search the web** sends a query the model wrote. **Let the model read a web page** opens an address, and that address can have come from the text you were typing: a link in a document someone sent you, say. That makes it the one tool here that something in your own field could point somewhere, so it waits until you ask for it.

When it does run, the page is fetched as plain text and cut short at about 6,000 characters. No scripts run, nothing is sent *to* the page, and only `http://` and `https://` addresses are opened at all. A model that invents a `file://` or `content://` address gets a refusal, not your storage.

### What your provider needs to support

Most providers do tool calling properly, as part of their API: Claude, OpenAI, Gemini, Grok, DeepSeek, Ollama, LM Studio and any other OpenAI-compatible service. Two can't:

- **Brave** takes a single message with no system prompt and has no tool field at all.
- **On-device models** have no tool API in the local runtime.

For those two the keyboard falls back to explaining the tools to the model in words and watching its answer for a reply in the agreed format. The reader is deliberately forgiving about that format, because local models mangle it: `tool_call` is a special token in several chat templates, so the template rewrites the very thing the keyboard asked for, and a marker can come back with its brackets moved, doubled, or missing altogether. All of those are read, and anything that looks like one is hidden from the answer whether or not it turns out to be a call, because machinery the model wrote for the keyboard is never yours to read.

It works, and a capable local model will use it, but a small one still ignores the format sometimes, or asks you to narrow a request it could have searched as written. The settings group says so when you're on one of those providers. If lookups matter to you, a provider with real tool support is far more reliable.

Thinking models add one more rule. Before a model calls a tool it may reason, and several providers make the keyboard hand that reasoning back, untouched, along with the tool's answer: Gemini 3 attaches a signature to every call, Claude signs its thinking blocks, DeepSeek wants its reasoning text, and Gemini reached through OpenRouter or an OpenAI-compatible address hides the same signature in other fields. The keyboard keeps each model's turn exactly as it arrived and sends it back that way. Some providers reject the request if anything is missing.

### Seeing what it looked up

An answer that used a tool says so. A line sits over the result, in the panel and in every chat answer: **Searched the web for "…"** or **Read example.com** for a single lookup, or **Used tools 3 times** when there were several. No line means the model answered from what it already knew.

Tap the line to unfold it. Each lookup is listed with what came back:

- **A search** shows the query and every result the model was handed, by title and site. **No results** if the search came back empty.
- **A page read** shows the page's title and its full address, so you can check it's the page you think it is.
- **A lookup that failed** is marked as failed, with the reason: **Web search is not set up**, a network error, the model asking for something that isn't a web address, and so on. The model is told too, and usually says it couldn't check.

Tap a result to open it in your browser, or press and hold to copy its address. Only `http://` and `https://` addresses ever open. Anything else the model wrote can be copied but not opened. The lines aren't part of what **Replace** or **Insert** puts in your field.

In [AI chat](https://wmkeyboard.pages.dev/tools/ai-chat/) the record is saved with the answer, so reopening a conversation later still shows what each answer was built on. The panel's actions keep it only while the result is on screen.

Every search and every page fetch is also a row in the [network activity log](https://wmkeyboard.pages.dev/privacy/network-activity/), filed under **AI tool use** rather than **Web search**: you didn't run that search, the model did, and the log should say which.

## The keyboard's own tools

Tools / AI writing tools / Keyboard tools

Besides searching and reading pages, the model can use the keyboard's own tools, the same ones on your toolbar, through the same services and with the same settings. Ask "what's 18% of 2,450 in euros?" and it works the sum out on the calculator and converts it at a live rate instead of guessing either.

Each tool has its own **Run**, **Ask** or **Off**, the same three choices an [MCP server's tools](#run-ask-or-off) have:

- **Run**: the model uses it whenever it needs to.
- **Ask**: the model's request stops and waits for you, with what it is about to do and the values it will send. See [Allowing a call](#allowing-a-call).
- **Off**: the model isn't told the tool exists.

| Tool | What it does | Starts on |
|---|---|---|
| **Image search** | Finds pictures through the [search tool's](https://wmkeyboard.pages.dev/tools/search/) service. Only listed once one is set up. | Run |
| **Weather** | Today's and tomorrow's weather for any place, or the place saved in the [weather tool](https://wmkeyboard.pages.dev/tools/instruments/#weather) when the model names none. | Run |
| **Wikipedia** | Reads the summary of the best-matching article, in the Wikipedia language the [encyclopedia tool](https://wmkeyboard.pages.dev/tools/search/#reading-wikipedia) uses. | Run |
| **Dictionary** | Looks a word up in the [dictionary tool's](https://wmkeyboard.pages.dev/tools/search/#looking-up-definitions) sources, in your order. | Run |
| **Translate** | Translates with the [translate tool's](https://wmkeyboard.pages.dev/tools/translate/) engine: on your device, online, or on your device first, as set there. | Run |
| **Currency converter** | Converts money at live rates from the [currency tool's](https://wmkeyboard.pages.dev/tools/utilities/#converting-units-and-currencies) sources, coins included. | Run |
| **Unit converter** | Converts lengths, weights, temperatures and other measurements. | Run |
| **Calculator** | Works an expression out exactly, on your phone, with the [calculator's](https://wmkeyboard.pages.dev/tools/utilities/#doing-the-math) degree and precision settings. | Run |
| **WM Keyboard docs** | Searches this documentation for questions about the keyboard itself ("how do I import a Chinese dictionary?") and reads the best-matching sections, with their links. Sends only the search words, to this site. See [Using the docs with AI](https://wmkeyboard.pages.dev/reference/ai-access/). | Run |
| **Read your calendar** | Lists your events for the days the model asks about, up to a month at a time. Needs the [calendar tool's](https://wmkeyboard.pages.dev/tools/calendar/) access. | Ask |
| **Read your notes** | Searches your [notes](https://wmkeyboard.pages.dev/tools/notes/) and reads up to five that match. With [Search by meaning](https://wmkeyboard.pages.dev/smart/search-by-meaning/) on, notes that say the same thing in other words follow under "Close in meaning". A locked note's text stays hidden. | Ask |
| **Read your snippets** | Searches your [snippets](https://wmkeyboard.pages.dev/tools/snippets/) by their name, trigger, tags and text, and reads up to five that match, or the five newest when the model asks for none in particular: each one's name, how it is typed and its text, cut short. A [template](https://wmkeyboard.pages.dev/tools/snippets/#template-variables) or [pattern](https://wmkeyboard.pages.dev/tools/snippets/#pattern-triggers) is read as written, `{date}` and `$1` included, and never expanded. With [Search by meaning](https://wmkeyboard.pages.dev/smart/search-by-meaning/) on, snippets that say the same thing in other words follow under "Close in meaning". | Ask |
| **Search your clipboard** | Finds the clips in your [clipboard history](https://wmkeyboard.pages.dev/tools/clipboard/) that mean what the model asks about, in any language, and reads up to eight short excerpts. Passwords, one-time codes and deleted clips are left out. Only listed while [Search by meaning](https://wmkeyboard.pages.dev/smart/search-by-meaning/) is on and its model is downloaded, and it only finds what the keyboard has already indexed, which happens when the clipboard panel opens. | Ask |
| **Your location** | Reads where your phone is, rounded to the precision the [location tool](https://wmkeyboard.pages.dev/tools/location/) shares at, with the address if you have that on. | Ask |
| **Add to your calendar** | Adds an event, see [Adding calendar events](#adding-calendar-events). | Ask |
| **Save a note** | Adds a new note. | Ask |
| **Set an alarm** | Sets an alarm in the [clock](https://wmkeyboard.pages.dev/tools/clock/), once or on repeat days. | Ask |
| **Start a timer** | Starts a countdown in the [clock](https://wmkeyboard.pages.dev/tools/clock/). | Ask |
| **Open an app** | Opens an installed app by its name, from the [app launcher's](https://wmkeyboard.pages.dev/tools/app-launcher/) list. | Ask |
| **Control media** | Plays, pauses or skips what is playing, or says what it is. Needs the [media tool's](https://wmkeyboard.pages.dev/tools/media/) notification access. | Ask |
| **Send to your computer** | Sends text or a link to a computer paired through [KDE Connect](https://wmkeyboard.pages.dev/tools/kde-connect/). Only listed while KDE Connect is on. | Ask |
| **Read your home** | Lists what is on or open across every home of the [Smart home tool](https://wmkeyboard.pages.dev/tools/home-assistant/), or whatever matches a home, room, device or sensor name. Only listed while a home is set up. | Ask |
| **Control your home** | Works one device on any home of the [Smart home tool](https://wmkeyboard.pages.dev/tools/home-assistant/): lights, fans, temperature, scenes, locks. The model finds the device with **Read your home** first. With a Home Assistant on the list it can also send one plain sentence to Assist. | Ask |
| **Read text in a picture** | For a model that can't see pictures: reads the text in one you [sent in the chat](https://wmkeyboard.pages.dev/tools/ai-chat/#sending-pictures), with ML Kit on your device google, or the [scan text tool's](https://wmkeyboard.pages.dev/tools/scanner/) online engine if you picked that. | Run |
| **Search by picture** | Uploads a picture you sent to the [camera tool's](https://wmkeyboard.pages.dev/tools/camera/) search site (Google Lens unless you changed it) and gives you the link to the results. The model can't read that page itself. | Off |

The tools that read what's on your phone, and the ones that change something, start on **Ask**. A web page the model reads could carry instructions of its own, and **Ask** means nothing it says reaches your calendar or your notes without you seeing the request first. None of the tools can delete or edit anything: they only read, add or start. **Always allow** on the request card switches that tool to **Run**.

The calendar tools, and the alarm, are told today's date and your time zone, so "tomorrow at five" lands on the right day.

Every tool that is on is described to the model in every request, whether it gets used or not. That costs a little each time, and a small model on your device copes best with a short list, so turn off what you don't want it reaching for.

While one runs, the line over the answer says what it is doing: **Checking the weather in Dhaka…**, **Converting 12 USD → EUR…**. Unfolded, a Wikipedia lookup lists the article it read, and an image search the pages its pictures came from.

### Adding calendar events

Tools / AI writing tools / Keyboard tools / How events are added

**Add to your calendar** works one of two ways:

- **Open the app**, the default: your calendar app opens with the event filled in, and nothing is saved until you press its own Save. It needs no permission. The model is told the event isn't saved yet, so it says so.
- **Add directly**: the keyboard writes the event into your calendar itself. Picking it asks for permission to add calendar events, with the usual explanation first, then lets you choose which calendar they go in. The first writable one, primary first, is used until you pick.

## MCP servers

Tools / AI writing tools / MCP servers

MCP (the Model Context Protocol) is a standard way for a server to offer tools to a model. Add a server here and the model can use its tools in the panel's actions and in [AI chat](https://wmkeyboard.pages.dev/tools/ai-chat/), the same way it uses the two lookups above: to read your notes, search a wiki, add a calendar event, or whatever the server does. The tools run on that server, not on your phone.

### Adding a server

Press the **+** button. The editor asks for:

- **Name**: what the list calls it. Leave it blank and the host name is used.
- **Address**: the server's `https://` or `http://` address, for example `https://example.com/mcp`.
- **Type**: **Auto**, **Streamable HTTP** or **SSE**. Auto works for almost every server. It tries Streamable HTTP, the current transport, and falls back to the older SSE one when the server answers like an old server. Pick SSE for an old server that Auto can't reach. Its address often ends in `/sse`.
- **Headers**: sent with every request to this server. This is where a token goes. A typical one is the name `Authorization` with the value `Bearer` followed by a space and your token. Values are hidden until you tap the eye.

**Save** stores the server. **Connect** saves it too, then asks the server for its tools and reports back: "Connected to *server name*: 12 tools", or the reason it couldn't. A server you just added says **Not connected** in the list until a Connect has worked, and the model is offered nothing from it until then.

Change the address later and the tool list no longer describes the server, so the editor asks you to connect again.

### Pasting a JSON config

Most desktop apps that use MCP keep their servers in a JSON file. Copy that file's text, then press **Paste a JSON config** on the server list. The keyboard reads the formats in common use: Claude Desktop and Cursor's `mcpServers`, VS Code's `servers`, the inner map on its own, or a single server. It shows what it found before anything is saved, then **Add 2 servers** adds them and connects each one.

```json
{
  "mcpServers": {
    "notes": {
      "url": "https://notes.example.com/mcp",
      "headers": { "Authorization": "Bearer your-token" }
    },
    "files": {
      "command": "npx",
      "args": ["-y", "some-file-server"]
    }
  }
}
```

Here **notes** is added. **files** is listed as skipped: it starts a program on a computer (the `command` line), and a phone has nothing to start it with. A server with no address, or one that isn't `http` or `https`, is skipped too, with the reason beside it.

### Run, Ask or Off

Once a server is connected, its editor lists every tool, with the server's own description and badges for what the server says about it: **Read only**, or **Can change or delete data**. Each tool has three choices:

- **Run**: the tool runs when the model calls it, without asking you.
- **Ask**: the answer stops and waits for you to allow the call.
- **Off**: the model isn't told the tool exists.

A new tool starts on **Run** if the server marks it read only, and on **Ask** otherwise. That default matters because a tool can send mail or delete a file, and text in the field you're editing could try to talk a model into calling one. Your own choice always wins over the server's marks.

The list row for each server sums it up ("12 tools: 3 run, 7 ask, 2 off") with a switch that turns the whole server off without deleting it.

### Allowing a call

When the model calls a tool set to **Ask**, an approval card appears in the panel or in the chat. It names the tool and the server and shows exactly what the model wrote as the tool's input. Three buttons:

- **Deny**: the call doesn't happen. The model is told you said no and answers without it.
- **Allow once**: this call goes ahead.
- **Always allow**: this call goes ahead, and the tool is set to **Run** from now on.

### What is sent, and where

Nothing goes to a server until the model calls one of its tools, or you press Connect. A call sends the tool's name and the values the model wrote for it straight to your server, with your headers. It doesn't go through any WM Keyboard service. Whatever the server sends back is handed to the model. Every request shows in the [network activity log](https://wmkeyboard.pages.dev/privacy/network-activity/) under **MCP servers**.

### Limits

- **No sign-in yet.** A server that wants you to log in through OAuth can't be used. If it gives out tokens or API keys, add one as a header instead.
- **Only `http` and `https` servers.** Local servers that start as a program (`command`, `npx`, `uvx`) need a computer to run on.
- **Every tool costs prompt space.** The name, description and input format of each tool that isn't **Off** go with every request, so 30 tools can cost more than the text you asked about. The server list warns you past 20. Turn off the tools you don't need.
- **Text only.** Images, audio and links a tool sends back aren't shown to the model. It is told that something was there.

## Suggestions in the strip

Tools / AI writing tools

Off by default. Turn on **AI suggestions** and the provider you set up here offers one short continuation of what you're typing, as a chip in front of the word suggestions. The chip carries a sparkle and an accent outline, so it never passes for a dictionary word. The word suggestions stay where they are, and a [smart chip](https://wmkeyboard.pages.dev/smart/chips/) still wins the strip when one comes up.

A request goes only after a word ends (a space or a punctuation mark) and you stop typing for about 0.6 seconds. In Chinese and Japanese, which put no space between words, a committed Han or kana character counts as a word end too, so the chip can follow 你好 with nothing typed after it. On a pinyin, kana or other conversion layout it waits until the reading is committed and the candidates have left the strip. Any key you press cancels it. The answer has 1.5 seconds to arrive; after that the chip doesn't appear and nothing tells you, since a late suggestion is for text you've already moved past. The chip shows an answer only if it is one line of at most 48 characters that doesn't just repeat your text.

The request asks the model not to think first, where the provider has a way to say so. A model that reasons anyway, such as DeepSeek's default or OpenAI's reasoning models, gets up to 512 tokens and 5 seconds instead, since its reasoning comes out of the same budget as the answer.

It normally asks once a word has ended. **Ask while typing a word** (off by default, because it sends many more requests) also asks mid-word. While a Chinese or Japanese reading is still unconverted it asks a different question: what the reading most likely spells in your sentence. The chip then shows those characters, and tapping it puts them in place of the reading, like picking a candidate. If the model takes the reading for Latin text there is no chip, and shape codes such as Wubi, Cangjie and strokes aren't sent at all. **Ask after a word ends** can then be turned off. Each has its own **Wait before asking** and **Give up after** time, and a model that always thinks first is skipped mid-word, though a reading waits for it, up to the same 5 seconds. Thai, Lao, Khmer, Burmese, Tibetan, Javanese and Balinese count as spaceless, like Chinese and Japanese: their letters can end a word, and answers in them stop at 16 characters.

A conversion you tap is learned like a picked candidate, unless learning is off, you're in incognito or it's a password field, so the same reading ranks it higher next time. On the built-in Pinyin engine that holds even for a word the dictionary lacks, as long as you typed full pinyin and the word has one character per syllable. On RIME and Mozc it's learned only if the engine lists the word too.

Tap a continuation to insert it after your text, with a space in front where one is missing (never next to Chinese or Japanese). Since 0.6.6 the word it ends on is learned once, as if you'd typed it, so a word the model found for you counts toward your [personal dictionary](https://wmkeyboard.pages.dev/smart/personal-dictionary/) the way your own do. When the continuation finishes a word you'd started, the whole word is learned. Only that word: the rest is the model's phrasing, so no other word and no word pair comes from it. Nothing is learned with learning off, in incognito or in a password field, and nothing you've learned is ever sent to the provider. Hold it to hide AI suggestions until you move to a different field. **Show after the words**, off by default, keeps the chip at the end of the strip instead of in front of the words, so a chip arriving late never moves a word you're about to tap.

What each request carries:

- **Text to send**: the last 120 characters before the cursor by default, cut forward to a whole word. The slider goes from 40 to 500.
- **Send only the last word**: sends that word and nothing else. Less goes out, and the suggestions get worse.
- A short system prompt asking for a continuation in the same language, with no explanation and no quotes.

No request is made at all in a password field, in a field that turns suggestions off, in [incognito](https://wmkeyboard.pages.dev/privacy/incognito/), when no provider is set up, or while [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/) holds cloud AI back. With an **On-device** model the text stays on the phone. A phone model can't be stopped part way, so no new request starts while one is still running, or while the AI panel is using the model. The first request also has to load the model, which can take longer than the 1.5 seconds.

## Chatting instead of transforming

Tools / AI writing tools / Chat with the model

The panel's actions all do the same shape of thing: take the text in a field, hand it back changed. When you want an actual conversation instead, tap **Chat** on the panel's header. The panel swaps its actions for a conversation with any model you've already set up here: ask something, then **Insert** the answer into what you're writing. **Actions** on the same header swaps back, and the panel reopens on whichever you used last.

The same chats open full-screen in the settings app, at the path above or from a launcher shortcut of their own. Both places share one list of saved conversations. See [AI chat](https://wmkeyboard.pages.dev/tools/ai-chat/).

Two more ways in work from any app. The [floating button](https://wmkeyboard.pages.dev/reference/settings/floating-button/#asking-the-ai-about-the-screen), set to **Ask AI about this screen**, opens a chat as a sheet over the screen you're on, with that screen's text attached when screen reading is on. **Ask AI** in the text selection menu opens the same sheet with the selected text; **Ask AI in the selection menu** below takes it out of that menu. The phone's assistant gesture can open it too, with **Use as the phone's assistant** below. **WM Keyboard** in the same menu runs your actions on the selected text directly (Explain, Summarize, Shorter and the rest) and can put the answer in place of the selection; see [In any app's selection menu](https://wmkeyboard.pages.dev/smart/selection-actions/#in-any-apps-selection-menu).

## Memory

AI / Memory

The AI can keep short facts about you between chats: where you live, what you do, how you like answers. A chat saves them, with an **Undo** under the answer each time, and the panel's actions and AI reply are handed them too, so a reply or a fresh piece of writing can use them. Actions only read memory, never save to it, and are told to leave the facts out unless the task needs them. A model on your phone gets memory while it's on; a cloud or self-hosted service gets it only after you turn on **Use with cloud models**. Incognito fields get none. See [AI memory](https://wmkeyboard.pages.dev/tools/ai-memory/) for the whole of it.

## History

Tools / AI writing tools / History

Off by default. Turn it on and the keyboard keeps a record of each run: what you sent, what came back, which action and model, how long it took, how much of that was reasoning, and whether you used the answer. The screen searches across the input, the answer and the model name. It filters by action, and expands a row so you can copy either side or delete it.

Failed runs are recorded too, with the error the panel showed, since "it keeps failing" is the most likely reason to open the screen at all.

Four things have to be true before anything is written down, and any one of them being false is the end of it:

1. You turned the history on.
2. The device has been unlocked at least once since it restarted.
3. The field isn't a password box.
4. [Incognito](https://wmkeyboard.pages.dev/privacy/incognito/) isn't in force, from either the setting or a field that asked for it, like a private browser tab.

API keys are never part of a record. It carries the model's name and nothing about the connection.

**Number of records to keep** defaults to 100 and tops out at 500. The oldest falls off once it's full, and each side of a record is capped at 4,000 characters. **Delete all history** clears the lot, and so does turning the setting off. An off switch that quietly leaves the log on disk isn't an off switch.

## Options

Tools / AI writing tools

Like every tool, AI has an **Enabled** toggle (on by default) and, if [colorful tool icons](https://wmkeyboard.pages.dev/reference/settings/tools/) are on, a per-tool icon color override. Below that:

- **Keyword**: the word that offers this tool on the suggestion strip when you type it alone. Defaults to `ai`.
- **Extra headers**: on each service's own screen, none to start with, see [Extra headers](#extra-headers).
- **AI providers**: the services and models the AI can use, see [Choosing where it runs](#choosing-where-it-runs). Empty on a fresh install, which lands on the empty-state screen until you add one. Until you edit it, the list holds every service your earlier settings had a key or an address for.
- **In use**: which row answers, shown once the list has two. The model picker on the panel changes it too.
- **Max response length**: a token ceiling, roughly ¾ of a word each, picked from steps rather than a slider: 1k through 128k, plus **Provider maximum**. Default 8,192. A model the app takes for a reasoning model gets 4× this ceiling on its own, capped at 131,072, because reasoning tokens come out of the same budget before the answer starts. That guess is a substring match on the model ID, for strings like "thinking", "qwen3", "grok-4" or "deepseek-v4". The On-device provider swaps this row for **Context size**.
- **Translate action's target language**: free text, defaults to **English**.
- **Show model reasoning**: off by default. Turning it on streams a reasoning model's raw `<think>` passages instead of the step-by-step progress readout. It's for the panel's actions only: the [AI chat](https://wmkeyboard.pages.dev/tools/ai-chat/#reasoning-and-tool-calls) always keeps reasoning in a folded step over the answer.
- **Model picker on the panel**: on by default. Adds a horizontally-scrolling row to the panel itself for switching between every configured provider and downloaded local model in one tap. Under it sits the name of the model in use: press it and the keyboard asks the provider what it offers, then lists those to pick from. **Choose from the list** beside the Model field on a provider's page does the same in settings. It's hidden automatically whenever there are fewer than two to choose from.
- **Show a changes view**: on by default, see [Seeing what changed](#seeing-what-changed). **Open a result on the changes** below it is off.
- **AI suggestions** (under **Suggestion strip**): off by default. With it on, **Send only the last word** (off) and **Text to send** (120 characters) set what each request carries. See [Suggestions in the strip](#suggestions-in-the-strip).
- **Let the model search the web**: on by default, and only shown once a search service is set up. **Let the model read a web page** below it is off, and **Lookups per answer** defaults to 3. See [Letting the model look things up](#letting-the-model-look-things-up).
- **Keyboard tools**: Run, Ask or Off for each of the keyboard's own tools the model can use, see [The keyboard's own tools](#the-keyboards-own-tools). The information tools start on Run.
- **MCP servers**: the servers whose tools the model can use, see [MCP servers](#mcp-servers). None are set up to start with.
- **Chat with the model**: opens the conversation screen, see [AI chat](https://wmkeyboard.pages.dev/tools/ai-chat/).
- **Enter sends the message**: off by default. In the chat on the keyboard, Enter starts a new line and the send button sends. Turn this on to send with Enter instead.
- **Auto-rename chats**: on by default. The model that answered names each chat from your first message and its reply, in your language, and can rename it later when you ask and accept. See [Naming chats](https://wmkeyboard.pages.dev/tools/ai-chat/#naming-chats).
- **Ask AI in the selection menu**: on by default. Puts **Ask AI** in other apps' menu for selected text. Turn it off to remove the entry; turning the AI tool off removes it as well.
- **Use as the phone's assistant**: off by default. Lists WM Keyboard among Android's digital assistant apps; once you pick it there, the assistant gesture opens a menu of chat actions over the app you're in. **Choose the phone's assistant**, under it, opens Android's page for the pick. See [Using it as the phone's assistant](https://wmkeyboard.pages.dev/tools/ai-chat/#using-it-as-the-phones-assistant).
- **Actions on the panel**: the whole action list, see [Your own actions](#your-own-actions).
- **History**: off by default, see [History](#history).

### On the History screen

Tools / AI writing tools / History

One more row lives on the History screen, under a **Carry-on actions** heading. Up to 0.6.3 that heading read **Downloading models**, left over from a Wi-Fi switch that has since moved: model downloads on a metered connection are set in [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/), on the **Downloads** row, which starts at **Ask each time**: the size-keyed prompt (500 MB for a language model, 150 MB for a Whisper one) still fires on top, so a smaller model asks too rather than coming down without a word. Set the row to **Turn off** and no model downloads on mobile data at all.

- **How much the AI reads back**: 4,000 characters by default, anywhere from 500 to 32,000. This is how far back from the cursor a carry-on action such as **Continue** reads. More characters give the model more of your writing to follow, and cost more tokens and more time on every run. It has no effect when you've selected something, because a selection is always sent as it is.

### What the On-device provider adds

google

Adding **On your device** to the list puts the whole model manager on that row's own screen: press **On your device** to open it. [Local models](#local-models) covers the catalog, **Compute** and **Context size**. Three more things sit on that screen and nowhere else:

- **Hugging Face account**, holding an **Access token** field. Blank by default, stored the same way an API key is, and only the gated models need it. Under the field, **Get a token** opens `huggingface.co/settings/tokens` in your browser.
- **Import your own**, an **Import model file** button that opens the system file picker. It takes `.litertlm` and `.task` files only, and anything else is rejected with a message under the button.
- **Free up**, labelled with a size (like "Free up 584 MB"). It only appears when there are orphaned files, meaning model directories left behind by an app update that dropped a model from the catalog. No row can reach those files any more, so the button is the only way to delete them. They're reported rather than deleted for you, on the grounds that it's a multi-gigabyte download you already paid for.

Above that sits a line reporting how much storage your models take in total, partial downloads included. That figure is usually bigger than the one beside **Your models**, which only sums the finished ones you can see.

The first time a model loads, the engine also builds working files from it, about the model's own size again, and keeps them so later loads are fast. They count as a cache: **Settings → About → Storage** lists them as **AI model working files** under **Caches**, and **Free up** there clears them, at the cost of one slower load. Deleting a model deletes its working files with it.

### About "Provider maximum"

This one is worth understanding, because the old default was the reason a long **Improve** used to come back with only the first part rewritten. **Provider maximum** leaves the ceiling out of the request entirely and lets the service apply its own, which for a modern model is far more than any number you'd pick by hand. It's the right choice for long text.

Anthropic is the one exception: its API requires the field, so the app asks for a large number there instead. If the model you picked allows less, Anthropic rejects the request and names its real limit in the error. The app retries once at that number. Nothing has streamed at that point, so all you see is the answer.

Ollama had never been sent a limit at all before this. It gets one now, and **Provider maximum** there means what it always did.

## What leaves your phone

The panel itself sends nothing until you actually tap an action chip: opening the tool, or typing in the field behind it, sends nothing anywhere on its own.

- **On-device**: the text you run an action on never leaves the phone. Response length is bounded by the local model's own context window instead of a server-side limit.
- **Privatemode**: the text is encrypted on your phone before it's sent, or by your own proxy. See [Privatemode](#privatemode) for what the phone does and doesn't check.
- **Any other provider** (cloud or self-hosted): the text you run an action on is sent to that provider, only at the moment you tap the action. API keys and server addresses are stored on the device, not in any WM Keyboard service. See [Privacy at a glance](https://wmkeyboard.pages.dev/privacy/overview/) for how this fits the keyboard's broader per-tool approach to anything that leaves the device.
- **AI suggestions, when [Suggestions in the strip](#suggestions-in-the-strip) is on**: every time you stop typing after a word, the last 120 characters before the cursor (or the number you set, or only the last word) go to the same provider, with no tap. Never from a password field, a field that turns suggestions off, or incognito. Off by default, and an on-device model keeps it on the phone.
- **Dictation, when [Tidy dictation with AI](https://wmkeyboard.pages.dev/tools/voice/#tidying-dictation-with-ai) is on**: each phrase you dictate goes to the same provider as soon as it lands, with no tap, to have its filler words taken out. Its text only, never the audio. Off by default, and an on-device model keeps it on the phone like any other action.
- **Report**: builds a draft in your mail app and hands it over. The app has no reporting server and sends nothing itself, so a report only exists if you send the mail. API keys are never part of it.
- **AI memory**: with an on-device model, goes nowhere. With a cloud or self-hosted provider, the saved facts picked for a run go with it only once you've turned on **Use with cloud models**, which starts off. Incognito fields never send them. See [AI memory](https://wmkeyboard.pages.dev/tools/ai-memory/).
- **History**: goes nowhere. It's a file in the app's private storage, off unless you turn it on, never written from a password field or in incognito, and deleted when you turn it off. It's deliberately left out of the backup file too, so it can't ride along in something you export and mail to yourself. There's no section for it to travel in, unlike the learned dictionary, which is a section of its own that you tick or leave alone. See [Your data](https://wmkeyboard.pages.dev/privacy/data/).
- **A lookup the model asked for**: goes to the service that answers it, and only when the model actually asks. A search carries the query the model wrote, not your field text, to whichever search backend you set up. A page fetch carries nothing but the request for that page. Both show in the [network activity log](https://wmkeyboard.pages.dev/privacy/network-activity/) under **AI tool use**, and both are off entirely while [Data saver](https://wmkeyboard.pages.dev/privacy/data-saver/) is holding web search back.
- **Search by picture**: uploads the picture to the search site picked in the camera tool, and only when the model calls it, which it can't until you turn the tool on.
- **One of the keyboard's own tools**: goes where that tool always sends it, and only when the model calls it. The weather goes to Open-Meteo, a translation to the translate tool's service (or nowhere, on your device), a rate lookup to the currency tool's sources, and so on. The calculator and the unit converter send nothing. Each request shows in the [network activity log](https://wmkeyboard.pages.dev/privacy/network-activity/) under the tool it belongs to. What the tool hands back goes to your AI provider with the rest of the conversation: your events, a note, your location. That is why the tools that read your things start on **Ask**.
- **A tool on an MCP server**: goes to the server you added, and only when the model calls that tool or you press Connect. A call carries the tool's name and the values the model wrote for it, with your headers. See [MCP servers](#mcp-servers).
- **The changes view**: is computed on your phone. Comparing two pieces of text is arithmetic, not a request, so nothing is sent to build it.

## Details & edge cases

- **Not every row here can be reset.** The **Enabled** switch and four of the AI switches (Show model reasoning, Model picker on the panel, Show a changes view, Open a result on the changes) grow a restore button once you move them off the default listed above, and so does the Translate target-language field. See [Putting one setting back](https://wmkeyboard.pages.dev/reference/settings/#putting-one-setting-back). **Keyword** does it differently: change the word and a **Reset to defaults** row appears under the field, naming the word it would put back. Max response length and Context size are drawn as chip rows rather than settings rows, so neither has a button. The AI providers list has a **Reset to default** row of its own once you change it, which puts it back to what your earlier settings make.
- **Offline, an on-device model can stand in.** With [Use on-device models offline](https://wmkeyboard.pages.dev/privacy/data-saver/#with-no-connection) on and a [local model](#local-models) downloaded, an AI action set to a cloud provider or a server runs on the phone when there is no connection, or when the server does not answer. A short message says so the first time. AI chat always uses the provider you picked.
- **AI isn't pinned to the toolbar or the onboarding wizard's starter set by default.** You add it yourself from the toolbox once you want it, the same as most non-essential tools.
- **Not available on the lock screen.** AI needs credentials the direct-boot-safe mirror of your data deliberately doesn't carry, so it's one of the tools that only works after you've unlocked the device once since restart.
- **The Writing keyboard mode pins AI first** on its toolbar for apps it recognizes as writing-heavy (see [Keyboard modes](https://wmkeyboard.pages.dev/typing/modes/)). The pin shows in every build, same as the tool itself. Only the On-device provider is missing from the F-Droid build.
- **A reasoning model's guess is a substring match** against the model ID, on strings like "thinking", "-r1", "qwen3", "gpt-5", "gpt-6", "grok-4", "deepseek-v4". It can be wrong in either direction. A false positive only costs a larger token ceiling. A false negative risks a response cut off mid-thought.
- **Custom has no stored default prompt.** Every other action's prompt can be edited and still has a built-in fallback. Custom's instruction only ever exists for the run you type it for.
- **On-device answers never claim to have been cut off.** The local engine reports no stop reason, so there's nothing to read, and the app says nothing rather than guessing. Every other provider does report it.
- **The changes view can't spot moved text.** A paragraph the model relocated reads as a deletion where it was and an insertion where it went. Detecting moves properly needs a second pass that git only does on request. The actions that move text, Summarize and Rewrite, reword it at the same time, so that pass would fire rarely and mislead when it did.
- **Comparisons drop to whole characters for scripts without spaces** (Chinese, Japanese, Thai, and others). Word-sized pieces would make an entire sentence a single highlight there. Mixed text uses characters throughout, which is noisier but never wrong.
- **Local models are advisory about RAM, never a hard gate.** A phone under a model's stated minimum can still download and run it. You get a warning in the catalog first.
- **A model that will not stop looking things up still answers.** When it has used up its **Lookups per answer**, the last request is sent with no tools offered at all, so the model has to write something with what it found. It isn't shown an error.
- **The round limit is a ceiling, not a target.** Most answers use none. A model only calls a tool when it decides it needs to, and nothing is sent anywhere on a run where it doesn't.
- **A failed lookup is handed to the model, not to you.** A search with no backend, a page that 404s, an address that isn't one: the model is told in a sentence and carries on, usually by saying it couldn't check. A broken page doesn't fail the whole answer.
- **What a lookup found isn't kept in a chat's transcript.** Only the model's answer is saved. Ask a follow-up and the model has its own earlier reply to go on, not the search results behind it, so it may look again, which is usually what you'd want anyway.
- **Tools are off on a local model mid-conversation until the chat restarts.** A local chat session carries its instructions from the moment it opened, so turning a tool on part-way through takes effect on the next conversation rather than the next message.
- **No shared or built-in key for any cloud provider.** Claude, OpenAI, Gemini, Grok, DeepSeek and Brave each need your own API key. There's no free built-in quota here, the way some of the other network tools offer one.
- **A model download dies with the app's process.** It resumes rather than restarting, but it isn't a foreground service, so backgrounding the app for long enough can stop it. That hasn't changed. The panel just shows you it's happening now.
