Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions changelog.d/kv-unified-render-state.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
- With `foldingStrategy: 'kv-unified'`, the solver no longer counts as cached
a prefix whose bytes changed after the request that cached it while its
messages kept their place in the solved layout. A message changes in place
when `ContextManager.editMessage` rewrites it; when its image is stripped,
once newer images reach `maxLiveImages` or leave it no room in
`maxLiveImageBytes`, or once the conversation from it on runs past
`imageStripDepthTokens` (all on by default); when its tool result is
replaced, once newer results of the same tool reach `toolResultMaxLastN`;
when a late result moves up over the stub pairing repair had put after its
call; or when a different `maxMessageTokens` cuts it. A recall pair changes
in place under a different `maxMessageTokens`, `recallEnvelope`,
`carrierPolicy`, `summaryContextLabel` or `summaryParticipant`. The provider
serves nothing past such a change, but every cache marker after it was still
credited, so the solver priced no churn for a rewrite the provider charges.
An accepted request's cache reference now records, for each marker, a hash
of the entries up to it as the strategy emitted them. Before each solve, a
marker whose prefix no longer renders that way is left out of the reference
the solver weighs, and so is a marker on the head window's end once the head
window no longer ends at its message. The check renders the presentation the
reference kept, so a candidate that renders raw a message the reference had
folded, holding an image or a tool result newer than an earlier raw one, can
change that earlier one and still be credited. `beforeUser` and `afterUser`
injections, which `compile` adds after the strategy's selection, are outside
the record. A reference written before this release has no record, and its
markers are credited as before until the next request is accepted. Two new
warnings, each logged at most once per strategy instance, mean the check
itself has gone wrong: `[kv-unified] the cache check couldn't render recall
…`, after which the markers past that recall pair aren't credited, and
`[kv-unified] the cache check rebuilt the prefix through …`, when a compile
that kept the accepted presentation sent something other than the check
rebuilt.
1 change: 1 addition & 0 deletions src/adaptive/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export {
type PresentedLeaf,
type AcceptedPresentationReference,
type ProviderCacheReference,
type CacheRenderRecord,
type KvUnifiedWelfarePolicy,
type ExactPolicySolveOptions,
type ExactPolicyCandidate,
Expand Down
18 changes: 11 additions & 7 deletions src/adaptive/kv-cache-sim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@
* that prefix. `unitIndex === units.length` caches the whole render (the
* pure-append breakpoint).
*
* Cache identity: two rendered units are byte-identical iff they share
* (kind, key) — the same identity `render-offsets` uses — because a recall unit
* keyed by a summary id renders that summary's fixed recall pair, a raw unit
* keyed by a chunk id renders that chunk verbatim, and head/tail are fixed
* blocks. So a content hash over the unit (kind,key) sequence is a faithful,
* deterministic stand-in for the cached bytes.
* Cache identity: two rendered units match when they share (kind, key), the
* same identity `render-offsets` uses: a recall unit keyed by a summary id
* renders that summary's fixed recall pair, a raw unit keyed by a chunk id
* renders that chunk, and head/tail are fixed blocks. This simulator takes
* the (kind, key) sequence as the cached bytes. A raw or head unit's bytes
* can change in place under its key (an edit, an image stripped, a tool
* result replaced or moved, a cut), which it doesn't see; kv-unified's
* accepted cache reference records and checks those (CacheRenderRecord).
*
* Pure and deterministic (no Date/random; the store tracks a logical step clock
* advanced by the caller).
Expand Down Expand Up @@ -162,7 +164,9 @@ export function evaluateCacheHit(
* Deterministic prefix-hash chain over a layout's unit identities. Returns
* `H[k]` = a 64-bit-ish hash (two 32-bit FNV/xorshift lanes, joined) of the
* concatenation of the first `k` units' `(kind,key)`. `H[0]` is the empty
* prefix. Two layouts share `H[k]` iff their first `k` units are byte-identical.
* prefix. Two layouts share `H[k]` iff their first `k` units share their
* identities, which this simulator takes as their bytes (see Cache identity
* above).
*/
function prefixHashes(layout: RenderLayout): string[] {
const H = new Array<string>(layout.units.length + 1);
Expand Down
24 changes: 24 additions & 0 deletions src/adaptive/kv-unified-policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,30 @@ export interface ProviderCacheReference {
readonly immutablePrefixHash: string;
readonly layout: RenderLayout;
readonly markers: readonly CacheMarker[];
/** Per marker, which entry it sat on and a hash of the request's entries up
* to it, as the strategy emitted them, so a later compile can tell a
* prefix that changed in place under the same unit keys. Absent on a
* reference an earlier version wrote. */
readonly render?: CacheRenderRecord;
}

/**
* What an accepted request's prefixes were, byte for byte, as the strategy
* emitted them, where a later compile can change them under the same unit
* keys. A unit's key is its message id, or `head`, or a summary id. What
* renders under a key can still change: an edit rewrites a message, an image
* is stripped, last-N replaces a tool result, pairing repair moves or stubs
* one, the cap cuts a message or a recall answer, a recall's envelope or
* label changes. A provider serves a marker's prefix only while every byte
* before it is unchanged, so a marker whose prefix no longer matches is left
* out of the reference the solver gets. The context manager's message-position
* injections, which it adds after the strategy's selection, aren't in it.
*/
export interface CacheRenderRecord {
/** Per marker, parallel to `markers`: the identity of the entry the marker
* sat on (the message it ends on, or its layout key), and a hash of every
* entry up to it, by participant and whole content. */
readonly prefixes: readonly { readonly through: string; readonly hash: string }[];
}

export interface KvUnifiedWelfarePolicy {
Expand Down
10 changes: 7 additions & 3 deletions src/adaptive/render-offsets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,13 @@ export type Frontier = ReadonlyMap<ChunkId, number>;
export interface RenderedUnit {
kind: 'head' | 'raw' | 'recall' | 'tail';
/**
* Stable identity for prefix comparison. Two units are byte-identical iff
* they share (kind, key): 'head'/'tail' for the fixed blocks, the chunk id
* for a raw shard, the summary id for a recall pair.
* Stable identity for prefix comparison: 'head'/'tail' for the fixed
* blocks, the chunk id for a raw shard, the summary id for a recall pair.
* Two units that share (kind, key) are the same part of the conversation,
* but what they render can still change in place under the key (an edit,
* an image stripped, a tool result replaced or moved, a cut, a recall's
* envelope), which kv-unified's accepted cache reference records and checks
* (CacheRenderRecord).
*/
key: string;
tokens: number;
Expand Down
Loading
Loading