cachetoast 0.1.1 → 0.1.2

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
@@ -36,6 +36,8 @@ For sessions of at least 100k tokens (`--min-context-tokens`), Claude Code and P
36
36
 
37
37
  New activity cancels the timer. Successful compaction avoids the handoff for that idle period and does not start another timer. If the agent closes or the computer sleeps past expiry, Cachetoast skips the cold compaction. Resuming while still warm can restore the remaining timer.
38
38
 
39
+ Claude Code 2.1.299+ has its own [idle compaction](https://code.claude.com/docs/en/settings-reference#idlecompaction): terminal sessions on the 1h TTL, about 54 minutes idle, 200k tokens by default. Cachetoast lowers that minimum to match its own (`CLAUDE_CODE_IDLE_COMPACT_MIN_TOKENS`, 100k at least), so Claude usually compacts first and Cachetoast's timer covers what it skips: the 5m TTL, the desktop app and other clients, older versions, and `idleCompaction: false`. Either compaction prevents the hold.
40
+
39
41
  If compaction failed, was skipped, or is unavailable, your next prompt after the deadline is held once:
40
42
 
41
43
  ```text
@@ -60,11 +62,11 @@ Small sessions are left alone, since a fresh session wouldn't be cheaper. Cachet
60
62
  The hold starts at the TTL minus up to 5 minutes (1h → 55 min, 30m → 25 min, 5m → 4 min).
61
63
 
62
64
  - **Claude Code:** read from the session (1h on Pro/Max; 5m on API keys, cloud providers, or usage credits).
63
- - **Codex:** 30m (GPT-5.6 and later).
65
+ - **Codex:** 30m (GPT-5.6 and later). OpenAI's API now also offers `12h`, but Codex CLI 0.163 doesn't request it. A Codex lead has said signed-in Codex users keep their cache indefinitely; OpenAI hasn't documented this, so Cachetoast still assumes 30m. If you trust it, raise the TTL with `--codex-cache-ttl 12h` or `24h`.
64
66
  - **Pi:** 5m for Claude models (1h with `PI_CACHE_RETENTION=long`), otherwise 30m.
65
67
  - **Anything else:** 30m.
66
68
 
67
- To override, run `init --pi-cache-ttl 1h` (or `--codex-cache-ttl`, `--claude-cache-ttl`; `5m`, `30m`, `1h`, `24h`). That saves to `~/.config/cachetoast/config.json`; `.cachetoast/local.json` takes the same format per repo. A TTL reported by a Claude session always wins.
69
+ To override, run `init --pi-cache-ttl 1h` (or `--codex-cache-ttl`, `--claude-cache-ttl`; `5m`, `30m`, `1h`, `12h`, `24h`). That saves to `~/.config/cachetoast/config.json`; `.cachetoast/local.json` takes the same format per repo. A TTL reported by a Claude session always wins.
68
70
 
69
71
  ## Commands
70
72
 
@@ -88,7 +90,7 @@ Commit `.cachetoast/runtime`, `.cachetoast/config.json`, and the agent settings,
88
90
  - Timers require the agent to remain running and awake. Starting a compaction before expiry does not guarantee completion or a cache hit. A failed attempt keeps the handoff available.
89
91
  - Codex exposes compaction to an owning app-server connection, but its repo hooks cannot portably reach an arbitrary open session. Codex gets the native context ceiling and handoff guard, without a proactive timer.
90
92
  - No hold if hooks are disabled, untrusted, or time out.
91
- - Claude plugin statically validated with Claude Code 2.1.292; adapter behavior tested with synthetic sessions. No paid live-compaction experiment was run. Other integration baselines: Codex CLI 0.159.2 and Pi 0.85.1. Research: [docs/research.md](docs/research.md).
93
+ - Claude plugin statically validated with Claude Code 2.1.292; adapter behavior tested with synthetic sessions. Claude's native idle compaction is read from its transcript summary; no live run confirmed it. No paid live-compaction experiment was run. Other integration baselines: Codex CLI 0.163.0 and Pi 0.85.1. Research: [docs/research.md](docs/research.md).
92
94
 
93
95
  ## Develop
94
96
 
@@ -4,7 +4,7 @@ import path from 'node:path';
4
4
  import { handleHook, idleState, recordIdle, configAt, ttlChoice, statePath, userConfigPath, KEY_RE, PROVIDERS } from '../lib/runtime.mjs';
5
5
  import { readJson, readText, safePath } from '../lib/storage.mjs';
6
6
 
7
- const HELP = `Cachetoast 0.1.1 — local handoffs, inspired in part by Antiburn
7
+ const HELP = `Cachetoast 0.1.2 — local handoffs, inspired in part by Antiburn
8
8
  Usage: cachetoast <command> [--repo PATH] [options]
9
9
  init Install in the current repo (or --repo) for the agents you have installed.
10
10
  [--provider claude,codex,pi] [--dry-run]
@@ -17,7 +17,7 @@ Usage: cachetoast <command> [--repo PATH] [options]
17
17
  uninstall Restore installation files if they have no external edits
18
18
  hook --provider claude|codex: JSON lifecycle event on stdin (internal; Pi runs in-process)
19
19
  Cache TTLs are read from Claude Code sessions, 30m for Codex (GPT-5.6+), and guessed from Pi's model.
20
- To override (5m, 30m, 1h, 24h), pass --<agent>-cache-ttl: saved per user in ~/.config/cachetoast/config.json,
20
+ To override (5m, 30m, 1h, 12h, 24h), pass --<agent>-cache-ttl: saved per user in ~/.config/cachetoast/config.json,
21
21
  or per repo in the gitignored .cachetoast/local.json. A TTL a Claude session reports always wins.
22
22
  Claude Code and Pi attempt idle native compaction at 4m / 25m / 55m for 5m / 30m / 1h TTLs.
23
23
  Codex uses the handoff fallback; its hooks cannot portably compact an already-open session.
package/docs/research.md CHANGED
@@ -4,6 +4,8 @@ Checked October 1, 2026 against primary provider documentation. Installed client
4
4
 
5
5
  Updated October 17, 2026: Claude's current mods API supports host timers and in-process compaction. The generated plugin passed strict static validation with Claude Code 2.1.292. Timing, cancellation, native API calls, and fallback state are tested with synthetic sessions; no paid live-compaction experiment was performed.
6
6
 
7
+ Updated October 21, 2026: Claude Code added native idle compaction (2.1.299), and OpenAI added a 12-hour cache lifetime for GPT-5.6+ (October 20). Checked against Codex CLI 0.163.0; Pi was not rechecked.
8
+
7
9
  ## What survives cache expiry
8
10
 
9
11
  Cache expiry removes reusable inference state, not the transcript or its context window. Continuing sends the current context again. Earlier compacted-away history is not magically restored. Native compaction changes the prompt prefix and can require a new cache write. A handoff prepared locally avoids submitting long history solely to summarize it.
@@ -14,13 +16,15 @@ Claude’s current docs say a cold `/compact` summarization request reprocesses
14
16
 
15
17
  Included subscription main turns normally request one hour. API keys, cloud providers, and usage-credit billing normally use five minutes. Most helpers, including compaction, use five minutes unless overridden. Explicit controls exist: `promptCacheTtl` and `subagentPromptCacheTtl` settings, `CLAUDE_CODE_PROMPT_CACHE_TTL` (`5m`/`1h`), `ENABLE_PROMPT_CACHING_1H`, and `FORCE_PROMPT_CACHING_5M`. Switching from included usage to credits can shorten the effective lifetime. These are per-request controls, not a universal one-hour guarantee. Each assistant transcript record's `usage.cache_creation` splits cache writes into `ephemeral_1h_input_tokens` and `ephemeral_5m_input_tokens`, so Cachetoast reads the TTL a session actually gets from its latest write rather than from settings or billing. See [Claude Code prompt caching](https://code.claude.com/docs/en/prompt-caching).
16
18
 
19
+ Claude Code 2.1.299 added [idle compaction](https://code.claude.com/docs/en/settings-reference#idlecompaction) (settings exist from 2.1.290): in an interactive terminal session on the one-hour TTL, with feature flags reachable, a conversation of at least 200K tokens is compacted after about 54 idle minutes, and the conversation shows `Compacted while idle, before the prompt cache expired`. `CLAUDE_CODE_IDLE_COMPACT_MIN_TOKENS` changes the minimum (values below 100K are raised to 100K); `idleCompaction: false` or `DISABLE_AUTO_COMPACT` turns it off. It does not cover the five-minute TTL, the desktop app, or 100K–200K sessions at its default. Cachetoast sets the minimum to its own (at least 100K) and keeps its mod for the rest. The docs do not say whether this compaction reaches a mod's `session.compact` hook, so Cachetoast instead treats a compaction summary after the latest usage record as an unmeasured context: no hold until the next response reports a size.
20
+
17
21
  Anthropic’s API supports five-minute and one-hour cache writes, priced at 1.25× and 2× ordinary input respectively. Reads are usually 0.1× with model-specific exceptions. Hits refresh the TTL. This is API pricing, not a formula for subscription quota. See [Anthropic prompt caching](https://platform.claude.com/docs/en/build-with-claude/prompt-caching).
18
22
 
19
23
  Claude plan usage is shared across Claude and Claude Code. API-key authentication can use separate API billing. Exact subscription allowance savings should not be inferred from API-dollar estimates. See [Claude Pro/Max usage and billing](https://support.claude.com/en/articles/11145838-use-claude-code-with-your-pro-or-max-plan).
20
24
 
21
25
  ## OpenAI API versus Codex quota/credits
22
26
 
23
- Current API docs distinguish model generations. GPT-5.6 and later support a default/minimum 30-minute cache lifetime and charge 1.25× uncached input for cache writes. GPT-5.6+ set this with `prompt_cache_options.ttl` (only `30m`). Older models use `prompt_cache_retention`: `in_memory` entries commonly last 5–10 idle minutes, up to an hour; `24h` extended retention can last a day and is the default for organizations without Zero Data Retention where both are supported. GPT-5.5 supports only `24h`. Prefix changes and routing also affect hits. A single “30-minute Codex cache” rule cannot be applied to every model and billing mode. Codex transcripts record no TTL, so Cachetoast supports GPT-5.6 and later only and assumes their 30-minute minimum. See [OpenAI prompt caching](https://developers.openai.com/api/docs/guides/prompt-caching).
27
+ Current API docs distinguish model generations. GPT-5.6 and later set the lifetime with `prompt_cache_options.ttl`: `30m` (default and minimum, writes at 1.25× uncached input) or, since October 20, 2026, `12h` (Responses API only, writes at 2.5×); `prompt_cache_breakpoint.ttl` overrides one breakpoint. Reads are 0.1× on most models and 0.05× on GPT-6.1 Sol. Codex CLI 0.163.0 sends neither `prompt_cache_options` nor `prompt_cache_retention` (the strings appear only in its bundled migration notes), so API-key Codex gets the 30-minute default. Older models use `prompt_cache_retention`: `in_memory` entries commonly last 5–10 idle minutes, up to an hour; `24h` extended retention can last a day and is the default for organizations without Zero Data Retention where both are supported. GPT-5.5 supports only `24h`. Prefix changes and routing also affect hits. A single “30-minute Codex cache” rule cannot be applied to every model and billing mode. Codex transcripts record no TTL (0.163 adds `cache_write_input_tokens` but no lifetime), so Cachetoast supports GPT-5.6 and later only and assumes their 30-minute minimum. On October 20, 2026, Codex lead Tibo Sottiaux posted on X that signed-in Codex and Work users keep their cache indefinitely. Neither the Codex nor the API docs say so, and the API docs state cache entries are not stored indefinitely, so Cachetoast still assumes 30m for Codex. Users who rely on the claim can set `--codex-cache-ttl 12h` or `24h`. See [OpenAI prompt caching](https://developers.openai.com/api/docs/guides/prompt-caching).
24
28
 
25
29
  Codex credit billing explicitly has no separate cache-write charge; API-key usage follows API pricing. Cached and uncached input have distinct credit rates. Included subscription usage is not determined solely by credit prices. Consequently “rehydration is free/irrelevant on subscriptions” does not follow. No public source reviewed establishes a universal exact quota weighting or Codex-client retention contract. See [Codex pricing and limits](https://learn.chatgpt.com/docs/pricing).
26
30
 
@@ -40,7 +44,7 @@ Both clients expose command hooks including `UserPromptSubmit`, `Stop`, and comp
40
44
 
41
45
  Claude's newer [mods API](https://code.claude.com/docs/en/plugins/mods/reference) exposes `$.clock.after()` and `$.session.compact()`. The latter uses native semantic compaction between turns, rejects while a turn runs, and returns a skip reason when vetoed. Cachetoast installs a bundled module under `.claude/skills/cachetoast-idle`; [project plugins](https://code.claude.com/docs/en/plugins/loading#plugins-shared-through-a-repository) load after trust, from the primary working directory (desktop worktrees may load the main checkout's plugin). Launch from the repo root. Mods require terminal v2.1.287+ or desktop v2.1.286+ per the [mod creation docs](https://code.claude.com/docs/en/plugins/mods/create). The module uses the host process API to read/write Cachetoast state through its copied Node runtime, without invoking another agent or editing the transcript itself.
42
46
 
43
- Claude and Pi now attempt compaction at TTL minus up to five minutes: 5m → 4m, 30m → 25m, 1h → 55m. New activity cancels the timer. Each idle cycle has at most one attempt; success suppresses the fallback for that cycle. Suspended timers waking at or beyond expiry skip compaction. Native compaction has model cost and cache hits remain uncertain.
47
+ Claude (alongside Claude Code's own idle compaction, which fires about a minute earlier on one-hour terminal sessions) and Pi attempt compaction at TTL minus up to five minutes: 5m → 4m, 30m → 25m, 1h → 55m. New activity cancels the timer. Each idle cycle has at most one attempt; success suppresses the fallback for that cycle. Suspended timers waking at or beyond expiry skip compaction. Native compaction has model cost and cache hits remain uncertain.
44
48
 
45
49
  Codex's [app-server compaction API](https://learn.chatgpt.com/docs/app-server) exposes `thread/compact/start` to an owning connection. This does not establish a portable repo-hook connection to an arbitrary running CLI or desktop session. Cachetoast keeps Codex's threshold and handoff fallback, and installs no private-socket, UI, or second-process transcript workaround.
46
50
 
package/lib/install.mjs CHANGED
@@ -97,13 +97,18 @@ export function install(root, options = {}) {
97
97
  const prior = settings.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW;
98
98
  if (prior != null && !/^\d+$/.test(String(prior))) throw new Error('Existing Claude compact window must be a plain integer');
99
99
  settings.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW = String(Math.min(prior == null ? target : Number(prior), target));
100
+ // Claude Code's own idle compaction (1h TTL, terminal) starts at 200K; lower it to this policy's minimum
101
+ // (Claude's floor is 100K) so it covers the same sessions. The mod still covers 5m TTLs and other clients.
102
+ const priorIdle = settings.env.CLAUDE_CODE_IDLE_COMPACT_MIN_TOKENS, idleMin = Math.max(100_000, config.minContextTokens);
103
+ if (priorIdle != null && !/^\d+$/.test(String(priorIdle))) throw new Error('Existing Claude idle compaction minimum must be a plain integer');
104
+ settings.env.CLAUDE_CODE_IDLE_COMPACT_MIN_TOKENS = String(Math.min(priorIdle == null ? idleMin : Number(priorIdle), idleMin));
100
105
  // The bootstrap explicitly enables this compaction policy in the repo.
101
106
  settings.autoCompactEnabled = true;
102
107
  for (const flag of ['DISABLE_AUTO_COMPACT', 'DISABLE_COMPACT']) if (settings.env[flag] === '1') settings.env[flag] = '0';
103
108
  set('.claude/settings.json', JSON.stringify(mergeHooks(settings, 'claude'), null, 2) + '\n');
104
109
  // Claude discovers trusted project plugins here. Relative paths survive moves and clones.
105
110
  const plugin = '.claude/skills/cachetoast-idle';
106
- set(`${plugin}/.claude-plugin/plugin.json`, JSON.stringify({ name: 'cachetoast-idle', version: '0.1.1', author: { name: 'R. Hunter Harris' }, description: 'Compact large idle sessions before estimated cache expiry' }, null, 2) + '\n');
111
+ set(`${plugin}/.claude-plugin/plugin.json`, JSON.stringify({ name: 'cachetoast-idle', version: '0.1.2', author: { name: 'R. Hunter Harris' }, description: 'Compact large idle sessions before estimated cache expiry' }, null, 2) + '\n');
107
112
  set(`${plugin}/hooks/hooks.json`, JSON.stringify({ modules: ['./register.mjs'] }, null, 2) + '\n');
108
113
  const idle = readText(path.join(PACKAGE_ROOT, 'lib/idle.mjs')).replace(/^export /gm, '');
109
114
  const adapter = readText(path.join(PACKAGE_ROOT, 'lib/claude-mod.mjs')).replace(/^import .*\n/, '');
@@ -122,7 +127,7 @@ export function install(root, options = {}) {
122
127
  set('.pi/extensions/cachetoast.ts', '// Managed by Cachetoast; edits are overwritten by init.\nexport { default } from "../../.cachetoast/runtime/lib/pi-extension.mjs";\n');
123
128
  }
124
129
  set('.cachetoast/config.json', JSON.stringify(config, null, 2) + '\n');
125
- set('.cachetoast/README.md', '# Cachetoast\n\nThe runtime, config.json, and provider hook settings can be committed. local.json (optional per-repo cache TTL overrides), state, handoffs, and the manifest stay local: handoffs contain conversation excerpts.\n\nLarge idle Claude Code and Pi sessions get one native compaction attempt before estimated expiry: 4m for a 5m TTL, 25m for 30m, 55m for 1h. Claude uses the trusted repo plugin in .claude/skills/cachetoast-idle (launch from the repo root; requires mods support); Pi uses its extension. Keep the agent running and awake. Native compaction uses the agent’s model and normal billing. Codex has no portable repo-hook compaction trigger. If compaction cannot run or fails, the first prompt after the idle deadline is held once with a fresh-session handoff (in a new session, copy `/cachetoast-handoff KEY` in Claude Code or `$cachetoast-handoff KEY` in Codex from the notice; in Pi, `/handoff` opens one); sending it again continues. The key selects the held session even when others have been held since. Omitting it loads the latest handoff for that provider. Handoffs are local extracts, not model summaries. Cache TTLs are read from Claude Code sessions, 30m for Codex, and guessed from the Pi model; override one with `node .cachetoast/runtime/bin/cachetoast.mjs init --pi-cache-ttl 1h` (saved in ~/.config/cachetoast/config.json). Inspect with `status` and `handoff --key KEY`.\n\nInspired in part by [Antiburn](https://antiburn.com/) and its [open source work](https://github.com/antiburn/antiburn).\n');
130
+ set('.cachetoast/README.md', '# Cachetoast\n\nThe runtime, config.json, and provider hook settings can be committed. local.json (optional per-repo cache TTL overrides), state, handoffs, and the manifest stay local: handoffs contain conversation excerpts.\n\nLarge idle Claude Code and Pi sessions get one native compaction attempt before estimated expiry: 4m for a 5m TTL, 25m for 30m, 55m for 1h. Claude uses the trusted repo plugin in .claude/skills/cachetoast-idle (launch from the repo root; requires mods support); Pi uses its extension. Keep the agent running and awake. Native compaction uses the agent’s model and normal billing. Claude Code 2.1.299+ also compacts idle 1h terminal sessions itself (about 54 minutes); its minimum is set to match. Codex has no portable repo-hook compaction trigger. If compaction cannot run or fails, the first prompt after the idle deadline is held once with a fresh-session handoff (in a new session, copy `/cachetoast-handoff KEY` in Claude Code or `$cachetoast-handoff KEY` in Codex from the notice; in Pi, `/handoff` opens one); sending it again continues. The key selects the held session even when others have been held since. Omitting it loads the latest handoff for that provider. Handoffs are local extracts, not model summaries. Cache TTLs are read from Claude Code sessions, 30m for Codex, and guessed from the Pi model; override one with `node .cachetoast/runtime/bin/cachetoast.mjs init --pi-cache-ttl 1h` (saved in ~/.config/cachetoast/config.json). Inspect with `status` and `handoff --key KEY`.\n\nInspired in part by [Antiburn](https://antiburn.com/) and its [open source work](https://github.com/antiburn/antiburn).\n');
126
131
  const ignore = readText(safePath(root, '.gitignore'));
127
132
  const ignores = ['state/', 'handoffs/', 'manifest.json', 'local.json'].map(p => `.cachetoast/${p}`);
128
133
  const missing = ignores.filter(p => !ignore.split('\n').includes(p));
package/lib/policy.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  export const DEFAULT_TARGET = 250_000;
2
2
  export const DEFAULT_MIN_CONTEXT = 100_000;
3
- export const CACHE_TTLS = Object.freeze({ '5m': 300_000, '30m': 1_800_000, '1h': 3_600_000, '24h': 86_400_000 });
3
+ export const CACHE_TTLS = Object.freeze({ '5m': 300_000, '30m': 1_800_000, '1h': 3_600_000, '12h': 43_200_000, '24h': 86_400_000 });
4
4
 
5
5
  export function positive(value, name) {
6
6
  if (!Number.isSafeInteger(value) || value <= 0) throw new Error(`${name} must be a positive integer`);
@@ -59,6 +59,9 @@ export function readTranscript(file, provider) {
59
59
  if (assistant) result.assistant = terse(assistant, 2400);
60
60
  if (summary) result.summary = terse(summary, 3000);
61
61
  if (Number.isFinite(tokens) && tokens >= 0) result.contextTokens = tokens;
62
+ // A summary after the latest usage means the context was replaced, e.g. by Claude Code's own idle
63
+ // compaction, which no hook reports. The old size no longer applies until the next response measures it.
64
+ else if (summary) result.contextTokens = null;
62
65
  const ts = Date.parse(r.timestamp);
63
66
  if (activity && Number.isFinite(ts)) result.lastActivity = Math.max(result.lastActivity ?? 0, ts);
64
67
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cachetoast",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Local, provider-neutral handoffs and cache-aware session rotation for coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",