Skip to content
Runic
English
Esc
navigateopen⌘Jpreview
On this page

Ledger

Append-only accounting of tokens spent vs. tokens saved.

@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?

npm install @runic-labs/ledger
pnpm add @runic-labs/ledger
yarn add @runic-labs/ledger
bun add @runic-labs/ledger

createLedger(store?)

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.

PropType
recordMiss(signature, tokensSpent)?void

Logs a cache miss — the agent generated its own way and spent tokensSpent.

Typevoid
recordHit(signature)?void

Logs a cache hit, recording the same tokensSpent as the original miss for this signature.

Typevoid
summary()?LedgerSummary

Aggregated totals — see below.

TypeLedgerSummary
all()?LedgerEvent[]

The full raw event log.

TypeLedgerEvent[]
clear()?void

Removes all events.

Typevoid

LedgerSummary

interface LedgerSummary {
  totalSpent: number;
  totalSaved: number;
  hitsBySignature: Record<string, number>;
}
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.

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:

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

Next

  • Cache — the matching signature-keyed store
  • CLIrunic ledger status reads this summary from the command line

Was this page helpful?