Saltar para o conteúdo
Runic
Português
Esc
navegarabrir⌘Jpré-visualizar
Nesta página

Como funciona

O algoritmo de assinatura, por que a correspondência é exata e o que o registro realmente grava.

Assinaturas

Cada decisão é normalizada em uma assinatura estável antes de ser usada como chave de cache:

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

A normalização é deliberadamente superficial — as chaves de objeto são ordenadas recursivamente para que { a: 1, b: 2 } e { b: 2, a: 1 } gerem hashes idênticos, mas nada relacionado a intent ou aos valores dos parâmetros é alterado. Não há stemming, normalização de maiúsculas/minúsculas nem canonização semântica.

{ 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) ──┘

Por que correspondência exata, e não semântica

Um cache semântico/aproximado capturaria mais repetições — “resuma este PR” e “dê-me um resumo desta solicitação de pull” resultariam ambos em acertos. Isso também significaria que o Runic teria de avaliar se duas solicitações formuladas de maneira diferente são próximas o suficiente para compartilhar uma resposta em cache, que é exatamente o tipo de decisão não determinística que o Runic foi projetado para evitar.

A correspondência exata significa que um acerto de cache é comprovadamente a mesma decisão, e não provavelmente a mesma decisão. O custo é que os chamadores precisam ser intencionais quanto ao que entra em params — consulte Início rápido.

O que o registro grava

O registro é um log somente de anexação, não um total acumulado que pode se desviar:

interface LedgerEvent {
  signature: string;
  kind: "hit" | "miss";
  tokensSpent: number;
  timestamp: number;
}
  • Em uma falha, o Runic registra os tokensSpent que seu código informou para produzir o artefato.
  • Em um acerto, o Runic procura a falha original para essa assinatura e registra o mesmo valor de tokensSpent que foi salvo — nunca uma estimativa, nunca um palpite. Se, de alguma forma, não houver nenhuma falha anterior para uma assinatura que está sendo acertada, ele registra 0 em vez de inventar um número.
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

O Runic nunca mede o custo de tokens por conta própria. Ele apenas repete o que o código chamador informou por meio de storeResult(decision, artifact, tokensSpent) — consulte Benchmarks para saber como openrouter-savings obtém esse número de uma resposta de API real, em vez de uma estimativa.

Backends de armazenamento

Tanto @runic-labs/cache quanto @runic-labs/ledger são construídos com base em uma pequena interface de armazenamento, com duas implementações distribuídas na v1:

Backend Duração Caso de uso
MemoryCacheStore / MemoryLedgerStore Apenas durante a vida do processo Testes, scripts efêmeros, benchmarks
FileCacheStore / FileLedgerStore Persistido em disco (.runic/ por padrão) Um processo de CLI e um processo de agente lendo o mesmo estado sem um servidor em execução

Ambos têm escopo por sessão na v1 — consulte Escopo para entender por que o compartilhamento entre sessões é deliberadamente uma fase posterior, em vez de algo acrescentado às pressas agora.

A expiração é apenas informativa

As entradas de cache têm uma flag stale, calculada a partir de uma janela configurável (24h por padrão). O Runic nunca remove nem recusa automaticamente uma entrada expirada — é uma informação para o chamador usar, se quiser, e não um mecanismo de imposição. Se o seu caso de uso exigir expiração rígida, verifique entry.stale você mesmo e decida o que fazer com ela.

Esta página foi útil?