pi-keepwarm
Pi extension: manually keep a long session's prompt cache warm while you're idle, so the next message after a long break doesn't pay a full cache rewrite.
Package details
Install pi-keepwarm from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-keepwarm- Package
pi-keepwarm- Version
0.1.1- Published
- Sep 25, 2026
- Downloads
- 341/mo · 12/wk
- Author
- cminn10
- License
- MIT
- Types
- extension
- Size
- 40.5 KB
- Dependencies
- 0 dependencies · 1 peer
Pi manifest JSON
{
"extensions": [
"./extensions/keepwarm/index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-keepwarm
English | 中文
A pi extension that keeps a long session's prompt cache warm while you're idle, so the first message after a 20–60 minute break reads the cache instead of paying for a full cache rewrite.
By default you switch it on per session with /keepwarm and off when you're done. With autoStart in the config file it turns on in every session.
Why
Provider prompt caches expire quickly when nothing touches them:
| Provider | Default cache lifetime |
|---|---|
| Anthropic (Claude) | 5 min (a hit resets the timer; optional 1h tier writes at 2× base input) |
| OpenAI, GPT-5.6+ | ≥ 30 min |
| OpenAI, earlier models | in_memory: ~5–10 min; 24h retention where supported |
In a 150k-token Claude session, stepping away for 10 minutes means the next message rewrites the whole prefix at 1.25× base input price. Refreshing the cache before it expires only costs a cache read of the prefix: 0.1× base input on most Claude models, lower on some.
Pi's built-in cacheWarming setting refreshes during runs, or for at most 30 minutes after a run with "idle". keepwarm is for longer breaks: you turn it on yourself, and it keeps refreshing until you turn it off or a time or cost limit is reached.
Install
pi install npm:pi-keepwarm
or from git:
pi install git:github.com/cminn10/pi-keepwarm
Usage
| Command | Effect |
|---|---|
/keepwarm |
Toggle; turning on uses the defaults from the config file |
/keepwarm on 5h $20 |
Turn on; given limits override the configured defaults |
/keepwarm on forever |
No time limit (the configured cost cap still applies) |
/keepwarm on 5h (while on) |
Change only the time limit (counted from now) |
/keepwarm on $30 (while on) |
Change only the cost cap; nocap removes it |
/keepwarm status |
Show state, TTL, next refresh, refreshes, cost, failures |
/keepwarm config |
Show the config file path and effective defaults |
/keepwarm off |
Stop |
The cost cap counts all keepwarm spend in the session, including before an off/on.
Configuration
On first session start keepwarm creates ~/.pi/agent/keepwarm.json (or the equivalent under PI_CODING_AGENT_DIR) with the defaults:
{
"autoStart": false,
"duration": "2h",
"maxCost": null,
"maxRetries": 2
}
| Key | Meaning |
|---|---|
autoStart |
true turns keepwarm on automatically in every session |
duration |
Default time limit: "90m", "2h", "forever" |
maxCost |
Default cost cap in USD for keepwarm spend per session, or null for no cap |
maxRetries |
Stop after this many consecutive failed refreshes (errors or cache misses) |
Example: always on, no time limit, at most $10 per session:
{ "autoStart": true, "duration": "forever", "maxCost": 10, "maxRetries": 2 }
The file is re-read on every session start and every /keepwarm / /keepwarm on, so edits apply without reloading. Invalid values fall back to the built-in default and are reported. Keys starting with _ (like the generated _help) are ignored.
Status bar:
🔥 keepwarm · ⏳ active · 3× $0.09/$20.00 agent is running, refreshes paused
🔥 keepwarm · next 10:52 PM · 4× $0.12/$20.00 idle, next refresh time
How it works
- Every real provider request in the session is captured in
before_provider_request. - While the agent is idle, just before the cache would expire, the last captured request is re-sent byte-for-byte with the smallest output cap (
max_tokens: 1on Anthropic,max_output_tokens: 16on OpenAI Responses). An identical prefix means the provider serves it from cache and resets the cache lifetime. Nothing is added to the conversation. - The next refresh is scheduled at 90% of the TTL, and at least 15s before expiry, measured from the start of the last request that touched the cache. Provider TTLs run from request start.
- No refreshes during a run. keepwarm pauses on
agent_startand resumes onagent_settled. Long tool calls inside a run are covered by pi's built-incacheWarming: "streaming"(the default). While idle, keepwarm tells the built-in warmer to stand down so the two don't double-refresh. - TTL is detected from the payload:
cache_control.ttl: "1h"on Anthropic,prompt_cache_retention/prompt_cache_optionson OpenAI. A model'spromptCachelifetimes inmodels.jsontake precedence. - Warming pauses until the next real message on model switch, compaction,
/treenavigation, or if the refresh timer fires after the cache has already expired (for example after the machine slept). A refresh at that point would be a full-price write. - Each refresh is recorded as a
keepwarmcustom entry in the session file with its provider usage and cost. - A failed refresh is retried after 15s while the cache is still alive. After
maxRetriesconsecutive failures keepwarm turns itself off. A refresh that reports a cache write instead of a read counts as a failure, since the replay no longer matches. A successful refresh or a new real request resets the count.
When it pays off
With a context of P tokens and base input price B on Anthropic's 5-minute cache (a refresh every 4.5 minutes):
| Cost | |
|---|---|
| One refresh (cache read) | P × B × read multiplier (0.1× on most Claude models) |
| Idle for T minutes with keepwarm | about T / 4.5 refreshes |
| One cold rewrite after the cache expired | P × B × 1.25 |
Keeping the cache warm is cheaper than one rewrite for breaks up to about 4.5 × 1.25 / read multiplier minutes: about 55 minutes at 0.1×, about 110 minutes at 0.05×. Past that point, letting the cache expire is cheaper. Use duration / maxCost to match your typical breaks.
Supported APIs
anthropic-messages, openai-responses, azure-openai-responses, openai-codex-responses, openai-completions.
Notes and limits
- Runtime state (on/off, spend, captured request) is per session and in memory. After
/reloador reopening a session, keepwarm starts fresh: auto-started ifautoStartis set, otherwise run/keepwarmagain. It arms on the next message. - If another extension rewrites the provider payload after keepwarm captures it, the replay won't match and will be a cache write. keepwarm reports such refreshes as a cache miss, and they count toward
maxRetries. - Refresh cost is recorded in the session file but not included in pi's own session cost totals.
- Tested with pi 0.87.1.
Testing overrides
PI_KEEPWARM_EVERY_SEC forces the refresh interval. PI_KEEPWARM_TTL_SEC forces the assumed TTL. PI_KEEPWARM_TEST_FAIL=1 makes refreshes fail (invalid max_tokens) to exercise retry handling.
Development
bun install
bun run check # typecheck + tests + npm pack --dry-run
pi install . # load your working copy in pi
Releases are cut by the manual Release GitHub Actions workflow (Actions → Release → Run workflow). It runs CI, bumps the version (patch / minor / major, or none), pushes the commit and tag, publishes to npm via trusted publishing (OIDC, with provenance), and creates a GitHub release. Tick dry run to only run CI and npm publish --dry-run.
License
MIT