A pi extension that overrides the built-in read tool with hash-based, replay-aware caching.
It reduces token usage and context bloat from repeated file reads while preserving correctness as session state evolves.
Correctness is maintained across:
- range reads (
path:START-END) - tree navigation (
/tree) - compaction boundaries
- restart/resume replay
pi-readcache runs automatically in the background by overriding read and managing replay trust for you. Refresh/invalidation can also be triggered by the model itself via the readcache_refresh tool when it decides a fresh baseline is needed. You can still run /readcache-refresh manually for explicit control.
When the extension is active, read may return:
- full content (
mode: full) - unchanged marker (
mode: unchanged) - unchanged range marker (
mode: unchanged_range) - unified diff for full-file reads (
mode: diff) - baseline fallback (
mode: baseline_fallback)
Plus:
/readcache-statusto inspect replay/coverage/savings/readcache-refresh <path> [start-end]to invalidate trust for next readreadcache_refreshtool (same semantics as command)
Preferred (npm):
pi install npm:pi-readcacheAlternative (git):
pi install git:https://github.com/Gurpartap/pi-readcacheAfter installation, you can use pi normally. If pi is already running when you install or update, run /reload in that session.
| Action | Command | Expected result |
|---|---|---|
| Baseline read | read src/foo.ts |
mode: full or mode: baseline_fallback |
| Repeat read (no file change) | read src/foo.ts |
[readcache: unchanged, ...] |
| Range read | read src/foo.ts:1-120 |
mode: full, baseline_fallback, or unchanged_range |
| Inspect replay/cache state | /readcache-status |
tracked scopes, replay window, mode counts, estimated savings |
| Invalidate full scope | /readcache-refresh src/foo.ts |
next full read re-anchors |
| Invalidate range scope | /readcache-refresh src/foo.ts 1-120 |
next range read re-anchors |
- Sensitive-path bypass: readcache does not cache/diff these patterns and falls back to baseline
readoutput:.env*,*.pem,*.key,*.p12,*.pfx,*.crt,*.cer,*.der,*.pk8,id_rsa,id_ed25519,.npmrc,.netrc. - Compaction is a strict replay barrier for trust reconstruction:
- replay starts at the latest active
compaction + 1. - pre-compaction trust is not used after that barrier.
- replay starts at the latest active
- First read after that barrier for a path/scope will re-anchor with baseline (
full/baseline_fallback). - For exact current file text, the assistant should still perform an actual
readin current context.
For full-file reads, mode: diff is emitted only when the generated patch is clearly more useful than baseline text.
Current defaults:
MAX_DIFF_TO_BASE_RATIO = 0.9- if
diffBytes >= selectedBytes * 0.9, diff is considered not useful and falls back to baseline (mode: baseline_fallback)
- if
MAX_DIFF_TO_BASE_LINE_RATIO = 0.85- if patch line count is greater than
selectedRequestedLines * 0.85, diff is considered too large/noisy and falls back to baseline
- if patch line count is greater than
Why these defaults:
- avoids near-full-file patch spam on high-churn edits
- improves readability for the model versus very large hunks
- keeps token savings meaningful when diff mode is used
flowchart TD
A[LLM calls read] --> B[read override tool]
B --> C[Run baseline built-in read]
B --> D[Load current file bytes/text + sha256]
B --> E[Rebuild replay knowledge for active leaf]
E --> F{base trust exists?}
F -- no --> G[mode=full, attach metadata]
F -- yes + same hash --> H[mode=unchanged/unchanged_range]
F -- yes + full scope + useful diff --> I[mode=diff]
F -- otherwise --> J[mode=baseline_fallback]
G --> K[persist object + overlay trust]
H --> K
I --> K
J --> K
K --> L[tool result + readcache metadata]
- Trust key:
(pathKey, scopeKey)where scope is:fullr:<start>:<end>
- Trust value:
{ hash, seq } - Replay source:
- prior
readtool result metadata (details.readcache) - custom invalidation entries (
customType: "pi-readcache")
- prior
- Overlay:
- in-memory, per
(sessionId, leafId), high seq namespace for same-turn freshness
- in-memory, per
flowchart LR
R[root] --> C1[compaction #1]
C1 --> N1[reads...]
N1 --> C2[compaction #2]
C2 --> L[active leaf]
B[replay start] --> S[latest compaction + 1 on active path]
Rules:
- replay boundary = latest compaction on active branch path + 1
- if no compaction on path, replay starts at root
- tree/fork/switch/compact/shutdown clear in-memory memo/overlay caches
index.ts- extension entrypoint + lifecycle reset hookssrc/tool.ts-readoverride decision enginesrc/replay.ts- replay reconstruction, trust transitions, overlaysrc/meta.ts- metadata/invalidation validators and extractorssrc/commands.ts-/readcache-status,/readcache-refresh,readcache_refreshsrc/object-store.ts- content-addressed storage (.pi/readcache/objects)src/diff.ts- unified diff creation + usefulness gatingsrc/path.ts- path/range parsing and normalizationsrc/telemetry.ts- replay window/mode/savings reporting
Because this overrides built-in read, it must preserve:
- same tool name + parameters (
path,offset?,limit?) - baseline-compatible content shapes (including image passthrough)
- truncation behavior and
details.truncationcompatibility
npm install
npm run typecheck
npm testTargeted suites:
npm test -- test/unit/replay.test.ts
npm test -- test/integration/compaction-boundary.test.ts
npm test -- test/integration/tree-navigation.test.ts
npm test -- test/integration/selective-range.test.ts
npm test -- test/integration/refresh-invalidation.test.ts
npm test -- test/integration/restart-resume.test.tsMIT © 2026 Gurpartap Singh (https://x.com/Gurpartap)