pub struct CacheManager { /* private fields */ }Expand description
SHA-256 content-hash deduplication cache backed by SessionStore,
with delta encoding for near-duplicate content and compaction awareness.
§Freshness model
A dedup ref is considered fresh (safe to serve instead of the full
content) when the cache entry’s accessed_at timestamp in SQLite is
within max_ref_age of now. When sqz is invoked from shell hooks each
invocation is a short-lived process, so the freshness check must be
persistent — in-memory state is gone the moment the process exits.
The previous turn-counter heuristic was in-memory only and therefore never registered freshness across hook invocations, which silently disabled the dedup feature in production. Issue found April 18 2026.
Default TTL: 30 minutes. Empirically matches a typical active coding
session before a context compaction. Use
with_ref_age to tune.
Implementations§
Source§impl CacheManager
impl CacheManager
Sourcepub fn new(store: SessionStore, max_size_bytes: u64) -> Self
pub fn new(store: SessionStore, max_size_bytes: u64) -> Self
Create a new cache manager backed by the given session store.
max_size_bytes controls when LRU eviction kicks in. A good default
is 512 MB (512 * 1024 * 1024). Dedup refs go stale after 30 minutes
of wall-clock time by default; use with_ref_age
to tune.
Sourcepub fn with_ref_age_duration(
store: SessionStore,
max_size_bytes: u64,
max_ref_age: Duration,
) -> Self
pub fn with_ref_age_duration( store: SessionStore, max_size_bytes: u64, max_ref_age: Duration, ) -> Self
Create a CacheManager with an explicit wall-clock ref-age cap.
Sourcepub fn sha256_hex(bytes: &[u8]) -> String
pub fn sha256_hex(bytes: &[u8]) -> String
Compute the SHA-256 hex digest of bytes.
Sourcepub fn advance_turn(&self)
pub fn advance_turn(&self)
Advance the turn counter. Retained for API compatibility; not used
for freshness. The context_evictor still reads current_turn for
LRU scoring during sqz compact.
Sourcepub fn current_turn(&self) -> u64
pub fn current_turn(&self) -> u64
Get the current turn number. Used by the context_evictor for scoring.
Sourcepub fn notify_compaction(&self)
pub fn notify_compaction(&self)
Notify the cache that a context compaction has occurred.
Persists a compaction timestamp into the session store so any cache
entry whose accessed_at predates the marker is considered stale
by every subsequent sqz process, not just this one. The shell-
hook invocation model means this method is typically called from a
short-lived sqz hook precompact process, and the check runs in a
different sqz compress process milliseconds later.
Call this when:
- The harness signals a compaction event (PreCompact hook)
- A session is resumed after being idle
- The user runs
sqz compact
Sourcepub fn get_or_compress(
&self,
path: &Path,
content: &[u8],
pipeline: &CompressionPipeline,
) -> Result<CacheResult>
pub fn get_or_compress( &self, path: &Path, content: &[u8], pipeline: &CompressionPipeline, ) -> Result<CacheResult>
Look up content in the cache with compaction awareness.
- On exact dedup with fresh ref: return
CacheResult::Dedup(~13 tokens). - On exact dedup with stale ref: re-compress and return
CacheResult::Fresh(the original content may have been compacted out of the LLM’s context). - On near-duplicate: return
CacheResult::Deltawith a compact diff. - On cache miss: compress via
pipeline, persist, returnCacheResult::Fresh.
Sourcepub fn get_or_compress_with(
&self,
path: &Path,
content: &[u8],
compress: &dyn Fn(&str) -> Result<CompressedContent>,
) -> Result<CacheResult>
pub fn get_or_compress_with( &self, path: &Path, content: &[u8], compress: &dyn Fn(&str) -> Result<CompressedContent>, ) -> Result<CacheResult>
get_or_compress with the caller’s own
compressor for misses and stale refs, so a cached result goes through
the same verification as an uncached one.
Sourcepub fn get_or_compress_lossless(
&self,
path: &Path,
content: &[u8],
pipeline: &CompressionPipeline,
) -> Result<CacheResult>
pub fn get_or_compress_lossless( &self, path: &Path, content: &[u8], pipeline: &CompressionPipeline, ) -> Result<CacheResult>
Lossless variant used by the file-read MCP tools (sqz_read_file /
sqz_grep / sqz_list_dir): on a cache miss the content is returned
faithfully via CompressionPipeline::compress_lossless (no entropy
truncation / pruning / re-encoding). The general compress tool keeps
using get_or_compress for aggressive
compression. See issue #32.
Sourcepub fn check_dedup(&self, content: &[u8]) -> Result<Option<String>>
pub fn check_dedup(&self, content: &[u8]) -> Result<Option<String>>
Check if content is already in the persistent cache (dedup lookup only).
Returns Some(inline_ref) if cached AND the ref is still fresh,
None if the content is not cached or the ref is stale.
Unlike get_or_compress, this method does
not touch accessed_at until after the freshness check; otherwise
every read would make itself “fresh.”
Sourcepub fn check_dedup_with_meta(&self, content: &[u8]) -> Result<Option<DedupHit>>
pub fn check_dedup_with_meta(&self, content: &[u8]) -> Result<Option<DedupHit>>
check_dedup plus the metadata callers need for
regret tracking: the full content hash and the entry’s accessed_at
from before this lookup bumped it. A hit whose previous access was
seconds ago means the agent re-produced content it already had.
Sourcepub fn check_slice_with_meta(&self, content: &[u8]) -> Result<Option<SliceHit>>
pub fn check_slice_with_meta(&self, content: &[u8]) -> Result<Option<SliceHit>>
Is content a line range of something the model already has?
Exact-hash dedup misses the common case: the agent read a file in full, then re-reads lines 40-80 of it. Near-duplicate matching misses it too (a 40-line slice of a 500-line file has a Jaccard similarity near 0.1). This checks containment instead: the slice must appear verbatim, on line boundaries, in a recent cached original that is still fresh, and the same bytes must also appear in what was actually served for that original, so a ref never points at lines a lossy stage dropped. Touches the parent entry on a hit.
Sourcepub fn store_compressed(
&self,
original_content: &[u8],
compressed: &CompressedContent,
) -> Result<()>
pub fn store_compressed( &self, original_content: &[u8], compressed: &CompressedContent, ) -> Result<()>
Store a compressed result in the persistent cache, keyed by the SHA-256 hash of the original content.
Also records the ref as sent at the current turn for compaction tracking.
Persists original_content alongside compressed so that
sqz expand <prefix> can recover the raw bytes for agents that
cannot parse §ref:…§ dedup tokens.
Sourcepub fn expand_prefix(&self, prefix: &str) -> Result<Option<ExpandResult>>
pub fn expand_prefix(&self, prefix: &str) -> Result<Option<ExpandResult>>
Resolve a hex prefix (the 16-char tail of a §ref:<prefix>§ token,
or any longer hex string pasted by a user) to the cached content.
Designed for the sqz expand <prefix> CLI: the agent sees a ref
token it can’t parse, runs sqz expand a1b2c3d4e5f6g7h8, and gets
back the raw bytes that produced the ref. This is the
user-visible escape hatch SquireNed asked for.
Three outcomes:
Ok(Some(Original))— the original bytes were captured when the entry was stored (new cache entries from v0.10.0+ always capture the original). Writebytesto stdout.Ok(Some(CompressedOnly))— the entry exists but itsoriginalcolumn isNULL(pre-migration data). We still return the compressed form — always legible, always useful — and the CLI surfaces a note that tells the user to re-run their original command with--no-cacheto capture a truly uncompressed copy.Ok(None)— no entry matches. Usually means the ref was truncated from a different sqz database (a different machine, a wiped~/.sqz/sessions.db, etc.).Err(_)— prefix was ambiguous or the DB is broken. The error carries a user-readable message explaining what went wrong.
Touches accessed_at on hit (consistent with get_cache_entry).
Sourcepub fn expand_ref(&self, raw: &str) -> Result<Option<ExpandResult>>
pub fn expand_ref(&self, raw: &str) -> Result<Option<ExpandResult>>
Resolve a ref token in any of the forms an agent might paste:
a1b2c3d4, §ref:a1b2c3d4§, ref:a1b2c3d4, or the ranged
§ref:a1b2c3d4:L40-80§. A range narrows the returned bytes to
those lines (1-based, inclusive).
Sourcepub fn invalidate(&self, path: &Path) -> Result<()>
pub fn invalidate(&self, path: &Path) -> Result<()>
Invalidate the cache entry for path if its current content is known.
Reads the file at path, computes its hash, and removes the matching
entry from the store. If the file does not exist the call is a no-op.