System Prompt Assembly
- Entity ID:
ent-20260409-ca81f348de0c - Type:
concept - Scope:
shared - Status:
active - Aliases: system prompt, prompt assembly, prompt compilation
Description
The system prompt in Claude Code is not a static string — it is a compiled artifact assembled from multiple layers on every conversational turn, with a critical cache boundary dividing static from dynamic sections. Before the user types anything, Claude Code compiles 15,000-40,000 tokens of hidden context including identity instructions, tool descriptions, CLAUDE.md rules, git status, memory, and system reminders.
Assembly pipeline
Entry point: getSystemPrompt() in src/constants/prompts.ts (lines 444-577)
Static sections (cacheable)
Assembled in order:
1. getSimpleIntroSection() — main identity statement ("You are Claude Code...")
2. getSimpleSystemSection() — tool use and permissions guidance
3. getSimpleDoingTasksSection() — software engineering task guidance
4. getActionsSection() — risk assessment and confirmation rules
5. getUsingYourToolsSection() — tool preference guidance (Read over cat, Edit over sed, etc.)
6. getSimpleToneAndStyleSection() — communication style
Cache boundary
STATIC SECTION (cached globally, shared across sessions)
────────────── __SYSTEM_PROMPT_DYNAMIC_BOUNDARY__ ──────────────
DYNAMIC SECTION (session-specific, changes per turn)
SYSTEM_PROMPT_DYNAMIC_BOUNDARY (line 114-115) splits the prompt. Everything above the boundary is globally cacheable (byte-identical across all sessions). Everything below is recomputed per turn or per session. The boundary is only inserted when shouldUseGlobalCacheScope() is true (1P API only).
Dynamic sections (registry-managed)
Registered as SystemPromptSection objects with caching (src/constants/systemPromptSections.ts):
Standard cached sections (computed once per session, cached until /clear or /compact):
- session_guidance — session-specific tool recommendations
- memory — loaded memory prompt from memdir
- ant_model_override — internal-only model-specific instructions
- env_info_simple — environment details (CWD, git status, platform, model, shell)
- language — language preference
- output_style — output style configuration
- scratchpad — scratchpad directory instructions (if enabled)
- frc — function result clearing instructions (feature-gated)
- summarize_tool_results — tool result summary guidance
- numeric_length_anchors — length limits (internal-only)
- token_budget — token target tracking (feature-gated)
- brief — brief mode section (Kairos feature)
Cache-breaking sections (recomputed every turn):
- mcp_instructions — MCP server instructions. Uses DANGEROUS_uncachedSystemPromptSection() because MCP servers can connect/disconnect between turns.
The DANGEROUS_ naming convention
Any function that forces cache invalidation is prefixed with DANGEROUS_:
DANGEROUS_uncachedSystemPromptSection(name, computeFn, reason: string)
The reason parameter is mandatory — it documents why cache invalidation is necessary. This naming convention makes cache-breaking visible in code review and grep.
Section caching mechanism
src/constants/systemPromptSections.ts — two section types:
systemPromptSection(name, compute)— computed once, cached inSTATE.systemPromptSectionCacheuntil/clearor/compactDANGEROUS_uncachedSystemPromptSection(name, compute, reason)— recomputed every turn, invalidates prompt cache if the value changes
resolveSystemPromptSections() checks the cache for each section, recomputes on miss or if cacheBreak: true, and stores back to cache.
Cache boundary at the API level
src/utils/api.ts (lines 321-435, splitSysPromptPrefix()) splits the prompt into cache blocks:
With global cache (1P API, boundary found): up to 4 blocks:
1. Attribution header (no cache)
2. System prompt prefix (no cache)
3. Static content before boundary (cacheScope: 'global') — shared across all organizations
4. Dynamic content after boundary (no cache)
With MCP tools (skipGlobalCache): 3 blocks with org-level caching:
1. Attribution header (no cache)
2. System prompt prefix (cacheScope: 'org')
3. Everything else (cacheScope: 'org')
Default (3P providers): 3 blocks with org-level caching.
Tool pool assembly
src/tools.ts (lines 345-367, assembleToolPool()) — combines built-in and MCP tools with cache-stable ordering:
const byName = (a, b) => a.name.localeCompare(b.name)
return uniqBy(
[...builtInTools].sort(byName).concat(allowedMcpTools.sort(byName)),
'name',
)
Key design: built-in tools form a contiguous prefix, sorted alphabetically. MCP tools follow, also sorted alphabetically. This partition boundary is critical — the server's claude_code_system_cache_policy places a global cache breakpoint after the last built-in tool. A flat sort would interleave MCP tools into built-ins, invalidating all downstream cache keys.
On name conflict, uniqBy preserves insertion order, so built-in tools win.
System reminders
40+ short behavioral instructions injected as <system-reminder> tags into user messages, not the system prompt. This preserves the prompt cache — adding a reminder to a user message doesn't invalidate the static system prompt cache.
Injection point: prependUserContext() in src/utils/api.ts (lines 449-474). Context sources:
- System context (src/context.ts:116-150): git status, optional cache breaker
- User context (src/context.ts:155-189): CLAUDE.md content, current date
Applied in the query flow: user context prepended to messages, system context appended to system prompt.
Ant-only two-tier prompt
Internal Anthropic employees (process.env.USER_TYPE === 'ant') receive a materially different system prompt:
- Collaborative and verification-focused (vs terse, task-oriented for external)
- Deeper code style guidance (comment reduction, verification before completion)
- False-claims mitigation (faithfully report outcomes, don't hide failures)
- Numeric length anchors for output tokens
- Undercover mode suppression (hide model names in internal repos)
- Three-layer verification instructions (internal-only)
Proactive mode variant
When feature('PROACTIVE') is active (src/constants/prompts.ts:467-489), the system prompt is replaced with a lean autonomous agent prompt:
- Autonomous work instructions (tick handling, pacing)
- Sleep tool guidance
- First wake-up greeting requirements
- Bias toward autonomous action
- Skips most standard static sections
Hidden context scale
Before the user types anything:
| Component | Tokens (approx) |
|---|---|
| System prompt (static) | 5,000-10,000 |
| CLAUDE.md (up to 40K chars) | 2,000-8,000 |
| Git status, file diffs | 1,000-5,000 |
| IDE selections, LSP diagnostics | 500-2,000 |
| Auto-memory index (200 lines) | 500-1,500 |
| System reminders (40+) | 1,000-3,000 |
| Sonnet memory side-query result | 500-2,000 |
Three models run per interaction: the main model, a Sonnet memory side-query, and the two-stage auto-mode classifier.
What depends on it
- Cache economics — the entire prompt cache strategy depends on the static/dynamic boundary being stable
- Tool system — tool descriptions are part of the prompt; their ordering affects cache stability
- MCP — MCP server instructions are the only cache-breaking dynamic section
- Auto-compact — compaction triggers section cache clearing
- Agent lifecycle — sub-agents can receive modified prompts (coordinator mode, fork path)
Design trade-offs
| Decision | Trade-off |
|---|---|
| Global cache boundary | Massive cost savings across all sessions, but static sections can never include session-specific content |
| Built-in/MCP tool partition | Cache-stable ordering, but MCP tools can never appear in the cached prefix |
| System reminders in user messages | Preserves prompt cache, but inflates every turn's input tokens |
| DANGEROUS_ naming convention | Makes cache-breaking visible, but only enforced by convention (no build-time check) |
| Ant-only two-tier prompt | Better experience for internal users, but two prompts to maintain |
| Section caching until /clear | Reduces recomputation, but stale sections possible if external state changes mid-session |
Key claims
- 15,000-40,000 tokens of hidden context compiled before the user types anything
- Static/dynamic cache boundary enables global prompt caching across all sessions
- Tool pool ordering (built-in prefix + MCP suffix, both alphabetical) is cache-load-bearing
- System reminders use user message injection to avoid invalidating the prompt cache
- Only MCP instructions are cache-breaking (via DANGEROUS_ function)
Sources
src-20260409-a14e9e98c3cd, src-20260409-037a8abb6277, source code at src/constants/prompts.ts, src/constants/systemPromptSections.ts, src/utils/api.ts, src/tools.ts, src/context.ts