T10 — Cache-aware 2-part system prompt
header tĩnh (identity + rules + tool defs) và rest động (env, git status, skills) — đặt cache_control: ephemeral chỉ trên header để tối đa cache hit rate.Tổng quan Context
opencode giải quyết bằng cách tách system prompt thành 2 phần:
- header: identity, rules, tool descriptions — thay đổi rất ít
(chỉ khi cài plugin mới). Được đánh dấu
cache_control: { type: "ephemeral" }. - rest: env block, git status, skills list, working directory — thay đổi mỗi session hoặc mỗi vài calls. Không cache.
Với cấu trúc này, header (thường 5 000–15 000 token) được cache từ call thứ 2 trở đi. Chỉ rest (thường 500–2 000 token) phải tính lại. Cache hit rate thực tế đạt 80–90% tổng token hệ thống.
[header, rest]
riêng lẻ (không phải 1 string), đảm bảo breakpoint cache không bị mất khi plugin
inject nội dung vào rest.
Phân tích code chi tiết Anatomy
Bước 1 — Định nghĩa 2-part system prompt type
session/system.ts — type definition
{`
export type SystemPrompt = {
header: string // static — identity, rules, tool descriptions
rest: string // dynamic — env, git status, skills list
}
// Plugin interface — nhận [header, rest], trả về [header, rest]
// Không được concat thành 1 string — mất cache breakpoint
export type SystemPromptTransform = (
prompt: SystemPrompt
) => SystemPrompt | Promise<SystemPrompt>
`}Bước 2 — Build system prompt với plugin transforms
session/system.ts — buildSystemPrompt
{`
export async function buildSystemPrompt(
session: Session
): Promise<SystemPrompt> {
// Base system prompt: header tĩnh
let prompt: SystemPrompt = {
header: BASE_IDENTITY + RULES + formatToolDescriptions(session.tools),
rest: '',
}
// Dynamic content vào rest
prompt.rest = [
formatEnvBlock(session.env),
await getGitStatus(session.cwd),
formatSkillsList(session.plugins),
].filter(Boolean).join('\n\n')
// Plugin transforms — chỉ được sửa rest, không được sửa header
for (const plugin of session.plugins) {
if (plugin.systemPromptTransform) {
prompt = await plugin.systemPromptTransform(prompt)
}
}
return prompt
}
`}Bước 3 — Serialize sang Anthropic API format
Anthropic API nhận system là array of content blocks, mỗi block
có thể có cache_control riêng.
session/llm.ts:99-160 — serialize 2-part prompt
{`
function serializeSystemPrompt(
prompt: SystemPrompt,
provider: Provider
): AnthropicSystemBlock[] | string {
if (provider !== 'anthropic') {
// Providers khác: concat thành 1 string
return [prompt.header, prompt.rest].filter(Boolean).join('\n\n')
}
// Anthropic: array of blocks với cache_control trên header
const blocks: AnthropicSystemBlock[] = [
{
type: 'text',
text: prompt.header,
cache_control: { type: 'ephemeral' },
// ephemeral cache TTL = 5 phút (Anthropic default)
},
]
if (prompt.rest) {
blocks.push({
type: 'text',
text: prompt.rest,
// Không có cache_control — tính mỗi call
})
}
return blocks
}
`}Tại sao ephemeral?
Anthropic prompt cache có TTL 5 phút (ephemeral) hoặc 1 giờ (persistent, beta). opencode dùng ephemeral — đủ cho một coding session liên tục, không cần phức tạp hóa với persistent cache management.
Tương tác với các kỹ thuật khác Interaction
T10 tương tác chặt chẽ với T7: khi header thay đổi (plugin mới được install),
cache.write tăng đột biến → T7 có thể trigger compact sớm hơn dự kiến.
Đây là behavior chính xác — sau cache miss, tổng token tăng.
Failure modes Risk
FM-1: Concat toàn bộ thành 1 string
Anti-pattern — single string system prompt
{`
// ❌ Concat tất cả → cache miss mỗi call
function buildSystemPrompt(session) {
return [
BASE_IDENTITY,
RULES,
formatToolDescriptions(session.tools),
formatEnvBlock(session.env), // thay đổi mỗi call!
await getGitStatus(session.cwd), // thay đổi mỗi call!
].join('\n\n')
// Bất kỳ thay đổi nhỏ nào → toàn bộ cache invalidate
// 10-15k token × 100 calls/ngày → chi phí tăng gấp 10
}
`}FM-2: Plugin sửa header thay vì rest
Nếu plugin inject nội dung vào header (thường xuyên thay đổi),
cache sẽ miss mọi call — tương đương với không có caching.
Anti-pattern — plugin modify header
{`
// ❌ Plugin inject dynamic content vào header
plugin.systemPromptTransform = (prompt) => ({
header: prompt.header + '\n\nCurrent time: ' + new Date(), // luôn thay đổi!
rest: prompt.rest,
})
// Đúng: inject vào rest
plugin.systemPromptTransform = (prompt) => ({
header: prompt.header, // không thay đổi
rest: prompt.rest + '\n\nCurrent time: ' + new Date(),
})
`}FM-3: Không có rest block
Nếu bỏ qua phần rest hoàn toàn và nhét tất cả vào header,
header sẽ thay đổi theo môi trường — mất hết lợi ích caching.
Ưu điểm
- Cache hit rate 80–90% trên header tĩnh
- Cost reduction 5–10x cho sessions dài
- Plugin-safe: chỉ được inject vào rest
- Tương thích multi-provider: fallback graceful cho non-Anthropic
Nhược điểm
- Cache TTL 5 phút — cần re-warm sau khi idle lâu
- Header phải thực sự tĩnh — khó khi tool list thay đổi
- Chỉ có ích với Anthropic — không có lợi với OpenAI (không cache riêng)
So sánh với các harness khác Compare
| Harness | System prompt caching | Static/dynamic split? | Plugin safe? |
|---|---|---|---|
| opencode | Có — 2-part với cache_control | Có (header / rest) | Có (plugin nhận tuple) |
| Claude Code | Có (cùng Anthropic) | Có | Không public |
| Aider | Không dùng Anthropic caching | Không | N/A |
| Cursor | Không public chi tiết | Không rõ | Không rõ |
cache_control — opencode follow đúng best practice này.
Implementation recipe Recipe
system.ts — 2-part cache-aware system prompt recipe
{`
// system.ts — cache-aware 2-part system prompt
export type SystemPrompt = { header: string; rest: string }
export function buildBasePrompt(tools: Tool[]): SystemPrompt {
return {
header: [
IDENTITY_BLOCK,
RULES_BLOCK,
formatToolDescriptions(tools),
].join('\n\n'),
rest: '',
}
}
export async function enrichWithDynamic(
prompt: SystemPrompt,
session: Session
): Promise<SystemPrompt> {
return {
...prompt,
rest: [
formatEnvBlock(process.env),
await getGitStatus(session.cwd),
formatSkillsList(session.plugins),
].filter(Boolean).join('\n\n'),
}
}
export function toAnthropicBlocks(
prompt: SystemPrompt
): AnthropicTextBlock[] {
const blocks: AnthropicTextBlock[] = [
{ type: 'text', text: prompt.header, cache_control: { type: 'ephemeral' } },
]
if (prompt.rest.trim()) {
blocks.push({ type: 'text', text: prompt.rest })
}
return blocks
}
`}Tham khảo Refs
- Anthropic docs — Prompt caching: cache_control và cache blocks
- Anthropic cookbook — Prompt caching patterns
- MindStudio blog — How to use Anthropic Claude prompt caching
- Anthropic — Prompt caching announcement và cost breakdown
- opencode source — session/llm.ts (lines 99–160)
- Simon Willison — Anthropic prompt caching deep dive