Skip to content

[Prefix-KV #6] Anthropic cache_control integration via structured system prompt blocks #47

Description

@AugustChaoTW

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

  • OpenCode upstream issue filed (link in comments)
  • opencode-owl adds structured block support once upstream merges it
  • cache_control marker placed at the boundary between stable and dynamic content
  • memory_prefix_report reports cache_control_enabled: true when active
  • Cache read tokens tracked in memory_access_analytics

Effort: ~1h for opencode-owl side (blocked on upstream) | Priority: High (long-term)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions