RedaktAIDocs

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:

StoreUse casePersistence
MemoryStoreSingle-process, developmentLost on restart
SQLiteStoreSingle-server, productionPersisted to disk
RedisStoreMulti-server, distributedPersisted 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"}}

On this page