---
title: "Cache"
description: Signature-keyed artifact storage — in-memory and file-backed.
sidebar:
  order: 4
---

`@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.

```package-install
npm i @runic-labs/cache
```

## `signature(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](/how-it-works) for the full algorithm.

```ts
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?)`

```ts
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.

| Prop | Type | Default | Description |
| - | - | - | - |
| `get(decision)?` | `CachedEntry \| null` | - | Looks up a decision. Bumps hit stats on a hit. |
| `set(decision, artifact, meta)?` | `CachedEntry` | - | Stores an artifact for a decision. meta = { tokensSpent }. |
| `list()?` | `CachedEntry[]` | - | All entries currently in the store. |
| `clear()?` | `void` | - | Removes all entries. |

## Storage 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.

```ts
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.

```ts
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:

```ts
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.

## Next

- [Ledger](/ledger) — the matching spent/saved accounting layer
- [SDK](/sdk) — the two-function contract most integrations actually use