@bivelio/savings-layer 0.5.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,50 +11,119 @@
11
11
  <p align="center">
12
12
  <a href="https://www.npmjs.com/package/@bivelio/savings-layer"><img src="https://img.shields.io/npm/v/%40bivelio%2Fsavings-layer?color=2F63D8&label=npm" alt="npm version"></a>
13
13
  <img src="https://img.shields.io/badge/node-%E2%89%A520-14A06F" alt="Node ≥ 20">
14
- <img src="https://img.shields.io/badge/license-commercial-0F1218" alt="Commercial license">
14
+ <img src="https://img.shields.io/badge/license-Apache--2.0-2F63D8" alt="Apache-2.0">
15
+ <img src="https://img.shields.io/badge/runtime%20dependencies-0-14A06F" alt="0 runtime dependencies">
16
+ <img src="https://img.shields.io/badge/telemetry-off%20by%20default-0F1218" alt="Telemetry off by default">
15
17
  </p>
16
18
 
17
- ### In plain words
18
-
19
- Every request your app sends to an AI is paid for by the text going in and out.
20
- **Savings** sits on that path and lets through only what's needed — skipping calls
21
- that don't have to happen, and trimming what does — so you pay for less. It is not a
22
- promise you take on faith: **every saving we report is measured with your provider's
23
- own usage counter, the one your bill is built from** (for a repeat served without
24
- calling the provider, the counter of the identical call that ran), and every saving —
25
- measured or estimated, and labelled as such — is handed back to you request by request.
26
-
27
- **Measure for free, then pay per seat — with no commission on your savings.** The
28
- free **Measure** license (no card) runs the layer in shadow mode on your own traffic and
29
- shows what it would have saved, valued with your provider's counter. To turn the levers
30
- on, **Pro** is **€15 / seat / month** or **€150 / seat / year**, and **Team** is **€129 / month
31
- with 10 seats included** (€12 per extra seat; €1,290 / year, €120 per extra seat), EUR,
32
- VAT not included — a fixed fee, paid whether or not you save, and no commission on what
33
- you save. Enterprise (more seats, your own contract or invoicing terms) is by contract.
34
- And your prompts and completions **never reach BiVelio**: what BiVelio receives is per‑request
35
- usage — token counts, cost, the model id and opaque ids — never your content
36
- ([LICENSE](./LICENSE) §5 lists every field; [savings.bivelio.com/privacy](https://savings.bivelio.com/privacy)
37
- explains each one and how long it is kept).
38
-
39
- ### For engineers, in one line
40
-
41
- `@bivelio/savings-layer` (**BV‑SALA**) is one layer you add inside your code, right
42
- where your app calls the OpenAI‑compatible or Anthropic API. For every request it
43
- picks the cheapest execution that still meets your quality bar and hands you an
44
- auditable `SavingsReport` — the same numbers your dashboard reads. Traffic still
45
- goes straight from your app to your provider; the SDK is never a middleman server.
46
-
47
- > **Commercial software — © 2026 BiVelio Inc.** Free to install, **paid to save**:
48
- > BV‑SALA is fail‑closed and executes only with an active BiVelio license key. The
49
- > free **Measure** license (no card) runs it in shadow mode: nothing you send is
50
- > changed, nothing is served from cache, and you see what it would have saved.
51
- > The license signature is verified **offline**; with `licenseServerUrl` set, the SDK
52
- > also calls `savings.bivelio.com` for the revocation list, license renewal and usage
53
- > metering. **Your prompts, completions and provider keys never reach BiVelio** — what
54
- > travels is per‑request usage: token counts, cost, the model id, a request id and
55
- > opaque seat and session ids (the exact list is in [LICENSE](./LICENSE) §5). Use is
56
- > governed by the LICENSE, which also sets out the fees, and the
57
- > BiVelio Terms. Not a wrapper around any third‑party library.
19
+ ### Open core: install it and it saves. No key. No signup. No telemetry.
20
+
21
+ `@bivelio/savings-layer` (**BV‑SALA**) sits between your app — or your coding agent —
22
+ and the LLM provider, and lets through only what has to be paid for: it skips calls
23
+ that don't have to happen (exact + semantic cache, deduplication), trims what does
24
+ (prompt-cache placement, context and tool-output compaction, serialization), and
25
+ routes each request to the cheapest execution that meets your quality bar (model
26
+ cascade, sibling models, provider failover) — on OpenAI, Anthropic, Gemini,
27
+ **DeepSeek, Qwen, GLM, Moonshot**, any OpenAI‑compatible endpoint, and local
28
+ servers (Ollama, vLLM, LM Studio).
29
+
30
+ It is not a promise you take on faith: **every saving is reported request by
31
+ request, valued with your provider's own usage counter at versioned list prices**,
32
+ split into four ledgers (`avoided_calls`, `eliminated_tokens`, `compressed_tokens`,
33
+ `provider_cached_tokens`) and labelled `measured`, `reference` or `estimated`.
34
+ Nothing is sent to BiVelio unless you choose to add a key: **zero telemetry by
35
+ default**, and prompts, completions and provider keys never reach BiVelio
36
+ (the exact per-request fields a cloud key sends are in [LICENSE-CLOUD.md](./LICENSE-CLOUD.md) §5).
37
+
38
+ The free part is the whole savings layer. What a (paid, optional) BiVelio key adds
39
+ is the cloud: the team dashboard, shared semantic caches across seats, org
40
+ entitlements and license management. The cloud subscription is a fixed fee, paid
41
+ whether or not you save — Pro €15 / seat / month or €150 / seat / year; Team
42
+ €129 / month with 10 seats included (€12 per extra seat; €1,290 / year, €120 per
43
+ extra seat), EUR, VAT not included — and there is never a commission on what you
44
+ save (see [LICENSE-CLOUD.md](./LICENSE-CLOUD.md) §6).
45
+
46
+ ### Install anywhere, in 60 seconds
47
+
48
+ ```shell
49
+ # 1 · Any OpenAI SDK, any language — point it at the local gateway (Python, Node, Cursor, Aider, Cline, OpenWebUI…)
50
+ npx @bivelio/savings-layer gateway --upstream openai --port 8403 # or: deepseek | qwen | glm | moonshot | gemini | ollama | vllm | lmstudio
51
+ # python: OpenAI(base_url="http://127.0.0.1:8403/v1")
52
+
53
+ # 2 · Coding agents, one command, zero configuration
54
+ npx @bivelio/savings-layer claude # Claude Code through the gateway
55
+ npx @bivelio/savings-layer gemini # Gemini CLI (native Gemini protocol)
56
+ npx @bivelio/savings-layer codex # Codex CLI (OpenAI or ChatGPT auth)
57
+ npx @bivelio/savings-layer qwen # Qwen Code
58
+ npx @bivelio/savings-layer aider # Aider
59
+
60
+ # Claude Code with DeepSeek's Anthropic-format endpoint (cache reads at $0.003/1M):
61
+ # ANTHROPIC_BASE_URL=http://127.0.0.1:8402 ANTHROPIC_AUTH_TOKEN=<deepseek key> claude
62
+ # with: npx @bivelio/savings-layer gateway --upstream deepseek
63
+
64
+ # 3 · Your app, TypeScript/JavaScript — the SDK in your code (zero runtime dependencies)
65
+ npm i @bivelio/savings-layer
66
+ ```
67
+
68
+ ```ts
69
+ // The imports come from the "Wrap the call" block below; this block compiles
70
+ // together with it (the repo test concatenates every README block into one
71
+ // program, as if you pasted them in order).
72
+ const provider = new OpenAICompatibleProvider({
73
+ baseUrl: "https://api.openai.com/v1",
74
+ apiKey: process.env.OPENAI_API_KEY,
75
+ });
76
+ const layer = createSavingsLayer({
77
+ provider,
78
+ // OPEN CORE: nothing else is required — COMMUNITY mode, zero telemetry.
79
+ // For the cloud dashboard + team metering, add (all optional):
80
+ // // licenseKey: process.env.BIVELIO_LICENSE_KEY, (optional: cloud)
81
+ // // licenseServerUrl: "https://savings.bivelio.com",
82
+ // serviceAccountKey: process.env.BIVELIO_SERVICE_KEY,
83
+ });
84
+
85
+ const { text, report } = await layer.generate({
86
+ model: "openai/gpt-4o-mini",
87
+ messages: [{ role: "user", content: "What is 2 + 2 * 3?" }],
88
+ });
89
+ console.log(text); // "8" — computed locally, no LLM call
90
+ console.log(report.calls.avoidedLlm); // → 1: a whole call you never paid for
91
+ console.log(report.cost.basis); // "estimated": nothing ran, so there is no provider counter
92
+ ```
93
+
94
+ `npx @bivelio/savings-layer init` prints the exact three steps for your stack
95
+ (Next.js, Express, Vercel AI SDK, Mastra, LangChain.js, Anthropic/OpenAI SDK) and
96
+ `npx @bivelio/savings-layer doctor` prints the exact configuration for every
97
+ installed coding agent. The published benchmark, trial by trial, with the raw CSV,
98
+ is at **[savings.bivelio.com/en/how-it-works](https://savings.bivelio.com/en/how-it-works)**.
99
+
100
+ ### Community vs Cloud
101
+
102
+ | | Community (default, no keys) | Cloud (optional `BIVELIO_*` keys) |
103
+ | --- | --- | --- |
104
+ | Exact + semantic cache, deduplication of identical requests | ✅ | ✅ |
105
+ | Model cascade, sibling routing, prompt-cache placement | ✅ | ✅ |
106
+ | Compression, output contracts, four savings ledgers | ✅ | ✅ |
107
+ | Gateway + `bvsala` CLI + MCP server | ✅ | ✅ |
108
+ | Team dashboard, seat metering, shared caches | — | ✅ |
109
+ | Anything sent to BiVelio | **Nothing** | Per-request usage only (counts, cost, model id, opaque ids — [LICENSE-CLOUD.md](./LICENSE-CLOUD.md) §5), never prompts or keys |
110
+
111
+ A license key that **is** configured but invalid or expired still fails closed with
112
+ `LicenseError` — only its absence is a working product.
113
+
114
+ ### Cloud pricing (the optional, paid part)
115
+
116
+ The cloud subscription is a fixed fee, paid whether or not you save: **Pro
117
+ €15 / seat / month** or **€150 / seat / year**; **Team €129 / month with 10 seats included** (**€12 per extra seat**; **€1,290 / year, €120 per extra seat**), EUR, VAT not included. The first month carries no commission, and the
118
+ commission on measured savings runs from **10 % down to 4 %** by volume —
119
+ keeping you **at least 90 % of every euro saved**, never from estimates, and
120
+ shadow-mode usage is never part of any commission. No trial. The free
121
+ **Measure** license (no card) still exists for the cloud: it runs the layer in
122
+ **shadow mode** — nothing is served from any cache, nothing you send is
123
+ changed — and shows what the levers would have saved, request by request. The
124
+ older commission-only modality is **closed to new customers**. Your
125
+ prompts, completions and provider keys never reach BiVelio: a cloud key sends
126
+ only the per-request usage fields listed in [LICENSE-CLOUD.md](./LICENSE-CLOUD.md) §5.
58
127
 
59
128
  ## Where it installs
60
129
 
@@ -74,25 +143,22 @@ products charge a flat subscription, so there is no per‑token bill to cut. The
74
143
  story, in plain words:
75
144
  **[Where does the saving install?](https://savings.bivelio.com/en/how-it-works)**
76
145
 
77
- **A coding agent with an API key?** The package also ships a local measuring
78
- gateway: `bvsala claude` (or `bvsala codex`, `goose`, `qwen`, `opencode`, `kilo`,
79
- `copilot` with your own Anthropic key, `aider`) launches the agent through it and your real
80
- spend shows up in your dashboard, token by token — nothing to configure, nothing
81
- persisted. On exit it tells you how many requests it metered and which ones it
82
- could not, and why. For Crush, Droid, Cline, Continue or Copilot in VS Code,
83
- `bvsala doctor` prints the exact config block. Gemini CLI and Cursor cannot be
84
- metered today, and the doctor says so. Like the SDK, the gateway is
85
- fail‑closed: it starts only with your service key (`BIVELIO_SERVICE_KEY`) and an
86
- active BiVelio license, which it collects with that key and verifies against the
87
- key embedded in the package. Once running it never interrupts a request in flight:
88
- if the license lapses, only new requests are refused (HTTP 402) until it is
89
- renewed. It measures every request. With a PRO license it also applies one saving
90
- by default: it shortens the cache-write TTL while your session stays hot, which
91
- changes the price, never the content the model reads. The other levers (sibling
92
- model, per-turn effort) stay off until you switch them on. On a Claude or ChatGPT
93
- subscription there is no per-token invoice, so that traffic is metered at
94
- list-price equivalent and never carries commission; the per-seat license applies
95
- as for any other use.
146
+ **A coding agent with an API key?** The package also ships a local gateway:
147
+ `bvsala claude` (or `bvsala codex`, `goose`, `qwen`, `opencode`, `kilo`, `gemini`,
148
+ `copilot` with your own Anthropic key, `aider`) launches the agent through it and
149
+ your real spend is metered locally, token by token — nothing to configure,
150
+ nothing persisted. On exit it tells you how many requests it metered and which
151
+ ones it could not, and why. For Crush, Droid, Cline, Continue, Cursor or Copilot
152
+ in VS Code, `bvsala doctor` prints the exact config block. The gateway starts
153
+ with **no keys at all** (COMMUNITY mode: every local saving lever armed, zero
154
+ telemetry); `BIVELIO_SERVICE_KEY` adds the cloud dashboard and org entitlements.
155
+ Once running it never interrupts a request in flight: with a live license that
156
+ lapses, only new requests are refused (HTTP 402) until it is renewed. The
157
+ cache-write TTL lever (1h→5m, the saving that changes the rate, never the
158
+ content the model reads) is armed by default; the other levers (sibling model,
159
+ per-turn effort) switch on with their flags. On a Claude or ChatGPT subscription
160
+ there is no per-token invoice, so that traffic is metered at list-price
161
+ equivalent and never carries commission.
96
162
 
97
163
  **The gateway forwards what your terminal sends.** With no lever armed, your
98
164
  provider receives the request body byte for byte and every header your client
@@ -117,6 +183,8 @@ the language:
117
183
  ```bash
118
184
  bvsala gateway --upstream openai --port 8403 # one gateway per upstream
119
185
  bvsala gateway # Anthropic, the default, on 8402
186
+ bvsala gateway --upstream deepseek # DeepSeek: /v1/messages → its /anthropic endpoint
187
+ bvsala gateway --upstream gemini # Gemini protocol (bvsala gemini wraps the CLI)
120
188
  ```
121
189
 
122
190
  Flags and environment variables are in English (`--port`, `--project`, `--force`,
@@ -142,13 +210,14 @@ claude = Anthropic(base_url="http://127.0.0.1:8402") # NO /v1
142
210
  ```
143
211
 
144
212
  With `BIVELIO_SERVICE_KEY` set, the gateway measures every request whose usage the
145
- provider reports — Anthropic Messages, OpenAI Chat Completions and Responses, streamed
146
- or not — for models with a list price in its catalog, priced at that list price. A
213
+ provider reports — Anthropic Messages, OpenAI Chat Completions and Responses, and
214
+ the Gemini protocol (`:generateContent` / `:streamGenerateContent`), streamed or
215
+ not — for models with a list price in its catalog, priced at that list price. A
147
216
  request it cannot meter (a model without a list price, a body it cannot read, a stream
148
- that never asked for usage) is still forwarded, and its log line says so and why. That
149
- is measurement, not savings: the saving levers of the SDK are TypeScript, and the
150
- gateway's own levers apply to Anthropic `/v1/messages` traffic with a PRO license.
151
- `bvsala doctor` prints the recipe for each SDK.
217
+ that never asked for usage) is still forwarded, and its log line says so and why.
218
+ Without a key (COMMUNITY mode) the metering numbers stay local: the local report
219
+ routes below work the same, and nothing is sent to BiVelio.
220
+ `bvsala doctor` prints the recipe for each SDK and agent.
152
221
 
153
222
  Each request can also bring its report back to your code. Send the header
154
223
  `x-bvsala-report: 1` (in Python, `default_headers={"x-bvsala-report": "1"}` on
@@ -163,12 +232,11 @@ provider, and a request without it is not changed at all:
163
232
 
164
233
  ## Check it before you install
165
234
 
166
- What you can check before paying is what we have measured: the published benchmark,
167
- trial by trial, with the raw CSV linked, at
168
- **[savings.bivelio.com/en/how-it-works](https://savings.bivelio.com/en/how-it-works)**,
169
- and the free **Measure** license, which runs the layer in shadow mode on your own
170
- traffic and shows what it would have saved, valued with your provider's counter. The verifier that replays the optimizer on your own data
171
- lives in your dashboard once you sign in.
235
+ What you can check before installing is what we have measured: the published
236
+ benchmark, trial by trial, with the raw CSV linked, at
237
+ **[savings.bivelio.com/en/how-it-works](https://savings.bivelio.com/en/how-it-works)**.
238
+ With no keys at all, the layer runs the full savings stack locally on your own
239
+ traffic and reports per request — nothing leaves your machine.
172
240
 
173
241
  **Upgrading from 0.4?** Every breaking change of 0.5, with a before and after for
174
242
  each, is in [Migrating from 0.4 to 0.5](https://savings.bivelio.com/en/migrate-to-0.5).
@@ -204,13 +272,13 @@ to a built‑in estimate and the reconstructed savings are less accurate. Either
204
272
  saving where the layer removed something is reported as `estimated`, and estimated
205
273
  savings are never billed — the SDK warns once when the tokenizer is missing.
206
274
 
207
- **2 · The license** — from your [dashboard](https://savings.bivelio.com/dashboard/install)
208
- (free **Measure** license, no card; **Pro** €15 / seat / month or €150 / seat / year;
209
- **Team** €129 / month with 10 seats included — EUR)
275
+ **2 · The cloud (optional)** — a [BiVelio key](https://savings.bivelio.com/dashboard/install)
276
+ adds the team dashboard, shared caches and license management. Skip this step and
277
+ nothing changes except the dashboard: the layer keeps working, fully, offline.
210
278
 
211
279
  ```bash
212
- export BIVELIO_LICENSE_KEY=… # your license key
213
- export BIVELIO_SERVICE_KEY=… # service-account key: auto-renewal + seat metering (required by the gateway)
280
+ export BIVELIO_LICENSE_KEY=… # optional: a key that IS set but invalid still fails closed
281
+ export BIVELIO_SERVICE_KEY=… # optional: cloud metering + license auto-renewal
214
282
  ```
215
283
 
216
284
  **3 · Wrap the call** — this is the entire code change. The package is ESM only:
@@ -232,9 +300,11 @@ const provider = new OpenAICompatibleProvider({
232
300
 
233
301
  const layer = createSavingsLayer({
234
302
  provider,
235
- licenseKey: process.env.BIVELIO_LICENSE_KEY, // required — from your savings.bivelio.com dashboard
236
- licenseServerUrl: "https://savings.bivelio.com", // revocation list, renewal + metering — omit it and the two lines below do nothing
237
- serviceAccountKey: process.env.BIVELIO_SERVICE_KEY, // seat metering + AUTO-RENEWAL pickup
303
+ // OPEN CORE: nothing else is required — COMMUNITY mode, zero telemetry.
304
+ // For the cloud dashboard + team metering, add (all optional):
305
+ // // licenseKey: process.env.BIVELIO_LICENSE_KEY, (optional: cloud)
306
+ // // licenseServerUrl: "https://savings.bivelio.com",
307
+ // serviceAccountKey: process.env.BIVELIO_SERVICE_KEY,
238
308
  });
239
309
 
240
310
  const { text, report } = await layer.generate({
@@ -372,7 +442,7 @@ point, from a subpath of this same package. The four adapters behave alike:
372
442
  - **Tools are treated as having side effects** unless you list them in
373
443
  `readOnlyTools`: a request that offers any other tool is never answered from a
374
444
  cache, because the action has to run.
375
- - **Fail‑closed, like the SDK**: without an active license the call fails with
445
+ - **Open core, like the SDK**: with no key the adapter runs in COMMUNITY mode; a key that is set but invalid fails closed with
376
446
  `LicenseError` (a framework may wrap it, keeping its name and message) and the
377
447
  model is not called. **Put the adapter last**, so nothing sits between it and the
378
448
  model.
@@ -392,8 +462,8 @@ import { openai } from "@ai-sdk/openai";
392
462
  import { savingsMiddleware, getSavingsReport } from "@bivelio/savings-layer/ai-sdk";
393
463
 
394
464
  const savings = savingsMiddleware({
395
- licenseKey: process.env.BIVELIO_LICENSE_KEY,
396
- licenseServerUrl: "https://savings.bivelio.com",
465
+ // licenseKey: process.env.BIVELIO_LICENSE_KEY, (optional: cloud)
466
+ // licenseServerUrl: "https://savings.bivelio.com",
397
467
  serviceAccountKey: process.env.BIVELIO_SERVICE_KEY,
398
468
  readOnlyTools: ["get_weather"],
399
469
  });
@@ -419,8 +489,8 @@ import { Agent } from "@mastra/core/agent";
419
489
  import { savingsProcessor } from "@bivelio/savings-layer/mastra";
420
490
 
421
491
  const bivelio = savingsProcessor({
422
- licenseKey: process.env.BIVELIO_LICENSE_KEY,
423
- licenseServerUrl: "https://savings.bivelio.com",
492
+ // licenseKey: process.env.BIVELIO_LICENSE_KEY, (optional: cloud)
493
+ // licenseServerUrl: "https://savings.bivelio.com",
424
494
  serviceAccountKey: process.env.BIVELIO_SERVICE_KEY,
425
495
  onReport: (r) => console.log(r.cost.basis),
426
496
  });
@@ -451,8 +521,8 @@ import { createAgent } from "langchain";
451
521
  import { savingsAgentMiddleware, getSavingsReportFromMessage } from "@bivelio/savings-layer/langchain";
452
522
 
453
523
  const agentSavings = savingsAgentMiddleware({
454
- licenseKey: process.env.BIVELIO_LICENSE_KEY,
455
- licenseServerUrl: "https://savings.bivelio.com",
524
+ // licenseKey: process.env.BIVELIO_LICENSE_KEY, (optional: cloud)
525
+ // licenseServerUrl: "https://savings.bivelio.com",
456
526
  serviceAccountKey: process.env.BIVELIO_SERVICE_KEY,
457
527
  });
458
528
  const lcAgent = createAgent({ model: "openai:gpt-4o-mini", tools: [], middleware: [agentSavings] }); // last
@@ -474,8 +544,8 @@ import Anthropic from "@anthropic-ai/sdk";
474
544
  import { anthropicSavings } from "@bivelio/savings-layer/anthropic-sdk";
475
545
 
476
546
  const claudeSavings = anthropicSavings({
477
- licenseKey: process.env.BIVELIO_LICENSE_KEY,
478
- licenseServerUrl: "https://savings.bivelio.com",
547
+ // licenseKey: process.env.BIVELIO_LICENSE_KEY, (optional: cloud)
548
+ // licenseServerUrl: "https://savings.bivelio.com",
479
549
  serviceAccountKey: process.env.BIVELIO_SERVICE_KEY,
480
550
  });
481
551
  const client = new Anthropic({ middleware: [claudeSavings.middleware] }); // last in the list
@@ -578,18 +648,20 @@ single-shot requests only: requests that declare tools, carry tool calls or resu
578
648
  hold more than one user turn are left out unless you set
579
649
  `savings.semanticCache: "include-agentic"`.
580
650
 
581
- **Calibrated reuse (opt-in).** A fixed similarity floor means something different for
582
- every embedder and every workload, so you can make the cache earn its floor on your own
583
- traffic instead: `new InMemorySemanticCache({ embed, calibration: { maxErrorRate: 0.02 } })`.
584
- It then serves no reworded answer until your labels show, at 95 % confidence, that at
585
- most 2 % of the answers it would reuse from some score up are wrong, and it re-tests that
586
- floor as labels accumulate. Until then every candidate goes to your provider, exactly as
587
- it would without the cache, and the fresh answer is compared with the stored one by your
588
- `judge` (by default only identical answers count, which suits classification and
589
- structured output; pass your own for free text). Each calibrated reuse carries its bound
590
- and the labels behind it in `report.qualityLedger.cacheMatch.calibration`. Calibration
591
- costs reuses, never an extra call, and it does not change how anything is billed. If you
592
- already have labeled pairs, `calibrateSemanticFloor()` runs the same test offline.
651
+ **Calibrated reuse (required to serve).** A fixed similarity floor means something different
652
+ for every embedder and every workload, so the cache must earn its floor on your own
653
+ traffic: `new InMemorySemanticCache({ embed, calibration: { maxErrorRate: 0.02 } })`.
654
+ Without `calibration`, it runs in shadow mode: it may store answers for future calibration,
655
+ but `find()` and `lookup()` never return a hit. With calibration it serves no reworded answer
656
+ until your labels show, at 95 % confidence, that at most 2 % of the answers it would reuse
657
+ from some score up are wrong, and it re-tests that floor as labels accumulate. Until then
658
+ every candidate goes to your provider, exactly as it would without the cache, and the fresh
659
+ answer is compared with the stored one by your `judge` (by default only identical answers
660
+ count, which suits classification and structured output; pass your own for free text). Each
661
+ calibrated reuse carries its bound and the labels behind it in
662
+ `report.qualityLedger.cacheMatch.calibration`. Calibration costs reuses, never an extra call,
663
+ and it does not change how anything is billed. If you already have labeled pairs,
664
+ `calibrateSemanticFloor()` runs the same test offline.
593
665
 
594
666
  And a promise you can hold it to: **never worse than your JSON.** Payload encodings
595
667
  are chosen by counting real tokens with the destination model's own tokenizer, and
@@ -606,8 +678,8 @@ your provider, turn on verification sampling:
606
678
  ```ts
607
679
  const checkedLayer = createSavingsLayer({
608
680
  provider,
609
- licenseKey: process.env.BIVELIO_LICENSE_KEY,
610
- licenseServerUrl: "https://savings.bivelio.com",
681
+ // licenseKey: process.env.BIVELIO_LICENSE_KEY, (optional: cloud)
682
+ // licenseServerUrl: "https://savings.bivelio.com",
611
683
  serviceAccountKey: process.env.BIVELIO_SERVICE_KEY,
612
684
  verification: { sampleRate: 0.02 }, // about 2 in every 100 eligible requests
613
685
  });
@@ -640,9 +712,11 @@ and every pair as a CSV download.
640
712
  - **Only counts leave your infrastructure.** The copy goes to your own provider, like
641
713
  the original; BiVelio receives token counts, costs, model ids and outcomes — never
642
714
  prompts or answers.
643
- - **`close()` settles every copy still running:** it is recorded as abandoned, never
644
- as a pair. On serverless platforms, pass your runtime's `waitUntil` as
645
- `verification.waitUntil` so checks can finish.
715
+ - **`await close()` settles every copy still running and drains usage:** an
716
+ unfinished copy is recorded as abandoned, never as a pair. Awaiting close makes
717
+ a bounded final delivery attempt; calling it without await remains safe. On
718
+ serverless platforms, pass your runtime's `waitUntil` as `verification.waitUntil`
719
+ so checks can finish.
646
720
 
647
721
  </details>
648
722
 
@@ -686,7 +760,7 @@ default was not run.
686
760
  ```ts
687
761
  const leveredLayer = createSavingsLayer({
688
762
  provider,
689
- licenseKey: process.env.BIVELIO_LICENSE_KEY,
763
+ // licenseKey: process.env.BIVELIO_LICENSE_KEY, (optional: cloud)
690
764
  priceLevers: { deferrable: true, onCapacityError: "standard" }, // layer-wide defaults
691
765
  });
692
766
 
@@ -757,7 +831,7 @@ verbatim, so an agent that edits by exact match still sees the file as it is.
757
831
  ```ts
758
832
  const agentLayer = createSavingsLayer({
759
833
  provider,
760
- licenseKey: process.env.BIVELIO_LICENSE_KEY,
834
+ // licenseKey: process.env.BIVELIO_LICENSE_KEY, (optional: cloud)
761
835
  toolOutputCompaction: true, // or { exceptTools: ["read_logs"], minSavedChars: 512 }
762
836
  });
763
837
  // or per request: savings: { toolOutputCompaction: true }
@@ -825,7 +899,7 @@ saving and never part of any commission.
825
899
  ```ts
826
900
  const placedLayer = createSavingsLayer({
827
901
  provider, // Anthropic, or OpenAI GPT-5.6+ on OpenAI's own API
828
- licenseKey: process.env.BIVELIO_LICENSE_KEY,
902
+ // licenseKey: process.env.BIVELIO_LICENSE_KEY, (optional: cloud)
829
903
  cacheBreakpoints: "auto",
830
904
  });
831
905
  ```
@@ -860,7 +934,7 @@ async function serverlessLayer(kv: SharedKeyValue) {
860
934
 
861
935
  const layer = createSavingsLayer({
862
936
  provider,
863
- licenseKey: process.env.BIVELIO_LICENSE_KEY,
937
+ // licenseKey: process.env.BIVELIO_LICENSE_KEY, (optional: cloud)
864
938
  semanticCacheStore: semanticCache,
865
939
  // Passing the cache does not switch it on; this asks for it on every request.
866
940
  semanticCacheDefault: true,
@@ -893,13 +967,14 @@ async function serverlessLayer(kv: SharedKeyValue) {
893
967
  verifier, judge or configuration is refused with `CalibrationStateError`. If you pass
894
968
  your own `embed`, `verify` or `judge`, name them (`embedderId`, `verifierId`,
895
969
  `calibration.judgeId`) so a state can say what it was recorded with.
896
- - **One line of history.** The bound holds along one line: restore the latest state and
897
- save from where it went. Instances that continue the same state at the same time
898
- each spend its remaining confidence again, and the last one to save overwrites the
899
- others' labels. For one bound across many concurrent instances, calibrate in a single
900
- long-lived process, or offline with `calibrateSemanticFloor()`, and give every
901
- instance the certified floor as `acceptThreshold`. The cached answers themselves
902
- still live in each instance's memory.
970
+ - **One line of history for in-memory snapshots.** Restoring and saving the latest
971
+ state continues one line. Instances that manually continue the same snapshot at
972
+ the same time each spend its remaining confidence again, and the last save
973
+ overwrites the others. Use one long-lived process or offline calibration for that
974
+ manual model. For shared records and one coordinated history across instances,
975
+ use `PgSemanticCache`: its transactions and observation IDs prevent lost labels
976
+ and repeated confidence spending. The in-memory cached answers still live in
977
+ each instance unless you inject a persistent store.
903
978
  - **Why a predicted cache read did not happen** (Anthropic's own API). With
904
979
  `cacheMissDiagnostics: true`, the layer asks Anthropic to compare each marked request
905
980
  with the one whose cache entry it expected to read, and puts the provider's
@@ -909,6 +984,32 @@ async function serverlessLayer(kv: SharedKeyValue) {
909
984
 
910
985
  </details>
911
986
 
987
+ <details>
988
+ <summary><b>Customer-operated PostgreSQL for caches and learning</b></summary>
989
+
990
+ The optional `@bivelio/persistence-postgres` package provides `PgExactCache`,
991
+ `PgSemanticCache`, `PgCacheBreakpointStore` and `PgConstraintMemoryStore` without
992
+ adding a database driver to the core SDK. You supply the `pg` pool, apply its
993
+ versioned SQL migrations yourself, and inject the stores; nothing connects or
994
+ changes your schema automatically. Exact and semantic entries contain customer
995
+ queries, answers or derived embeddings and need the same access, retention and
996
+ deletion controls as the original data. Breakpoints contain fingerprints,
997
+ timestamps and counters, never prompt text. Constraint learning stores only a
998
+ hash of tenant, scope, provider, served model and contract.
999
+
1000
+ The breakpoint adapter uses revisions and compare-and-set. Semantic calibration
1001
+ uses short transactions and idempotent observation IDs, so concurrent instances
1002
+ continue one history instead of spending the same confidence budget independently.
1003
+ Both use the same deterministic candidate guards and statistical journal as memory.
1004
+ The semantic backend requires the PostgreSQL vector extension and is opt-in; existing key-value stores
1005
+ and in-memory defaults keep their previous behavior. Removing `constraintMemoryStore`
1006
+ rolls constraint learning back to process memory without reading the new table.
1007
+
1008
+ See [`packages/persistence-postgres/README.md`](packages/persistence-postgres/README.md)
1009
+ for setup, ownership, cleanup, benchmark evidence and shutdown.
1010
+
1011
+ </details>
1012
+
912
1013
  <details>
913
1014
  <summary><b>Structured output — validated <code>data</code>, not just text</b></summary>
914
1015
 
@@ -933,10 +1034,11 @@ const { data, text } = await layer.generate<{ answer: number }>({
933
1034
  console.log(data.answer); // 8
934
1035
  ```
935
1036
 
936
- A question the layer answers on its own (the arithmetic of the first example)
937
- never reaches the model, so its `data` is the resolver's `{ value: 8 }`, not
938
- your schema's shape: `report.qualityLedger` says the contract was not met.
939
- Ask the model for anything you want in your contract's shape.
1037
+ A question the layer can answer on its own (the arithmetic of the first
1038
+ example) is only served without the model when that answer meets your schema.
1039
+ The arithmetic resolver returns `{ value: 8 }`: with a `{ answer: number }`
1040
+ contract it steps aside and the request goes to the model, which answers in
1041
+ your contract's shape.
940
1042
 
941
1043
  </details>
942
1044
 
@@ -952,7 +1054,7 @@ import { GoogleProvider } from "@bivelio/savings-layer";
952
1054
 
953
1055
  const gemini = createSavingsLayer({
954
1056
  provider: new GoogleProvider({ apiKey: process.env.GEMINI_API_KEY, requireUsage: true }),
955
- licenseKey: process.env.BIVELIO_LICENSE_KEY,
1057
+ // licenseKey: process.env.BIVELIO_LICENSE_KEY, (optional: cloud)
956
1058
  });
957
1059
 
958
1060
  const { text: answer, report: geminiReport } = await gemini.generate({
@@ -1158,7 +1260,7 @@ import { AnthropicProvider } from "@bivelio/savings-layer";
1158
1260
 
1159
1261
  const claude = createSavingsLayer({
1160
1262
  provider: new AnthropicProvider({ apiKey: process.env.ANTHROPIC_API_KEY }),
1161
- licenseKey: process.env.BIVELIO_LICENSE_KEY,
1263
+ // licenseKey: process.env.BIVELIO_LICENSE_KEY, (optional: cloud)
1162
1264
  });
1163
1265
 
1164
1266
  const mcpTools: ToolDescriptor[] = []; // every tool your MCP servers list
@@ -1358,7 +1460,7 @@ BVSALA_SEAT_ID=worker-03 # stable per replica; [A-Za-z0-9._:/-], max 128 chars
1358
1460
  BV‑SALA is commercial software. The plans are **Measure** (free), **Pro** (per seat),
1359
1461
  **Team** (a number of seats included, plus each extra seat) and **Enterprise** (by
1360
1462
  contract); Pro and Team are paid whether or not you save and carry **no commission** on
1361
- your savings. The prices are at the top of this page, in [LICENSE](./LICENSE) §6 and in
1463
+ your savings. The prices are at the top of this page, in [LICENSE-CLOUD.md](./LICENSE-CLOUD.md) §6 and in
1362
1464
  the Published Pricing it refers to,
1363
1465
  **[savings.bivelio.com/pricing](https://savings.bivelio.com/pricing)**. No trial. There is one free license,
1364
1466
  **Measure** — no card, one organization per verified account, up to 5 seats and 20,000
@@ -1382,7 +1484,7 @@ The license is a signed token your SDK and the
1382
1484
  gateway verify **offline**; the managed control plane and the revocation kill‑switch
1383
1485
  are served from BiVelio. Your LLM traffic goes **straight to your provider** —
1384
1486
  BiVelio receives per‑request usage (counts, cost, the model id and opaque ids), never
1385
- prompts, completions or provider keys; [LICENSE](./LICENSE) §5 lists every field, and
1487
+ prompts, completions or provider keys; [LICENSE-CLOUD.md](./LICENSE-CLOUD.md) §5 lists every field, and
1386
1488
  **[savings.bivelio.com/privacy](https://savings.bivelio.com/privacy)** says how long
1387
1489
  each one is kept.
1388
1490