API Reference
Core Cache
- class cache.async_cache.AsyncCache(maxsize=128, default_ttl=None, batch_window_ms=5, max_batch_size=100, backup=None, remote_cache=None)[source]
Bases:
object- async get(key, loader=None, batch_loader=None, ttl=None)[source]
Two-tier cache get: L1 (local) -> L2 (remote) -> loader.
- Read pattern:
Check L1 (in-memory) — immediate
If miss and remote_cache configured, check L2 (remote)
If miss, call loader (with thundering herd protection)
Write result to L1 synchronously, L2 asynchronously
Thundering herd protection and batch loading work with remote_cache. Redis errors are caught and logged — cache degrades to L1 only.
- set(key, value, ttl=<object object>, write_remote=True)[source]
Set a value in the cache. Writes to L1 synchronously, L2 async in background.
- class cache.async_lru.AsyncLRUInvalidator(cached_fn, skip_args=0, clear_all=False, key_fn=None)[source]
Bases:
objectDecorator that invalidates a specific AsyncLRU-cached function when the decorated mutation/write function is called.
Usage:
@AsyncLRU(maxsize=128) async def get_user(user_id): return await db.get_user(user_id) @AsyncLRUInvalidator(get_user) async def update_user(user_id, data): await db.update_user(user_id, data) await get_user(1) # miss -> loads await get_user(1) # hit await update_user(1, {}) # invalidates get_user(1), then runs mutation; # cache key is evicted before the mutation so # no reader ever sees stale data, even if the # mutation raises await get_user(1) # miss -> reloads
Invalidation happens before the mutation executes so that no reader ever sees stale data, even if the mutation raises.
- Parameters:
cached_fn – The AsyncLRU-wrapped function to invalidate.
skip_args – Removed. Passing a non-zero value raises
TypeError. Usekey_fninstead to map mutation args to cache-key args.clear_all – If True, clear the entire cache instead of a specific key.
key_fn – Optional callable
(mutation_args, mutation_kwargs) -> (args, kwargs)that extracts the cached function’s key args from the mutation function’s args. If None (default), the mutation function’s full positional args and kwargs are passed to the cached function’sinvalidate_cache(), which applies its ownskip_argsto derive the correct key.
- class cache.async_lru.AsyncLRU(maxsize=128, skip_args=0, backup=None, remote_cache=None)[source]
Bases:
object- Parameters:
skip_args (int)
- class cache.async_ttl.AsyncTTLInvalidator(cached_fn, skip_args=0, clear_all=False, key_fn=None)[source]
Bases:
objectDecorator that invalidates a specific AsyncTTL-cached function when the decorated mutation/write function is called.
Usage:
@AsyncTTL(time_to_live=60) async def get_session(sid): return await db.get_session(sid) @AsyncTTLInvalidator(get_session) async def end_session(sid): await db.delete_session(sid) await get_session("s1") # miss -> loads await get_session("s1") # hit await end_session("s1") # invalidates get_session("s1"), then runs # mutation; cache key is evicted before the # mutation so no reader ever sees stale data, # even if the mutation raises await get_session("s1") # miss -> reloads
Invalidation happens before the mutation executes so that no reader ever sees stale data, even if the mutation raises.
- Parameters:
cached_fn – The AsyncTTL-wrapped function to invalidate.
skip_args – Removed. Passing a non-zero value raises
TypeError. Usekey_fninstead to map mutation args to cache-key args.clear_all – If True, clear the entire cache instead of a specific key.
key_fn – Optional callable
(mutation_args, mutation_kwargs) -> (args, kwargs)that extracts the cached function’s key args from the mutation function’s args. If None (default), the mutation function’s full positional args and kwargs are passed to the cached function’sinvalidate_cache(), which applies its ownskip_argsto derive the correct key.
Cache Backends
Pluggable cache backend abstraction.
Provides: - CacheBackend: abstract interface for persistent cache storage - DiskBackend: file-based persistence using pickle (stdlib only)
Designed for extensibility: Redis, Memcached, or custom backends can be added by subclassing CacheBackend without changing core cache logic.
- class cache.backend.CacheBackend[source]
Bases:
ABCAbstract interface for persistent cache storage.
Subclass this to implement Redis, Memcached, or any other backend. All methods are synchronous; async wrappers live in the cache layer.
- abstractmethod load()[source]
Load persisted data and return a dict of {key: (value, expiration)}.
expiration is a float (monotonic deadline) or None (no TTL). Expired entries MAY be included; the caller filters them. Returns an empty dict if no persisted data exists.
- class cache.backend.DiskBackend(path)[source]
Bases:
CacheBackendFile-based cache persistence using pickle.
Data is written atomically (write-to-temp then rename) to prevent corruption on crash. No external dependencies required.
- Parameters:
path – filesystem path for the cache file (e.g. ‘/tmp/my_cache.pkl’)
- load()[source]
Load cache entries from disk.
Returns {key: (value, monotonic_expiration_or_None)}. Translates stored relative TTLs back to absolute monotonic deadlines, subtracting time elapsed since save.
Remote Cache Backends
Remote cache backend abstraction for distributed (L2) caching.
Provides: - RemoteCacheBackend: async abstract interface for remote cache stores - RedisBackend: lightweight pure-Python Redis client (no external deps)
- The remote cache is used as L2 in the two-tier caching architecture:
L1 = local in-memory cache (AsyncCache) L2 = remote cache (Redis, Memcached, etc.)
All methods are async since remote operations involve network I/O.
- class cache.remote.RemoteCacheBackend[source]
Bases:
ABCAsync abstract interface for remote (L2) cache storage.
Subclass this to implement Redis, Memcached, or any other remote store. All methods are async to support non-blocking network I/O.
- abstractmethod async get(key)[source]
Get a value from the remote cache.
- Parameters:
key (
str) – string cache key- Returns:
deserialized value, or None on miss
- class cache.remote.RedisBackend(host='localhost', port=6379, db=0, password=None, prefix='ac:', connect_timeout=5, socket_timeout=5)[source]
Bases:
RemoteCacheBackendPure-Python async Redis client for L2 caching.
Uses raw TCP sockets speaking the RESP protocol — no external dependencies required. Suitable for GET/SET/DEL/FLUSHDB operations.
- Parameters:
host – Redis server hostname (default “localhost”)
port – Redis server port (default 6379)
db – Redis database number (default 0)
password – Optional Redis password
prefix – Key prefix to namespace entries (default “ac:”)
connect_timeout – Connection timeout in seconds (default 5)
socket_timeout – Read/write timeout in seconds (default 5)
- async get(key)[source]
Get a value from Redis. Returns None on miss or error.
- Parameters:
key (str)
Key Generation
- class cache.key.KEY(args, kwargs)[source]
Bases:
objectImmutable, hash/eq-stable key for cache (args + kwargs). Fixes prior bugs: - __eq__ was hash-only (violated contract: a==b but hashes differ possible; collisions). - Hash unstable (dict.items() order pre-3.7, str(vars) arbitrary, kwargs.pop mutated caller!). - Now: frozen tuples, recursive _to_hashable (sorted dicts, stable obj repr), no mutation. Guarantees hash(a)==hash(b) iff a==b; stable across runs/Python versions. Used via make_key in decorators/AsyncCache.
Agent Cache
Agent-aware caching layer for AI agent workflows.
Provides: - AgentCache: decorator for caching async read-tool results - AgentCacheInvalidator: decorator for write-tools that invalidate cached reads - AgentCacheSession: async context manager with per-session caching and loop detection
- exception agent_cache.core.AgentLoopDetectedError[source]
Bases:
ExceptionRaised when an agent execution loop is detected (on_loop=’raise’).
- agent_cache.core.get_metrics()[source]
Return global aggregate metrics across all sessions.
- Return type:
dict
- class agent_cache.core.AgentCacheSession(session_id=None, loop_detection=True, max_tool_repeats=5, max_execution_depth=50, on_loop='raise')[source]
Bases:
objectAsync context manager providing session-scoped caching and loop detection.
Usage:
async with AgentCacheSession(loop_detection=True, max_tool_repeats=5) as session: await some_cached_tool(...)
- class agent_cache.core.AgentCache(resource, scope='global', ttl=None, maxsize=128, skip_args=0, backup=None, remote_cache=None)[source]
Bases:
objectDecorator that caches async tool results with resource tagging and scope.
Example:
@AgentCache(resource="cart", scope="global", ttl=60) async def get_cart(user_id): ...
- Parameters:
resource (str)
scope (str)
maxsize (int)
skip_args (int)
- class agent_cache.core.AgentCacheInvalidator(resource, scope='global', skip_args=0, clear_all=True, key_fn=None)[source]
Bases:
objectDecorator for write/mutation tools that invalidate related cached reads.
Invalidation happens before the mutation executes so that no reader ever sees stale data, even if the mutation raises.
Example:
@AgentCacheInvalidator(resource="cart", scope="global") async def add_to_cart(user_id, item): ...
- Parameters:
resource (
str) – The resource tag to invalidate (must match an@AgentCache).scope (
str) –"global"or"session".clear_all (
bool) – If True (default), clear all entries for the resource. If False, requireskey_fnto target a specific key.key_fn – Optional
(args, kwargs) -> cache_keyfor selective invalidation whenclear_all=False.skip_args (
int) – Removed. Passing a non-zero value raisesTypeError. Usekey_fninstead to map mutation args to cache-key args.