Skip to content

Valkey — Overview

Valkey (a Redis-compatible in-memory store) is used for chat session persistence, anonymous user identity, and request/token rate limiting. The server connects on startup and exposes its health via GET /health.


Connection

Setting Default Env variable
URL valkey://localhost:6379/0 TURINGDB_VALKEY_URL

The client is created with decode_responses=True, so all values are returned as strings rather than bytes.

Relevant files: server/config.py, server/main.py


Key namespaces

All keys follow a hierarchical, colon-separated naming convention.

Namespace prefix Type TTL Description
chat:session:{session_id} String (JSON) 7 days Full chat session (messages + metadata)
chat:job:{job_id} String (JSON) 10 minutes Job record (status, result, error)
chat:job_queue List FIFO queue of pending job IDs (BLPOP/RPUSH)
user:{user_id} String (JSON) none (permanent) User record
user:{user_id}:sessions List none (permanent) Ordered list of session IDs for a user
anon:{anon_id} String (JSON) 3 days Anonymous ID → user mapping
rate_limit:{user_id} List 10 minutes (rolling) Request timestamps for sliding-window request limiting
token_limit:{user_id} Sorted Set 10 minutes (rolling) Token usage entries for sliding-window token limiting

session_id and user_id are UUID v4 strings. anon_id is a secrets.token_urlsafe(32) token.

TTLs on rate_limit:* and token_limit:* keys are refreshed on every write. TTL on chat:session:* keys is refreshed on every write.


Identity and session lifecycle

GET /chat/bootstrap-anon
  │
  ├─ anon_id cookie absent → generate anon_id token
  │     SETEX anon:{anon_id}  259200  {user_id, created_at}
  │     SET   user:{user_id}          {user_id, created_at}
  │     Set cookie anon_id (max-age=259200, httponly=True)
  │
  └─ anon_id cookie present
        GET anon:{anon_id}
        ├─ not found → treat as absent (new user)
        ├─ age < 2 h → return existing user_id unchanged
        └─ age ≥ 2 h → rotate: delete old anon key, write new one

POST /chat/message  {message, session_id?}   [X-Anon-Id header]
  │
  ├─ GET anon:{anon_id} → resolve user_id
  ├─ check rate_limit:{user_id}     (reject 429 if exceeded)
  ├─ check token_limit:{user_id}    (reject 429 if exceeded)
  ├─ GET chat:session:{session_id}  (or create empty ChatSession)
  ├─ SET  chat:job:{job_id}  <JobRecord json>  (TTL 10 min)
  ├─ RPUSH chat:job_queue  {job_id}
  └─ → returns 202 JobSubmission immediately

Background worker (BLPOP chat:job_queue):
  ├─ run intent router → emit routing events to session
  ├─ run lookup or agent handler
  ├─ SETEX chat:session:{session_id}  604800  <json>
  ├─ SET   chat:job:{job_id}  <JobRecord status=done>  (TTL refresh)
  └─ if new session: LPUSH user:{user_id}:sessions {session_id}

Operations reference

Operation Command Where
Read session GET chat:session:{id} chat-agent/session.py:get_or_create_session
Write session SETEX chat:session:{id} {ttl} {json} chat-agent/session.py:save_session
Read anon mapping GET anon:{anon_id} server/user.py
Write anon mapping SETEX anon:{anon_id} 259200 {json} server/user.py
Write user SET user:{user_id} {json} server/user.py
Prepend session ID LPUSH user:{user_id}:sessions {session_id} server/user.py:add_session_to_user
List session IDs LRANGE user:{user_id}:sessions 0 -1 server/user.py:get_user_sessions
Prune expired sessions DEL + LPUSH (surviving IDs only) server/user.py:get_user_sessions
Check request rate LRANGE + LPUSH + LTRIM + EXPIRE on rate_limit:{user_id} server/rate_limit.py:check_rate_limit
Check token budget ZREMRANGEBYSCORE + ZRANGE on token_limit:{user_id} server/rate_limit.py:check_token_budget
Record token usage ZADD + EXPIRE on token_limit:{user_id} server/rate_limit.py:record_token_usage
Health check PING server/main.py:/health
Shutdown aclose() server/main.py:api_lifespan

Known limitations and planned work

Limitation Notes
secure=False on the anon_id cookie Development only — must be True before any HTTPS deployment.
User records have no TTL Permanent user keys accumulate indefinitely. Expiry or archival policy needed before production.
Session list not capped user:{user_id}:sessions grows unboundedly. A LTRIM after LPUSH (e.g. keep last 100) should be added.
No Valkey persistence config documented appendonly / save settings for the Valkey process itself are not yet specified. Data is lost on restart unless configured.
CORS is ["*"] Must be restricted to known origins before any HTTPS deployment.