Background
Anthropic's Prompt Caching API requires explicit cache_control: { type: "ephemeral" } markers in content blocks to designate cache breakpoints. A cached prefix costs only 10% of normal input token price and eliminates recomputation for up to 1 hour (extended TTL).
Currently opencode-owl's chat.system.transform hook only supports string arrays — there is no way to attach cache_control metadata to injected content blocks.
Constraint
This is a platform-level limitation. The OpenCode plugin API (experimental.chat.system.transform) passes strings:
// Current capability (src/index.ts:2448)
output.system.push(...lines); // string[]
The Anthropic API requires structured objects:
// What we need
{
type: "text",
text: stableContent,
cache_control: { type: "ephemeral" } // ← cache breakpoint
}
Proposed Solution
Phase 1: Feature Request to OpenCode Upstream
File an issue on opencode-ai/opencode requesting that chat.system.transform support structured content blocks with optional cache_control metadata, e.g.:
interface SystemBlock {
text: string;
cache_control?: { type: "ephemeral" };
}
// Hook signature change
output.system.push(...blocks: (string | SystemBlock)[]);
Phase 2: opencode-owl Implementation (once upstream supports it)
Once OpenCode exposes structured system blocks, implement in src/index.ts:
"experimental.chat.system.transform": async (h, output) => {
// ... retrieve memories as before ...
// Stable block — gets cache_control marker
const stableContent = buildStableBlock(globalPrefs, globalSkills, projectSkills);
output.system.push({
text: stableContent,
cache_control: { type: "ephemeral" }
});
// Dynamic block — no cache marker, changes every turn
const dynamicContent = buildDynamicBlock(projectFacts, ctx);
output.system.push({ text: dynamicContent });
}
This ensures:
- Everything up to the
cache_control breakpoint is cached for 5 min (or 1h with extended TTL)
- Per-turn dynamic content is always freshly computed
- Input token cost drops by up to 90% for repeated sessions with the same system prompt prefix
Phase 3: Cache Hit Tracking
Extend memory_access_log with a new access_type: "anthropic_cache_hit" populated from Anthropic API response headers (anthropic-cache-read-input-tokens).
Workaround Until Phase 1 Lands
Apply Prefix-KV #1 (stable-first ordering) so that even without explicit cache_control, the stable portion forms a naturally long prefix that benefits from any automatic prefix caching the underlying system applies.
Acceptance Criteria
Effort: ~1h for opencode-owl side (blocked on upstream) | Priority: High (long-term)
Background
Anthropic's Prompt Caching API requires explicit
cache_control: { type: "ephemeral" }markers in content blocks to designate cache breakpoints. A cached prefix costs only 10% of normal input token price and eliminates recomputation for up to 1 hour (extended TTL).Currently opencode-owl's
chat.system.transformhook only supports string arrays — there is no way to attachcache_controlmetadata to injected content blocks.Constraint
This is a platform-level limitation. The OpenCode plugin API (
experimental.chat.system.transform) passes strings:The Anthropic API requires structured objects:
Proposed Solution
Phase 1: Feature Request to OpenCode Upstream
File an issue on opencode-ai/opencode requesting that
chat.system.transformsupport structured content blocks with optionalcache_controlmetadata, e.g.:Phase 2: opencode-owl Implementation (once upstream supports it)
Once OpenCode exposes structured system blocks, implement in
src/index.ts:This ensures:
cache_controlbreakpoint is cached for 5 min (or 1h with extended TTL)Phase 3: Cache Hit Tracking
Extend
memory_access_logwith a newaccess_type: "anthropic_cache_hit"populated from Anthropic API response headers (anthropic-cache-read-input-tokens).Workaround Until Phase 1 Lands
Apply Prefix-KV #1 (stable-first ordering) so that even without explicit
cache_control, the stable portion forms a naturally long prefix that benefits from any automatic prefix caching the underlying system applies.Acceptance Criteria
cache_controlmarker placed at the boundary between stable and dynamic contentmemory_prefix_reportreportscache_control_enabled: truewhen activememory_access_analyticsEffort: ~1h for opencode-owl side (blocked on upstream) | Priority: High (long-term)