Skip to content

feat(offload): cacheanchor, a breakpoint before the first edited message, and mask deep_batch - #495

Closed
OsherElhadad wants to merge 1 commit into
mainfrom
feat/cache-anchor
Closed

OsherElhadad wants to merge 1 commit into
mainfrom
feat/cache-anchor

Conversation

@OsherElhadad

Copy link
Copy Markdown
Collaborator

What

An edit inside the cached history normally makes the provider write everything after the system prompt again: the client marks only its last message, and the provider's look-back is 20 blocks. This PR adds a pipeline-level component, cacheanchor, that keeps such an edit to a tail rewrite, and a deep_batch option for mask that makes use of it. Both are opt-in: cacheanchor is in no preset and deep_batch defaults to 0.

How it works

  • cacheanchor runs last. It compares the request with the thread's previous forwarded request and finds the first message p where they differ.
  • It then puts one cache_control mark on the assistant turn that follows the latest earlier request end whose forwarded prefix is unchanged and whose entry is younger than the cache lifetime. That breakpoint looks back one block, finds the entry the earlier request wrote, and reads the prefix up to it; only the tail is rewritten.
  • It adds at most one breakpoint and never goes beyond four counting the client's own (the host's raw count). It skips cold requests and appends, and does nothing when no valid entry, markable assistant turn or free slot exists. Gates: anchor_no_valid_entry, anchor_not_markable, anchor_no_free_slot; event: anchor_placed.
  • mask with deep_batch: N lifts the cached-prefix gate on a turn when N or more older outputs are waiting, masking them together (one rewrite instead of one per turn). Decisions are still frozen and replayed byte-identically.

Tests

Unit tests for the anchor (placement, cap, TTL, cold request, appends), for deep_batch, and a wire test through apply.BodyOpts with a Claude Code shaped body: with [mask, cacheanchor] an assistant turn carries the mark and no request exceeds four breakpoints; the control without cacheanchor carries none.

Related: #475 uses the same idea inside extract_llm_ride.

…t before the first edited message

Signed-off-by: Osher-Elhadad <Osher.Elhadad@ibm.com>
@OsherElhadad

Copy link
Copy Markdown
Collaborator Author

Review of af57def. go vet, gofmt clean; go test ./components/offload ./apply ./config pass. Attribution clean; the PR body carries no measurements. Off or absent: cacheanchor is in no preset and is its own component, so a pipeline without it is untouched, and deep_batch defaults to 0 (the gate expression is unchanged then). The breakpoint cap holds (it adds at most one mark and refuses at four counting the host's raw count). The wire test does fail without the component (the a == 0 check) and has the control.

CHANGES REQUESTED

Blocking

  1. TTL ordering is not kept. The new mark is a plain ephemeral (5m) and is placed on an assistant turn in the middle of the request. The provider rejects a one-hour mark that comes after a five-minute mark in request order. I ran the PR's own wire scenario with the client asking for the one-hour tier on its marks (system and last message ttl: 1h): on turns 9, 12 and 15 the request on the wire carries [1h 1h 5m 1h], i.e. a 5m mark between 1h marks, which is a 400. The wire test only uses 5m marks, so it cannot see this. Fix: give the anchor the TTL of the nearest following mark (so the order stays non-increasing), or refuse to place it when any mark after the target position is 1h and the anchor would be 5m; and add a wire test with 1h client marks asserting that no 1h mark follows a 5m mark on any request (and that the anchor is still placed, when allowed).
  2. This PR extends mask, which refactor(components): remove offloaders that delete by heuristic #474 (open, labelled ready-to-merge) deletes, and deep_batch masks outputs inside the cached prefix on the strength of a count of older outputs, which is the delete-by-heuristic behaviour that refactor(components): remove offloaders that delete by heuristic #474 and Compaction cleanup: remove failed_run, collapse and other heuristic-deletion components #471 remove. Merging both leaves one of them broken: the wire test and mask_deepbatch_test.go depend on mask. Decide which direction is intended with the lead: either drop deep_batch and make the wire test use a component that survives refactor(components): remove offloaders that delete by heuristic #474 (for example extract or dedup editing an old output), keeping cacheanchor as the generic piece, or hold refactor(components): remove offloaders that delete by heuristic #474. As written the two PRs conflict in intent.

Non-blocking
3. The prefix hash uses role and MessageText only. A change to a non-text block earlier in the transcript (an assistant tool_use input, a thinking block, an image) is not seen, so p can be later than the real first difference and the anchor can point at an entry the provider no longer matches; the cost is a missed saving, not an error. Hash the serialized content blocks, or note the limit in the doc.
4. After placing the mark the code sets the end's timestamp to now ("our read refreshes it"). If the read misses, that entry is trusted for another 270 s. The response usage shows whether the anchor hit (cache_read versus cache_write); feed it back when it is available, or document the assumption.
5. The doc page should state that the component must run last and is incompatible with a later component that adds or moves breakpoints (cachesplit, cacheinject) unless it counts them; say where it sits relative to those in the examples.

Re-review after 1 and 2. Not labelled.

@OsherElhadad

Copy link
Copy Markdown
Collaborator Author

Superseded: mask is removed by #474, and house makes no mid-history edits, so a general anchor has no user yet. The TTL-ordering finding is fixed in #475.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants