---
title: "Ledger"
description: Append-only accounting of tokens spent vs. tokens saved.
sidebar:
  order: 5
---

`@runic-labs/ledger` is pure bookkeeping — it never enforces a budget, never refuses a call, never estimates a cost you didn't report. It answers one question: *what did resolving these decisions actually cost, and how much of that was avoided by reuse?*

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

## `createLedger(store?)`

```ts
import { createLedger, FileLedgerStore, defaultLedgerFilePath } from "@runic-labs/ledger";

const ledger = createLedger(new FileLedgerStore({ filePath: defaultLedgerFilePath() }));
```

Defaults to a `FileLedgerStore` under `.runic/ledger.json` (or `$RUNIC_HOME/ledger.json`) if no store is passed.

| Prop | Type | Default | Description |
| - | - | - | - |
| `recordMiss(signature, tokensSpent)?` | `void` | - | Logs a cache miss — the agent generated its own way and spent tokensSpent. |
| `recordHit(signature)?` | `void` | - | Logs a cache hit, recording the same tokensSpent as the original miss for this signature. |
| `summary()?` | `LedgerSummary` | - | Aggregated totals — see below. |
| `all()?` | `LedgerEvent[]` | - | The full raw event log. |
| `clear()?` | `void` | - | Removes all events. |

## `LedgerSummary`

```ts
interface LedgerSummary {
  totalSpent: number;
  totalSaved: number;
  hitsBySignature: Record<string, number>;
}
```

```ts
const summary = ledger.summary();
const totalHits = Object.values(summary.hitsBySignature).reduce((a, b) => a + b, 0);
const tokensWithoutRunic = summary.totalSpent + summary.totalSaved;
const savingsPercent = Math.round((summary.totalSaved / tokensWithoutRunic) * 100);
```

This is exactly the calculation both `benchmarks/openrouter-savings` and `benchmarks/reuse-sweep` use to print their final numbers — see [Benchmarks](/benchmarks).

## Why `recordHit` doesn't take a `tokensSpent` argument

A hit's value is defined as *whatever the original miss for that signature cost* — the ledger looks that up itself rather than trusting the caller to repeat it correctly on every hit. If no prior miss exists for a signature that's somehow being hit (shouldn't happen in normal use, but the ledger doesn't assume it can't), it records `0` rather than guessing.

## Storage backends

Same pattern as `@runic-labs/cache`: `MemoryLedgerStore` for tests and ephemeral runs, `FileLedgerStore` for persistence across separate process invocations. Implement the `LedgerStore` interface yourself for a different backend:

```ts
interface LedgerStore {
  append(event: LedgerEvent): void;
  all(): LedgerEvent[];
  clear(): void;
}
```

## Next

- [Cache](/cache) — the matching signature-keyed store
- [CLI](/cli) — `runic ledger status` reads this summary from the command line