Documents
Home>Documents>AI>Agent>Geny

Sharing Session State Across Pods with Redis

6 min readFeb 3, 2026Feb 22, 2026

Implementing Redis-Based Session State Sharing

The initial version of Claude Control stored session state in memory. Every server restart wiped all session data. This approach had obvious limitations in a multi-pod environment, so we introduced Redis for shared session state.

Designing the Redis Session Store

A session's state consists of multiple fields, making a Hash structure the natural fit.

import redis.asyncio as redis
import json
from datetime import datetime

class RedisSessionStore:
    def __init__(self, redis_url: str = "redis://localhost:6379"):
        self.redis = redis.from_url(redis_url, decode_responses=True)
        self.prefix = "claude:session:"

    async def save_session(self, session_id: str, data: dict):
        key = f"{self.prefix}{session_id}"
        data["updated_at"] = datetime.now().isoformat()
        await self.redis.hset(key, mapping={
            k: json.dumps(v) if isinstance(v, (dict, list)) else str(v)
            for k, v in data.items()
        })
        await self.redis.expire(key, 86400)  # 24-hour TTL

    async def get_session(self, session_id: str) -> dict | None:
        key = f"{self.prefix}{session_id}"
        data = await self.redis.hgetall(key)
        if not data:
            return None
        return {k: self._parse_value(v) for k, v in data.items()}

    def _parse_value(self, value: str):
        try:
            return json.loads(value)
        except (json.JSONDecodeError, TypeError):
            return value

Session Data Caching Strategy

Input, output, and status for every session are cached in Redis. The key schema is as follows:

# Session base info
claude:session:{session_id}           # Hash: status, workdir, created_at
# Session I/O logs
claude:session:{session_id}:logs      # List: up to 100 most recent log entries
# Session metrics
claude:session:{session_id}:metrics   # Hash: prompt_count, token_usage
# Active session registry
claude:sessions:active                # Set: IDs of currently active sessions

Pub/Sub is used to propagate session state changes to other instances in real time.

async def publish_session_event(self, session_id: str, event: str, data: dict):
    channel = "claude:events"
    message = json.dumps({
        "session_id": session_id,
        "event": event,
        "data": data,
        "timestamp": datetime.now().isoformat(),
    })
    await self.redis.publish(channel, message)

async def subscribe_events(self):
    pubsub = self.redis.pubsub()
    await pubsub.subscribe("claude:events")
    async for message in pubsub.listen():
        if message["type"] == "message":
            event = json.loads(message["data"])
            await self._handle_event(event)

Pain Points

Connection pool management turned into a lengthy debugging session. redis.asyncio was exhausting the connection pool, which turned out to be caused by code that wasn't returning connections promptly. Tuning max_connections and tracking down those code paths fixed it. There was also a serialization issue where datetime objects weren't handled during JSON encoding — a custom encoder took care of that.

After introducing Redis, sessions survive server restarts and multiple pods can reference the same session state consistently. This foundation becomes central to the Kubernetes multi-pod support built later.

Tags
Redissession managementstate sharingPythonasyncio