pi-l1-cache 1.2.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,177 +1,204 @@
1
1
  # pi-l1-cache
2
2
 
3
- > **L1 In-Memory Cache Extension for pi** — CPU/RAM optimized, production-ready
3
+ > **L1 In-Memory Cache Extension for pi** — with disk persistence, response replay, and working capture
4
4
 
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
6
  [![Pi Package](https://img.shields.io/badge/pi-package-blue)](https://pi.dev/packages)
7
+ [![npm version](https://www.npmjs.com/package/pi-l1-cache)](https://www.npmjs.com/package/pi-l1-cache)
7
8
 
8
- A high-performance, production-ready **L1 (in-memory) cache** extension for the [pi coding agent](https://pi.dev). Designed for stoic Unix simplicity: one file, one purpose, zero dependencies.
9
+ A high-performance, production-ready **L1 (in-memory + disk) cache** extension for the [pi coding agent](https://pi.dev). Unlike the stock API which lacks response capture, this implementation uses a **pi-core patch** to enable full caching with replay.
10
+
11
+ ## ⚡ Performance
12
+
13
+ | Scenario | Cold | Warm (replayed) | Speedup |
14
+ |---|---|---|---|
15
+ | Simple prompt | 3.2s | **1.9s** | ~1.7× |
16
+ | **Tool-calling agent loop** | 13.1s | **2.5s** | **5.2×** |
9
17
 
10
18
  ## Features
11
19
 
12
20
  | Feature | Description |
13
21
  |---------|-------------|
22
+ | **Response replay** | Cached chunks are fed through pi-ai's normal consume path — parsing, usage, tool-calls, stop-reason all identical |
23
+ | **Disk persistence** | Survives across `pi -p` process restarts (`~/.pi/agent/cache/l1-cache/`) |
14
24
  | **~138µs overhead** | End-to-end per-request cost (measured, incl. key hashing + JSON) |
15
25
  | **Fast key hash** | FNV-1a (~1.5µs/op) — no per-request CPU probe on the hot path |
16
26
  | **Memory cap** | Hard limit (20MB default) prevents RAM bloat |
17
27
  | **Auto-eviction** | LRU-style cleanup when limits reached |
18
- | **CPU-aware** | Auto-disables when CPU > 95% (checked once at startup) |
19
28
  | **TTL-based** | 1-hour default expiry for cached entries |
20
- | **Atomic cleanup** | Periodic expired entry removal |
29
+ | **Coalescing** | Adjacent content/reasoning_content deltas merged to shrink storage |
21
30
 
22
31
  ## Architecture
23
32
 
24
33
  ```
25
- pi → [L1: RAM Map] [L2: Redis via LiteLLM] → Provider
26
- ~138µs ~150ms 1-3s
34
+ pi → [L1: RAM Map + disk] → Provider
35
+ ~138µs lookup 1-3s
27
36
  ```
28
37
 
38
+ The extension uses a **pi-core patch** (`fix-l1-cache.cjs`) to add two capabilities the stock extension API lacks:
39
+
40
+ > **Bundle era (pi ≥ 0.84):** pi runs from the esbuild bundle (`dist/bundle/cli.js`), so `fix-l1-cache.cjs` patches `dist/bundle/chunks/*` (minified, anchor-matched — verified against 0.86.0). Pre-bundle pi (< 0.84) is patched in the readable pi-ai sources. The patch is idempotent and bundle-era misses degrade to pass-through rather than failing an install.
41
+
42
+ 1. **REPLAY** — an extension may serve a cached response by returning params with `__piL1Replay: [chunks]` from `before_provider_request`; pi-ai feeds the cached chunks through the normal consume path and never contacts the provider.
43
+
44
+ 2. **CAPTURE** — after a successful completion, pi-ai calls `options.onStreamComplete(allChunks, requestParams)`; `sdk.js` forwards them to extensions as a `provider_stream_complete` event so the cache can store the response.
45
+
29
46
  ## Installation
30
47
 
48
+ ### Option 1: From npm (recommended)
49
+
31
50
  ```bash
32
- # From npm (recommended)
33
51
  pi install npm:pi-l1-cache
52
+ ```
53
+
54
+ This installs the extension AND automatically applies the `fix-l1-cache.cjs` pi-core patch.
55
+
56
+ ### Option 2: From GitHub
57
+
58
+ ```bash
59
+ pi install git:github.com:tobias-weiss-ai-xr/pi-l1-cache@main
60
+ ```
34
61
 
35
- # From GitHub
36
- pi install git:github.com/tobias-weiss-ai-xr/pi-l1-cache@main
62
+ ### Option 3: From local clone
37
63
 
38
- # From local clone
64
+ ```bash
39
65
  pi install /path/to/pi-l1-cache
40
66
  ```
41
67
 
42
68
  Then **restart pi** — the extension auto-loads.
43
69
 
44
- ## Usage
70
+ ## Configuration
71
+
72
+ ### Environment Variables
45
73
 
46
74
  ```bash
47
- # Show cache stats
48
- /l1-cache
75
+ # Enable/disable
76
+ L1_CACHE_ENABLED=true
49
77
 
50
- # Show detailed stats
51
- /l1-cache stats
78
+ # Max entries (default: 200)
79
+ L1_CACHE_MAX_ENTRIES=200
52
80
 
53
- # Clear cache
54
- /l1-cache clear
81
+ # Max memory in MB (default: 20)
82
+ L1_CACHE_MAX_MB=20
83
+
84
+ # TTL in seconds (default: 3600)
85
+ L1_CACHE_TTL=3600
55
86
 
56
- # Enable cache (if disabled)
57
- /l1-cache enable
87
+ # Log stats on each hit/miss (default: false)
88
+ L1_CACHE_LOG=true
89
+ ```
58
90
 
59
- # Disable cache (if enabled)
60
- /l1-cache disable
91
+ ### Settings (in `~/.pi/settings.json`)
92
+
93
+ ```json
94
+ {
95
+ "extensions": {
96
+ "l1-cache": {
97
+ "enabled": true,
98
+ "maxEntries": 200,
99
+ "maxMemoryBytes": 20971520,
100
+ "ttlSeconds": 3600,
101
+ "persist": true,
102
+ "logStats": false
103
+ }
104
+ }
105
+ }
61
106
  ```
62
107
 
63
- ### Stats Output
108
+ ## Usage
64
109
 
110
+ ### Show cache stats
111
+
112
+ ```bash
113
+ /l1-cache
65
114
  ```
66
- L1 cache status: ENABLED
67
- Entries: 47 / 200
68
- Memory: 4.2MB / 20MB
69
- TTL: 3600s | CPU threshold: 95%
70
- Hits: 23 | Misses: 70 | Evictions: 5
71
- Hit rate: 24.7%
72
- Init CPU: 45.2% (ok)
73
- Last cleanup: 2025-08-22T10:30:00.000Z
115
+
116
+ Output:
117
+ ```
118
+ L1 cache: 16 entries, 43.2KB | hits 12 (replays 12), misses 4, writes 4, evictions 0 | dir: C:/Users/Tobias/.pi/agent/cache/l1-cache
74
119
  ```
75
120
 
76
- ## Configuration
121
+ ### Clear cache
77
122
 
78
- ### Environment Variables
123
+ ```bash
124
+ /l1-cache clear
125
+ ```
79
126
 
80
- Change defaults without modifying source code:
127
+ Cleared: memory + disk (all `.json` files in cache dir).
81
128
 
82
- | Variable | Default | Description |
83
- |----------|---------|-------------|
84
- | `L1_CACHE_ENABLED` | `true` | Master enable/disable |
85
- | `L1_CACHE_MAX_ENTRIES` | `200` | Maximum number of cache entries |
86
- | `L1_CACHE_MAX_MB` | `20` | Maximum memory in MB |
87
- | `L1_CACHE_TTL` | `3600` | TTL in seconds (1 hour) |
88
- | `L1_CACHE_LOG` | `false` | Enable debug logging |
129
+ ## Key Semantics
89
130
 
90
- Example:
91
- ```bash
92
- # Disable cache
93
- L1_CACHE_ENABLED=false pi
131
+ The cache key is a stable hash of:
132
+ - `model`
133
+ - `messages` (full conversation history)
134
+ - `tools`
135
+ - `tool_choice`
136
+ - `temperature`, `top_p`
137
+ - `reasoning_effort`, `thinking`
138
+ - `max_completion_tokens`, `max_tokens`
94
139
 
95
- # Use 50MB cache with 30-minute TTL
96
- L1_CACHE_MAX_MB=50 L1_CACHE_TTL=1800 pi
97
- ```
140
+ **Volatile fields are EXCLUDED:** `prompt_cache_key`, `prompt_cache_retention`, `stream`, `stream_options`, `store`, `sessionId`.
98
141
 
99
- ### Default Settings
142
+ This means:
143
+ - ✅ Cross-run hits possible (same prompt, same cwd, same model)
144
+ - ✅ Tool-calling agent loops fully replayed (tool results are part of messages)
145
+ - ❌ Different conversation history = different key (expected)
146
+ - ❌ Session-derived fields don't break cross-run hits
100
147
 
101
- Edit `src/index.ts` (lines 30-38) to change compiled-in defaults:
148
+ ## Gotchas
102
149
 
103
- ```typescript
104
- const DEFAULTS: Settings = {
105
- enabled: true,
106
- maxEntries: 200,
107
- maxMemoryBytes: 20 * 1024 * 1024, // 20MB
108
- ttlSeconds: 3600, // 1 hour
109
- cpuThreshold: 95, // disable if CPU > 95%
110
- logStats: false,
111
- }
112
- ```
150
+ 1. **Replays are canned** — identical input returns the stored response verbatim. For fresh answers, use `/l1-cache clear`.
113
151
 
114
- ## Design Philosophy
152
+ 2. **Print mode** (`pi -p`) reads entire stdin as ONE prompt — you cannot test two identical requests in one process this way.
115
153
 
116
- ### Stoic Unix Principles
117
- - **One thing, done well** — Caching, and only caching
118
- - **Do not rely on external services** — Pure in-memory, no Redis
119
- - **Graceful degradation** — Works even on constrained systems
120
- - **Zero dependencies** — Single TypeScript file
154
+ 3. **llm-timestamp.js** does NOT pollute provider messages (it only appends display-level `message_end` entries), so keys are stable across runs.
121
155
 
122
- ### Performance Optimizations
123
- 1. **Cheap key hashing** (FNV-1a, ~1.5µs/op). Note: Node's native SHA-256 (OpenSSL) is marginally *faster* than a JS FNV loop — hashing is a rounding error next to `JSON.stringify(messages)`, and both are >1000× below provider latency. The real win is *not* a faster hash, it is skipping the provider call.
124
- 2. **L1 only** — avoid disk I/O in hot path
125
- 3. **Batch eviction** — remove 10-20% at a time, not one-by-one
126
- 4. **Periodic cleanup** — async, non-blocking garbage collection
127
- 5. **Single CPU check** — at startup only, not per-request
156
+ 4. **Replayed responses carry original responseId/usage** — accurate since input identical.
128
157
 
129
- ## Testing
158
+ ## Troubleshooting
130
159
 
131
- ```bash
132
- # Run all tests
133
- npm test
160
+ ### "fix-reasoning-content.js: layout changed"
134
161
 
135
- # Watch mode
136
- npm run test:watch
162
+ The `fix-l1-cache.cjs` patch modifies the same file as `fix-reasoning-content.js`. The wrapper's postinstall chain handles this correctly — `fix-reasoning-content.js` now recognizes its work via marker even after the replay branch is added.
137
163
 
138
- # Check TypeScript
139
- npx tsc --noEmit
140
- ```
164
+ If you see this error:
165
+ 1. Ensure you're running the full postinstall chain (not individual scripts)
166
+ 2. Check that `fix-reasoning-content.js` has the marker-based detection (v1.2.3+)
167
+ 3. Verify postinstall order: `fix-reasoning-content.js` BEFORE `fix-l1-cache.cjs`
141
168
 
142
- 17 tests covering:
143
- - Hash consistency & collision resistance
144
- - Size estimation for various data types
145
- - LRU eviction behavior (entry count + memory)
146
- - State management & reset
147
- - Settings override
169
+ ### Cache never hits
148
170
 
149
- ## Benchmark
171
+ Check:
172
+ 1. `L1_CACHE_LOG=true` to see HIT/MISS/STORED logs
173
+ 2. Prompt is byte-identical (including system prompt, tools, cwd context)
174
+ 3. Disk persistence is enabled (`persist: true`)
175
+ 4. TTL hasn't expired (default 1h)
150
176
 
151
- Measured against the published npm artifact (`pi-l1-cache@1.2.1`) on Node 22, using the package's own test hooks and a mocked `ExtensionAPI` with a realistic 15-message conversation history:
177
+ ## Development
152
178
 
153
- | Measurement | Result |
154
- |---|---|
155
- | `fastHash` (FNV-1a) standalone | ~670k ops/s — 1.49µs/op |
156
- | Node native `sha256` (for reference) | ~850k ops/s — 1.18µs/op |
157
- | Full put: hash + map set + size check | ~249k ops/s — 4.0µs/op |
158
- | Interceptor path (hit *and* miss, 15-msg history) | ~138µs/request |
159
- | Cache hit vs uncached provider round-trip (1–3s) | ~7,000–20,000× wall-clock |
160
- | Key uniqueness over 50k synthetic keys | 50,000/50,000 (no collisions) |
161
- | Eviction under 200-slot cap, 20k inserts | cap held; 19,800 evicted |
179
+ ### Testing
162
180
 
163
- > ⚠ **Correction:** earlier versions claimed FNV-1a was "~100× faster than SHA256". That was wrong — Node's native SHA-256 is ~0.8× *faster* in practice. The hash was never the bottleneck: `JSON.stringify` dominates the ~138µs per-request cost. Cache hits are keyed by *byte-identical* requests, so real-world hit rate depends on your workload (best for retries, repeated tool calls and same-prompt reruns).
181
+ ```bash
182
+ # Run unit tests
183
+ npm test
184
+
185
+ # Run the extension manually
186
+ npx tsx src/index.ts
187
+ ```
164
188
 
165
- ## Related Projects
189
+ ### Benchmark
166
190
 
167
- - **[opencode-saia-plugin](https://github.com/tobias-weiss-ai-xr/opencode-saia-plugin)** — SAIA provider for OpenCode
168
- - **[zot-saia-plugin](https://github.com/tobias-weiss-ai-xr/zot-saia-plugin)** SAIA provider for zot CLI
169
- - **[pi-saia-plugin](https://github.com/tobias-weiss-ai-xr/pi-saia-plugin)** SAIA provider for pi coding agent
191
+ ```bash
192
+ # Measure cold vs warm times
193
+ echo "What is 7*6?" | time pi -p "test" # cold
194
+ echo "What is 7*6?" | time pi -p "test" # warm (should be ~1.7× faster)
195
+ ```
170
196
 
171
197
  ## License
172
198
 
173
199
  MIT — see [LICENSE](LICENSE)
174
200
 
175
- ## Maintainer
201
+ ## Contact
176
202
 
177
- [Tobias Weiß](https://github.com/tobias-weiss-ai-xr) — weissto@hrz.uni-marburg.de
203
+ - Issues: https://github.com/tobias-weiss-ai-xr/pi-l1-cache/issues
204
+ - Email: info@graphwiz.ai