Sessions & Deanonymization
How entity mappings persist across requests and how responses are restored
A session is a bidirectional map between anonymized tokens and their original values. It's the core mechanism that allows Rdakt AI to anonymize outbound text and deanonymize inbound responses.
How Sessions Work
When the middleware anonymizes text, it creates mappings:
"John Smith" → "<PERSON_1>" (or "Robert Johnson" for synthetic)
"john@example.com" → "<EMAIL_1>"These mappings are stored in the session. When the LLM response arrives and contains <PERSON_1>, the session looks up the original value and replaces it.
Multi-Turn Conversations
Sessions accumulate mappings across multiple requests. In a conversation:
Turn 1:
- User: "Summarize the case for John Smith"
- Mapping added:
<PERSON_1>→"John Smith" - LLM sees: "Summarize the case for
<PERSON_1>"
Turn 2:
- User: "What about his colleague Jane Doe?"
- Mapping added:
<PERSON_2>→"Jane Doe" - LLM sees: "What about his colleague
<PERSON_2>?" - Session now has both mappings
Turn 3:
- LLM response references both
<PERSON_1>and<PERSON_2> - Both are deanonymized correctly because the session retained all mappings
Session Keys
By default, each middleware instance creates a new session. For multi-user applications, use session_key to isolate sessions:
# Each user gets their own session
middleware = RdaktMiddleware(
session_key=f"user-{user_id}",
store=SQLiteStore(path="sessions.db"),
)Session Stores
Sessions are stored in a pluggable backend:
| Store | Use case | Persistence |
|---|---|---|
MemoryStore | Single-process, development | Lost on restart |
SQLiteStore | Single-server, production | Persisted to disk |
RedisStore | Multi-server, distributed | Persisted with optional TTL |
See the Session Stores guide for setup details.
Deanonymization
Full Response
For non-streaming responses, the entire response body is deanonymized at once:
session.deanonymize("The report for <PERSON_1> shows...")
# → "The report for John Smith shows..."Streaming Chunks
For streaming responses, deanonymization happens chunk-by-chunk with intelligent buffering. The session buffers text when it encounters a potential token start (<) and releases it when:
- A complete token is found and replaced
- The buffer content is determined not to be a token (e.g.,
x < 5) - The buffer exceeds
MAX_BUFFER(256 characters) — a safety valve
This means the middleware doesn't block on every < character in code or math — it only buffers when the content could plausibly be a token.
Structured Data
The session can also deanonymize structured data like tool call arguments:
session.deanonymize_structured({
"name": "<PERSON_1>",
"email": "<EMAIL_1>",
"nested": {"address": "<ADDRESS_1>"},
})
# → {"name": "John Smith", "email": "john@example.com", "nested": {"address": "123 Main St"}}