pi-l1-cache 1.2.2 → 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 +128 -112
- package/fix-l1-cache.cjs +405 -0
- package/package.json +11 -9
- package/src/index.test.ts +457 -179
- package/src/index.ts +272 -255
package/README.md
CHANGED
|
@@ -1,188 +1,204 @@
|
|
|
1
1
|
# pi-l1-cache
|
|
2
2
|
|
|
3
|
-
> **L1 In-Memory Cache Extension for pi** —
|
|
3
|
+
> **L1 In-Memory Cache Extension for pi** — with disk persistence, response replay, and working capture
|
|
4
4
|
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
6
|
[](https://pi.dev/packages)
|
|
7
|
-
[](https://github.com/tobias-weiss-ai-xr/pi-l1-cache/actions/workflows/ci.yml)
|
|
7
|
+
[](https://www.npmjs.com/package/pi-l1-cache)
|
|
9
8
|
|
|
10
|
-
A high-performance, production-ready **L1 (in-memory) cache** extension for the [pi coding agent](https://pi.dev).
|
|
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×** |
|
|
11
17
|
|
|
12
18
|
## Features
|
|
13
19
|
|
|
14
20
|
| Feature | Description |
|
|
15
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/`) |
|
|
16
24
|
| **~138µs overhead** | End-to-end per-request cost (measured, incl. key hashing + JSON) |
|
|
17
25
|
| **Fast key hash** | FNV-1a (~1.5µs/op) — no per-request CPU probe on the hot path |
|
|
18
26
|
| **Memory cap** | Hard limit (20MB default) prevents RAM bloat |
|
|
19
27
|
| **Auto-eviction** | LRU-style cleanup when limits reached |
|
|
20
|
-
| **CPU-aware** | Auto-disables when CPU > 95% (checked once at startup) |
|
|
21
28
|
| **TTL-based** | 1-hour default expiry for cached entries |
|
|
22
|
-
| **
|
|
29
|
+
| **Coalescing** | Adjacent content/reasoning_content deltas merged to shrink storage |
|
|
23
30
|
|
|
24
31
|
## Architecture
|
|
25
32
|
|
|
26
33
|
```
|
|
27
|
-
pi → [L1: RAM Map
|
|
28
|
-
~138µs
|
|
34
|
+
pi → [L1: RAM Map + disk] → Provider
|
|
35
|
+
~138µs lookup 1-3s
|
|
29
36
|
```
|
|
30
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
|
+
|
|
31
46
|
## Installation
|
|
32
47
|
|
|
48
|
+
### Option 1: From npm (recommended)
|
|
49
|
+
|
|
33
50
|
```bash
|
|
34
|
-
# From npm (recommended)
|
|
35
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.
|
|
36
55
|
|
|
37
|
-
|
|
38
|
-
|
|
56
|
+
### Option 2: From GitHub
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pi install git:github.com:tobias-weiss-ai-xr/pi-l1-cache@main
|
|
60
|
+
```
|
|
39
61
|
|
|
40
|
-
|
|
62
|
+
### Option 3: From local clone
|
|
63
|
+
|
|
64
|
+
```bash
|
|
41
65
|
pi install /path/to/pi-l1-cache
|
|
42
66
|
```
|
|
43
67
|
|
|
44
68
|
Then **restart pi** — the extension auto-loads.
|
|
45
69
|
|
|
46
|
-
##
|
|
70
|
+
## Configuration
|
|
71
|
+
|
|
72
|
+
### Environment Variables
|
|
47
73
|
|
|
48
74
|
```bash
|
|
49
|
-
#
|
|
50
|
-
|
|
75
|
+
# Enable/disable
|
|
76
|
+
L1_CACHE_ENABLED=true
|
|
51
77
|
|
|
52
|
-
#
|
|
53
|
-
|
|
78
|
+
# Max entries (default: 200)
|
|
79
|
+
L1_CACHE_MAX_ENTRIES=200
|
|
54
80
|
|
|
55
|
-
#
|
|
56
|
-
|
|
81
|
+
# Max memory in MB (default: 20)
|
|
82
|
+
L1_CACHE_MAX_MB=20
|
|
57
83
|
|
|
58
|
-
#
|
|
59
|
-
|
|
84
|
+
# TTL in seconds (default: 3600)
|
|
85
|
+
L1_CACHE_TTL=3600
|
|
60
86
|
|
|
61
|
-
#
|
|
62
|
-
|
|
87
|
+
# Log stats on each hit/miss (default: false)
|
|
88
|
+
L1_CACHE_LOG=true
|
|
63
89
|
```
|
|
64
90
|
|
|
65
|
-
###
|
|
66
|
-
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
+
}
|
|
76
106
|
```
|
|
77
107
|
|
|
78
|
-
##
|
|
108
|
+
## Usage
|
|
79
109
|
|
|
80
|
-
###
|
|
110
|
+
### Show cache stats
|
|
81
111
|
|
|
82
|
-
|
|
112
|
+
```bash
|
|
113
|
+
/l1-cache
|
|
114
|
+
```
|
|
83
115
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
| `L1_CACHE_MAX_MB` | `20` | Maximum memory in MB |
|
|
89
|
-
| `L1_CACHE_TTL` | `3600` | TTL in seconds (1 hour) |
|
|
90
|
-
| `L1_CACHE_LOG` | `false` | Enable debug logging |
|
|
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
|
|
119
|
+
```
|
|
91
120
|
|
|
92
|
-
|
|
93
|
-
```bash
|
|
94
|
-
# Disable cache
|
|
95
|
-
L1_CACHE_ENABLED=false pi
|
|
121
|
+
### Clear cache
|
|
96
122
|
|
|
97
|
-
|
|
98
|
-
|
|
123
|
+
```bash
|
|
124
|
+
/l1-cache clear
|
|
99
125
|
```
|
|
100
126
|
|
|
101
|
-
|
|
127
|
+
Cleared: memory + disk (all `.json` files in cache dir).
|
|
102
128
|
|
|
103
|
-
|
|
129
|
+
## Key Semantics
|
|
104
130
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
```
|
|
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`
|
|
115
139
|
|
|
116
|
-
|
|
140
|
+
**Volatile fields are EXCLUDED:** `prompt_cache_key`, `prompt_cache_retention`, `stream`, `stream_options`, `store`, `sessionId`.
|
|
117
141
|
|
|
118
|
-
|
|
119
|
-
-
|
|
120
|
-
-
|
|
121
|
-
-
|
|
122
|
-
-
|
|
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
|
|
123
147
|
|
|
124
|
-
|
|
125
|
-
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.
|
|
126
|
-
2. **L1 only** — avoid disk I/O in hot path
|
|
127
|
-
3. **Batch eviction** — remove 10-20% at a time, not one-by-one
|
|
128
|
-
4. **Periodic cleanup** — async, non-blocking garbage collection
|
|
129
|
-
5. **Single CPU check** — at startup only, not per-request
|
|
148
|
+
## Gotchas
|
|
130
149
|
|
|
131
|
-
|
|
150
|
+
1. **Replays are canned** — identical input returns the stored response verbatim. For fresh answers, use `/l1-cache clear`.
|
|
132
151
|
|
|
133
|
-
|
|
134
|
-
# Run all tests
|
|
135
|
-
npm test
|
|
152
|
+
2. **Print mode** (`pi -p`) reads entire stdin as ONE prompt — you cannot test two identical requests in one process this way.
|
|
136
153
|
|
|
137
|
-
|
|
138
|
-
npm run test:watch
|
|
154
|
+
3. **llm-timestamp.js** does NOT pollute provider messages (it only appends display-level `message_end` entries), so keys are stable across runs.
|
|
139
155
|
|
|
140
|
-
|
|
141
|
-
npx tsc --noEmit
|
|
142
|
-
```
|
|
156
|
+
4. **Replayed responses carry original responseId/usage** — accurate since input identical.
|
|
143
157
|
|
|
144
|
-
|
|
145
|
-
- Hash consistency & collision resistance
|
|
146
|
-
- Size estimation for various data types
|
|
147
|
-
- LRU eviction behavior (entry count + memory)
|
|
148
|
-
- State management & reset
|
|
149
|
-
- Settings override
|
|
158
|
+
## Troubleshooting
|
|
150
159
|
|
|
151
|
-
|
|
160
|
+
### "fix-reasoning-content.js: layout changed"
|
|
152
161
|
|
|
153
|
-
|
|
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.
|
|
154
163
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
| Full put: hash + map set + size check | ~249k ops/s — 4.0µs/op |
|
|
160
|
-
| Interceptor path (hit *and* miss, 15-msg history) | ~138µs/request |
|
|
161
|
-
| Cache hit vs uncached provider round-trip (1–3s) | ~7,000–20,000× wall-clock |
|
|
162
|
-
| Key uniqueness over 50k synthetic keys | 50,000/50,000 (no collisions) |
|
|
163
|
-
| Eviction under 200-slot cap, 20k inserts | cap held; 19,800 evicted |
|
|
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`
|
|
164
168
|
|
|
165
|
-
|
|
169
|
+
### Cache never hits
|
|
166
170
|
|
|
167
|
-
|
|
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)
|
|
168
176
|
|
|
169
|
-
|
|
177
|
+
## Development
|
|
170
178
|
|
|
171
|
-
|
|
172
|
-
- `after_provider_response` currently carries only `{ status, headers }` — **no response body**. Until a pi version exposes the body, responses cannot be stored; the extension detects this, logs a one-time note, and keeps `/l1-cache` stats working. It upgrades automatically (no config) on any pi version that exposes the body.
|
|
179
|
+
### Testing
|
|
173
180
|
|
|
174
|
-
|
|
181
|
+
```bash
|
|
182
|
+
# Run unit tests
|
|
183
|
+
npm test
|
|
184
|
+
|
|
185
|
+
# Run the extension manually
|
|
186
|
+
npx tsx src/index.ts
|
|
187
|
+
```
|
|
175
188
|
|
|
176
|
-
|
|
189
|
+
### Benchmark
|
|
177
190
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
+
```
|
|
181
196
|
|
|
182
197
|
## License
|
|
183
198
|
|
|
184
199
|
MIT — see [LICENSE](LICENSE)
|
|
185
200
|
|
|
186
|
-
##
|
|
201
|
+
## Contact
|
|
187
202
|
|
|
188
|
-
|
|
203
|
+
- Issues: https://github.com/tobias-weiss-ai-xr/pi-l1-cache/issues
|
|
204
|
+
- Email: info@graphwiz.ai
|