ModelRig Quickstart Routing & reliability Route bundles Probes Bake-offs & replay How it fits Grade protocol Optimization loop Caching lifecycle Bring your traces (OTLP) Observe a pipeline Migration playbook (T0–T2) Recognition playbooks Tenants & statements Published receipts Provenance & trust Artifact content custody The MCP oracle Use-case templates Template: ticket triage Template: document extraction Template: CS next action Template: lead qualification Template: compliance review Template: catalog cleansing Template: call disposition QA Template: financial classification Template: medical classification Leaderboard

Provider caching — the lifecycle your integration owns

Written for the coding agent doing the integration. If you are migrating a pipeline that already caches (a Gemini cachedContents handle, an Anthropic cache_control block, an OpenAI prompt_cache_key), read this before you write a single route. The one thing that silently costs money is dropping a cache you used to have — and the one thing that silently breaks a cache is letting it expire under a long job. ModelRig passes your cache handle through and accounts for the hits; it never creates, refreshes, or deletes the underlying resource. That lifecycle is yours. This page teaches you how to own it.

The headline, up top so you cannot miss it:

Long cached runs need a TTL heartbeat, and your code owns it. A cache resource has a lifetime. A multi-hour pipeline outlives whatever TTL you set. ModelRig will not refresh or recreate your cache for you — by design (see "Who owns what"). If your job runs longer than the TTL, your code must refresh or recreate the resource. Otherwise the first call after expiry fails cache_invalid (ModelRig retries it without the handle — correct result, full price) and every call after pays uncached. The run keeps succeeding; the failure mode is cost, and the miss-streak alert is how you notice.

Terms this page leans on: a route (also called a task — the named unit rig.run("my.task") serves, defined in a route-bundle YAML) lists candidate models it may dispatch to, in order. Shapes and options: route bundles · quickstart. The migration protocol that sends you here — including the C2 caching inventory you run before writing any route — is the migration playbook.

The three caching regimes

Providers cache in three fundamentally different ways. Which regime a model uses decides whether anybody owns a lifecycle at all.

RegimeProviders (v1 registry)Who engages itWho owns a lifecycle
Automatic prefixOpenAI, DeepSeek, Grok, Fireworks, Gemini (implicit)Nobody — the provider caches the longest repeated prefix with no request changeNobody. Just don't break the prefix (see "Prefix hygiene")
MarkerAnthropic (cache_control)The request marks a cache breakpoint; no marker, no cacheModelRig, via cache: auto on the route — you do nothing
Explicit resourceGemini (cachedContents)You create a named resource up front and reference it by handleYou. Create it, heartbeat it, recreate it on change, delete it when done

The first two regimes need nothing from you beyond a stable prompt prefix and, for Anthropic, opting the route into cache: auto. The third — Gemini's explicit cachedContents — is the only one where a resource with a lifetime exists that somebody has to manage. That somebody is you, and the rest of this page is mostly about that case, because it is the one that bit the first migration.

One directive, provider-appropriate meaning

There is exactly one cache field on the customer surface — rig.run(task, { input, cache: { key, provider } }) and the Lane-B rig.runRaw({ …, cache: { key } }). It is deliberately provider-neutral: the adapter maps the same key to whatever the named provider's caching mechanism actually is. There is no separate promptCacheKey field — one directive, appropriate meaning per provider, zero churn for gemini callers:

Provider(s)What cache.key meansRetention
Geminia resource HANDLE — the cachedContents/<id> you created and ownyour TTL heartbeat (this page)
OpenAIa routing HINTprompt_cache_key (keeps the prefix cache warm)cache.ttlSeconds ≥ 86400prompt_cache_retention: "24h"
Groka routing HINTprompt_cache_keyprovider-managed
DeepInfra / Fireworksa routing HINTprompt_cache_key _(0.4.0+)_provider-managed

The Gemini/OpenAI/Grok rows were true on 0.3.0; the DeepInfra/Fireworks prompt_cache_key mapping ships in 0.4.0 (a 0.3.0 SDK ignores the key on those hosts — a documented, not silent, difference).

On the HINT providers the key never names a resource you have to create or delete — it only helps the provider route repeated calls to a warm cache. So the cost-accounting and cache-key provenance rules are identical across all of them: a customer-supplied cache.key reads as customer provenance (the raw lane runs no prefix fingerprinting), and cache-read tokens bill at the read rate the same way everywhere. Only the Gemini HANDLE case carries a lifecycle you own — which is the rest of this page.

For the raw lane, which provider honors which of these (and the other knobs — grounding, reasoning, responseFormat) is the provider × knob matrix.

Explicit-cache lifecycle (Gemini cachedContents) — customer-owned

You already have this code if you engineered explicit caching; ModelRig changes only how you reference the handle at call time. The full lifecycle, in order:

``ts const { output } = await rig.run("my.task", { input: { … }, tags: { run_id }, cache: { key: "cachedContents/abc123", provider: "gemini" }, }); ``

provider is required — the handle names a Gemini resource, so ModelRig applies it only to Gemini candidates. A fallback candidate on another provider dispatches without the handle (an uncached correct call beats handing a provider an identifier it would reject). If losing the cache on fallback is unacceptable for your economics, pin the route: a route whose candidates are all the one Gemini model never falls elsewhere. On the Lane-B runRaw seam the same field rides on the raw input: rig.runRaw({ provider, model, apiKey, …, cache: { key } }) (runRaw is BYOK — pass your provider key).

Grounded + cached on the raw lane. When a runRaw step declares both grounding: { mode: "native" } and a cache.key, ModelRig needs no special handling: the cached path already drops tools, so the googleSearch tool must be baked into the cache resource at creation (as above). Create the cache with the googleSearch tool, pass its handle plus grounding: { mode: "native" }, and grounding fires against the cached prefix with the cached-token ratio intact. A grounded runRaw step without a cache.key gets the googleSearch tool attached on the request as usual.

You do not need to empty your route's template when you pass a handle. When cache.key is set on a Gemini dispatch, ModelRig automatically drops systemInstruction and tools from the request — the API requires it, and the adapter handles it. The flip side is load-bearing: the dispatched request then carries only what the cache contains, so the resource must have been created with the system content and tools the task depends on. A handle whose cache lacks your system prompt does not error — it answers without it.

``ts // Gemini SDK: extend the TTL while the job runs (interval << ttl) const beat = setInterval( () => ai.caches.update({ name: handle, config: { ttl: "3600s" } }), 20 * 60_000, ); // clearInterval(beat) + ai.caches.delete({ name: handle }) when the job ends ``

cache.key is read fresh on every rig.run call, so recreating the resource and swapping the variable you pass takes effect on the next call — no restart. Without a heartbeat, the first call after expiry gets a cache_invalid failure (ModelRig then retries that candidate without the handle — a correct but full-price call), and every call after pays uncached. The drop lasts for that candidate's retries within the run; a later rig.run call tries the handle again (and fails the same way if it is still expired) — when a handle is gone, recreate it or stop passing it.

What invalidates a cache

A cache is a bet that the next request's prefix is byte-identical to a previous one. Anything that changes the prefix loses the bet — silently, with no error, just a full-price call:

Prefix hygiene

Two rules cover almost everything:

Verify, don't assume — the week-one checklist

Caching fails quietly, so the only honest confirmation is to look at the numbers on real traffic. After your first production runs:

Who owns what (the design line)

Managed cache lifecycle (ModelRig creating and heartbeating the resource for you) is deliberately not in v1 — it is revisited only if it offers real customer value over you owning the resource you already understand.

Where to go next