Cache
Signature-keyed artifact storage — in-memory and file-backed.
@runic-labs/cache maps a normalized decision signature to the artifact produced for it. Most integrations don’t need this package directly — @runic-labs/sdk wraps it — but it’s useful for custom store setups or inspecting cache internals.
npm install @runic-labs/cachepnpm add @runic-labs/cacheyarn add @runic-labs/cachebun add @runic-labs/cachesignature(decision)
Normalizes a Decision and returns its SHA-256 signature. Object keys in params are sorted recursively so key order never affects the hash. See How it works for the full algorithm.
import { signature } from "@runic-labs/cache";
signature({ intent: "summarize_pr", params: { repo: "acme/widgets", pr: 482 } });
// => same hash regardless of param key order
createCache(store?)
import { createCache, FileCacheStore, defaultCacheFilePath } from "@runic-labs/cache";
const cache = createCache(new FileCacheStore({ filePath: defaultCacheFilePath() }));
Defaults to a FileCacheStore under .runic/cache.json (or $RUNIC_HOME/cache.json) if no store is passed.
get(decision)?CachedEntry | null
Looks up a decision. Bumps hit stats on a hit.
CachedEntry | nullset(decision, artifact, meta)?CachedEntry
Stores an artifact for a decision. meta = { tokensSpent }.
CachedEntrylist()?CachedEntry[]
All entries currently in the store.
CachedEntry[]clear()?void
Removes all entries.
voidStorage backends
MemoryCacheStore
In-memory only, lives for the process’s lifetime. Used by both benchmarks and by tests, so runs are self-contained and repeatable.
import { createCache, MemoryCacheStore } from "@runic-labs/cache";
const cache = createCache(new MemoryCacheStore({ staleAfterMs: 60_000 }));
FileCacheStore
Persists to a JSON file so a CLI process and an agent process can read the same cache without a running server.
import { createCache, FileCacheStore } from "@runic-labs/cache";
const cache = createCache(new FileCacheStore({ filePath: "./my-agent/.runic/cache.json" }));
A corrupt or partially-written file is treated as empty rather than thrown — a crash mid-write should never take down the next read.
Implementing your own CacheStore
Both built-in stores implement the same small interface, so a Redis- or SQLite-backed store is a drop-in replacement:
interface CacheStore {
get(signature: string): CachedEntry | null;
set(signature: string, artifact: unknown, meta: { tokensSpent: number }): CachedEntry;
touch(signature: string): CachedEntry | null;
list(): CachedEntry[];
clear(): void;
}
touch is called internally on every cache hit to bump hitCount and lastUsedAt — implement it as a read-modify-write against whatever backing store you choose.