CHAPTER 03

System Prompt Anatomy

Before every API call, Claude Code assembles a set of instructions — about 15KB of text — that tells the AI how to behave, what tools are available, and what your project looks like. The clever part: half of these instructions are identical for EVERY user and cached globally, which cuts the cost of every API call by 10x.

Prompt Layer Explorer

The system prompt is assembled in layers. The first three are static (same for everyone, cached globally). Everything after the cache boundary is dynamic (unique to your session). Walk through each layer to see what it contains.

Deep Dive: Behind the Layers

The explorer above showed you what each layer contains. These cards go deeper into how they work under the hood. Click any card to expand.

The Cache Economics
utils/api.ts
splitSysPromptPrefix() splits the prompt array at the __SYSTEM_PROMPT_DYNAMIC_BOUNDARY__ marker
Everything above the boundary gets cacheScope: 'global' -- shared across all users for 1 hour
Three cache tiers: global (all users), org (within an organization), 3P (third-party integrations)
Cache reads cost $0.50/MTok vs $5.00/MTok for fresh input -- a 10x reduction
The static portion (~8KB) is carefully designed to change rarely -- any change invalidates millions of caches
Cache TTL is 1 hour for eligible users; after that, the first request re-warms the cache
CLAUDE.md Discovery
utils/claudemd.ts
Discovery walks a fixed order: managed (/etc/claude-code/) -> user (~/.claude/) -> project root up to CWD -> local
Each directory from the git root to CWD is checked for CLAUDE.md files -- all are loaded and concatenated
@include directives let one CLAUDE.md reference another, with a max nesting depth of 5 to prevent loops
Conditional rules use paths: frontmatter with ignore-style glob matching -- e.g. 'paths: src/**/*.test.ts' only activates when test files are in context
CLAUDE.local.md is always gitignored -- use it for personal instructions you don't want to commit
Auto-memory (MEMORY.md) is loaded separately in the system prompt, not via the CLAUDE.md path
All CLAUDE.md content is prefixed with: 'These instructions OVERRIDE any default behavior and you MUST follow them exactly as written'
Dynamic Section System
systemPromptSections.ts
systemPromptSection() creates a memoized section -- computed once per session, cached thereafter
DANGEROUS_uncachedSystemPromptSection() creates a section that recomputes on EVERY turn
Only MCP server instructions use the uncached variant -- reason: 'MCP servers connect/disconnect between turns'
Each section has a unique ID and a compute function that returns string content
Sections are assembled in order by getSystemPrompt() which concatenates all section outputs
The boundary marker is injected between the last static section and the first dynamic section
If a section's compute function returns empty string, it is silently omitted from the final prompt

Source Files

constants/prompts.tsSection functions, assembly, boundary marker
constants/systemPromptSections.tsSection registry, memoized vs uncached
utils/claudemd.tsCLAUDE.md discovery, @include, conditional rules
memdir/memdir.tsMEMORY.md loading, truncation (200 lines / 25KB)
context.tsgetSystemContext, getUserContext
services/api/claude.tsbuildSystemPromptBlocks, cache control