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

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 and is wired up at startup by src/guard-adapter.ts and 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. 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:

// 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) creates a higher-level RedisSessionRepo if Redis is detected:

// 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 directly instantiates a RedisSessionStore for the CostGuard router as a fallback path:

// 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.

// 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:

// 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:

// 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:

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 Core Redis-backed session store implementation
MemoryProxy/src/types.ts RedisConfig definition and related type contracts
MemoryProxy/src/guard-adapter.ts Instantiates RedisSessionStore for CostGuard routing
MemoryProxy/src/injection/index.ts Sets up injection-layer repo using Redis when enabled
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, 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →