Skip to content
Preptima
mediumConceptDesignScenarioMidSeniorStaff

How do you handle personal data in prompts, logs and traces for an LLM feature?

Decide what may cross the provider boundary and redact before it does, checking the provider's retention and training-use terms rather than assuming them. Then treat the debugging trace as a second copy of user data, with its own retention, access control and deletion obligations.

5 min readUpdated 2026-07-28
Practice answering out loud

What the interviewer is scoring

  • Whether redaction is placed before the outbound call rather than after the response
  • Does the candidate treat the trace store as in-scope for deletion and access requests
  • That provider retention and training-use terms are checked rather than assumed
  • Whether the answer preserves debuggability instead of just deleting everything
  • Can they name what still leaks after redaction, such as free text or an unusual name

Answer

Draw the boundary before you write any code

The first artefact is a map of where the data goes. A single LLM feature typically produces four copies of the same user text: the request to the provider, the trace or span you record for observability, the application log line somebody added while debugging, and any evaluation dataset assembled from real traffic. Each copy has its own storage, its own access list and its own lifetime, and the ones nobody planned are the ones that outlive the policy.

Classify the fields flowing through the feature. Direct identifiers such as name, email, phone number and account reference are usually removable without hurting the task. Special-category data — health, financial detail, anything in a support ticket about a person's circumstances — raises the stakes and often decides the deployment model. And free-form user text is the hard case, because it is simultaneously the payload the model needs and the place where anything at all can appear.

Redact on the way out, not on the way back

Redaction belongs in the outbound path, in one place that every call goes through, so that no code path can send raw text by accident. A wrapper around the client is the right shape; per-call redaction at each call site guarantees that the one added next quarter will forget.

The mechanism has two halves. Deterministic detectors handle structured identifiers reliably: emails, card numbers with a checksum, national identifiers with a known format, your own account-number pattern. A model-based or NER detector catches names, addresses and organisations that no regex will. Use both and accept that recall is imperfect, which means redaction is a risk-reduction control and not a compliance guarantee — say that plainly rather than presenting it as sanitisation.

Prefer reversible pseudonymisation to deletion where the task needs coherence. Replacing every occurrence of a name with a stable token per request keeps the model able to reason about "the same person" and lets you rehydrate the answer before showing it to the user, which straight masking destroys.

def ask_model(text: str, session: Session) -> str:
    scrubbed, mapping = pseudonymise(text)       # PERSON_1, EMAIL_1, stable per request
    answer = provider.complete(scrubbed)         # the mapping never leaves the process
    trace.record(session, scrubbed, answer)      # the trace stores the scrubbed form only
    return rehydrate(answer, mapping)            # the user sees the real names back

Be honest about what still crosses. Redaction removes identifiers, not content: a paragraph describing a specific dispute at a specific branch on a specific date identifies someone regardless of whether the name was masked. Where that residual is unacceptable, the answer is not better redaction but a different deployment — a provider tier with contractual zero retention, a private regional deployment, or an open-weights model you host yourself. The reason to know this is that it is the branch point in a real design review, and the cost difference is large enough that someone will need the argument for it.

Read the provider's terms, do not infer them

Three things need checking per provider and per tier rather than assumed from the marketing page: whether inputs and outputs are retained and for how long, whether they may be used to train or improve models, and where the processing physically happens. Enterprise and API tiers commonly differ from consumer products on all three, and abuse-monitoring retention often exists independently of training use, which is the detail people miss when they tell a customer nothing is stored.

The commercial artefacts follow from this. You need a data processing agreement naming the provider as a processor, the sub-processor list for your own customer-facing terms, and a record of the transfer mechanism if the processing leaves your jurisdiction. If your feature is embedded in a product with existing customer commitments, adding a provider is a change to those commitments and usually requires notice, so this is a legal path with a lead time, not a configuration flag.

The trace is a copy of user data

This is the part teams get wrong, and it is the most useful thing to say. LLM observability tooling exists because debugging these systems without seeing the prompt is close to impossible, so the default configuration of every tracing library captures the full prompt and completion. That trace store is now a database of user content, frequently hosted by a third party, frequently readable by every engineer, and almost always exempted from the retention schedule that governs the primary datastore because nobody thought of it as user data.

Treat it as in scope for everything. Retention should be short and enforced by the store rather than by intention — long enough to investigate an incident, not indefinite. Access should be limited and audited, and if traces contain special-category data, engineer-wide read access is not defensible. Deletion requests must reach it, which means traces need to be keyed by the same subject identifier as your primary data, or you cannot honour an erasure request even in principle. Sampling helps twice over: tracing a small fraction of requests in full, plus metadata for all of them, gives you most of the debugging value with a fraction of the exposure.

You can also make the trace less sensitive without making it useless. Store the scrubbed prompt rather than the raw one, since the scrubbed form is what the provider saw and therefore what you need to reproduce a bad answer. Store hashes and lengths where the value itself is not needed. Record the retrieved document identifiers and the prompt version rather than the assembled prompt text, and reconstruct it on demand — the reconstruction is usually what you wanted, and it stores a reference instead of a copy.

What the design should look like when you are done

One outbound wrapper that redacts, one trace schema that holds scrubbed content with a subject key and a short retention, one place recording which provider and tier processed what, and an evaluation dataset built from consented or synthetic material rather than scraped silently out of production traffic. The last one catches people out: an eval set assembled from real conversations is a permanent copy of user data with no retention limit at all, sitting in a repository, being copied to laptops.

Likely follow-ups

  • A user exercises their right to erasure. Which systems must you touch, and which can you not?
  • How would you debug a bad answer when the trace no longer holds the prompt?
  • Your redaction regex misses an identifier format used by one customer. How would you have caught it?
  • When is a zero-retention or self-hosted deployment the only acceptable answer?

Related questions

Further reading

piidata-protectionobservabilityretentionllm-security