Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 && \
Expand Down
4 changes: 4 additions & 0 deletions src/agents/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
installDependencies,
readContextFiles,
startPostgres,
startRedis,
warmTypeScriptCache,
} from './utils/index.js';
import { createAgentLogger } from './utils/logging.js';
Expand Down Expand Up @@ -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);
Expand Down
15 changes: 15 additions & 0 deletions src/agents/prompts/templates/partials/environment.eta
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
4 changes: 4 additions & 0 deletions src/agents/review.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
installDependencies,
readContextFiles,
startPostgres,
startRedis,
} from './utils/index.js';
import { createAgentLogger } from './utils/logging.js';

Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions src/agents/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export {
LOG_LEVELS,
getLogLevel,
startPostgres,
startRedis,
generateDirectoryListing,
type ContextFile,
readContextFiles,
Expand Down
94 changes: 94 additions & 0 deletions src/agents/utils/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,100 @@ export async function startPostgres(): Promise<void> {
logger.info('PostgreSQL started successfully');
}

// ============================================================================
// Redis Startup
// ============================================================================

let redisStarted = false;

export async function startRedis(): Promise<void> {
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
// ============================================================================
Expand Down