From 987822aef01da5db55793d1745e3d4ae274b9784 Mon Sep 17 00:00:00 2001 From: zbigniew sobiecki Date: Fri, 2 Jan 2026 14:32:26 +0100 Subject: [PATCH] feat: add Redis configuration and startup for agents MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add Redis support alongside PostgreSQL, enabling agents to use in-memory data storage for caching, queues, and session management. Changes: - Install redis-server and redis-tools in Docker image - Configure Redis with AOF persistence, 256MB memory limit, and LRU eviction - Add startRedis() function following the same pattern as startPostgres() - Integrate Redis startup into agent lifecycle (base.ts, review.ts) - Document Redis CLI commands and usage in agent prompts - Both PostgreSQL and Redis are critical: agents fail if either service doesn't start Redis configuration: - Port: 6379 (standard) - Bind: localhost only for security - Persistence: AOF with everysec fsync - Memory: 256MB with allkeys-lru eviction policy - Access: via redis-cli through Tmux gadget 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- Dockerfile | 35 +++++++ src/agents/base.ts | 4 + .../templates/partials/environment.eta | 15 +++ src/agents/review.ts | 4 + src/agents/utils/index.ts | 1 + src/agents/utils/setup.ts | 94 +++++++++++++++++++ 6 files changed, 153 insertions(+) diff --git a/Dockerfile b/Dockerfile index 6c21fd45..af590192 100644 --- a/Dockerfile +++ b/Dockerfile @@ -55,6 +55,41 @@ RUN mkdir -p /run/postgresql && chown -R postgres:postgres /run/postgresql \ && su postgres -c "psql -c \"ALTER USER postgres WITH PASSWORD 'postgres';\"" \ && su postgres -c "/usr/lib/postgresql/*/bin/pg_ctl stop -D /var/lib/postgresql/data" +# Install and configure Redis for local development use by agents +RUN apt-get update && apt-get install -y \ + redis-server \ + redis-tools \ + && rm -rf /var/lib/apt/lists/* + +# Configure Redis +# - Port: 6379 (standard) +# - Bind: localhost only for security +# - Persistence: AOF enabled +# - Memory: 256MB limit with LRU eviction +RUN mkdir -p /var/lib/redis /var/log/redis /var/run/redis && \ + chown -R redis:redis /var/lib/redis /var/log/redis /var/run/redis && \ + { \ + echo "# Redis Configuration for CASCADE Agents"; \ + echo "bind 127.0.0.1"; \ + echo "port 6379"; \ + echo "daemonize no"; \ + echo "supervised systemd"; \ + echo "pidfile /var/run/redis/redis-server.pid"; \ + echo "loglevel notice"; \ + echo "logfile /var/log/redis/redis-server.log"; \ + echo "dir /var/lib/redis"; \ + echo "# Persistence"; \ + echo "appendonly yes"; \ + echo "appendfilename \"appendonly.aof\""; \ + echo "appendfsync everysec"; \ + echo "# Memory Management"; \ + echo "maxmemory 256mb"; \ + echo "maxmemory-policy allkeys-lru"; \ + echo "# Disable protected mode for local dev"; \ + echo "protected-mode no"; \ + } > /etc/redis/redis.conf && \ + chown redis:redis /etc/redis/redis.conf + # Install ast-grep RUN ARCH=$(dpkg --print-architecture) && \ if [ "$ARCH" = "amd64" ]; then AST_ARCH="x86_64"; else AST_ARCH="aarch64"; fi && \ diff --git a/src/agents/base.ts b/src/agents/base.ts index bad7595d..8e97305b 100644 --- a/src/agents/base.ts +++ b/src/agents/base.ts @@ -30,6 +30,7 @@ import { installDependencies, readContextFiles, startPostgres, + startRedis, warmTypeScriptCache, } from './utils/index.js'; import { createAgentLogger } from './utils/logging.js'; @@ -63,6 +64,9 @@ async function setupRepository( // Start PostgreSQL if available (for local database testing) await startPostgres(); + // Start Redis if available (for caching, queues, session storage) + await startRedis(); + // Clone repo to temp directory const repoDir = createTempDir(project.id); cloneRepo(project, repoDir); diff --git a/src/agents/prompts/templates/partials/environment.eta b/src/agents/prompts/templates/partials/environment.eta index 20ac381b..4533bcc5 100644 --- a/src/agents/prompts/templates/partials/environment.eta +++ b/src/agents/prompts/templates/partials/environment.eta @@ -7,6 +7,21 @@ - **Stop**: `su postgres -c 'pg_ctl stop -D /var/lib/postgresql/data'` - **Status**: `su postgres -c 'pg_ctl status -D /var/lib/postgresql/data'` - **Create database**: `psql -U postgres -h localhost -c 'CREATE DATABASE mydb;'` +- **Redis**: In-memory data store running on port 6379 + - **Connection**: `redis://localhost:6379` (no password) + - **Connect via CLI**: `redis-cli` + - **Common commands**: + - `redis-cli ping` - Check if Redis is running + - `redis-cli SET key value` - Set a key + - `redis-cli GET key` - Get a key's value + - `redis-cli KEYS "*"` - List all keys (use sparingly) + - `redis-cli FLUSHALL` - Clear all data (use with caution) + - `redis-cli INFO` - Get server statistics + - **Start**: `su redis -c 'redis-server /etc/redis/redis.conf --daemonize yes'` + - **Stop**: `redis-cli SHUTDOWN` + - **Monitor**: `redis-cli MONITOR` - Watch all commands in real-time + - **Data persistence**: AOF enabled (data survives restarts) + - **Memory limit**: 256MB with LRU eviction - **Node.js 22**: With npm, pnpm, yarn, bun - **Git & GitHub CLI**: `git` and `gh` commands available - **Search tools** (use via Tmux for fast codebase exploration): diff --git a/src/agents/review.ts b/src/agents/review.ts index 2d9215e4..7fa8dd04 100644 --- a/src/agents/review.ts +++ b/src/agents/review.ts @@ -25,6 +25,7 @@ import { installDependencies, readContextFiles, startPostgres, + startRedis, } from './utils/index.js'; import { createAgentLogger } from './utils/logging.js'; @@ -57,6 +58,9 @@ async function setupRepository( // Start PostgreSQL if available (for local database testing) await startPostgres(); + // Start Redis if available (for caching, queues, session storage) + await startRedis(); + // Clone repo to temp directory const repoDir = createTempDir(project.id); cloneRepo(project, repoDir); diff --git a/src/agents/utils/index.ts b/src/agents/utils/index.ts index be42c02d..7724f729 100644 --- a/src/agents/utils/index.ts +++ b/src/agents/utils/index.ts @@ -2,6 +2,7 @@ export { LOG_LEVELS, getLogLevel, startPostgres, + startRedis, generateDirectoryListing, type ContextFile, readContextFiles, diff --git a/src/agents/utils/setup.ts b/src/agents/utils/setup.ts index 52db41bf..1331c7eb 100644 --- a/src/agents/utils/setup.ts +++ b/src/agents/utils/setup.ts @@ -95,6 +95,100 @@ export async function startPostgres(): Promise { logger.info('PostgreSQL started successfully'); } +// ============================================================================ +// Redis Startup +// ============================================================================ + +let redisStarted = false; + +export async function startRedis(): Promise { + if (redisStarted) return; + + const REDIS_CONF = '/etc/redis/redis.conf'; + const REDIS_LOG = '/var/log/redis/redis-server.log'; + + try { + // Check if Redis is already running + const statusResult = await execCommand('redis-cli', ['ping'], '/'); + + if (statusResult.stdout.trim() === 'PONG') { + logger.info('Redis already running'); + redisStarted = true; + return; + } + } catch { + // Not running, continue to start it + } + + // Start Redis as redis user + logger.info('Starting Redis...'); + + // Ensure runtime directories exist (may not persist across container restarts) + try { + await execCommand('mkdir', ['-p', '/var/run/redis'], '/'); + await execCommand('chown', ['redis:redis', '/var/run/redis'], '/'); + await execCommand('mkdir', ['-p', '/var/lib/redis'], '/'); + await execCommand('chown', ['redis:redis', '/var/lib/redis'], '/'); + await execCommand('mkdir', ['-p', '/var/log/redis'], '/'); + await execCommand('chown', ['redis:redis', '/var/log/redis'], '/'); + } catch (err) { + logger.warn('Failed to create Redis directories', { error: String(err) }); + } + + try { + // Start Redis in background using redis-server + // We use su to run as redis user, and redirect stderr to capture startup errors + const startResult = await execCommand( + 'su', + ['redis', '-c', `redis-server ${REDIS_CONF} --daemonize yes 2>&1`], + '/', + ); + logger.debug('redis-server start output', { + stdout: startResult.stdout, + stderr: startResult.stderr, + }); + } catch (err) { + // Try to read the log file for more details + try { + const logResult = await execCommand('cat', [REDIS_LOG], '/'); + logger.error('Redis log contents', { log: logResult.stdout }); + } catch { + // Ignore if log file doesn't exist + } + logger.error('Failed to start Redis', { error: String(err) }); + throw new Error(`Redis failed to start. Check ${REDIS_LOG} for details. Error: ${err}`); + } + + // Wait a moment for Redis to initialize + await new Promise((resolve) => setTimeout(resolve, 500)); + + // Verify it's actually running + try { + const verifyResult = await execCommand('redis-cli', ['ping'], '/'); + logger.debug('redis-cli ping output', { + stdout: verifyResult.stdout, + stderr: verifyResult.stderr, + }); + + if (verifyResult.stdout.trim() !== 'PONG') { + // Try to read the log file for more details + try { + const logResult = await execCommand('cat', [REDIS_LOG], '/'); + logger.error('Redis log contents', { log: logResult.stdout }); + } catch { + // Ignore if log file doesn't exist + } + throw new Error(`Redis ping check failed. Output: ${verifyResult.stdout}`); + } + } catch (err) { + logger.error('Redis ping check failed after start', { error: String(err) }); + throw new Error(`Redis failed to start properly: ${err}`); + } + + redisStarted = true; + logger.info('Redis started successfully'); +} + // ============================================================================ // Log Level Configuration // ============================================================================