fix(mllm-cache): media turns can capture companions again, and every companion store now uses the key its reader asks with
Multimodal chats never reused cache. Four separate defects, all in the hybrid SSM companion lane: 1. _ssm_capture_boundaries_for returned [] for EVERY media turn. The discard path records the boundary it needed via _ssm_required_checkpoint_tokens and this function is its only consumer, so the requirement was re-recorded and re-ignored every turn -- a media conversation could never heal, and an identical repeat turn still reported cached=0. Media turns now capture, but only inside the pure-text prefix that precedes the first media placeholder, block-aligned so the KV chain can actually pair. 2. _derive_hybrid_companion_delta would advance recurrent state across a gap containing media placeholders with a TEXT-ONLY forward. Those positions get re-fed without their pixel values: coherent output, wrong content, no error anywhere. The fetch side now declines the same pairing the store side already refuses. 3. _store_companion_from_clean_pass stored with no cache_extra_keys at all, on the premise that media prompts never route through it. That premise is false -- the vmlx#91 delta derive routes through _prefill_for_clean_ssm. So a media companion landed under the bare token key, where the media-lane reader (which asks with the media salt) can never find it, and a reader asking WITHOUT the salt would find it for a DIFFERENT image: two turns can carry byte-identical placeholder token ids and completely different pictures. The salt is now threaded through the whole chain, and an unsalted media store is declined rather than guessed at. A media probe that FAILS is not a positive reading and never blocks a store. 4. run_idle_rederive popped the salt off the queue entry, probed has_complete WITH it, then ran the clean prefill WITHOUT it. The probe below therefore always missed, so every media re-derive paid the 50-200MB deep clone plus disk write TWICE -- and the first copy was the unsalted media companion from defect 3. Tests: 10 new, covering the non-block-aligned media boundary (372 tokens, media at 300 -- deliberately not a multiple of 64 or 256), media-at-position-0, text-lane invariance, store/fetch key symmetry, the unsalted-media decline, the failed-probe-must-not-block invariant, and the idle re-derive double-store. Full A/B against a stashed tree: same 3 pre-existing failures before and after (bundled-python drift gate plus two cross-file pollution cases in test_engine_audit), 1449 -> 1459 passing.
J
Jinho Jang committed
dc56278151bb9ebe202aef0576b7d8698b3876f6
Parent: e89006f