| Field | Value |
|---|---|
| RFC | 0116 |
| Title | Portable Prompt-Prefix Cache (cachePrefixId) |
| Status | Accepted |
| Author(s) | David Tufts (@davidscotttufts) |
| Created | 2026-06-26 |
| Updated | 2026-07-06 (Active → Accepted — single-witness bootstrap steward waiver + maintainer autonomy grant. tier-1 reference host openwop-app advertises aiProviders.promptPrefixCache {supported:true, providers:["anthropic"]} live + serves the prefix-cache probe seam. Steward-curl-verified on app.openwop.dev/api (POST /v1/host/openwop-app/aiProviders/prefix-cache-probe): tenant-A prefix probe-prefix-1 first call → {cacheWriteTokens:1000, cacheHit:false} (write); tenant-A same prefix → {cacheReadTokens:1000, cacheHit:true} (hit); tenant-B same prefix → {cacheReadTokens:0, cacheHit:false} (MISS) — the prompt-prefix-cache-cross-tenant-isolation protocol-tier invariant, falsifiable: a cachePrefixId collision across tenants does NOT hit, proving (tenant, cachePrefixId) keying with no cross-tenant leak. Cost-hint contract (cacheReadTokens/cacheWriteTokens) observed on the wire. The Anthropic provider call is mocked → RFC 0108 production-dark tier: the cache-KEY isolation is real; the provider routing is dark. Invariant was already protocol-tier (no graduation). Tier-2 (second host advertising promptPrefixCache) witness carried forward.) · 2026-06-26 |
| Affects | spec/v1/ai-envelope.md, spec/v1/provider-policy.md, schemas/capabilities.schema.json, schemas/run-event-payloads.schema.json, SECURITY/invariants.yaml, SECURITY/threat-model-provider-policy.md, conformance |
| Compatibility | additive per COMPATIBILITY.md |
| Supersedes | — |
| Superseded by | — |
Status: Active. Merge candidate. The two High-severity risks are resolved as normative MUSTs (architect review 2026-06-26): cross-tenant cache-key leakage → a mandated
(tenant, cachePrefixId)key construction + the new protocol-tier invariantprompt-prefix-cache-cross-tenant-isolation; replay divergence →cachePrefixIdis a cost-hint-only MUST (recorded envelope +provider.usageinput/output tokens identical hit-vs-miss). The advert is provider-scoped (per ADR 0148 A2), and the cache hit/non-share is non-vacuously observable via new cost-onlyprovider.usage.cacheReadTokens/cacheWriteTokens— so this no longer needs a host-attested benchmark.
Summary
Modern providers (e.g., Anthropic prompt caching, OpenAI/others) offer server-side context caching keyed by a stable prefix, turning a repeated system-prompt + tool-surface prefix from full-price input into a cheap cache read. OpenWOP has no portable way for a client to declare that a request's prefix is cacheable and stable. This RFC adds an optional cachePrefixId — a tenant-namespaced, secret-free label carried on the AI-envelope generate request — that a supporting host MAY use to route cacheable prefix content to its provider's cache. It is made safe + testable by three normative pillars: a mandated (tenant, cachePrefixId) cache key (cross-tenant isolation), a cost-hint-only contract (replay-invariant outcome), and a wire-observable witness via new cost-only cache-token fields on provider.usage.
Motivation
The tool surface (RFC 0112) and a front-loaded system prompt are the same bytes turn after turn; providers already let you avoid re-billing them, but only if the host pins a stable cache key. Without a protocol field, that optimization is invisible to clients and non-portable across hosts. A declared cachePrefixId makes "this prefix is stable, cache it" a first-class, portable hint. The red-team rates this the highest-leverage Tier-B item and the riskiest — which is exactly why it warrants an RFC with an honest gap list rather than a silent host feature.
Proposal
Field
Add optional cachePrefixId to the AI-envelope generate request (ai-envelope.md §host.aiEnvelope ctx.aiEnvelope.generate):
interface AiEnvelopeGenerateRequest {
// ...existing fields...
+ cachePrefixId?: string; // RFC 0116. Stable, tenant-namespaced, secret-free label for a cacheable prompt prefix.
}
Normative requirements
- A host advertising
aiProviders.promptPrefixCachefor the routed provider that receives a request withcachePrefixIdMAY route the stable prefix to that provider's context cache. A host that does not support it (or whose routed provider isn't in the advertisedproviders) MUST ignore the field — no error, no behavior change. - BYOK (F5).
cachePrefixIdis a client-chosen opaque label that MUST NOT encode or be derived from secret/BYOK material: it is the provider cache key and appears in host logs/metrics. PerSECURITY/threat-model-secret-leakage.md, a host MUST NOT emitcachePrefixIdin any payload that the SR-1 harness would otherwise have to redact, and MUST NOT persist prompt/response substrings keyed by it. - Cross-tenant isolation (F1, normative key construction — invariant
prompt-prefix-cache-cross-tenant-isolation). A host MUST key its provider context cache by the pair (resolved tenant,cachePrefixId) —cachePrefixIdis tenant-namespaced, never global. Two distinct tenants supplying the samecachePrefixIdMUST NOT share a provider cache entry; tenant B's first use of acachePrefixIdthat tenant A primed MUST be a cache miss. (Cross-tenant cache sharing = context leakage — the central hazard; this mirrors thekv/queue/workspacecross-tenant isolation invariants.) - Replay invariance (F2, normative).
cachePrefixIdis a cost hint, never a semantic input. The recorded run outcome — the accepted envelope andprovider.usage.inputTokens+outputTokens— MUST be identical whether the prefix cache hit or missed, and MUST replay (RFC 0041) identically. A host whose output differs on a cache hit is non-conformant. Only the cost-side cache metrics (below) may differ between hit and miss, and those are explicitly replay-non-asserted.
provider.usage extension — the observable witness (F3)
provider.usage (RFC 0026, schemas/run-event-payloads.schema.json §providerUsage) gains two optional, cost-only fields, denominated like the provider's usage block:
"providerUsage": {
"properties": {
"inputTokens": { "...": "MUST replay identically" },
"outputTokens": { "...": "MUST replay identically" },
+ "cacheReadTokens": { "type": "integer", "minimum": 0,
+ "description": "RFC 0116. Optional, cost-only. Provider-reported tokens served from the prefix cache this call (a cache HIT shows > 0). MAY be omitted on replay (NOT replay-asserted, like costEstimateUsd). MUST NOT carry prompt substrings (SR-1)." },
+ "cacheWriteTokens": { "type": "integer", "minimum": 0,
+ "description": "RFC 0116. Optional, cost-only. Provider-reported tokens written to the prefix cache this call (a cache PRIME). MAY be omitted on replay." }
}
}
This is what makes 0116 non-vacuously testable without breaking replay: a cache hit is observable (cacheReadTokens > 0) while the recorded outcome stays invariant, and a cross-tenant non-share is observable (B's first use → cacheReadTokens == 0).
Capability (provider-scoped, F4)
"aiProviders": {
"properties": {
+ "promptPrefixCache": { "type": "object", "additionalProperties": false,
+ "properties": {
+ "supported": { "type": "boolean" },
+ "providers": { "type": "array", "items": { "type": "string" },
+ "description": "RFC 0116. The providers for which the host honors cachePrefixId (prefix caching is provider-specific, e.g. Anthropic ephemeral). NOT a universal claim — per provider-policy.md routing." } },
+ "required": ["supported"],
+ "description": "RFC 0116. Host honors cachePrefixId as a provider-cache routing hint — tenant-namespaced (prompt-prefix-cache-cross-tenant-isolation), secret-free, replay-invariant." }
}
}
Examples
Positive — two generates in run R with cachePrefixId:"std-base-v1" → the second's provider.usage shows cacheReadTokens > 0 (hit), while its inputTokens/outputTokens and accepted envelope equal a no-cachePrefixId control (outcome-invariant).
Cross-tenant negative — tenant A primes cachePrefixId:"std-base-v1" (cacheWriteTokens > 0); tenant B's first generate with the same id → cacheReadTokens == 0 (no cross-tenant hit). A cacheReadTokens > 0 here is a conformance + invariant failure.
Compatibility
Additive. cachePrefixId is a new optional request field (absent ⇒ no behavior change); aiProviders.promptPrefixCache is a new optional, provider-scoped capability; the two provider.usage fields are new optional, server-emitted (open per RFC 0094), cost-only fields. No existing field changes; inputTokens/outputTokens replay semantics are untouched. The new prompt-prefix-cache-cross-tenant-isolation invariant is a normative requirement on previously-undefined behavior (additive per COMPATIBILITY.md §4).
Conformance
Resolving the original Q4 — there is a non-vacuous wire witness via the provider.usage cache tokens:
prompt-prefix-cache.test.ts, gated oncapabilityFamily(d,'aiProviders')?.promptPrefixCache?.supported. Asserts (driven via the host-sample envelope seam): (a) outcome-invariance — a run withcachePrefixIdand a control without produce the same accepted envelope + identicalinputTokens/outputTokens; (b) cache hit observable — the repeatgenerateshowscacheReadTokens > 0; (c) cross-tenant isolation — tenant B's first use of tenant A'scachePrefixIdshowscacheReadTokens == 0(theprompt-prefix-cache-cross-tenant-isolationinvariant); (d) secret-free — acachePrefixIdis never emitted where SR-1 would redact. Soft-skips on hosts that don't advertise.- New SECURITY invariant row
prompt-prefix-cache-cross-tenant-isolation(protocol-tier) → the (c) scenario is its public test.
Alternatives considered
1. Leave provider caching entirely host-internal (do-nothing). Rejected: a host can pin its own cache keys today, but only the protocol field gives portability (a client telling any host "this prefix is stable") and a cross-host-honest cross-tenant + replay contract. The architect review made the security/replay surface safe-by-MUST + testable, so do-nothing no longer wins. 2. Trust host tenant-scoping without mandating the key construction. Rejected: the kv/queue/workspace/memory CTI family all mandate the scope; this RFC follows suit with (tenant, cachePrefixId) + a protocol-tier invariant, rather than trusting each host. 3. Graduate on a host-attested benchmark (no wire witness). Rejected: the provider.usage cache-token extension is a genuine wire witness (cache hit + cross-tenant non-share both observable), so the project's no-benchmark-graduation norm is preserved. 4. Tie caching to RFC 0112 compact-tool projection only. Narrower, but misses the system-prompt prefix; the general field is safe with the mandated key + cost-hint contract, so no need to restrict.
Unresolved questions
1. ~~Portability across providers?~~ Resolved: the advert is provider-scoped (promptPrefixCache.providers[]); cachePrefixId is a host-honored hint per routed provider, not a cross-provider semantic guarantee. 2. ~~Cross-tenant safety — trust scoping or mandate the key?~~ Resolved: mandated (tenant, cachePrefixId) key + invariant prompt-prefix-cache-cross-tenant-isolation. 3. ~~Replay enforceable?~~ Resolved: cost-hint-only MUST; recorded envelope + provider.usage input/output tokens identical hit-vs-miss; cache metrics are cost-only + replay-non-asserted. 4. ~~Observable conformance signal?~~ Resolved: provider.usage.cacheReadTokens/cacheWriteTokens (cost-only) make hit + cross-tenant-non-share observable. 5. Could a provider's cache subtly alter output (e.g. truncation) and violate the cost-hint MUST? If a provider is found to do so, the host MUST NOT advertise promptPrefixCache for it (honest provider-scoping). (Open monitoring item, not a blocker.)
Implementation notes (non-normative)
- The cross-tenant key + cost-hint contract are the load-bearing safety properties — implement the
(tenant, cachePrefixId)keying and the outcome-invariance assertion FIRST. - Sequence after RFC 0112: a stable compact tool catalog is the natural first cacheable prefix.
- Reconcile with openwop-app ADR 0148 A2 (Anthropic-
ephemeralprefix): A2 is the provider-specific producer; this RFC'scachePrefixId+ provider-scoped advert is its portable wire form.
Acceptance criteria
- [ ] Spec text merged (
ai-envelope.mdcachePrefixId +provider.usagecache-token fields +provider-policy.mdprovider-scoping). - [ ]
capabilities.schema.json(aiProviders.promptPrefixCache) +run-event-payloads.schema.json(cache tokens) updated. - [ ]
SECURITY/invariants.yamlrowprompt-prefix-cache-cross-tenant-isolation(protocol-tier) + its conformance test. - [ ]
prompt-prefix-cache.test.ts(outcome-invariance + cache-hit-observable + cross-tenant + secret-free). - [ ] CHANGELOG entry.
- [ ] Reference host implements tenant-keyed, replay-invariant caching + the witness, or impl explicitly deferred (provider-gated — needs a provider that offers prefix caching).
References
spec/v1/ai-envelope.md(generate request;provider.usage/RFC 0026; replay/correlationId);spec/v1/provider-policy.md(per-provider routing).SECURITY/threat-model-provider-policy.md,SECURITY/threat-model-secret-leakage.md; thekv/queue/workspacecross-tenant isolation invariants inSECURITY/invariants.yaml(the pattern the new invariant follows).- RFC 0041 (replay determinism), RFC 0026 (
provider.usage), RFC 0112 (compact tool projection — first cacheable prefix). - Prior art: Anthropic prompt caching; provider context-cache pricing models.
- Sibling RFCs: 0111, 0112, 0113, 0114, 0115.