agent-cache-optimizer 0.1.1
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/CHANGELOG.md +17 -0
- package/LICENSE +21 -0
- package/README.md +274 -0
- package/README.zh-CN.md +200 -0
- package/adapters/claude-code.md +119 -0
- package/docs/cross-cli.md +89 -0
- package/docs/upstream.md +65 -0
- package/package.json +70 -0
- package/scripts/cache-status.sh +170 -0
- package/scripts/check-cache-friendly.sh +122 -0
- package/skills/cache-status/SKILL.md +81 -0
- package/src/__tests__/core.test.ts +97 -0
- package/src/core.ts +98 -0
- package/src/heuristics.ts +109 -0
- package/src/index.ts +127 -0
- package/src/splitting.ts +66 -0
- package/src/types.ts +39 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# KV Cache Optimization — Cross-CLI Architecture
|
|
2
|
+
|
|
3
|
+
## Core Principle (CLI-agnostic)
|
|
4
|
+
|
|
5
|
+
All LLM CLI agents assemble a system prompt from:
|
|
6
|
+
1. **Stable blocks**: CLAUDE.md / AGENTS.md / rules / tool definitions / agent configs
|
|
7
|
+
2. **Semi-dynamic blocks**: currentDate (changes daily)
|
|
8
|
+
3. **Dynamic blocks**: session handoff / memory injections / conversation history
|
|
9
|
+
|
|
10
|
+
Prefix-match KV caches (DeepSeek, Anthropic, Google, OpenAI) reuse computed
|
|
11
|
+
states when the prompt PREFIX is byte-identical to a cached request. Dynamic
|
|
12
|
+
content at the front busts everything.
|
|
13
|
+
|
|
14
|
+
## Architecture
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
┌──────────────────────────────────────────────────────┐
|
|
18
|
+
│ agent-cache-optimizer-core │
|
|
19
|
+
│ │
|
|
20
|
+
│ hash(track) → stability score → reorder │
|
|
21
|
+
│ Fully content-agnostic, zero external deps │
|
|
22
|
+
│ Input: string[] (system blocks) │
|
|
23
|
+
│ Output: string[] (reordered blocks) │
|
|
24
|
+
│ State: per-agent stability.json │
|
|
25
|
+
└──────────────────────────────────────────────────────┘
|
|
26
|
+
│ │ │
|
|
27
|
+
┌────▼─────┐ ┌─────▼──────┐ ┌────▼──────┐
|
|
28
|
+
│ OpenCode │ │Claude Code │ │ Codex │
|
|
29
|
+
│ adapter │ │ adapter │ │ adapter │
|
|
30
|
+
│ │ │ │ │ │
|
|
31
|
+
│ plugin.ts │ │ hook.js │ │ plugin.py │
|
|
32
|
+
│ system │ │ pre-launch │ │ (TBD) │
|
|
33
|
+
│ .transform│ │ validator │ │ │
|
|
34
|
+
└───────────┘ └────────────┘ └────────────┘
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Per-CLI Strategy
|
|
38
|
+
|
|
39
|
+
### OpenCode (✅ implemented)
|
|
40
|
+
|
|
41
|
+
- Hook: `experimental.chat.system.transform` — direct system prompt access
|
|
42
|
+
- Fallback: `chat.params` + `chat.headers` for diagnostics
|
|
43
|
+
- Adapter: `plugins/agent-cache-optimizer.ts`
|
|
44
|
+
|
|
45
|
+
### Claude Code (see [adapters/claude-code.md](../adapters/claude-code.md))
|
|
46
|
+
|
|
47
|
+
- No direct prompt-transform hook available
|
|
48
|
+
- Anthropic has native prompt caching (automatic prefix reuse)
|
|
49
|
+
- Strategy: **pre-session validator** + CLAUDE.md structure optimization
|
|
50
|
+
- Check: CLAUDE.md for date stamps, session IDs, dynamic includes
|
|
51
|
+
- Check: `.claude/settings.json` hooks for cache-busting patterns
|
|
52
|
+
- Wrap `claude` invocation to compare cache metrics before/after
|
|
53
|
+
|
|
54
|
+
### Codex (OpenAI)
|
|
55
|
+
|
|
56
|
+
- Plugin API likely similar to OpenCode (both from AI SDK ecosystem)
|
|
57
|
+
- Strategy: adapt OpenCode plugin once Codex plugin API is confirmed
|
|
58
|
+
- OpenAI prompt caching uses similar prefix-match mechanism
|
|
59
|
+
|
|
60
|
+
### Gemini CLI
|
|
61
|
+
|
|
62
|
+
- Uses `activate_skill` + GEMINI.md for project context
|
|
63
|
+
- Google context caching API has explicit cache tokens
|
|
64
|
+
- Strategy: inject cache_control markers at stable block boundaries
|
|
65
|
+
|
|
66
|
+
## Shared Core Module
|
|
67
|
+
|
|
68
|
+
`agent-cache-optimizer-core.ts`:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
// Pure functions, no CLI-specific dependencies
|
|
72
|
+
export function hashBlock(content: string): string
|
|
73
|
+
export function coldStartScore(block: string, index: number, total: number): number
|
|
74
|
+
export function classifyBlocks(blocks: string[], db: StabilityDB): Classified
|
|
75
|
+
export function updateStabilityDB(db: StabilityDB, blocks: string[]): StabilityDB
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Each adapter imports these and adds CLI-specific glue:
|
|
79
|
+
1. Extract blocks from CLI's prompt representation
|
|
80
|
+
2. Call `classifyBlocks()`
|
|
81
|
+
3. Inject reordered blocks back into CLI's prompt
|
|
82
|
+
|
|
83
|
+
## Roadmap
|
|
84
|
+
|
|
85
|
+
1. [x] OpenCode plugin (V3: per-agent + block splitting + diagnostics)
|
|
86
|
+
2. [ ] Extract shared core to `agent-cache-optimizer-core.ts`
|
|
87
|
+
3. [ ] Claude Code pre-session validator
|
|
88
|
+
4. [ ] Codex adapter (once API confirmed)
|
|
89
|
+
5. [ ] Gemini CLI adapter (Google context caching)
|
package/docs/upstream.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# KV Cache Optimization for DeepSeek — Upstream Changes Needed
|
|
2
|
+
|
|
3
|
+
## Problem
|
|
4
|
+
|
|
5
|
+
OpenCode's system prompt assembly places **dynamic blocks** (SessionStart HANDOFF,
|
|
6
|
+
.remember/ files, memory-dream injections, currentDate) at the **front** of the
|
|
7
|
+
system prompt array. This means the very first bytes of every session's system
|
|
8
|
+
prompt are different, which completely busts prefix-match KV caches (DeepSeek,
|
|
9
|
+
Anthropic prompt caching, Google context caching).
|
|
10
|
+
|
|
11
|
+
## Current prompt assembly order (observed)
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
[0] SessionStart HANDOFF + REMEMBER + MEMORY ← ⚡ changes every session
|
|
15
|
+
[1] CLAUDE.md ← ✅ stable
|
|
16
|
+
[2] Agent definitions ← ✅ stable
|
|
17
|
+
[3] MCP server instructions ← ✅ stable
|
|
18
|
+
[4] Skills list ← ✅ stable
|
|
19
|
+
[5] currentDate ← ⚠️ changes daily
|
|
20
|
+
[6] Tool definitions ← ✅ stable
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Impact
|
|
24
|
+
|
|
25
|
+
- **Every new session: 100% cache miss** on system prompt
|
|
26
|
+
- **Every subagent call: fresh KV computation** for the full system prompt
|
|
27
|
+
- **Estimated waste**: 40-60% of system prompt tokens recomputed unnecessarily
|
|
28
|
+
- For DeepSeek where cache hit cost is near-zero, this is purely wasted compute
|
|
29
|
+
|
|
30
|
+
## Fix (in OpenCode core)
|
|
31
|
+
|
|
32
|
+
Move dynamic blocks to the **end** of the system prompt array:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
[0] CLAUDE.md ← ✅ stable
|
|
36
|
+
[1] Agent definitions ← ✅ stable
|
|
37
|
+
[2] MCP server instructions ← ✅ stable
|
|
38
|
+
[3] Skills list ← ✅ stable
|
|
39
|
+
[4] Tool definitions ← ✅ stable
|
|
40
|
+
[5] currentDate ← ⚠️ changes daily
|
|
41
|
+
[6] SessionStart HANDOFF + REMEMBER + MEMORY ← ⚡ changes per session
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
This way, the KV cache prefix (blocks [0]-[4]) stays identical across sessions
|
|
45
|
+
and subagent calls, giving 70-80% cache reuse.
|
|
46
|
+
|
|
47
|
+
## Implementation in OpenCode
|
|
48
|
+
|
|
49
|
+
In the prompt assembly code (likely in the system prompt builder), change:
|
|
50
|
+
|
|
51
|
+
1. **Build stable prefix first**: all static configuration (CLAUDE.md, agent
|
|
52
|
+
definitions, MCP/Skills/tools lists)
|
|
53
|
+
2. **Append semi-dynamic content**: currentDate
|
|
54
|
+
3. **Append fully-dynamic content last**: SessionStart hook output,
|
|
55
|
+
.remember/ injection, memory-dream retrieval results
|
|
56
|
+
|
|
57
|
+
## Interim workaround
|
|
58
|
+
|
|
59
|
+
A plugin (`agent-cache-optimizer.ts`) uses `experimental.chat.system.transform` to
|
|
60
|
+
reorder blocks at runtime. This works but:
|
|
61
|
+
- Depends on an experimental hook
|
|
62
|
+
- Adds a small processing overhead
|
|
63
|
+
- Can't reorder content WITHIN blocks, only BETWEEN blocks
|
|
64
|
+
|
|
65
|
+
The proper fix is in the core prompt builder.
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agent-cache-optimizer",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Content-agnostic KV cache optimizer for LLM CLI agents — boosts prompt cache hit rate by 40-88% through automatic stability tracking and block reordering",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"kv-cache",
|
|
7
|
+
"prompt-caching",
|
|
8
|
+
"llm",
|
|
9
|
+
"deepseek",
|
|
10
|
+
"anthropic",
|
|
11
|
+
"openai",
|
|
12
|
+
"opencode",
|
|
13
|
+
"claude-code",
|
|
14
|
+
"optimization",
|
|
15
|
+
"token-savings"
|
|
16
|
+
],
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"author": {
|
|
19
|
+
"name": "Chris",
|
|
20
|
+
"url": "https://github.com/chris"
|
|
21
|
+
},
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/uuie/agent-cache-optimizer"
|
|
25
|
+
},
|
|
26
|
+
"type": "module",
|
|
27
|
+
"main": "./src/index.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": "./src/index.ts",
|
|
30
|
+
"./core": "./src/core.ts",
|
|
31
|
+
"./heuristics": "./src/heuristics.ts",
|
|
32
|
+
"./splitting": "./src/splitting.ts"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"src/",
|
|
36
|
+
"adapters/",
|
|
37
|
+
"scripts/",
|
|
38
|
+
"skills/",
|
|
39
|
+
"docs/",
|
|
40
|
+
"README.md",
|
|
41
|
+
"README.zh-CN.md",
|
|
42
|
+
"LICENSE",
|
|
43
|
+
"CHANGELOG.md",
|
|
44
|
+
"tsconfig.json"
|
|
45
|
+
],
|
|
46
|
+
"scripts": {
|
|
47
|
+
"test": "vitest run",
|
|
48
|
+
"test:watch": "vitest",
|
|
49
|
+
"typecheck": "tsc --noEmit",
|
|
50
|
+
"format": "prettier --write \"src/**/*.ts\" \"*.md\"",
|
|
51
|
+
"format:check": "prettier --check \"src/**/*.ts\" \"*.md\"",
|
|
52
|
+
"check": "bash scripts/cache-status.sh",
|
|
53
|
+
"audit": "bash scripts/check-cache-friendly.sh --all"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@opencode-ai/plugin": "^1.15.0",
|
|
57
|
+
"@types/node": "^26.0.0",
|
|
58
|
+
"prettier": "^3.3.0",
|
|
59
|
+
"typescript": "^6.0.3",
|
|
60
|
+
"vitest": "^2.1.0"
|
|
61
|
+
},
|
|
62
|
+
"peerDependencies": {
|
|
63
|
+
"@opencode-ai/plugin": "^1.15.0"
|
|
64
|
+
},
|
|
65
|
+
"peerDependenciesMeta": {
|
|
66
|
+
"@opencode-ai/plugin": {
|
|
67
|
+
"optional": true
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# cache-status.sh — display KV Cache Optimizer status
|
|
3
|
+
# Usage: cache-status.sh [--json]
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
CACHE_DIR="${HOME}/.cache/opencode/agent-cache-optimizer"
|
|
7
|
+
BOLD='\033[1m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; CYAN='\033[0;36m'; NC='\033[0m'
|
|
8
|
+
|
|
9
|
+
# ── Check if plugin has run ────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
if [[ ! -d "$CACHE_DIR" ]]; then
|
|
12
|
+
echo "╔══════════════════════════════════════════════════════════════╗"
|
|
13
|
+
echo "║ KV Cache Optimizer Status ║"
|
|
14
|
+
echo "╠══════════════════════════════════════════════════════════════╣"
|
|
15
|
+
echo "║ Status: NO DATA ║"
|
|
16
|
+
echo "║ ║"
|
|
17
|
+
echo "║ Plugin has not been invoked yet. ║"
|
|
18
|
+
echo "║ ║"
|
|
19
|
+
echo "║ 1. Verify agent-cache-optimizer.ts is in opencode.json plugins ║"
|
|
20
|
+
echo "║ 2. Restart OpenCode ║"
|
|
21
|
+
echo "║ 3. Make at least one request to trigger the hook ║"
|
|
22
|
+
echo "╚══════════════════════════════════════════════════════════════╝"
|
|
23
|
+
exit 0
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
# ── Parse stability DBs ────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
declare -A agent_obs agent_positions agent_stable_pct agent_mode
|
|
29
|
+
|
|
30
|
+
for db in "$CACHE_DIR"/stability-*.json; do
|
|
31
|
+
[[ -f "$db" ]] || continue
|
|
32
|
+
agent=$(basename "$db" .json | sed 's/^stability-//')
|
|
33
|
+
|
|
34
|
+
obs=$(python3 -c "
|
|
35
|
+
import json
|
|
36
|
+
d=json.load(open('$db'))
|
|
37
|
+
print(d.get('observations', 0))
|
|
38
|
+
" 2>/dev/null || echo 0)
|
|
39
|
+
|
|
40
|
+
pos_count=$(python3 -c "
|
|
41
|
+
import json
|
|
42
|
+
d=json.load(open('$db'))
|
|
43
|
+
print(len(d.get('positions', {})))
|
|
44
|
+
" 2>/dev/null || echo 0)
|
|
45
|
+
|
|
46
|
+
# Average score across all known hashes
|
|
47
|
+
avg_score=$(python3 -c "
|
|
48
|
+
import json
|
|
49
|
+
d=json.load(open('$db'))
|
|
50
|
+
scores = list(d.get('scores', {}).values())
|
|
51
|
+
if scores:
|
|
52
|
+
stable = sum(1 for s in scores if s >= 0.7)
|
|
53
|
+
dynamic = sum(1 for s in scores if s <= 0.3)
|
|
54
|
+
print(f'{stable}/{len(scores)}')
|
|
55
|
+
else:
|
|
56
|
+
print('0/0')
|
|
57
|
+
" 2>/dev/null || echo "0/0")
|
|
58
|
+
|
|
59
|
+
agent_obs[$agent]=$obs
|
|
60
|
+
agent_positions[$agent]=$pos_count
|
|
61
|
+
agent_stable_pct[$agent]=$avg_score
|
|
62
|
+
|
|
63
|
+
if [[ $obs -ge 3 ]]; then
|
|
64
|
+
agent_mode[$agent]="WARM"
|
|
65
|
+
else
|
|
66
|
+
agent_mode[$agent]="COLD"
|
|
67
|
+
fi
|
|
68
|
+
done
|
|
69
|
+
|
|
70
|
+
# ── Parse diag log ─────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
diag_entries=0
|
|
73
|
+
last_entry=""
|
|
74
|
+
if [[ -f "$CACHE_DIR/diag.log" ]]; then
|
|
75
|
+
diag_entries=$(wc -l < "$CACHE_DIR/diag.log" | tr -d ' ')
|
|
76
|
+
last_entry=$(tail -1 "$CACHE_DIR/diag.log" 2>/dev/null || echo "(empty)")
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# ── Display ────────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
echo ""
|
|
82
|
+
echo -e "${BOLD}╔══════════════════════════════════════════════════════════════╗${NC}"
|
|
83
|
+
echo -e "${BOLD}║ KV Cache Optimizer Status ║${NC}"
|
|
84
|
+
echo -e "${BOLD}╠══════════════════════════════════════════════════════════════╣${NC}"
|
|
85
|
+
|
|
86
|
+
# Status line
|
|
87
|
+
if [[ $diag_entries -gt 0 ]]; then
|
|
88
|
+
echo -e "║ ${GREEN}Status: ACTIVE${NC} ║"
|
|
89
|
+
else
|
|
90
|
+
echo -e "║ ${RED}Status: NO ACTIVITY (check plugin loading)${NC} ║"
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
# Mode: cold/warm per agent
|
|
94
|
+
modes=""
|
|
95
|
+
for agent in "${!agent_obs[@]}"; do
|
|
96
|
+
modes+="$agent=${agent_mode[$agent]} "
|
|
97
|
+
done
|
|
98
|
+
printf "║ Mode: %-52s ║\n" "$modes"
|
|
99
|
+
|
|
100
|
+
# Uptime from diag.log first/last
|
|
101
|
+
if [[ $diag_entries -gt 0 ]]; then
|
|
102
|
+
first_ts=$(head -1 "$CACHE_DIR/diag.log" | grep -oP '^\[[^\]]+\]' | tr -d '[]' || echo "?")
|
|
103
|
+
last_ts=$(tail -1 "$CACHE_DIR/diag.log" | grep -oP '^\[[^\]]+\]' | tr -d '[]' || echo "?")
|
|
104
|
+
printf "║ Uptime: %-52s ║\n" "$first_ts → $last_ts"
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
echo -e "${BOLD}╠══════════════════════════════════════════════════════════════╣${NC}"
|
|
108
|
+
|
|
109
|
+
# Per-agent breakdown
|
|
110
|
+
if [[ ${#agent_obs[@]} -eq 0 ]]; then
|
|
111
|
+
echo -e "║ ${YELLOW}No per-agent data yet — make requests with different agents${NC} ║"
|
|
112
|
+
else
|
|
113
|
+
printf "║ ${CYAN}%-20s %6s %8s %10s${NC} ║\n" "Agent" "Obs" "Positions" "Stable"
|
|
114
|
+
for agent in $(printf '%s\n' "${!agent_obs[@]}" | sort); do
|
|
115
|
+
printf "║ %-20s %6s %8s %10s ║\n" \
|
|
116
|
+
"$agent" "${agent_obs[$agent]}" "${agent_positions[$agent]}" "${agent_stable_pct[$agent]}"
|
|
117
|
+
done
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
echo -e "${BOLD}╠══════════════════════════════════════════════════════════════╣${NC}"
|
|
121
|
+
|
|
122
|
+
# Last reorder entries
|
|
123
|
+
if [[ $diag_entries -gt 0 ]]; then
|
|
124
|
+
echo -e "║ ${CYAN}Last reorders (diag.log):${NC} ║"
|
|
125
|
+
tail -5 "$CACHE_DIR/diag.log" 2>/dev/null | while IFS= read -r line; do
|
|
126
|
+
# Truncate to fit in box
|
|
127
|
+
printf "║ %-56s ║\n" "${line:0:56}"
|
|
128
|
+
done
|
|
129
|
+
else
|
|
130
|
+
echo -e "║ ${YELLOW}No reorder events logged yet${NC} ║"
|
|
131
|
+
fi
|
|
132
|
+
|
|
133
|
+
echo -e "${BOLD}╠══════════════════════════════════════════════════════════════╣${NC}"
|
|
134
|
+
|
|
135
|
+
# Estimated savings
|
|
136
|
+
total_obs=0
|
|
137
|
+
for o in "${agent_obs[@]}"; do total_obs=$((total_obs + o)); done
|
|
138
|
+
if [[ $total_obs -gt 0 ]]; then
|
|
139
|
+
total_stable=0
|
|
140
|
+
total_blocks=0
|
|
141
|
+
for agent in "${!agent_stable_pct[@]}"; do
|
|
142
|
+
s=$(echo "${agent_stable_pct[$agent]}" | cut -d/ -f1)
|
|
143
|
+
t=$(echo "${agent_stable_pct[$agent]}" | cut -d/ -f2)
|
|
144
|
+
total_stable=$((total_stable + s))
|
|
145
|
+
total_blocks=$((total_blocks + t))
|
|
146
|
+
done
|
|
147
|
+
if [[ $total_blocks -gt 0 ]]; then
|
|
148
|
+
pct=$((total_stable * 100 / total_blocks))
|
|
149
|
+
printf "║ Estimated cache reuse: %-36s ║\n" "~${pct}% of system prompt"
|
|
150
|
+
fi
|
|
151
|
+
fi
|
|
152
|
+
|
|
153
|
+
echo -e "${BOLD}╚══════════════════════════════════════════════════════════════╝${NC}"
|
|
154
|
+
echo ""
|
|
155
|
+
|
|
156
|
+
# ── Quick stats ────────────────────────────────────────────────────
|
|
157
|
+
|
|
158
|
+
echo "Files:"
|
|
159
|
+
echo " $(ls "$CACHE_DIR"/stability-*.json 2>/dev/null | wc -l) stability DBs"
|
|
160
|
+
echo " ${diag_entries} diagnostic log entries"
|
|
161
|
+
echo ""
|
|
162
|
+
|
|
163
|
+
if [[ $diag_entries -eq 0 && ${#agent_obs[@]} -eq 0 ]]; then
|
|
164
|
+
echo "⚠️ Plugin hasn't fired yet. Troubleshooting:"
|
|
165
|
+
echo " 1. grep 'agent-cache-optimizer' ~/.config/opencode/opencode.json"
|
|
166
|
+
echo " 2. Restart OpenCode"
|
|
167
|
+
echo " 3. Check: the experimental.chat.system.transform hook"
|
|
168
|
+
echo " may not be available in your OpenCode version."
|
|
169
|
+
echo " Fallback: chat.params hook writes to diag.log on first call."
|
|
170
|
+
fi
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# check-cache-friendly.sh — scan any CLI agent's config for KV-cache-busting patterns
|
|
3
|
+
# Usage: ./check-cache-friendly.sh [file] (default: CLAUDE.md)
|
|
4
|
+
# ./check-cache-friendly.sh --opencode (scan opencode config)
|
|
5
|
+
# ./check-cache-friendly.sh --all (scan all known configs)
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
RED='\033[0;31m'; YELLOW='\033[1;33m'; GREEN='\033[0;32m'; NC='\033[0m'
|
|
9
|
+
|
|
10
|
+
warn() { echo -e "${YELLOW}⚠️ $1${NC}"; }
|
|
11
|
+
good() { echo -e "${GREEN}✅ $1${NC}"; }
|
|
12
|
+
bad() { echo -e "${RED}❌ $1${NC}"; }
|
|
13
|
+
|
|
14
|
+
check_file() {
|
|
15
|
+
local file="$1"
|
|
16
|
+
local label="${2:-$file}"
|
|
17
|
+
if [[ ! -f "$file" ]]; then
|
|
18
|
+
echo " (file not found: $file)"
|
|
19
|
+
return
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
echo ""
|
|
23
|
+
echo "─── $label ───"
|
|
24
|
+
local issues=0
|
|
25
|
+
|
|
26
|
+
# 1. Date stamps in first 10 lines
|
|
27
|
+
if head -10 "$file" | grep -qP '\d{4}-\d{2}-\d{2}'; then
|
|
28
|
+
warn "Date stamp in first 10 lines → move to file end for cache stability"
|
|
29
|
+
((issues++))
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# 2. Session IDs
|
|
33
|
+
if grep -qP 'ses_[a-z0-9]{8,}' "$file" 2>/dev/null; then
|
|
34
|
+
warn "Session ID patterns found → these change every session"
|
|
35
|
+
((issues++))
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
# 3. Dynamic file references (but NOT npm package names)
|
|
39
|
+
if grep -qP '@remember\b|\.remember/|memory-dream(?!@)' "$file" 2>/dev/null; then
|
|
40
|
+
warn "Dynamic file/memory references → content changes between sessions"
|
|
41
|
+
((issues++))
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# 4. Very short first line (often a date or status) — text files only
|
|
45
|
+
# Skip JSON/YAML config files and markdown section headers (## / #)
|
|
46
|
+
local first_line
|
|
47
|
+
first_line=$(head -1 "$file")
|
|
48
|
+
if [[ ! "$file" =~ \.(json|yaml|yml|toml|lock)$ ]]; then
|
|
49
|
+
if [[ ${#first_line} -lt 30 && ! "$first_line" =~ ^#+\ ]]; then
|
|
50
|
+
warn "Very short first line (${#first_line} chars): '$first_line'"
|
|
51
|
+
((issues++))
|
|
52
|
+
fi
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
# 5. File volatility check
|
|
56
|
+
local size lines mtime
|
|
57
|
+
size=$(wc -c < "$file" | tr -d ' ')
|
|
58
|
+
lines=$(wc -l < "$file" | tr -d ' ')
|
|
59
|
+
mtime=$(stat -c %Y "$file" 2>/dev/null || echo 0)
|
|
60
|
+
local now
|
|
61
|
+
now=$(date +%s)
|
|
62
|
+
local age_hrs=$(( (now - mtime) / 3600 ))
|
|
63
|
+
|
|
64
|
+
echo " Size: ${size}B, Lines: ${lines}, Age: ${age_hrs}h"
|
|
65
|
+
|
|
66
|
+
if [[ $issues -eq 0 ]]; then
|
|
67
|
+
good "No cache-busting patterns detected"
|
|
68
|
+
fi
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
check_opencode() {
|
|
72
|
+
local config_dir="${HOME}/.config/opencode"
|
|
73
|
+
echo ""
|
|
74
|
+
echo "══════════ OpenCode Config ══════════"
|
|
75
|
+
|
|
76
|
+
# Main config
|
|
77
|
+
check_file "$config_dir/opencode.json" "opencode.json"
|
|
78
|
+
|
|
79
|
+
# Append files
|
|
80
|
+
for f in "$config_dir"/oh-my-opencode-slim/*_append.md; do
|
|
81
|
+
[[ -f "$f" ]] || continue
|
|
82
|
+
check_file "$f" "$(basename "$f")"
|
|
83
|
+
done
|
|
84
|
+
|
|
85
|
+
# .remember files
|
|
86
|
+
for f in "$config_dir"/.remember/*.md; do
|
|
87
|
+
[[ -f "$f" ]] || continue
|
|
88
|
+
local name; name=$(basename "$f")
|
|
89
|
+
local lines; lines=$(wc -l < "$f" | tr -d ' ')
|
|
90
|
+
local size; size=$(wc -c < "$f" | tr -d ' ')
|
|
91
|
+
echo " .remember/$name: ${lines} lines, ${size}B"
|
|
92
|
+
done
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
check_claude() {
|
|
96
|
+
echo "══════════ Claude Code Config ══════════"
|
|
97
|
+
check_file "${HOME}/.claude/CLAUDE.md" "CLAUDE.md (global)"
|
|
98
|
+
for f in "${HOME}"/.claude/rules/*.md; do
|
|
99
|
+
[[ -f "$f" ]] || continue
|
|
100
|
+
check_file "$f" "rules/$(basename "$f")"
|
|
101
|
+
done
|
|
102
|
+
check_file "$(pwd)/CLAUDE.md" "CLAUDE.md (project)" 2>/dev/null || true
|
|
103
|
+
check_file "$(pwd)/AGENTS.md" "AGENTS.md (project)" 2>/dev/null || true
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
# ── Main ───────────────────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
case "${1:-}" in
|
|
109
|
+
--opencode) check_opencode ;;
|
|
110
|
+
--claude) check_claude ;;
|
|
111
|
+
--all)
|
|
112
|
+
check_opencode
|
|
113
|
+
echo ""
|
|
114
|
+
check_claude
|
|
115
|
+
;;
|
|
116
|
+
*)
|
|
117
|
+
check_file "${1:-CLAUDE.md}" "${1:-CLAUDE.md}"
|
|
118
|
+
;;
|
|
119
|
+
esac
|
|
120
|
+
|
|
121
|
+
echo ""
|
|
122
|
+
echo "Done."
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cache-status
|
|
3
|
+
description: Show KV cache optimizer status — stability DBs, reordering stats, diagnostics. Trigger: /cache-status
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# KV Cache Optimizer Status
|
|
7
|
+
|
|
8
|
+
Show the current state of the KV cache optimizer plugin (agent-cache-optimizer.ts).
|
|
9
|
+
|
|
10
|
+
## Data Locations
|
|
11
|
+
|
|
12
|
+
- Stability DBs: `~/.cache/opencode/agent-cache-optimizer/stability-*.json`
|
|
13
|
+
- Diagnostic log: `~/.cache/opencode/agent-cache-optimizer/diag.log`
|
|
14
|
+
|
|
15
|
+
## Display Format
|
|
16
|
+
|
|
17
|
+
Read all `stability-*.json` files and `diag.log`, then present:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
╔══════════════════════════════════════════════════════════════╗
|
|
21
|
+
║ KV Cache Optimizer Status ║
|
|
22
|
+
╠══════════════════════════════════════════════════════════════╣
|
|
23
|
+
║ Status: ACTIVE / NO DATA / ERROR ║
|
|
24
|
+
║ Mode: COLD START (heuristics) / WARM (hash-based) ║
|
|
25
|
+
║ Uptime: first_seen → last_seen ║
|
|
26
|
+
╠══════════════════════════════════════════════════════════════╣
|
|
27
|
+
║ Per-Agent Breakdown: ║
|
|
28
|
+
║ orchestrator: 12 obs, 88% cacheable ║
|
|
29
|
+
║ oracle: 5 obs, 75% cacheable ║
|
|
30
|
+
║ fixer: 3 obs, 90% cacheable ║
|
|
31
|
+
║ ... ║
|
|
32
|
+
╠══════════════════════════════════════════════════════════════╣
|
|
33
|
+
║ Last 5 reorders (from diag.log): ║
|
|
34
|
+
║ [time] [agent] S:X U:Y D:Z T:N obs:M ║
|
|
35
|
+
║ ... ║
|
|
36
|
+
╠══════════════════════════════════════════════════════════════╣
|
|
37
|
+
║ Estimated savings: XX KB / session ║
|
|
38
|
+
╚══════════════════════════════════════════════════════════════╝
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Key Metrics
|
|
42
|
+
|
|
43
|
+
From each `stability-{agent}.json`:
|
|
44
|
+
- `observations`: number of calls tracked
|
|
45
|
+
- `positions`: number of distinct block positions
|
|
46
|
+
- Average `scores` per block → classification quality
|
|
47
|
+
- Ratio of stable vs dynamic blocks
|
|
48
|
+
|
|
49
|
+
From `diag.log`:
|
|
50
|
+
- Last N reorder operations
|
|
51
|
+
- Confirm plugin is being invoked
|
|
52
|
+
|
|
53
|
+
## Interpretation
|
|
54
|
+
|
|
55
|
+
- **NO DATA**: Plugin hasn't been invoked yet. Restart OpenCode after adding the plugin to `opencode.json`.
|
|
56
|
+
- **COLD START**: First 2 sessions per agent — using position/size heuristics.
|
|
57
|
+
- **WARM**: 3+ sessions — using hash-based stability tracking. More accurate.
|
|
58
|
+
- **High stable %**: Good cache reuse across sessions.
|
|
59
|
+
- **High unknown %**: Heuristics can't classify some blocks → will improve with more observations.
|
|
60
|
+
|
|
61
|
+
## If Plugin Not Working
|
|
62
|
+
|
|
63
|
+
1. Check `opencode.json` has `"file:///home/chris/.config/opencode/plugins/agent-cache-optimizer.ts"` in `plugin` array
|
|
64
|
+
2. Check `diag.log` exists and has entries
|
|
65
|
+
3. If no diag.log: the `experimental.chat.system.transform` hook may not be firing → fall back to `chat.params` diagnostic
|
|
66
|
+
4. Run `opencode debug agent orchestrator` to verify plugin config
|
|
67
|
+
|
|
68
|
+
## Quick Health Check
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Check if plugin is loaded
|
|
72
|
+
ls ~/.cache/opencode/agent-cache-optimizer/
|
|
73
|
+
|
|
74
|
+
# View last reorder
|
|
75
|
+
tail -5 ~/.cache/opencode/agent-cache-optimizer/diag.log
|
|
76
|
+
|
|
77
|
+
# Count observations per agent
|
|
78
|
+
for f in ~/.cache/opencode/agent-cache-optimizer/stability-*.json; do
|
|
79
|
+
echo "$(basename $f .json): $(python3 -c "import json; d=json.load(open('$f')); print(d['observations'])") obs"
|
|
80
|
+
done
|
|
81
|
+
```
|