Lock the DeepSeek prompt prefix in OpenCode and slash input token costs by 50x to 120x.
Here is what this looks like in my own daily workflow. This is a screenshot directly from my DeepSeek developer console after an intensive coding run using CacheSnipe with official DeepSeek API keys:
Tokens Processed: 70,319,701 tokens (70.3 Million)
Total API Requests: 380 requests
Total Billed Cost: $0.53 USD
Blended Rate: ~$0.0075 / 1M tokens
Processing over 70 million tokens on advanced DeepSeek models across 380 requests for just fifty-three cents is only possible when the prompt prefix stays locked.
Without prefix locking, pushing 70.3 million input tokens through DeepSeek Pro would have cost over $30.00 (or over $10.50 even on Flash). With CacheSnipe locking the prefix, warm turns consistently achieved 95% to 99% KV cache hit rates at $0.0036 per million tokens, keeping the entire 380-request workload down to pocket change.
Two of my own API keys over similar 30-day windows of the same agentic coding workload. One runs behind CacheSnipe, the other does not:
| Tokens | Requests | Billed | Blended rate | |
|---|---|---|---|---|
| Without CacheSnipe | 71,842,615 | 392 | $11.02 | ~$0.153 / 1M |
| With CacheSnipe | 77,880,048 | 438 | $0.64 | ~$0.0082 / 1M |
The uncached key pays essentially full Flash input price ($0.15/M) on nearly every token. At the cached blended rate, the 71.8M-token workload would have cost about $0.59 instead of $11.02 — roughly 18x cheaper per token, same work.
Honest framing: these numbers assume long multi-turn sessions on official DeepSeek keys. The first turn of every session is always cold, and each turn's genuinely new content is always billed at full price. See Limits and Compatibility.
I do offload almost all of my building gruntwork to OpenCode (And I'm pretty sure most of you use cheaper models to do the same). If you have spent any serious time running multi-turn coding sessions with DeepSeek models, you know how fast input token costs can creep up once your context crosses 30,000 to 100,000 tokens.
DeepSeek actually offers unbelievable prompt-cache pricing:
| Model | Uncached Input | Cache Read Input | Discount Factor |
|---|---|---|---|
deepseek-v4-flash |
$0.15 / 1M | $0.003 / 1M | 50x cheaper |
deepseek-v4-pro (deepseek/deepseek-v4-pro-0813) |
$0.435 / 1M | $0.003625 / 1M | 120x cheaper |
The catch is how DeepSeek KV caching works under the hood. DeepSeek caches prompts server-side in contiguous 128-token blocks starting strictly from token 0. If a single character changes at token 40 (for instance, OpenCode updating the current date or pruning past tool results), every subsequent block misses the cache. Your warm session suddenly drops from a 98% discount down to 0%, and you get billed full cold rates on your entire 80k context.
I got tired of watching my cache hit rates collapse mid-session and burning through API credits for no reason. So I built CacheSnipe. It intercepts OpenCode's prompt pipeline and keeps your prompt prefix byte-identical across turns and app restarts, so turns 2 and onward consistently hit 90% to 99%+ cache hit rates with zero lost prefix tokens.
When I started digging into why my cache hit rates were dropping to zero in OpenCode, I found four quiet culprits under the hood:
- Dynamic Date Rollover in
<env>: OpenCode injectsToday's date: ...into the system prompt on every request. At local midnight or when resuming an older session the next morning, that date line changes. Because it sits right near the start of the prompt, the entire KV cache drops to 0% and your full context is re-billed at cold prices. - Skill and MCP Drift: OpenCode scans skills on each turn. If an MCP server connects slightly late or a directory scan reorders tools, the
<available_skills>block shifts and busts alignment. - Destructive Tool Compaction (
compaction.prune): By default, OpenCode compaction can replace older tool outputs mid-history with[Old tool result content cleared]. That rewrites earlier turns and completely ruins the prefix chain. - Desktop App Restarts: When you restart the OpenCode Desktop app, it rebuilds the
<env>block. A tiny variation in environment strings alters token 0 and busts the prefix.
CacheSnipe hooks into OpenCode across seven layers of the request lifecycle:
- L0 Date Freeze: Captures the date line upon first sighting, writes it to persistent session storage on disk, and rewrites any future date changes back to the session-start date. When you resume a session days later, the original cache chain stays intact.
- L0b Block Hashing and Drift Attribution: Computes SHA-256 hashes for
<env>,<available_skills>,<mcp_instructions>, and<available_references>. If drift happens, CacheSnipe pinpoints the exact block and the first line that differed. - L0c CWD Freeze and Order Canonicalization: Freezes
Working directoryandWorkspace root folderto their session-start values and sorts<skill>and MCP<server>entries into canonical order, so directory moves, app restarts, and scan-order jitter no longer bust the prefix. - L2 Prefix Guard: Tracks the message history hash chain. It classifies requests into extensions (healthy), rewinds (
/undoor retries, which remain valid prefixes), compactions, or genuine divergences. - L3 Safe Compaction: Enforces
"compaction": { "prune": false }in OpenCode configuration and directs session compaction todeepseek/deepseek-v4-flashat temperature 0. - L1 Live Telemetry and Reporting: Records exact cache-read tokens, miss input, reasoning tokens, and dollar costs directly from assistant message events.
- L4 Cache Warm-up: Replays the session's persisted baseline blocks through the DeepSeek API before the first large turn so it starts from persisted prefix units. The agent runs it itself via
/cache-warm; a stale resume surfaces a hint in/cache-stats.
- Node.js version 22 or higher (
node -v) - OpenCode Desktop or OpenCode CLI installed
- Official DeepSeek API key configured in OpenCode
Clone the repository and run the automated installer:
git clone https://github.com/Qu4rk/CacheSnipe.git
cd CacheSnipe
./install.shThe installer runs interactively by default:
- Compiles TypeScript to
dist/src/plugin.js. - Copies
/cache-stats,/cache-graph,/cache-reset, and/cache-warminto~/.config/opencode/commands/(creating backups of existing files). - Textually patches
~/.config/opencode/opencode.jsonandopencode.jsoncto enable CacheSnipe, preserve comments, and configure safe compaction.
./install.sh --dry-run # Preview all changes without writing to disk
./install.sh --yes # Unattended installation (auto-confirm)
./install.sh --strict-freeze # Replay session-start blocks so app restarts keep their prefix
./install.sh --no-commands # Skip installing markdown command files
./install.sh --stats-dir <d> # Specify a custom directory for cache telemetryPaste this prompt to your coding agent and it will install CacheSnipe for you:
Install the CacheSnipe OpenCode plugin for me:
1. Clone https://github.com/Qu4rk/CacheSnipe.git to ~/CacheSnipe (if that directory already exists, run `git pull` inside it instead of cloning).
2. Check `node -v` is version 22 or higher; if not, stop and tell me.
3. Run `./install.sh --yes` inside ~/CacheSnipe. Do not pass any other flags unless I ask.
4. Do not edit my opencode.json by hand; let the installer patch it.
5. Report exactly what the installer changed.
6. Tell me to restart the OpenCode app so it loads the plugin, and that I should run /cache-stats after my next DeepSeek turn to confirm it is working.
If you prefer to configure OpenCode manually, add the following to ~/.config/opencode/opencode.json (or opencode.jsonc):
After modifying the configuration, restart OpenCode Desktop so the server loads the plugin.
CacheSnipe provides four custom commands that integrate directly into OpenCode chat. All four commands intentionally omit agent and model frontmatter so running them appends to your current session without starting a new cache chain.
Injects summary.txt directly into your chat. It displays both active session metrics and all-time aggregate totals:
CacheSnipe: DeepSeek prompt-cache report
generated 2026-09-16T13:44:34Z
this session ses_f55dc6a2 deepseek/deepseek-v4-pro-0813
turns 32 assistant messages / 32 requests
hit rate (turns 2+) 96.1% ████████████████████████████░░
prefix lost (turns 2+) 0 tok chain intact (no cached prefix re-sent)
cache read 2,084,352 tok @ $0.003625/1M
miss input 95,744 tok @ $0.435/1M
cache write 0 tok
output 18,420 tok reasoning 4,110 tok
reported cost $0.0297
if nothing had cached $0.9482 -> est. saved $0.9185 (96.9%)
prefix breaks 0
system prompt breaks 0
rewinds 1 compactions 0
frozen date Mon Sep 14 2026
prompt hash c0e104044186a700
blocks env=389550df skills=4339ab3c date=9336994a
Key indicators I check:
- Prefix Lost: The primary health metric. Measures
max(0, cache.read(t-1) - cache.read(t)). In a healthy session, this is always 0. - Hit Rate: Warm hit percentage. The remaining percentage reflects each turn's new tool output and user message, which must be uploaded uncached by definition.
- Break Causes: If a break occurred, CacheSnipe prints the offending block and the first differing line so you can fix it immediately.
Injects graph.txt, rendering a clean ASCII trend chart of the current session hit rates:
CacheSnipe: Hit Rate Trend (ses_f55dc6a2)
T01 [ 0%] ░░░░░░░░░░░░░░░░░░░░ (cold start)
T02 [ 78%] ███████████████░░░░░
T03 [ 93%] ██████████████████░░
T04 [ 90%] ██████████████████░░
T05 [ 96%] ███████████████████░
...
T32 [ 99%] ████████████████████
A healthy session shows Turn 1 cold, followed immediately by a sustained 90%+ plateau. If you see a dip mid-session, the chart highlights where an environment drift or manual model switch occurred.
Archives and clears active session statistics.
Session JSON records are moved to a timestamped backup directory (~/.local/share/opencode/deepseek-cache-archive-<timestamp>) before removing active records. OpenCode's internal SQLite database (opencode.db) is never touched.
Warms the DeepSeek disk cache for the session so the next large turn starts from persisted prefix units instead of paying cold. The agent runs the warm-up script itself (no session id needed — it defaults to the most-recent session) and reports the repeat-ping hit rate:
/cache-warm
When the warmup plugin option is enabled, a session resumed after more than two hours of idle time also records a suggest /cache-warm hint in its notes, visible in /cache-stats, so the agent can offer warming on its own.
OpenCode plugins operate at the server level. This means all agents and subagents inherit CacheSnipe automatically without needing per-agent plugin declarations.
When OpenCode spawns background subagents or parallel workers to explore files, run tests, or execute terminal commands, those child sessions pass through CacheSnipe's hook pipeline automatically. Their prompt prefixes are guarded, their tool runs are tracked, and their token savings register in your aggregate stats.
If you declare named agents directly in ~/.config/opencode/opencode.json (or opencode.jsonc), declare them normally:
{
"agent": {
"code-reviewer": {
"model": "deepseek/deepseek-v4-pro-0813",
"temperature": 0.1,
"description": "Fast code review agent"
},
"test-runner": {
"model": "deepseek/deepseek-v4-flash",
"temperature": 0,
"description": "Deterministic test runner"
}
}
}When defining custom agents in ~/.config/opencode/agents/ (or project-level .opencode/agents/), simply specify a DeepSeek model:
# ~/.config/opencode/agents/code-reviewer.yaml
name: code-reviewer
description: Fast code reviewer with prompt-cache optimization
model: deepseek/deepseek-v4-pro-0813
temperature: 0.1
system_prompt: |
You are an expert code reviewer. Analyze the provided diff for correctness,
performance regressions, and security vulnerabilities.Rules for custom agent authors:
- Pin DeepSeek Models: Declare
deepseek/deepseek-v4-flashordeepseek/deepseek-v4-pro-0813. CacheSnipe detects DeepSeek model identifiers and engages automatically. Non-DeepSeek agents pass through completely untouched. - Keep System Prompts Deterministic: Avoid embedding dynamic runtime expressions (such as timestamps, random seeds, or process IDs) into custom agent system prompt templates. CacheSnipe freezes OpenCode's built-in date tags, but keeping custom agent instructions static ensures every turn aligns cleanly with the KV cache.
- Consistent Tool Configuration: When agents share the same MCP servers and tools across requests, the tool definitions remain stable in the prompt prefix.
You can pass options into CacheSnipe through your OpenCode configuration by replacing the plugin string with a two-element array:
"plugin": [
[
"file:///path/to/CacheSnipe/dist/src/plugin.js",
{
"strictFreeze": true,
"compactionPrompt": "context",
"notifications": false,
"sessionTitle": true,
"warmup": true,
"statsDir": "~/.local/share/opencode/deepseek-cache",
"retentionDays": 30
}
]
]| Option | Type | Default | Description |
|---|---|---|---|
strictFreeze |
boolean |
false |
Replays session-start prompt blocks across restarts. Prevents restart-induced prefix busts at the cost of withholding mid-session skill additions until the next session. |
compactionPrompt |
"context" | "replace" | "off" |
"context" |
Compaction prompt handling mode. "context" injects static compaction guidelines. |
notifications |
boolean |
false |
Sends native macOS desktop notifications on milestones (e.g. 1M cached tokens) and prefix breaks. |
sessionTitle |
boolean |
false |
Appends current cache performance to the session title in the sidebar (for example: [cache 96%]). |
warmup |
boolean |
true |
Records a suggest /cache-warm hint when a session resumes after 2+ hours idle. The plugin itself never fires network requests. |
statsDir |
string |
~/.local/share/opencode/deepseek-cache |
Directory where per-session JSON files and reports are saved. |
retentionDays |
number |
30 |
Number of days before old session telemetry records are pruned. |
providers |
string[] |
[] |
Additional provider prefixes that should trigger CacheSnipe. |
models |
string[] |
[] |
Additional model-id substrings that should trigger CacheSnipe. |
A few important technical details to know upfront. Stating them here so the proof above reads honestly:
- Turn 1 Is Always Cold: DeepSeek must ingest the prefix on Turn 1 before it can cache it. The first turn always incurs standard input pricing.
- 128-Token Tail Residue: Because DeepSeek caches in 128-token increments, the tail end of any prompt (typically 39 to 252 tokens) is re-sent on every turn. A hit rate of 100% is mathematically impossible; a session with 0 prefix lost will typically register between 90% and 99% hit rate depending on context size.
- New Content Is Always Full Price: Each turn's new tool output and user message must be uploaded uncached by definition. The savings apply to the repeated prefix, so short sessions with tiny shared context save less than long ones.
strictFreezeTradeoff: EnablingstrictFreezeensures that an app restart or environment drift cannot bust your cache prefix. The tradeoff is that any new skill or MCP tool added while a session is actively open will not be visible to the model until you start a new session.- Prune Warning: If
"compaction": { "prune": false }is omitted from your configuration, OpenCode may prune older tool results. CacheSnipe checks this on load and logs a visible warning in/cache-stats. - Best-Effort Cache: DeepSeek's disk cache is best-effort with no hit guarantee. It takes seconds to persist after a request and is evicted hours to days after last use, which is why stale resumes benefit from
/cache-warm. - Other Prompt-Rewriting Plugins: Any other plugin that rewrites the system prompt or message history will move the prefix CacheSnipe is guarding. If that happens, the L0b attribution names the offending block and the first differing line so you can identify the conflict immediately.
| Area | Supported |
|---|---|
| OpenCode | 1.18.x Desktop and CLI (experimental.chat.* hooks present). On builds without those hooks, CacheSnipe degrades to telemetry-only instead of breaking. |
| Models | deepseek-v4-flash and deepseek-v4-pro variants (deepseek/deepseek-v4-pro-0813), plus anything matching the providers/models options. Non-DeepSeek sessions pass through untouched and write nothing. |
| API keys | Official DeepSeek keys (https://api.deepseek.com) only. Third-party aggregators and routing proxies often balance turns across upstream servers with separate KV caches, so a byte-identical prefix can still miss server-side. |
| Node | Version 22 or higher. |
CacheSnipe includes an exhaustive test and verification suite:
# 1. Strict TypeScript typechecking
npm run typecheck
# 2. Run the unit test suite (59 tests covering freeze, guard, store, render)
npm test
# 3. Simulate all OpenCode hooks end-to-end against the built artifact
npm run wirecheck
# 4. Measure real session caching directly from OpenCode's SQLite database
npm run verify
# 5. Reconstruct past prefix breaks forensically
npm run breaks
# 6. Run controlled multi-turn experiments against live api.deepseek.com
npm run probescripts/wire-check.mjs verifies the compiled plugin artifact (dist/src/plugin.js) without needing OpenCode running. It stubs OpenCode's plugin environment, triggers every hook (experimental.chat.system.transform, experimental.chat.messages.transform, experimental.session.compacting, and event), asserts on date freezing, verifies block-drift attribution, tests rewind versus divergence classification, and validates that reports are written cleanly to a temporary directory.
Credit where credit is due: CacheSnipe is my own flavour and an architectural port of pi-deepseek-cache originally created by rohaquinlop for the Pi agent harness. A huge thank you to rohaquinlop for proving just how powerful prefix locking is for DeepSeek models and inspiring this work.
When I switched over to using OpenCode Desktop on macOS as my primary daily driver, I really missed the aggressive prompt-cache optimization that rohaquinlop built for Pi. But OpenCode runs on a completely different architecture, so porting it required several major structural changes:
- Electron Node Runtime (No Bun): The original Pi extension relied on Bun runtime primitives (
Bun.$). OpenCode Desktop runs plugins inside an Electron Node.js process where Bun is absent. I rewrote the codebase in TypeScript to compile into clean standard JavaScript with zero external runtime dependencies, using onlynode:*built-ins. - Persistent Date Freeze Across Days: Pi held the frozen date in memory for the active process. In my workflow, I frequently close OpenCode and resume sessions the next morning. CacheSnipe writes the session record to disk, so resuming an older session re-applies its original date and keeps the server-side cache warm.
strictFreezeMode for Desktop Restarts: A custom feature I created specifically for OpenCode Desktop. When enabled, CacheSnipe replays the exact session-start prompt blocks even if you restart the desktop app, completely eliminating restart-induced cache misses.- Granular Block Hashing and Diffing: OpenCode bundles skills, MCP tools, and environment variables into distinct XML blocks. CacheSnipe hashes each block independently and isolates the date line from general environment variables.
- Multi-Workspace Concurrency: I often run multiple OpenCode windows side by side across different projects. CacheSnipe uses atomic file writes and merge-on-read logic so multiple windows sharing the stats directory converge safely without race conditions.
- Automatic History Healing: Cleans trailing zero-usage placeholders and aborted requests from telemetry, keeping reports strictly aligned with actual billing events.
- License: MIT License.
- Acknowledgments & Thanks: Huge thanks to rohaquinlop for creating
pi-deepseek-cachefor the Pi agent harness. The original architecture demonstrated the power of DeepSeek prompt caching and served as the foundation for this OpenCode port. - Port Author: Built and maintained by Elias Liasides (Qu4rk) for the OpenCode community.



{ "compaction": { "prune": false }, "agent": { "compaction": { "model": "deepseek/deepseek-v4-flash", "temperature": 0 } }, "small_model": "deepseek/deepseek-v4-flash", "plugin": [ "file:///absolute/path/to/CacheSnipe/dist/src/plugin.js" ] }