Skip to content

feat(index): scope base-model evictions via a CacheEvent adapter_scoped flag#164

Open
EdHasNoLife wants to merge 4 commits into
mainfrom
sunxuedward/cac-243-cacheevent-adapter-presence
Open

feat(index): scope base-model evictions via a CacheEvent adapter_scoped flag#164
EdHasNoLife wants to merge 4 commits into
mainfrom
sunxuedward/cac-243-cacheevent-adapter-presence

Conversation

@EdHasNoLife

@EdHasNoLife EdHasNoLife commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Linear: CAC-243 (part 1 of the epic — the eviction over-sweep)

Problem

The index partitions cache entries by adapter, but PREFIX_EVICTED could not tell a base-model eviction apart from a legacy sweep. CacheEvent.adapter_id="" was ambiguous — it meant both "no adapter field → sweep every partition" and could not express "adapter-aware producer evicting the base ("") partition." So an adapter-aware subscriber's base-model eviction was treated as the cross-partition wildcard, dropping live LoRA-partition hints for the same token-derived hash. Strict non-regression and self-healing via ReportCacheState, but it undercut adapter-precise eviction.

Fix — an additive adapter_scoped flag

Add a new bool adapter_scoped = 8 to CacheEvent (adapter_id stays a plain string = 7). The server decides the removal scope as:

adapter_scoped adapter_id removal
true "" base partition only (the fix)
true "sql-lora" that adapter only
false "sql-lora" that adapter only — upgrade compat: a pre-adapter_scoped producer's LoRA eviction keeps narrowing on a mixed-version fleet
false "" cross-partition sweep (conservative legacy)

i.e. scope when adapter_scoped || adapter_id != ""; sweep only when both are empty/false.

  • Producer (kvevent-subscriber) always sets adapter_scoped on the evictions it emits (post fail-closed it always knows the adapter).
  • Consumer (inferencecache_service) carries the flag into index.Event.AdapterScoped; the index's ApplyEvent applies the table above.
  • Why a bool, not optional adapter_id: making adapter_id a proto3 optional changes its cardinality (implicit→explicit presence), which buf breaking rejects and this repo gates on. A new bool is purely additive.

Tests

  • pkg/index — base eviction with adapter_scoped spares a co-resident LoRA hint (the over-sweep this fixes); a non-empty adapter_id without the flag still narrows (upgrade compat); a fully empty/unset eviction still sweeps (legacy); scoped-to-its-adapter; ALL_CLEARED stays adapter-agnostic.
  • pkg/adapters/engineEvictedEvent sets adapter_scoped for both base and a named adapter.
  • pkg/server — full wire→index seam through PublishEvent: a base-scoped eviction removes only the base partition, LoRA untouched.

Scope

First of the CAC-243 sub-parts (the self-contained eviction correctness fix). Runtime adapter-name resolution and the managed-sidecar config surface remain separate follow-ups. docs/design/grpc-contract.md updated to match the proto; make proto-gen idempotent, proto-lint + buf breaking clean, full go test ./... green.

CacheEvent.adapter_id was a plain proto3 string, so an empty value meant both
a legacy producer (sweep every partition) and could not express an adapter-aware
producer evicting the base partition. A base-model eviction was thus treated as
the cross-partition wildcard and dropped live LoRA hints for the same token hash.

Make adapter_id an optional string (proto3 presence) and branch on presence, not
emptiness: present (even empty) narrows the removal to that one partition; absent
keeps the conservative legacy sweep. The subscriber always sets it; the server
carries presence into the index via ev.AdapterId != nil.

Wire-compatible (old producers read as absent). New tests at the index and the
full PublishEvent wire-to-index seam; grpc-contract.md updated to match.
@linear-code

linear-code Bot commented Jul 24, 2026

Copy link
Copy Markdown

CAC-243

@github-actions

Copy link
Copy Markdown

Codex review

Files reviewed

docs/design/

  • [reviewed] docs/design/grpc-contract.md

pkg/adapters/engine/

  • [reviewed] pkg/adapters/engine/mapper.go
  • [reviewed] pkg/adapters/engine/mapper_test.go

pkg/index/

  • [reviewed] pkg/index/adapter_partition_test.go
  • [reviewed] pkg/index/index.go

pkg/server/

  • [reviewed] pkg/server/inferencecache_service.go
  • [reviewed] pkg/server/server_test.go

pkg/server/proto/inferencecache/v1alpha1/

  • [skipped — generated] pkg/server/proto/inferencecache/v1alpha1/inferencecache.pb.go

proto/inferencecache/v1alpha1/

  • [reviewed] proto/inferencecache/v1alpha1/inferencecache.proto

Findings

Blocking

None.

Should-fix

docs/design/grpc-contract.md:67 — The contract incorrectly says an empty adapter_id sweeps every partition. With this PR, a present empty value targets the base partition; only an absent field triggers the legacy sweep.

Nit

pkg/index/adapter_partition_test.go:205 — Rename TestAdapterPartitionBaseEvictionWithPresenceSpareLoRA to ...SparesLoRA for grammatical consistency with the PR description and server test.

Per-category coverage

  1. Vendor-neutral naming: Clean
  2. Contract & spec / docs sync: findings above at: docs/design/grpc-contract.md:67
  3. gRPC fail-open semantics: Clean
  4. Quality (tests / error wrapping / package placement / generated drift): findings above at: pkg/index/adapter_partition_test.go:205

Verdict

changes-requested

buf breaking rejects changing CacheEvent.adapter_id from proto3 implicit to
explicit presence (a cardinality change), and the repo gates on it. Carry the
presence signal in a new, purely additive bool adapter_scoped = 8 instead:
adapter_scoped=true means adapter_id is authoritative — the removal targets
exactly that partition, including "" for a base-model eviction — while false
(legacy producers) keeps the conservative cross-partition sweep. Index-side
semantics (Event.AdapterSet) and all tests are unchanged; only the wire signal
moves from a pointer to the bool. Also fixes the SpareLoRA->SparesLoRA test name
and the grpc-contract PREFIX_EVICTED prose. buf breaking/lint, go test ./... pass.
@github-actions

Copy link
Copy Markdown

Codex review

Files reviewed

  • docs/design/
    • [reviewed] grpc-contract.md
  • pkg/adapters/engine/
    • [reviewed] mapper.go
    • [reviewed] mapper_test.go
  • pkg/index/
    • [reviewed] adapter_partition_test.go
    • [reviewed] index.go
  • pkg/server/
    • [reviewed] inferencecache_service.go
    • [skipped — generated] proto/inferencecache/v1alpha1/inferencecache.pb.go
    • [reviewed] server_test.go
  • proto/inferencecache/v1alpha1/
    • [reviewed] inferencecache.proto

Findings

Blocking

None.

Should-fix

None.

Nit

  • pkg/index/index.go:247AdapterSet is described as “proto3 presence,” but the wire contract now uses an independent adapter_scoped boolean, not field presence. Rename it to AdapterScoped or correct the wording so future callers do not conflate the two mechanisms.
  • pkg/index/adapter_partition_test.go:202 — The test name and comment still refer to adapter presence even though the implemented contract is an explicit scope flag. Updating them to “scoped” would accurately document the landed design.

Per-category coverage

  1. Vendor-neutral naming: Clean
  2. Contract & spec / docs sync: Clean
  3. gRPC fail-open semantics: Clean
  4. Quality (tests / error wrapping / package placement / generated drift): findings above at: pkg/index/index.go:247, pkg/index/adapter_partition_test.go:202

Verdict

approve-with-nits

Codex nits: the index-side field and comments called the eviction-scope signal
'proto3 presence', but the landed wire contract is the independent adapter_scoped
bool. Rename index.Event.AdapterSet -> AdapterScoped and reword the comments +
the test name so the two mechanisms aren't conflated. No behavior change; go test
./... , buf lint/breaking pass.
@github-actions

Copy link
Copy Markdown

Codex review

Files reviewed

  • docs/design/
    • [reviewed] grpc-contract.md
  • pkg/adapters/engine/
    • [reviewed] mapper.go
    • [reviewed] mapper_test.go
  • pkg/index/
    • [reviewed] adapter_partition_test.go
    • [reviewed] index.go
  • pkg/server/
    • [reviewed] inferencecache_service.go
    • [skipped — generated] proto/inferencecache/v1alpha1/inferencecache.pb.go
    • [reviewed] server_test.go
  • proto/inferencecache/v1alpha1/
    • [reviewed] inferencecache.proto

Findings

Blocking

  • pkg/index/index.go:916 — Pre-PR adapter-aware producers that send a non-empty adapter_id but lack the new adapter_scoped field now trigger a cross-partition sweep. Preserve the prior behavior for non-empty IDs (for example, scope when AdapterScoped || Adapter != "") while using the flag to distinguish base-partition evictions; add an upgrade-compatibility test for this case.

Should-fix

  • proto/inferencecache/v1alpha1/inferencecache.proto:305 — The implementation adds adapter_scoped instead of making adapter_id optional, contradicting the PR title/body and its stated wire-to-index translation. Update the PR description to accurately document the shipped contract and migration behavior.

Nit

None.

Per-category coverage

  1. Vendor-neutral naming: Clean
  2. Contract & spec / docs sync: findings above at: pkg/index/index.go:916, proto/inferencecache/v1alpha1/inferencecache.proto:305
  3. gRPC fail-open semantics: Clean
  4. Quality (tests / error wrapping / package placement / generated drift): findings above at: pkg/index/index.go:916

Verdict

changes-requested

Fix a mixed-version regression: a pre-adapter_scoped producer (new server, not-
yet-upgraded subscriber) sends adapter_id="sql-lora" without adapter_scoped, and
the new server would wildcard-sweep it — wiping another adapter's live hint.
Scope the removal when adapter_scoped is set OR adapter_id is non-empty; only an
empty adapter_id with adapter_scoped unset takes the legacy cross-partition sweep.
New TestAdapterPartitionEvictionNonEmptyAdapterScopesWithoutFlag; proto/contract
comments updated. go test ./..., buf lint/breaking pass.
@EdHasNoLife EdHasNoLife changed the title feat(index): scope base-model evictions via CacheEvent adapter presence feat(index): scope base-model evictions via a CacheEvent adapter_scoped flag Jul 24, 2026
@github-actions

Copy link
Copy Markdown

Codex review

Files reviewed

  • docs/design/
    • [reviewed] grpc-contract.md
  • pkg/adapters/engine/
    • [reviewed] mapper.go
    • [reviewed] mapper_test.go
  • pkg/index/
    • [reviewed] adapter_partition_test.go
    • [reviewed] index.go
  • pkg/server/
    • [reviewed] inferencecache_service.go
    • [reviewed] server_test.go
  • pkg/server/proto/inferencecache/v1alpha1/
    • [skipped — generated] inferencecache.pb.go
  • proto/inferencecache/v1alpha1/
    • [reviewed] inferencecache.proto

Findings

Blocking

None.

Should-fix

  • proto/inferencecache/v1alpha1/inferencecache.proto:318 — Align the PR description with the implemented contract — the description promises optional string adapter_id = 7 and presence-based consumption, while the actual diff retains the string and adds bool adapter_scoped = 8. This materially changes the API clients must generate and use, so the published PR contract and migration guidance are currently inaccurate.

Nit

None.

Per-category coverage

  1. Vendor-neutral naming: Clean
  2. Contract & spec / docs sync: findings above at: proto/inferencecache/v1alpha1/inferencecache.proto:318
  3. gRPC fail-open semantics: Clean
  4. Quality (tests / error wrapping / package placement / generated drift): Clean

Verdict

changes-requested

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant