# Pseudonymisation

> Names, addresses and identifiers leave your workspace as placeholders, and the answer comes back with the real values. The replacing is pure algorithm, without a model in the path, and it learns from what a model in your own network finds.

When the AI Assistant answers, the question goes to the model of the provider you connected. With pseudonymisation switched on, that model never sees your customers' names, their e-mail addresses or their IBANs. The nexxus AI gateway swaps each of them for a placeholder such as `Person 1` or `Firma 1` before the request leaves, and puts the real values back into the answer before you read it.

Take Brandt & Söhne again, the installation company from [Connect your AI](/setup/connect-ai/). Their everyday model runs at IONOS, a cloud in the EU, where pseudonymisation is on by default. An employee asks for a payment reminder to a customer. IONOS writes the reminder to `Person 1` at `Firma 1`, and the employee reads it addressed to Erika Musterfrau at Musterbau GmbH.

<PrivacySwap
  labels={{ write: 'You write', model: 'The model sees', read: 'You read' }}
  gates={{ out: 'Gateway: table lookup and fixed patterns, no model', back: 'Restored by table, without a model' }}
  question={[
    'Write a friendly payment reminder to ',
    { real: 'Erika Musterfrau', ph: 'Person 1' },
    ' at ',
    { real: 'Musterbau GmbH', ph: 'Firma 1' },
    ' (',
    { real: 'e.musterfrau@musterbau.example', ph: 'E-Mail 1' },
    '). The money goes to ',
    { real: 'DE89 3704 0044 0532 0130 00', ph: 'IBAN 1' },
    '.',
  ]}
  answer={[
    'Dear ',
    { real: 'Erika Musterfrau', ph: 'Person 1' },
    ', our invoice to ',
    { real: 'Musterbau GmbH', ph: 'Firma 1' },
    ' is still open. Please transfer the amount to ',
    { real: 'DE89 3704 0044 0532 0130 00', ph: 'IBAN 1' },
    '.',
  ]}
  caption="Erika Musterfrau is a contact in nexxus. Musterbau GmbH is recognised by its legal form, the e-mail address by its format and the IBAN by its check digits."
/>

## No model in the path

What the gateway replaces, it finds by table lookup and fixed patterns. No model decides whether a word is a name, and nothing is guessed: the same text gives the same placeholders every time. Replacing costs no tokens, and a request does not wait for a second model.

<Hub
  inputsLabel="What the gateway knows"
  inputs={[
    { icon: 'contact-round', title: 'Your records', text: 'Partners, contacts and employees from nexxus, with every spelling the rules know' },
    { icon: 'scan-line', title: 'Detectors', text: 'Formats and check digits: e-mail, phone, IBAN, address, tax ID and more' },
    { icon: 'list-checks', title: 'Your rules', text: 'Terms, patterns such as KD‑#####, exceptions' },
  ]}
  core={{
    title: 'AI gateway',
    text: 'Table lookup and patterns, no model',
    items: ['Person', 'Firma', 'E-Mail', 'Telefon', 'IBAN', 'Adresse', 'Nummer', 'Geburtsdatum', 'Begriff'],
  }}
  outputsLabel="What comes out"
  outputs={[
    { icon: 'cpu', title: 'For the model', text: 'Placeholders instead of the values' },
    { icon: 'message-square-text', title: 'For you', text: 'The answer with the real values' },
    { icon: 'chart-no-axes-column', title: 'For the hub', text: 'Counts per kind, never the values' },
  ]}
  caption="The gateway fetches the dictionary and the rules from nexxus and keeps them for up to a minute. A new contact or a new rule applies within that minute."
/>

### What is recognised without a model

| Kind | Recognised by | Placeholder |
|---|---|---|
| People | Partners, contacts and employees in nexxus. A name after "Herr" or "Frau". | `Person 1` |
| Companies | Partners in nexxus. A name in front of a legal form such as GmbH or AG. | `Firma 1` |
| E-mail | The format. File names such as `logo@2x.png` stay. | `E-Mail 1` |
| Phone | International numbers (`+49`, `0049`) and national ones with separators. A bare run of digits only as a German mobile number or after "Tel.", "Fax" and the like. An amount (`%`, `€`) is no phone number. | `Telefon 1` |
| IBAN | The length for the country and the ISO 13616 check (mod 97). | `IBAN 1` |
| Addresses | A German street with house number (Straße, Weg, Gasse, Platz, Allee, Ring, Damm, Ufer), with postcode and town if they follow. | `Adresse 1` |
| Identifiers | Tax ID (check digit ISO 7064 MOD 11,10, spaced as the tax office prints it or after a label), social insurance number (check digit), health insurance number (check digit, after a label), payment card (card network and Luhn check, grouped as on the card or after a label). Your own patterns. | `Nummer 1` |
| Birth dates | A date only after a label such as "geboren am" or "Geburtsdatum". Deadlines and appointments stay. | `Geburtsdatum 1` |
| Terms | Your term rules, for example internal project names. | `Begriff 1` |

Tool results get the same treatment, and one more: fields that hold a first name, a last name, an e-mail address, a phone number or a tax number are replaced whole, and their values then wherever else they occur in the same request.

The detectors would rather miss a value than catch a wrong one. A false hit breaks the answer: an amount or a deadline that turned into `Telefon 1` is useless to the model. A miss is what the learning check below is for.

### How the placeholders behave

- **Readable words, numbered per kind** in the order they first appear: `Person 1`, `Person 2`, `Firma 1`. The model reads them as names and writes them back. The words are German in every language, because the gateway uses one set of labels.
- **Stable for the whole conversation.** `Firma 1` stays `Firma 1` in every turn, and a number once given never goes to a second value. Different spellings of the same value ("0171 1234567", "0171/1234567") share one placeholder.
- **Restored however the model writes them**: `FIRMA 1`, `Firma1`, `[Firma 1]` and the genitive `Firma 1s` all come back as the real value. The gateway also restores them in tool calls before a tool runs, so a search for `Person 1` searches for the real name.
- **A number the table does not know stays visible.** If the model invents `Person 7`, you read `Person 7`.

## It learns by itself

Rules cannot know every name. Someone mentions a supplier that is not in nexxus yet, or a customer number in a format no detector knows. To find those gaps, nexxus runs a check on a model you trust: one in your own network, or at Privatemode, where the model computes in an encrypted environment (confidential computing). That model never sits between your people and the provider. It only reads along afterwards, and what it finds becomes rules that again run without a model.

<LearningLoop
  nodes={[
    { icon: 'messages-square', title: 'Requests go out pseudonymised', text: 'The gateway replaces what its rules know.' },
    { icon: 'scan-search', title: 'A safe model reads along', text: 'A sample of the last 24 hours, in your network or at Privatemode.' },
    { icon: 'circle-help', title: 'A miss becomes a question', text: 'In the hub, and in the chat under the message it came from.' },
    { icon: 'badge-check', title: 'A yes becomes a rule', text: 'For the whole workspace, without a model.' },
    { icon: 'trending-up', title: 'Recognition goes up', text: 'The learning curve shows the share per day.' },
  ]}
  center={{ value: '95 %', label: 'target', text: 'of what the check finds is recognised by the rules' }}
  caption="The check runs every ten minutes within a daily token budget. Requests are never held back for it."
/>

### What the check does

Every ten minutes the check takes a sample of the chat messages people wrote in the last 24 hours (25 % by default, only messages of at least 20 characters). The model of the check lists what in them it would protect, and nexxus compares that list with what the gateway replaced:

- **Recognised:** the gateway had replaced it. This is the share the hub shows.
- **Missed:** the gateway left it in the text. The miss becomes a question, together with the rule that would have caught it. If the request went out pseudonymised to a cloud provider, the miss is also a warning.
- **Too much:** the gateway replaced something the model did not list, for example a product name that looks like a company. That becomes a question for less protection.

Before a question is shown, nexxus tests its rule against all checked texts, so the question says how often it would hit (<Ui>hits in 3 texts</Ui>). Findings that need the same rule are merged into one question.

### Who answers

- **The person whose message it was** sees a card under that message in the chat: <Ui>Quick question about pseudonymisation</Ui>, with <Ui>Yes, replace</Ui>, <Ui>No</Ui> and <Ui>Stop asking</Ui>. A yes adds the term, spelling or pattern at once. More protection needs no administrator.
- **Administrators** decide everything in the hub, also what the chat cannot: less protection (an exception) and rules for a record field, which reach across all records.
- **Nobody**, for spellings of known records. If the check finds "Muster-Bau" for the partner Musterbau GmbH, the spelling becomes a rule without asking, because it only adds protection. You can switch that off with <Ui>Adopt spellings of known records without asking</Ui>.

### The protection prompt

In the tab <Ui>Protection prompt</Ui> you write in your own words what must never go out: "Our project names Falcon and Lighthouse are internal. Patient numbers look like P-123456." <Ui>Suggest rules</Ui> reads the text once with the model of the check and marks each passage: <Ui>becomes a rule</Ui>, <Ui>check only</Ui> (no fixed rule possible, the check watches for it) or <Ui>exists already</Ui>. You adopt the suggestions you want. That is the only model run. After it, the rules work without a model, and normal requests cost no tokens for them.

### The number and the next steps

The overview shows how much of what the check found the rules recognise: <Ui>of the data found recognised automatically</Ui>. The target is 95 %. Until the check has read texts there is no number, only <Ui>Nothing checked yet</Ui>, so a new workspace never starts with an estimate.

Below it, the card <Ui>How recognition goes up</Ui> lists what to do, in this order:

1. What keeps the check from measuring: pseudonymisation switched off, no safe model, none chosen or the chosen one no longer set up, the check switched off, today's budget used up.
2. Too few values found for a reliable number (it needs 10). A higher sample share gets there sooner.
3. Open questions to answer.
4. The kinds with the most misses, at most three, each with the step that fits: a partner or a term rule for names, a pattern rule for numbers with a fixed layout, the protection prompt for internal terms. If the misses are spread so thin that no kind stands out, the step is a higher sample share.
5. A protection prompt, if there is none yet, and spellings that wait for an answer.

Each step has a button that leads to where it is done. Once 95 % are reached, only the blockers remain.

## Before you start

- The admin role. The hub is for administrators only, because it can show the replaced values.
- At least one connected provider, see [Connect your AI](/setup/connect-ai/).
- For the learning check: a model in your own network (an OpenAI-compatible endpoint on a private host, for example Ollama or vLLM) or a model at Privatemode. Without one, replacing works all the same, but the check measures nothing.

## Switch it on

<Flow
  steps={[
    { icon: 'toggle-right', title: 'Switch on', text: 'For the workspace, on the Pseudonymisation page.' },
    { icon: 'cloud', title: 'Set per provider', text: 'Always, on or off by default, or never.', tone: 'cyan' },
    { icon: 'scan-search', title: 'Let the check run', text: 'On a model in your network or at Privatemode.', tone: 'lavender' },
    { icon: 'badge-check', title: 'Answer questions', text: 'Each yes is a rule for everyone.' },
  ]}
  caption="The first two steps protect at once. The last two make the protection better every day."
/>

<Steps>

1. Open <MenuPath to="AI > Set up AI > Pseudonymisation" /> and there the tab <Ui>Settings</Ui>. In the section <Ui>Pseudonymisation</Ui>, set <Ui>Pseudonymise AI requests</Ui> to <Ui>On</Ui> and click <Ui>Save</Ui>.

   Set to <Ui>Count only</Ui>, every request goes out in plain text and the gateway only counts what it would have replaced.

2. Leave <Ui>Always pseudonymise providers outside the EU</Ui> on. It is on from the start and puts every provider outside the EU on <Ui>Always</Ui>, also one set to <Ui>Never</Ui> or <Ui>Off by default</Ui>.

3. Open <MenuPath to="AI > Set up AI > AI Providers" /> and a provider. Under <Ui>Pseudonymisation</Ui>, choose how it applies:

   | Setting | In the chat |
   |---|---|
   | <Ui>Always</Ui> | Recognised names and identifiers go out as placeholders only. Cannot be switched off in the chat. |
   | <Ui>On by default</Ui> | New chats start pseudonymised. Each person can switch it off for a conversation. |
   | <Ui>Off by default</Ui> | New chats start in plain text. Each person can switch it on. |
   | <Ui>Never</Ui> | The model always sees plain text. Meant for models in your own network. |
   | <Ui>Automatic</Ui> | Follows where the provider computes, see below. |

   <Ui>Automatic</Ui> is the starting value of every provider:

   | Where the provider computes | Automatic means |
   |---|---|
   | A cloud outside the EU: OpenAI, Anthropic, Google and every other cloud, OpenAI-compatible cloud endpoints included | <Ui>Always</Ui> |
   | A cloud in the EU: IONOS, Scaleway | <Ui>On by default</Ui> |
   | Privatemode | <Ui>Off by default</Ui> |
   | Your own network | <Ui>Never</Ui> |

4. In the tab <Ui>Settings</Ui>, choose the model under <Ui>Model of the check</Ui> and save. Only models in your network and at Privatemode can be chosen. nexxus takes exactly this model for the check, the protection prompt suggestions and the sandbox comparison, and no other. As long as none is chosen, the check pauses.

   Optionally, under <Ui>Automatic check</Ui> you set:

   - <Ui>Check on</Ui>
   - <Ui>Tokens per day</Ui>, 50,000 by default
   - <Ui>Sample of messages</Ui>, 25 % by default
   - <Ui>Adopt spellings of known records without asking</Ui>

5. Switch to the tab <Ui>Overview</Ui>. The status reads <Ui>Protection on</Ui>. Work through <Ui>How recognition goes up</Ui> from the top.

6. Write the protection prompt in the tab <Ui>Protection prompt</Ui>, click <Ui>Suggest rules</Ui> and adopt the suggestions.

</Steps>

## What your team sees in the chat

In the chat's input area, a chip shows how the conversation goes out: <Ui>Pseudonymised</Ui>, <Ui>Plain text</Ui> or <Ui>Own network</Ui>. Where the provider is on <Ui>On by default</Ui> or <Ui>Off by default</Ui>, a click switches it for this conversation.

Every answer carries its data path. For a pseudonymised turn the gateway stop says "Replaces recognised names and identifiers with placeholders and restores them in the answer", and <Ui>Replaced</Ui> counts what was swapped. Placeholders do not show in the chat, because the answer arrives already restored. How to read an answer is described in [Working with the AI Assistant](/use/assistant/#read-an-answer).

## The hub

<MenuPath to="AI > Set up AI > Pseudonymisation" /> brings everything together on one page with eight tabs. The menu entry counts open questions and warnings, so they do not wait until someone opens the page.

| Tab | What you find there |
|---|---|
| <Ui>Overview</Ui> | The share recognised, the <Ui>Learning curve</Ui> (share per day, with a mark where the protection prompt changed), today's numbers (<Ui>pseudonymised today</Ui>, <Ui>replaced too much</Ui>, <Ui>went out in plain text</Ui>, <Ui>open questions</Ui>, <Ui>check budget today</Ui>), the share per kind and the next steps. <Ui>Export report</Ui> saves it as a file, for an auditor for example. |
| <Ui>Questions</Ui> | What the check found and votes from chats, each with its rule and how often it would hit. <Ui>Adopt</Ui> or <Ui>Reject</Ui>; for less protection <Ui>Stop replacing</Ui> or <Ui>Keep replacing</Ui>. <Ui>Check now</Ui> starts a check run at once. |
| <Ui>Warnings</Ui> | Data that went in plain text to a model outside your network although pseudonymisation was on: still in the request after replacing, or found by the check with no rule that knew it. |
| <Ui>Rules</Ui> | What nexxus replaces without asking a model, with origin and hits in 30 days. The built-in ones say <Ui>Built in, always applies.</Ui> <Ui>Add rule</Ui>, <Ui>Archive</Ui>, <Ui>Export</Ui>. |
| <Ui>Replaced</Ui> | What was replaced in the last 30 days. The values stay hidden until <Ui>Show values</Ui>, and showing them is logged. Entries disappear with their conversation. |
| <Ui>Protection prompt</Ui> | Your own words, the suggested rules and earlier versions under <Ui>History</Ui>. |
| <Ui>Sandbox</Ui> | Paste a text and see what the model would get. Nothing goes to a model unless you click <Ui>Compare</Ui>: then the same question runs once in plain text and once pseudonymised on the model of the check, and you see whether the answer gets worse. |
| <Ui>Settings</Ui> | The two switches of pseudonymisation, the model of the check and how much it reads. Nothing is stored until <Ui>Save</Ui>; <Ui>Discard</Ui> brings back the saved state. |

### Rules you add yourself

<Ui>Add rule</Ui> offers three kinds:

- **Term:** a word or a phrase, for example a project name.
- **Pattern:** for codes with a fixed layout. `#` is a digit, `A` an upper-case letter, `a` a lower-case letter, `*` a letter or a digit, and everything else stands for itself: `KD-#####` catches `KD-20417`. A pattern has 3 to 40 characters with at least three of these signs. nexxus never sends a regular expression to the gateway, so no rule can slow it down.
- **Exception:** a value that is never replaced, even when a pattern or a record matches. It means less protection, and the dialog says so.

A rule applies to the whole workspace. Spellings of records and rules for record fields come from the questions, not from this dialog. A spelling shares the placeholder of its record.

## What happens behind the scenes

<Sequence
  actors={[
    { name: 'You', icon: 'user-round' },
    { name: 'nexxus', icon: 'server' },
    { name: 'AI gateway', icon: 'shield-check' },
    { name: 'Model', icon: 'cpu' },
  ]}
  steps={[
    { from: 0, to: 1, label: 'Question with real names' },
    { from: 1, to: 2, label: 'Question, tools and whether to pseudonymise' },
    { from: 2, to: 2, label: 'Replaces by table and pattern, then checks for leftovers' },
    { from: 2, to: 3, label: 'Placeholders only' },
    { from: 3, to: 2, label: 'Answer with placeholders' },
    { from: 2, to: 2, label: 'Puts the real values back, while streaming' },
    { from: 2, to: 0, label: 'Answer with real names' },
  ]}
  caption="nexxus decides per turn whether it goes out pseudonymised. The gateway does the replacing and the restoring."
/>

- **One table per conversation.** The gateway keeps the pairs of value and placeholder next to the conversation's history, so a later turn restores placeholders from earlier turns.
- **No unprotected fallback.** If replacing fails, the request fails, rather than going out in plain text. If nexxus cannot be reached for the dictionary, the gateway uses the last one it fetched.
- **A second look before sending.** After replacing, the gateway scans the request again for protected values. Whatever it still finds becomes a warning in the hub.
- **Count only means count only.** In that mode the gateway runs the same work on a scratch copy, counts what it would have replaced and sends the request unchanged.
- **Images and files are not pseudonymised.** The gateway counts them and passes them through. Text in an image reaches the model as it is.
- **The check stays on the chosen model.** Before every call nexxus checks again that it runs in your network or at Privatemode. Once it is no longer set up there, the check pauses instead of moving to another model. It also stops when the day's token budget is used up.

## When something goes wrong

- **The overview says <Ui>Nothing checked yet</Ui>.** The card <Ui>How recognition goes up</Ui> names the reason. Most often: "The check needs a model in your network or at Privatemode. None is set up, so nothing is measured." Set up such a model as described in [Models on your own server](/setup/connect-ai/#models-on-your-own-server).
- **"No model is chosen for the check yet. Until one is, nothing is measured."** Choose it in the tab <Ui>Settings</Ui> under <Ui>Model of the check</Ui>.
- **"The chosen model of the check is no longer set up in your network or at Privatemode."** The provider was switched off, deleted or no longer runs in your network. Choose another model under <Ui>Settings</Ui>, or set the provider up again. Until then the check, the protection prompt suggestions and the sandbox comparison pause.
- **"Today's check budget is used up. It goes on tomorrow, or you allow more tokens per day."** Raise <Ui>Tokens per day</Ui> under <Ui>Settings</Ui>, or wait for the next day.
- **A name went out in plain text.** It shows in <Ui>Warnings</Ui>. <Ui>To the question</Ui> leads to the rule that would catch it. If there is no question, add a term rule, or create the person as a contact in nexxus, then nexxus recognises the name by itself.
- **Too much is replaced**, for example a product name with a legal form in it. Answer the question with <Ui>Stop replacing</Ui>, or add an exception.
- **The answer shows a placeholder** such as `Person 7`. The model wrote a number the table does not know. Ask again, or check in the <Ui>Sandbox</Ui> what the model got.
- **"Pseudonymisation could not be switched"** in the chat. The text after the colon gives the reason. Reload the page and switch again.

## Next steps

<CardGrid>
  <LinkCard title="Connect your AI" href="/setup/connect-ai/" description="Providers, models and the data protection settings around them." />
  <LinkCard title="Working with the AI Assistant" href="/use/assistant/" description="How your team reads answers and their sources." />
  <LinkCard title="Roles and rights" href="/setup/roles/" description="Who may do what in nexxus." />
</CardGrid>