/** * SavingsLedger — Singleton accumulator for token savings across all Lemma modules. * * Every module (ClipboardWatcher, ContextSqueezer, ComplexityRouter, SemanticCache) * calls `ledger.record(source, charsBefore, charsAfter)` — no one knows about the dashboard. * * The proxy exposes the ledger via GET /api/savings-breakdown for the dashboard to consume. */ export type SavingsSource = 'clipboard' | 'contextSqueeze' | 'historyPrune' | 'complexityRouting' | 'cache'; /** * Where Lemma *spends* tokens. Without these the ledger is single-entry bookkeeping: * it can only ever go up, which makes it useless as evidence that Lemma is net positive. * * - mcpInstructions: the TURBOMODE prompt + tool schemas injected into the model's * system prompt. Charged once per session, but re-read on every request. * - toolResult: bytes each tool result adds to the model's context. * - cacheMiss: round-trip burned on a lookup that returned nothing. */ export type CostSource = 'mcpInstructions' | 'toolResult' | 'cacheMiss'; export interface SourceStats { /** How many individual savings events were recorded */ events: number; /** Estimated tokens saved (chars / 4) */ tokensSaved: number; /** Estimated USD saved (tokensSaved * avg price per token) */ costSaved: number; } export interface CostStats { /** How many individual cost events were recorded */ events: number; /** Estimated tokens spent (chars / 4) */ tokensSpent: number; /** Estimated USD spent (tokensSpent * avg price per token) */ costSpent: number; } /** * Where a cost event came from. AN-01: the ledger used to aggregate every tool * result into one unattributed `toolResult` bucket, which made `netRatio` unusable * as accounting — no per-tool, per-host, or per-call breakdown existed. */ export interface CostAttribution { /** Lemma tool whose result was charged (e.g. "search_memory", "read_workspace_file") */ tool?: string; /** Connected host that issued the call ("claude-code", "cursor", ...; "unknown" when unreported) */ host?: string; /** * "lemma" = a Lemma tool result measured in-process. Anything else must arrive * through an explicit channel — never inferred by dividing totals by call counts. */ origin?: string; } /** Per-tool rollup of one CostSource — the breakdown token_budget prints. Bounded. */ export interface ToolCostStats { events: number; tokensSpent: number; } export interface NetStats { /** tokensSaved - tokensSpent. Negative means Lemma cost more than it saved. */ netTokens: number; /** USD equivalent of netTokens. Negative means net loss. */ netCost: number; /** tokensSaved / tokensSpent. Below 1.0 means Lemma is not paying for itself. */ ratio: number; } export interface SavingsSnapshot { clipboard: SourceStats; contextSqueeze: SourceStats; historyPrune: SourceStats; complexityRouting: SourceStats; cache: SourceStats; total: SourceStats; costs: Record; totalCost: CostStats; net: NetStats; sessionStartedAt: string; /** * AN-01 attribution: per-tool / per-host / per-origin rollups of toolResult. * Empty maps mean "recorded before attribution existed", never "zero". */ costAttribution: { byTool: Record; byHost: Record; byOrigin: Record; }; } declare class SavingsLedger { private sources; private costs; /** Per-tool rollup of toolResult spend: tool name -> { events, tokensSpent }. */ private costByTool; /** Per-host rollup (hosts are a handful of known values — no cap needed). */ private costByHost; /** Per-origin rollup: "lemma" vs anything explicitly reported otherwise. */ private costByOrigin; private readonly sessionStartedAt; constructor(); private load; private save; /** * Record a savings event by char counts (automatically converts to token estimate). * @param source Which module recorded the savings * @param charsBefore Original char count before compression/routing * @param charsAfter Resulting char count after compression/routing */ record(source: SavingsSource, charsBefore: number, charsAfter: number): void; /** * Record a savings event directly in tokens (for sources that already know their token count). */ recordTokens(source: SavingsSource, tokensSaved: number): void; /** * Record tokens Lemma *spent*. Counterpart to record()/recordTokens() — without * this the ledger can only ever grow, which is not evidence of anything. * * `attribution` is what makes the totals auditable (AN-01): every toolResult * recorded in-process carries its tool name, the connected host, and origin * "lemma". Rollups stay consistent with totals by construction — they are only * ever incremented alongside the bucket they annotate. */ recordCost(source: CostSource, tokensSpent: number, attribution?: CostAttribution): void; /** One attributed event into a rollup map. byTool is capped; host/origin are naturally small. */ private bumpRollup; /** Record a cost event by char count (converts with the same chars/4 heuristic). */ recordCostChars(source: CostSource, chars: number, attribution?: CostAttribution): void; /** Returns a full snapshot including computed totals, costs and net position */ getSnapshot(): SavingsSnapshot; /** Returns a pretty terminal summary block */ getTerminalSummary(): string; /** Reset all counters (e.g., on cache clear) */ reset(): void; } /** Global singleton — import this everywhere */ export declare const savingsLedger: SavingsLedger; export default savingsLedger; //# sourceMappingURL=SavingsLedger.d.ts.map