Zum Inhalt springen
Runic
Deutsch
Esc
navigierenöffnen⌘Jvorschau
Auf dieser Seite

So funktioniert es

Der Signaturalgorithmus, warum der Abgleich exakt ist und was das Ledger tatsächlich protokolliert.

Signaturen

Jede Entscheidung wird in eine stabile Signatur normalisiert, bevor sie als Cache-Schlüssel verwendet wird:

export function signature(decision: Decision): string {
  const normalized = {
    intent: decision.intent,
    params: sortKeysDeep(decision.params),
  };
  return createHash("sha256").update(JSON.stringify(normalized)).digest("hex");
}

Die Normalisierung ist bewusst oberflächlich — Objektschlüssel werden rekursiv sortiert, sodass { a: 1, b: 2 } und { b: 2, a: 1 } identisch gehasht werden, aber weder intent noch die Parameter-Werte werden verändert. Es gibt kein Stemming, keine Normalisierung der Groß-/Kleinschreibung und keine semantische Kanonisierung.

{ intent: "summarize_pr", params: { repo: "acme/widgets", pr: 482 } }
{ intent: "summarize_pr", params: { pr: 482, repo: "acme/widgets" } }
        │                                       │
        └──────────────── same signature ──────┘

{ intent: "summarize_pr", params: { repo: "acme/widgets", pr: 483 } }

        └── different signature (different pr) ──┘

Warum exakte Übereinstimmung statt semantischer Übereinstimmung

Ein semantischer/unscharfer Cache würde mehr Wiederholungen erfassen — „Fasse diesen PR zusammen“ und „Gib mir eine Zusammenfassung dieses Pull Requests“ würden beide einen Treffer liefern. Das würde aber auch bedeuten, dass Runic beurteilen muss, ob zwei unterschiedlich formulierte Anfragen ähnlich genug sind, um eine gecachte Antwort zu teilen — genau die Art nicht deterministischer Ermessensentscheidung, die Runic vermeiden soll.

Exakte Übereinstimmung bedeutet, dass ein Cache-Treffer nachweislich dieselbe Entscheidung ist, nicht nur vermutlich dieselbe Entscheidung. Der Preis dafür ist, dass Aufrufer bewusst festlegen müssen, was in params aufgenommen wird — siehe Schnellstart.

Was das Ledger protokolliert

Das Ledger ist ein Append-only-Log, keine laufende Summe, die abweichen kann:

interface LedgerEvent {
  signature: string;
  kind: "hit" | "miss";
  tokensSpent: number;
  timestamp: number;
}
  • Bei einem Miss protokolliert Runic die tokensSpent, die dein Code für die Erstellung des Artefakts gemeldet hat.
  • Bei einem Hit sucht Runic den ursprünglichen Miss für diese Signatur und protokolliert denselben gespeicherten tokensSpent-Wert — niemals eine Schätzung, niemals eine Vermutung. Falls für eine getroffene Signatur wider Erwarten kein vorheriger Miss existiert, protokolliert es 0, statt eine Zahl zu erfinden.
summary.totalSpent = sum of all miss events
summary.totalSaved = sum of all hit events
tokensWithoutRunic = totalSpent + totalSaved   (what it would've cost with no cache at all)
savingsPercent     = totalSaved / tokensWithoutRunic

Runic misst Token-Kosten niemals selbst. Es gibt nur wieder, was der aufrufende Code ihm über storeResult(decision, artifact, tokensSpent) mitgeteilt hat — siehe Benchmarks, um zu erfahren, wie openrouter-savings diese Zahl aus einer echten API-Antwort statt aus einer Schätzung erhält.

Speicher-Backends

Sowohl @runic-labs/cache als auch @runic-labs/ledger basieren auf einer kleinen Speicher-Schnittstelle, mit zwei in v1 ausgelieferten Implementierungen:

Backend Lebensdauer Anwendungsfall
MemoryCacheStore / MemoryLedgerStore Nur während der Prozesslaufzeit Tests, kurzlebige Skripte, Benchmarks
FileCacheStore / FileLedgerStore Auf Festplatte gespeichert (.runic/ standardmäßig) Ein CLI-Prozess und ein Agent-Prozess, die ohne laufenden Server denselben Zustand lesen

Beide haben in v1 einen Gültigkeitsbereich pro Sitzung — siehe Gültigkeitsbereich, warum sitzungsübergreifendes Teilen bewusst eine spätere Phase ist, statt jetzt nachträglich angebaut zu werden.

Veralterung ist nur ein Hinweis

Cache-Einträge enthalten ein stale-Flag, das anhand eines konfigurierbaren Zeitfensters berechnet wird (standardmäßig 24 Stunden). Runic entfernt einen veralteten Eintrag niemals automatisch und verweigert ihn auch nicht — es sind Informationen, auf die der Aufrufer bei Bedarf reagieren kann, kein Durchsetzungsmechanismus. Wenn dein Anwendungsfall einen harten Ablauf erfordert, prüfe entry.stale selbst und entscheide, was damit geschehen soll.

War diese Seite hilfreich?