CLAUDE LABJP
FORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtaskLIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its ownMCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSPLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callbackSONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as OctoberFORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtaskLIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its ownMCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSPLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callbackSONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as October
Articles/API & SDK
API & SDK/2026-04-29Advanced

Production Semantic Cache for Claude API — Similarity Thresholds, Pollution Defense, and What to Track

A production playbook for adding a semantic cache in front of Claude API — threshold tuning, multi-tenant isolation, pollution prevention, fallbacks, and the metrics that actually prove it works.

claude-api81semantic-cache2embedding2production111cost-optimization28observability20multi-tenant2

Premium Article

"I want to add caching, but what if a similar question returns a wrong answer?" — that was the first concern I heard, every single time, on three different products where I added semantic caching in front of Claude.

An exact-match cache (the SHA-256-keyed Redis pattern) is easy to ship. The problem is that real users phrase the same question differently — punctuation, word order, formality. Hit rates rarely cross 5%. Claude API spend stays flat and latency does not move.

A semantic cache uses embeddings to detect "near-equivalent" prompts and returns a previously generated response. Done well, hits return in roughly 50 ms with zero Claude API charge. Done naively, it serves a medical-domain response to a finance-domain question. This article is the playbook for drawing that line correctly, with the design choices I have made the hard way and the production checklist I now refuse to ship without.

Why a semantic cache — when an exact-match cache falls short

As an indie developer maintaining several small products, API spend is not an abstract line item for me — it is the difference between shipping a feature or shelving it. That pressure is exactly why I approached caching so carefully. In a support bot I run, 70% of user questions had the same intent under different wording. "I can't log in" / "Sign-in is failing" / "Can't get into my account" — three distinct keys to an exact-match cache. Embedded into the same vector space, the three collapse into one cache entry. That single observation is what makes the embedding-based approach worth the additional complexity.

Semantic caching pays off most when "vocabulary is broad but the response space is narrow" — FAQ bots, product-search assistants, error-help flows, customer-service routing, internal knowledge-base lookups. It pays off poorly when "every input is a unique code review" or "every prompt is a fresh creative generation" — forcing it there causes accidents and frustrated users.

Decide up front which surfaces are cacheable and which must always be fresh. In my deployments I add an explicit cacheable: true flag per endpoint. The default is false, and only endpoints we have verified empirically get the flag turned on. That single rule, more than any technical detail in this article, has prevented "we put it on everything and broke prod" mistakes. If you take only one thing from this guide, take that.

There is also a cost-benefit framing worth being honest about. If your monthly Claude API bill is below a few hundred dollars, the engineering effort to build, monitor, and maintain a semantic cache may not pay back. The real wins start when you are spending thousands per month on inference, when latency-sensitive endpoints have repeated query patterns, or when you want to make the same answer available across many users without re-paying for it.

Architecture — three cache tiers and the data flow

A production-grade setup is rarely a single layer. I run three:

  • Tier 1: exact-match (Redis, short TTL) — fastest, zero false positives
  • Tier 2: semantic (vector DB with threshold filter) — fast, requires guards
  • Tier 3: Claude API (fallback) — slowest, always correct

Queries cascade. Tier 1 first; if a hit, return immediately. Otherwise Tier 2 with a similarity threshold (I use ~0.92 in production, more on this below). If neither, fall through to Tier 3 and call Claude. After a Tier 3 call, the response is written back into both Tier 1 and Tier 2 — but only after passing the safety guards we will add in Step 3.

Why three tiers and not two? Because exact match is essentially free in latency and impossible to get wrong. Even if your semantic cache is perfect, the same user often re-issues the exact same query within a session, and you want that round-trip to be 1 ms, not 50 ms. Tier 1 handles the easy case; Tier 2 picks up the long tail.

Include tenant_id in the cache key. The single most common mistake I see (and one I have made myself) is filtering by tenant only at write time and forgetting it at read time, leaking responses across tenants. We will harden against this in Step 2.

# semantic_cache.py — three-tier lookup flow
import hashlib
import time
from typing import Optional
 
from anthropic import Anthropic
from redis import Redis
import qdrant_client
from qdrant_client.models import PointStruct, VectorParams, Distance, Filter, FieldCondition, MatchValue
 
EXACT_TTL_SEC = 3600                # Tier 1: 1 hour
SEMANTIC_THRESHOLD = 0.92           # Tier 2: cosine similarity floor
SEMANTIC_TTL_SEC = 86400            # Tier 2: 24 hours
 
def get_cached_response(
    tenant_id: str,
    user_query: str,
    redis: Redis,
    qdrant: qdrant_client.QdrantClient,
    embed_fn,                       # external embedding function (Voyage, Cohere, etc.)
    anthropic: Anthropic,
    feature_id: str,                # the cacheable: true endpoint identifier
) -> dict:
    # Tier 1: exact match
    exact_key = f"cache:exact:{tenant_id}:{feature_id}:{hashlib.sha256(user_query.encode()).hexdigest()}"
    cached = redis.get(exact_key)
    if cached:
        return {"text": cached.decode(), "layer": "exact", "latency_ms": 1}
 
    # Tier 2: semantic
    embedding = embed_fn(user_query)            # assume 1024-d vectors
    hits = qdrant.search(
        collection_name="claude_cache",
        query_vector=embedding,
        query_filter=Filter(must=[
            FieldCondition(key="tenant_id", match=MatchValue(value=tenant_id)),
            FieldCondition(key="feature_id", match=MatchValue(value=feature_id)),
        ]),
        limit=1,
        score_threshold=SEMANTIC_THRESHOLD,    # below threshold returns nothing
    )
    if hits:
        # backfill Tier 1 so the next exact match is even faster
        redis.setex(exact_key, EXACT_TTL_SEC, hits[0].payload["response"])
        return {"text": hits[0].payload["response"], "layer": "semantic", "score": hits[0].score}
 
    # Tier 3: Claude API (fallback)
    start = time.perf_counter()
    response = anthropic.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1024,
        messages=[{"role": "user", "content": user_query}],
    )
    latency_ms = int((time.perf_counter() - start) * 1000)
    text = response.content[0].text
 
    # Save to both tiers — but only after the safety check (next section)
    redis.setex(exact_key, EXACT_TTL_SEC, text)
    qdrant.upsert(
        collection_name="claude_cache",
        points=[PointStruct(
            id=hashlib.sha256(f"{tenant_id}:{feature_id}:{user_query}".encode()).hexdigest()[:16],
            vector=embedding,
            payload={"tenant_id": tenant_id, "feature_id": feature_id,
                     "query": user_query, "response": text, "ts": time.time()},
        )],
    )
    return {"text": text, "layer": "api", "latency_ms": latency_ms}

Expected log lines look like this:

{"layer": "exact",    "latency_ms": 1}
{"layer": "semantic", "score": 0.94}
{"layer": "api",      "latency_ms": 1820}

If layer is exact or semantic, you paid Claude nothing for that request. The score field on semantic hits is also valuable later when you debug a complaint — being able to answer "the system returned a near-match at 0.93 cosine; here is the original question" turns a vague support ticket into something you can actually triage.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
Threshold tuning from a labeled precision-recall curve with Precision fixed at 0.98 to hold false positives under 2%
A three-guard write path (injection filter, stop_reason check, Haiku LLM-as-a-judge) that prevents cache poisoning
A three-tier cache design with a measured 47% hit rate and 38% cost reduction, plus a full production checklist
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

API & SDK2026-06-18
When Your Claude API Response Cache Returns Stale Answers and Near-Miss Wrong Ones — Field Notes on Freshness and False-Hit Suppression
A Claude API response cache improves latency and cost immediately, but the problems that hurt in production are not average hit rate — they are stale hits and semantic false hits. Here is the key design, freshness management, false-hit suppression, and observability that keep a cache honest.
API & SDK2026-07-13
Coalescing Concurrent Claude API Calls: Single-Flight Against Duplicate Inference and Cache Stampede
A design for collapsing identical prompts that fire at the same instant into a single upstream Claude call, using single-flight (request coalescing). In-process and distributed implementations, jittered retries, and negative caching, with measured results.
API & SDK2026-06-23
When Claude API Prompt Caching Quietly Stops Hitting in Production — Field Notes on TTL and Measured Savings
Prompt caching works beautifully the day you ship it, then quietly stops hitting in production. The five things that break the prefix, how to choose between 5-minute and 1-hour TTL, and how to measure real savings from usage instead of guessing.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →