# How MemoryProxy Handles Redis Session Storage: A Deep Dive into RedisSessionStore

> Learn how MemoryProxy manages Redis session storage using RedisSessionStore. Discover JSON serialization, TTLs, atomic counters, and graceful degradation.

- Repository: [Tencent Cloud/TencentDB-Agent-Memory](https://github.com/TencentCloud/TencentDB-Agent-Memory)
- Tags: deep-dive
- Published: 2026-08-22

---

**TLDR: MemoryProxy stores transient session data in Redis through its `RedisSessionStore` class, which serializes session blobs as JSON, applies TTLs via `SETEX`, mantains atomic per-session turn-sequence counters with `INCR`, and gracefully degrades to a no-op mode whenever the Redis connection drops.**

MemoryProxy, part of the TencentCloud/TencentDB-Agent-Memory repository, uses Redis as the backing store for short-lived session state consumed by the CostGuard router and injection layer. The implementation lives primarily in [`MemoryProxy/src/redis-session-store.ts`](https://github.com/TencentCloud/TencentDB-Agent-Memory/blob/main/MemoryProxy/src/redis-session-store.ts) and is wired up at startup by [`src/guard-adapter.ts`](https://github.com/TencentCloud/TencentDB-Agent-Memory/blob/main/src/guard-adapter.ts) and [`src/injection/index.ts`](https://github.com/TencentCloud/TencentDB-Agent-Memory/blob/main/src/injection/index.ts). This article walks through the entire Redis session storage path — from configuration to instantiation to the atomic operations that keep session data consistent under concurrent loads.

## Redis Configuration via the RedisConfig Interface

The Redis connection details are defined by the `RedisConfig` interface in [`src/types.ts`](https://github.com/TencentCloud/TencentDB-Agent-Memory/blob/main/src/types.ts). Every field controls a specific aspect of how MemoryProxy talks to Redis:

- `enabled` — toggles Redis usage entirely. When `false`, the session store is skipped.
- `url` — full Redis connection string (mutually exclusive with discrete host fields).
- `host`, `port`, `password`, `db` — individual connection fields used when `url` is empty.
- `keyPrefix` — defaults to `cg:sess:` and isolates keys per tenant.
- `ttlSeconds` — defaults to `1800` seconds (30 minutes), determining how long a session blob survives.
- `injectionTtlSeconds` — optional override for injection-layer cache TTLs.

Here is the exact interface as written in the source:

```ts
// src/types.ts – Redis configuration
export interface RedisConfig {
  enabled: boolean;
  url: string;
  host: string;
  port: number;
  password: string;
  db: number;
  keyPrefix: string;
  ttlSeconds: number;
  injectionTtlSeconds?: number;
}

```

## The RedisSessionStore Implementation

MemoryProxy's Redis session storage centers on the **`RedisSessionStore`** class. This class implements the `SessionStore` contract used across the proxy layer, translating high-level session operations into Redis commands.

### Instantiation from Configuration

When `config.redis.enabled` is `true`, MemoryProxy creates the session store during startup. This happens in two places: the injection initializer and the guard adapter.

The injection layer ([`src/injection/index.ts`](https://github.com/TencentCloud/TencentDB-Agent-Memory/blob/main/src/injection/index.ts)) creates a higher-level `RedisSessionRepo` if Redis is detected:

```ts
// src/injection/index.ts – creating the session repo
const redis = config.redis?.enabled ? getRedisClient(config.redis) : null;
if (redis) {
  setSessionRepo(new RedisSessionRepo(redis, ttl));
}

```

Meanwhile, [`src/guard-adapter.ts`](https://github.com/TencentCloud/TencentDB-Agent-Memory/blob/main/src/guard-adapter.ts) directly instantiates a `RedisSessionStore` for the CostGuard router as a fallback path:

```ts
// src/guard-adapter.ts – fallback for CostGuard
if (config.redis.enabled) {
  redisSessionStore = new RedisSessionStore(config.redis);
  sessionStore = redisSessionStore;
}

```

### Connection Handling with ioredis

The store's constructor builds an `ioredis` client either from a full URL or from the discrete host options. It opens the connection lazily — meaning no network activity until the first command — and tracks the connection state via three events.

```ts
// src/redis-session-store.ts – constructor (excerpt)
if (config.url) {
  this.client = new Redis(config.url, { lazyConnect: true, ... });
} else {
  this.client = new Redis({ host, port, password, db, lazyConnect: true, ... });
}
this.client.on('connect', () => { this.connected = true; });
this.client.on('error',  err => { this.connected = false; });
this.client.on('close',  () => { this.connected = false; });

```

The `connected` flag is the linchpin of the store's graceful degradation strategy described later.

## Session Blob API: Get, Set, Delete

The session store exposes three core operations that map directly to Redis primitives. Each key is built by prepending `keyPrefix` to the session identifier.

### **`get(key)`**

Reads the JSON-encoded session blob from Redis, parses it, and returns the opaque state object — or `null` on a miss or any error.

### **`set(key, state)`**

Serializes the state to JSON, writes it with `SETEX` so the key expires after the configured TTL, and also refreshes the TTL of the associated turn-sequence counter:

```ts
// src/redis-session-store.ts – get / set / delete (excerpt)
async get(key) { ... const raw = await this.client.get(this.buildKey(key)); ... }
async set(key, state) { ... await this.client.setex(this.buildKey(key), this.ttlSeconds, serialized); ... }
async delete(key) { ... await this.client.del(this.buildKey(key)); ... }

```

### **`delete(key)`**

Removes both the session blob and the per-session turn-sequence key in one `DEL` call, so no orphaned Redis keys remain.

## Atomic Turn Sequence Counter

A distinguishing feature of MemoryProxy's Redis session storage is the per-session DNA counter. For every session, the store maintains a separate key (`<prefix>turnseq:<session>`). The `incrTurnSeq(key)` method uses Redis `INCR` to atomically increment this counter and refreshes its TTL:

```ts
// src/redis-session-store.ts – incrTurnSeq (excerpt)
async incrTurnSeq(key) {
  if (!this.connected) return 0;
  const turnSeqKey = this.buildTurnSeqKey(key);
  const next = await this.client.incr(turnSeqKey);
  await this.client.expire(turnSeqKey, this.ttlSeconds);
  return next;
}

```

`INCR` guarantees a strictly increasing turn identifier even under heavy concurrent requests, preserving the ordering expected by the CostGuard router. If Redis is unavailable, the method returns `0` so the host can fall back to a stateless counter without breaking the pipeline.

## Graceful Degradation Without Redis

Every operation in the store first checks the internal `this.connected` flag:

- **Reads** return `null` when disconnected.
- **Writes** become a permissive no-op.
- **Turn-sequence increments** return `0`.

When the connection drops and a method is called, MemoryProxy logs a warning via `log.warn` and immediately returns the fallback value. This preserves the "passthrough" behavior expected by the CostGuard router, ensuring requests continue processing even when Redis is entirely offline.

## Session Lifecycle Management

The store also exposes two lifecycle hooks for use in health checks and shutdown sequences:

- **`isConnected()`** — reports the current connection state.
- **`close()`** — calls `client.quit()` for a clean shutdown, or `client.disconnect()` if an error occurred.

Default TTL is 1800 seconds (30 minutes), after which both the session blob and the turn-sequence counter are automatically evicted by Redis.

## Code Example: Full Redis Session Storage Workflow

The following example shows how a host application would drive the `RedisSessionStore` directly:

```ts
import { RedisSessionStore } from './redis-session-store';
import { RedisConfig } from './types';

// 1️⃣ Create a store (using env-provided config)
const cfg: RedisConfig = {
  enabled: true,
  url: process.env.REDIS_URL ?? '',
  host: '127.0.0.1',
  port: 6379,
  password: '',
  db: 0,
  keyPrefix: 'cg:sess:',
  ttlSeconds: 1800,
};
const store = new RedisSessionStore(cfg);

// 2️⃣ Store a session blob
await store.set('session-123', { userId: 'u42', state: 'init' });

// 3️⃣ Retrieve it later
const sess = await store.get('session-123');
console.log(sess?.userId); // → 'u42'

// 4️⃣ Increment the per-session turn sequence
const turn = await store.incrTurnSeq('session-123');
console.log(`Turn #${turn}`); // → Turn #1 (first call)

// 5️⃣ Delete the session when finished
await store.delete('session-123');

```

## Key Files in the Repository

| File | Purpose |
|------|---------|
| [`MemoryProxy/src/redis-session-store.ts`](https://github.com/TencentCloud/TencentDB-Agent-Memory/blob/main/MemoryProxy/src/redis-session-store.ts) | Core Redis-backed session store implementation |
| [`MemoryProxy/src/types.ts`](https://github.com/TencentCloud/TencentDB-Agent-Memory/blob/main/MemoryProxy/src/types.ts) | `RedisConfig` definition and related type contracts |
| [`MemoryProxy/src/guard-adapter.ts`](https://github.com/TencentCloud/TencentDB-Agent-Memory/blob/main/MemoryProxy/src/guard-adapter.ts) | Instantiates `RedisSessionStore` for CostGuard routing |
| [`MemoryProxy/src/injection/index.ts`](https://github.com/TencentCloud/TencentDB-Agent-Memory/blob/main/MemoryProxy/src/injection/index.ts) | Sets up injection-layer repo using Redis when enabled |
| [`MemoryProxy/src/db/redis-client.js`](https://github.com/TencentCloud/TencentDB-Agent-Memory/blob/main/MemoryProxy/src/db/redis-client.js) (helper) | Provides `getRedisClient` that creates the `ioredis` instance |

## Summary

- **MemoryProxy's Redis session storage** is implemented by the `RedisSessionStore` class in [`src/redis-session-store.ts`](https://github.com/TencentCloud/TencentDB-Agent-Memory/blob/main/src/redis-session-store.ts), instantiated by both the CostGuard guard adapter and the injection initializer.
- **Configuration** comes from the `RedisConfig` interface, with a default TTL of 1800 seconds and `cg:sess:` key prefix.
- **The session blob API** uses `GET`, `SETEX`, and `DEL` Redis commands, while the turn-sequence counter uses atomic `INCR` with TTL refresh.
- **Graceful degradation** is enforced via the `connected` flag — reads return `null`, writes are no-ops, and turn counters return `0` when Redis is unreachable.
- **Lifecycle management** includes `isConnected()` and `close()` for health checks and clean shutdown.

## Frequently Asked Questions

### What happens when Redis goes down during a MemoryProxy session?

All `RedisSessionStore` operations first check the `this.connected` flag. On a connection failure, reads return `null`, writes become permissive no-ops, and the turn-sequence counter returns `0`. This preserves the passthrough behavior the CostGuard router expects, so requests continue processing without blocking on Redis.

### How does MemoryProxy guarantee atomic turn identifiers for concurrent requests?

It uses the Redis `INCR` command against a dedicated `<prefix>turnseq:<session>` key. Because `INCR` is atomic, each concurrent caller receives a unique, strictly increasing turn number. The store refreshes that key's TTL with `EXPIRE` as well so the counter aligns with the session blob's expiration.

### What is the default TTL for Redis session storage?

The default TTL is **1800 seconds** (30 minutes), configured via the `ttlSeconds` property in `RedisConfig`. An optional `injectionTtlSeconds` can override the TTL specifically for injection-layer caches.

### Does MemoryProxy use lazy connections to Redis?

Yes. The `RedisSessionStore` constructs its `ioredis` client with `lazyConnect: true`, meaning no network traffic occurs until the first command is issued. Connection state is tracked by listening to the `connect`, `error`, and `close` events, and any failure updates the internal `connected` flag that gates all operations.