The Teneo Agent SDK includes a built-in Redis caching framework that allows agents to persist data across restarts. This is particularly useful for maintaining state, caching expensive computations, and enabling distributed agent deployments.
- Easy to use: Just add Redis connection URL to environment variables
- Automatic key prefixing: All keys are automatically prefixed with agent name to avoid collisions
- Graceful degradation: If Redis is unavailable, the agent continues to work without caching
- Type-safe interface: Clean Go interface for all cache operations
- TTL support: Set expiration times for cached data
- Pattern-based deletion: Delete multiple keys matching a pattern
- Distributed rate limiting: Share rate limit state across multiple agent instances
Using Docker:
docker run -d -p 6379:6379 redis:latestUsing Homebrew (macOS):
brew install redis
brew services start redisAdd these environment variables to your .env file:
# Enable Redis cache
REDIS_ENABLED=true
# Redis connection (default: localhost:6379)
REDIS_ADDRESS=localhost:6379
# Optional: Redis password
REDIS_PASSWORD=
# Optional: Redis database number (0-15, default: 0)
REDIS_DB=0
# Optional: Custom key prefix (default: teneo:agent:<agent_name>:)
REDIS_KEY_PREFIX=myagent:Alternatively, you can use REDIS_URL instead of REDIS_ADDRESS for convenience.
package main
import (
"context"
"log"
"time"
"github.com/TeneoProtocolAI/teneo-agent-sdk/pkg/agent"
)
type MyAgent struct {
cache cache.AgentCache // Store reference to cache
}
func (a *MyAgent) Initialize(ctx context.Context, config interface{}) error {
// Get the enhanced agent instance and store cache reference
if enhancedAgent, ok := config.(*agent.EnhancedAgent); ok {
a.cache = enhancedAgent.GetCache()
}
return nil
}
func (a *MyAgent) ProcessTask(ctx context.Context, task string) (string, error) {
// Check if result is in cache
cacheKey := "task:" + task
cached, err := a.cache.Get(ctx, cacheKey)
if err == nil {
log.Printf("Cache hit for task: %s", task)
return cached, nil
}
// Process task
result := "Processed: " + task
// Store result in cache for 1 hour
if err := a.cache.Set(ctx, cacheKey, result, 1*time.Hour); err != nil {
log.Printf("Warning: failed to cache result: %v", err)
}
return result, nil
}The AgentCache interface provides the following methods:
// Set stores a value with optional TTL
Set(ctx context.Context, key string, value interface{}, ttl time.Duration) error
// Get retrieves a string value
Get(ctx context.Context, key string) (string, error)
// GetBytes retrieves a byte array value
GetBytes(ctx context.Context, key string) ([]byte, error)
// Delete removes a key
Delete(ctx context.Context, key string) error
// Exists checks if a key exists
Exists(ctx context.Context, key string) (bool, error)// DeletePattern removes all keys matching a pattern
DeletePattern(ctx context.Context, pattern string) error
// SetWithExpiry sets a key with absolute expiration time
SetWithExpiry(ctx context.Context, key string, value interface{}, expiryTime time.Time) error
// SetIfNotExists sets only if key doesn't exist (returns true if set)
SetIfNotExists(ctx context.Context, key string, value interface{}, ttl time.Duration) (bool, error)
// GetTTL returns remaining TTL for a key
GetTTL(ctx context.Context, key string) (time.Duration, error)
// Increment increments a counter by 1
Increment(ctx context.Context, key string) (int64, error)
// IncrementBy increments a counter by specific amount
IncrementBy(ctx context.Context, key string, value int64) (int64, error)
// Ping checks if cache is available
Ping(ctx context.Context) error
// Clear removes all keys with agent's prefix
Clear(ctx context.Context) error
// Close closes the cache connection
Close() errorfunc (a *MyAgent) fetchData(ctx context.Context, userID string) (string, error) {
cacheKey := "user:" + userID
// Try to get from cache
cached, err := a.cache.Get(ctx, cacheKey)
if err == nil {
return cached, nil
}
// Fetch from API
data := fetchFromAPI(userID)
// Cache for 5 minutes
a.cache.Set(ctx, cacheKey, data, 5*time.Minute)
return data, nil
}func (a *MyAgent) createSession(ctx context.Context, sessionID string, data interface{}) error {
// Store session data with 24 hour expiration
return a.cache.Set(ctx, "session:"+sessionID, data, 24*time.Hour)
}
func (a *MyAgent) getSession(ctx context.Context, sessionID string) (string, error) {
return a.cache.Get(ctx, "session:"+sessionID)
}
func (a *MyAgent) deleteSession(ctx context.Context, sessionID string) error {
return a.cache.Delete(ctx, "session:"+sessionID)
}func (a *MyAgent) checkRateLimit(ctx context.Context, userID string) (bool, error) {
key := "ratelimit:" + userID
// Increment counter
count, err := a.cache.Increment(ctx, key)
if err != nil {
return false, err
}
// Set expiration on first increment
if count == 1 {
a.cache.Set(ctx, key, count, 1*time.Minute)
}
// Allow up to 10 requests per minute
return count <= 10, nil
}func (a *MyAgent) acquireLock(ctx context.Context, resourceID string, ttl time.Duration) (bool, error) {
lockKey := "lock:" + resourceID
// Try to acquire lock (set only if not exists)
acquired, err := a.cache.SetIfNotExists(ctx, lockKey, "locked", ttl)
if err != nil {
return false, err
}
return acquired, nil
}
func (a *MyAgent) releaseLock(ctx context.Context, resourceID string) error {
return a.cache.Delete(ctx, "lock:"+resourceID)
}type UserProfile struct {
Name string `json:"name"`
Email string `json:"email"`
Score int `json:"score"`
}
func (a *MyAgent) saveProfile(ctx context.Context, userID string, profile UserProfile) error {
// The cache automatically marshals structs to JSON
return a.cache.Set(ctx, "profile:"+userID, profile, 24*time.Hour)
}
func (a *MyAgent) loadProfile(ctx context.Context, userID string) (*UserProfile, error) {
data, err := a.cache.GetBytes(ctx, "profile:"+userID)
if err != nil {
return nil, err
}
var profile UserProfile
if err := json.Unmarshal(data, &profile); err != nil {
return nil, err
}
return &profile, nil
}func (a *MyAgent) clearUserData(ctx context.Context, userID string) error {
// Delete all keys matching the pattern
return a.cache.DeletePattern(ctx, "user:"+userID+":*")
}
func (a *MyAgent) clearAllSessions(ctx context.Context) error {
// Delete all session keys
return a.cache.DeletePattern(ctx, "session:*")
}All keys are automatically prefixed with your agent's name. For example, if your agent is named "My Agent" and you set a key "task:123", the actual Redis key will be:
teneo:agent:my_agent:task:123
This prevents key collisions when multiple agents share the same Redis instance.
- Tasks:
task:<task_id>ortask:<hash> - Sessions:
session:<session_id> - Users:
user:<user_id>:<data_type> - Rate limits:
ratelimit:<identifier>:<timewindow> - Locks:
lock:<resource_id> - Temporary data:
temp:<identifier>
| Variable | Description | Default |
|---|---|---|
REDIS_ENABLED |
Enable Redis caching | false |
REDIS_ADDRESS |
Redis server address | localhost:6379 |
REDIS_URL |
Alternative to REDIS_ADDRESS | - |
REDIS_PASSWORD |
Redis password | `` (empty) |
REDIS_DB |
Redis database number (0-15) | 0 |
REDIS_KEY_PREFIX |
Custom key prefix | teneo:agent:<name>: |
You can also configure Redis programmatically:
config := agent.DefaultConfig()
config.RedisEnabled = true
config.RedisAddress = "redis.example.com:6379"
config.RedisPassword = "secret"
config.RedisDB = 1
config.RedisKeyPrefix = "myagent:"The cache is designed to be fault-tolerant. If Redis is unavailable:
- The agent will log a warning but continue to run
- A no-op cache is used (all operations succeed but do nothing)
- Your agent logic should handle cache misses gracefully
result, err := a.cache.Get(ctx, key)
if err == cache.ErrCacheKeyNotFound {
// Key not found - compute the value
result = computeValue()
} else if err != nil {
// Other error - log but continue
log.Printf("Cache error: %v", err)
result = computeValue()
}- Connection pooling: The cache uses connection pooling by default (10 connections)
- Timeouts: Operations have sensible timeouts (3s read, 3s write)
- Retries: Failed operations are retried up to 3 times
- TTL: Always set TTL for temporary data to prevent memory bloat
For testing, you can disable Redis or use a test Redis instance:
// Disable cache for tests
config.RedisEnabled = false
// Or use a separate Redis DB
config.RedisDB = 15 // Use DB 15 for testingYou can also clear all cache data:
// Clear all keys with agent's prefix
agent.GetCache().Clear(ctx)If you need advanced Redis features not covered by the interface, you can access the underlying Redis client:
import "github.com/TeneoProtocolAI/teneo-agent-sdk/pkg/cache"
if redisCache, ok := agent.GetCache().(*cache.RedisCache); ok {
client := redisCache.GetClient()
// Use client for advanced operations
client.HSet(ctx, "myhash", "field", "value")
}If you see connection errors:
- Verify Redis is running:
redis-cli ping(should return "PONG") - Check the Redis address is correct
- Verify firewall rules allow connections to Redis
- Check Redis logs for errors
If you see permission errors:
- Verify the Redis password is correct
- Check Redis ACL settings (if using Redis 6+)
If Redis is using too much memory:
- Set TTL on all keys
- Configure Redis maxmemory policy
- Use
redis-cli MEMORY STATSto analyze usage - Consider using key patterns to delete old data
- Use persistent Redis: Configure Redis with AOF or RDB persistence
- Set maxmemory: Configure Redis maxmemory and eviction policy
- Monitor: Use Redis INFO command to monitor performance
- Backup: Regular backups of Redis data
- Secure: Use strong passwords and TLS for production
- Scale: Consider Redis Cluster for horizontal scaling