SECURITY

Your Model's Reasoning Trace Is Stored on Someone Else's Server

Published September 04, 2026 — 6 min read

TL;DR: When your agent reasons through a sensitive business problem, that chain of thought is not ephemeral inference — on most reasoning APIs it defaults to stored data on the provider's side, and the "encrypted" alternative just moves an opaque, un-redactable blob into your own logs. Either way, you now own a data class your DPIA has never heard of.

Key Insight

Everyone treats reasoning tokens as exhaust. You pay for them, you never see most of them, and the mental model is "the model thought, then it answered, and the thinking evaporated."

It doesn't evaporate. Reasoning models have to carry their reasoning state across turns, or multi-step agents get dramatically worse at tool use — the model loses the plan it just made. So the state has to live somewhere between turn N and turn N+1. There are only two places it can live, and both of them are storage:

  1. The provider's server. In OpenAI's Responses API, store defaults to true. Your reasoning items are persisted server-side and referenced by response ID on the next turn. That's the happy path most SDK quickstarts put you on without a word.
  2. Your own infrastructure. Set store: false and ask for include: ["reasoning.encrypted_content"] and the API hands you an encrypted blob representing the model's reasoning state, which you pass back yourself. OpenAI's cookbook is explicit that this is "persisted entirely on the client side with OpenAI retaining no data." Anthropic does the analogous thing with thinking blocks and their signature field, which must be returned byte-for-byte unmodified.

Here is the contrarian part: option 2 is the privacy fix and a new compliance problem at the same time. You just took a ciphertext you cannot read, cannot redact, cannot inspect for PII, and cannot decrypt during an incident review — and you put it in your session store, your Redis, your Postgres, your S3 conversation archive. It's now covered by your retention policy, your breach notification obligations, and your customer's right-to-deletion request. And you have no idea what's inside it.

The reasoning trace was never ephemeral. The only question was ever whose database it lands in.

Why Teams Miss This

Three reasons, in rough order of how often we see them.

The threat model stops at the prompt. Teams do serious work scrubbing inputs — PII redaction on user messages, DLP on retrieved documents, careful system prompts. Then the model reasons over that scrubbed input and produces a trace that re-derives, restates, and expands on exactly the sensitive material you were careful about. Nobody scrubs the reasoning, because nobody thinks of it as data the system produced.

"Encrypted" reads as "handled." The word does a lot of unearned work in a security review. Encrypted-at-rest-by-someone-else's-key, held in your database, is not the same posture as data you control. You have confidentiality against a casual reader and zero ability to satisfy an auditor who asks "what's in this field?"

Nobody owns the boundary. The infra team sees an opaque string in a conversations table. The ML team sees a required API parameter for multi-turn tool use. Neither one files it as a data category. It shows up in no data map, no DPIA, no records-of-processing register — right up until a customer asks you to delete everything about them and someone realizes the deletion job doesn't know what that column is.

There's a fourth, quieter one: reasoning state is provider-bound and deployment-bound. Teams load-balancing across OpenAI direct, Azure, and a gateway assume these blobs are portable. They are not reliably portable, which means your "just fail over" resilience story has an undocumented coupling in it.

How to Actually Do It

Treat reasoning state as a first-class data category with its own lifecycle. Concretely:

1. Decide where it lives, explicitly, in code. Not by SDK default. Make the choice visible at the call site so a reviewer can see it:

# Explicit: nothing about this conversation is persisted provider-side.
# The encrypted reasoning blob comes back to us and WE own its lifecycle.
resp = client.responses.create(
    model="o4-mini",
    input=messages,
    store=False,                                   # no server-side retention
    include=["reasoning.encrypted_content"],       # we carry the state
)

If you're on the stateful path instead, own that too — keep the response IDs, and wire DELETE /v1/responses/{id} into the same job that handles user deletion requests. A stored response you can't enumerate is a stored response you can't delete.

2. Give the blob a real column and a real TTL. Don't let it ride along inside a JSON messages dump. It belongs in a named column with a documented type, an owner, and an expiry that matches your conversation retention policy — usually much shorter than the rest of the transcript, because reasoning state is only useful for the life of the active task.

ALTER TABLE agent_turns
  ADD COLUMN reasoning_state   bytea,           -- opaque provider ciphertext
  ADD COLUMN reasoning_expires timestamptz;     -- TTL; sweep on schedule

Then actually sweep it. The blob's usefulness ends when the task ends; its liability does not.

3. Write it into the data map before legal finds it. One line in your records of processing: model reasoning state, provider-encrypted, opaque to us, retained N hours, deleted with the parent conversation. That sentence is cheap to write today and expensive to retrofit during an audit.

4. Pin the deployment. If you round-robin across OpenAI, Azure, and a gateway, don't assume reasoning state carries across them. Pin a conversation to one deployment for its lifetime, or drop reasoning state on failover and accept the quality hit — a degraded turn beats a hard 400 mid-agent-run.

5. If it's truly sensitive, don't carry it. Drop reasoning state between turns and re-establish context from your own structured summary. You'll pay in latency and a little accuracy on long tool chains. For a workflow touching regulated data, that's a trade many teams should take and almost none consciously evaluate.

What We've Learned

The pattern generalizes past reasoning tokens: anything an agent needs to remember between turns is storage, and storage has a compliance surface — no matter how transient the API docs make it sound. Cached prompt prefixes, tool-call scratchpads, vector-store session buffers, and agent memory files all have the same shape. The moment state has to survive a request boundary, it's a data category, and somebody has to own it.

Next experiment for your team, and it takes about an hour: grep your production conversation store for reasoning or thinking fields. Then ask three questions — do we know it's there, does the deletion job touch it, and does it appear in our data map? In most codebases we've looked at, the answer to at least one of those is no, and the team is genuinely surprised. That surprise is the finding.

Sources

Have a specific workflow in mind?

Bring it to a Quick Scan — a live working session where we'll tell you honestly whether it should be an agent, a workflow, or left alone, before you spend a dollar building it. You get 3 prioritized recommendations on the call, a one-page summary after, and the $500 credited toward any engagement within 30 days.

See AI Agent Consulting →·Book an intro call →

Get new posts + practical agent-ops notes

One email when something new goes up. No nurture sequence, no spam — unsubscribe whenever you want.

Thanks — you're on the list.