Official Go SDK for cachly.dev —
Managed Valkey/Redis cache built for AI apps. GDPR-compliant · German servers · Live in 30 seconds.
go get github.com/cachly-dev/sdk-goRequires Go 1.21+. Uses
github.com/redis/go-redis/v9.
package main
import (
"context"
"fmt"
"os"
"time"
"github.com/cachly-dev/sdk-go/cachly"
)
func main() {
client, err := cachly.New(os.Getenv("CACHLY_URL"))
if err != nil {
panic(err)
}
defer client.Close()
ctx := context.Background()
// Set with TTL
if err := client.Set(ctx, "user:42", map[string]string{"name": "Alice"}, 5*time.Minute); err != nil {
panic(err)
}
// Get (auto-deserialises JSON); redis.Nil on miss
var user map[string]string
if err := client.Get(ctx, "user:42", &user); err != nil {
fmt.Println("miss or error:", err)
}
// Get-or-Set
val, err := client.GetOrSet(ctx, "report:monthly", func() (any, error) {
return db.RunExpensiveReport(ctx)
}, time.Hour)
if err != nil {
panic(err)
}
_ = val
// Atomic counter
views, _ := client.Incr(ctx, "page:views")
fmt.Println("views:", views)
}Create your free instance at cachly.dev — no credit card required.
Cache LLM responses by meaning, not exact text. The same prompt phrased differently returns the cached answer — cutting OpenAI costs by up to 60 %.
sem := client.Semantic(func(ctx context.Context, text string) ([]float64, error) {
return openaiClient.Embed(ctx, text)
})
result, err := sem.GetOrSet(ctx, userQuestion, func() (any, error) {
return openaiClient.Ask(ctx, userQuestion)
}, cachly.SemanticOptions{
Threshold: 0.92,
TTL: time.Hour,
Namespace: "cachly:sem", // optional, default: "cachly:sem"
})
if result.Hit {
fmt.Printf("cache hit (similarity=%.3f)\n", result.Similarity)
} else {
fmt.Println("miss")
}Bundle GET/SET/DEL/EXISTS/TTL operations into one HTTP request (or Redis pipeline). Saves up to 10× HTTP overhead for LLM pipelines with many parallel cache lookups.
// Optional: batchURL enables HTTP batching instead of Redis pipeline
client, _ := cachly.NewWithBatch(os.Getenv("CACHLY_URL"), os.Getenv("CACHLY_BATCH_URL"))
results, err := client.Batch(ctx, []cachly.BatchOp{
{Op: "get", Key: "user:1"},
{Op: "get", Key: "config:app"},
{Op: "set", Key: "visits", Value: "42", TTL: 86400},
{Op: "exists", Key: "session:xyz"},
{Op: "ttl", Key: "token:abc"},
})
// results[0].Value → *string (nil on miss)
// results[1].Value → *string
// results[2].Ok → *bool
// results[3].Exists → *bool
// results[4].TTLSeconds → *int64 (-1 = no TTL, -2 = key missing)Without CACHLY_BATCH_URL the client falls back automatically to a Redis pipeline.
func CacheMiddleware(client *cachly.Client, ttl time.Duration) gin.HandlerFunc {
return func(c *gin.Context) {
key := "http:" + c.Request.URL.String()
var body []byte
if err := client.Get(c, key, &body); err == nil {
c.Data(200, "application/json", body)
c.Abort()
return
}
c.Next()
// cache response body after handler
}
}cachly ships a 30-tool MCP server that gives Claude Code, Cursor, GitHub Copilot, and Windsurf a persistent memory across sessions — so they never forget your architecture, lessons learned, or last session context.
# One-time setup
npx @cachly-dev/initOr configure manually in your editor (~/.vscode/mcp.json / .cursor/mcp.json):
{
"servers": {
"cachly": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cachly-dev/mcp-server"],
"env": { "CACHLY_JWT": "your-jwt-token" }
}
}
}Add to your AI assistant instructions (e.g. .github/copilot-instructions.md):
## cachly AI Brain
At the START of every session:
session_start(instance_id = "your-instance-id", focus = "what you're working on today")
At the END of every session:
session_end(instance_id = "your-instance-id", summary = "...", files_changed = [...])
After any bug fix or deploy:
learn_from_attempts(instance_id = "your-instance-id", topic = "category:keyword",
outcome = "success", what_worked = "...", what_failed = "...", severity = "major")session_start returns a full briefing in one call: last session summary, relevant lessons, open failures, brain health. 60 % fewer file reads, instant context, zero re-discovery.
→ Full docs: cachly.dev/docs/ai-memory
Use cachly as a drop-in caching proxy for OpenAI or Anthropic — no SDK changes needed:
# Instead of https://api.openai.com — use your cachly proxy URL:
OPENAI_BASE_URL=https://api.cachly.dev/v1/llm-proxy/YOUR_TOKEN/openai
# Anthropic:
ANTHROPIC_BASE_URL=https://api.cachly.dev/v1/llm-proxy/YOUR_TOKEN/anthropicIdentical requests are served from cache with X-Cachly-Cache: HIT. Check savings via GET /v1/llm-proxy/YOUR_TOKEN/stats.
base := fmt.Sprintf("https://api.cachly.dev/v1/workflow/%s", token)
// Save a checkpoint after each workflow step
body := `{"run_id":"my-run-123","step_index":0,"step_name":"research",
"agent_name":"researcher","status":"completed",
"state":"{\"topic\":\"AI caching\"}"}`
http.Post(base+"/checkpoints", "application/json", strings.NewReader(body))
// Resume: get the latest checkpoint
resp, _ := http.Get(base + "/runs/my-run-123/latest")
// → {"step_index": 2, "step_name": "write", "state": "...", "status": "completed"}client, _ := cachly.NewWithConfig(cachly.Config{
URL: os.Getenv("CACHLY_URL"),
Pool: &cachly.PoolConfig{
PoolSize: 20, // max connections (default: 10×GOMAXPROCS)
MinIdleConns: 5, // keep 5 warm connections
KeepAlive: 30 * time.Second, // PING every 30s
MaxRetries: 3, // retry failed commands
MinRetryBackoff: 8 * time.Millisecond,
MaxRetryBackoff: 512 * time.Millisecond,
ConnMaxIdleTime: 5 * time.Minute,
ConnMaxLifetime: 30 * time.Minute,
},
})
defer client.Close()Disable retries with MaxRetries: -1.
import "github.com/redis/go-redis/extra/redisotel/v9"
client, _ := cachly.NewWithConfig(cachly.Config{URL: os.Getenv("CACHLY_URL")})
client.AddHook(redisotel.InstrumentTracing())
// Every Get/Set/Delete/Incr now produces OTEL spans:
// span: "redis.get" attributes: { db.statement: "get user:42" }| Method | Signature | Description |
|---|---|---|
New |
(url string) (*Client, error) |
Create client from Redis URL |
NewWithOptions |
(opts *redis.Options) *Client |
Create client from custom redis.Options |
Ping |
(ctx) error |
Check connectivity |
Get |
(ctx, key, dst) error |
Get + JSON-decode; redis.Nil on miss |
Set |
(ctx, key, value, ttl) error |
Set + JSON-encode; ttl=0 = no expiry |
Delete |
(ctx, ...keys) (int64, error) |
Delete one or more keys; returns count deleted |
Exists |
(ctx, ...keys) (bool, error) |
True if all given keys exist |
Expire |
(ctx, key, ttl) (bool, error) |
Update TTL; false if key not found |
TTL |
(ctx, key) (time.Duration, error) |
Remaining TTL of a key |
Incr |
(ctx, key) (int64, error) |
Atomic increment |
IncrBy |
(ctx, key, n) (int64, error) |
Atomic increment by n |
GetOrSet |
(ctx, key, fn, ttl) (any, error) |
Get-or-set; calls fn on miss and stores result |
Semantic |
(embedFn EmbedFn) *SemanticCache |
Semantic AI cache helper |
Raw |
() *redis.Client |
Direct go-redis access |
Close |
() error |
Close connection and stop keep-alive |
| Method | Signature | Description |
|---|---|---|
GetOrSet |
(ctx, prompt, fn, opts) (*SemanticResult, error) |
Get-or-set by semantic similarity |
Flush |
(ctx, namespace) (int64, error) |
Delete all entries in namespace |
Size |
(ctx, namespace) (int64, error) |
Count entries in namespace |
type EmbedFn func(ctx context.Context, text string) ([]float64, error)
type SemanticOptions struct {
Threshold float64 // cosine-similarity cutoff (0–1), default 0.85
TTL time.Duration // 0 = no expiry
Namespace string // key prefix, default "cachly:sem"
}
type SemanticResult struct {
Value any
Hit bool
Similarity float64 // only set on cache hit
}CACHLY_URL=redis://:your-password@my-app.cachly.dev:30101
CACHLY_BATCH_URL=https://api.cachly.dev/v1/cache/YOUR_TOKEN # optionalMIT © cachly.dev