System Prompt Assembly
The system prompt is the most critical piece of context in every Claude Code API call. It defines Claude’s identity, capabilities, behavioral constraints, and environmental awareness. The assembly process in src/constants/prompts.ts is a carefully layered pipeline that balances cache efficiency with per-session dynamism.
Two-Zone Architecture
Section titled “Two-Zone Architecture”The system prompt is split into two zones by a boundary marker:
export const SYSTEM_PROMPT_DYNAMIC_BOUNDARY = '__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__'graph TD subgraph "Static Zone (cross-org cacheable)" A[Identity & Introduction] B[System Information] C[Coding Instructions] D[Actions Section] E[Tool Usage Guide] F[Tone & Style] G[Output Efficiency] end H["__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__"] subgraph "Dynamic Zone (per-session)" I[Environment Info] J[Memory Prompt] K[Language Preference] L[Output Style Config] M[MCP Instructions] N[Scratchpad Instructions] O[Function Result Clearing] P[Token Budget] end A --> B --> C --> D --> E --> F --> G --> H --> I --> J --> K --> L --> M --> N --> O --> PEverything before the boundary can use scope: 'global' caching — it’s identical across all users and sessions. Everything after contains user/session-specific content and must not be globally cached.
The getSystemPrompt() Function
Section titled “The getSystemPrompt() Function”The main entry point is getSystemPrompt() in src/constants/prompts.ts:
// src/constants/prompts.ts — simplified structureexport async function getSystemPrompt( tools: Tools, model: string, additionalWorkingDirectories?: string[], mcpClients?: MCPServerConnection[],): Promise<string[]> { // Early exit for --bare / SIMPLE mode if (isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)) { return [`You are Claude Code...\n\nCWD: ${getCwd()}\nDate: ${getSessionStartDate()}`] }
// Early exit for proactive/autonomous mode if (proactiveModule?.isProactiveActive()) { return [/* minimal autonomous prompt */] }
// Standard prompt: static sections + dynamic sections const dynamicSections = [ systemPromptSection('env_info', () => envInfo), systemPromptSection('memory', () => loadMemoryPrompt()), systemPromptSection('language', () => getLanguageSection(settings.language)), systemPromptSection('output_style', () => getOutputStyleSection(outputStyleConfig)), DANGEROUS_uncachedSystemPromptSection('mcp_instructions', ...), systemPromptSection('scratchpad', () => getScratchpadInstructions()), systemPromptSection('frc', () => getFunctionResultClearingSection(model)), // ... ]
return [ // --- Static content (cacheable) --- getSimpleIntroSection(outputStyleConfig), getSimpleSystemSection(), getSimpleDoingTasksSection(), getActionsSection(), getUsingYourToolsSection(enabledTools), getSimpleToneAndStyleSection(), getOutputEfficiencySection(), // === BOUNDARY MARKER === ...(shouldUseGlobalCacheScope() ? [SYSTEM_PROMPT_DYNAMIC_BOUNDARY] : []), // --- Dynamic content --- ...resolvedDynamicSections, ].filter(s => s !== null)}The function returns an array of strings (sections), not a single string. This preserves the boundary marker for downstream cache logic.
Static Sections
Section titled “Static Sections”Identity
Section titled “Identity”function getSimpleIntroSection(outputStyleConfig: OutputStyleConfig | null): string { // "You are Claude Code, Anthropic's official CLI for Claude..." // Core identity, tool-using mandate, human-in-the-loop principle}Coding Instructions
Section titled “Coding Instructions”function getSimpleDoingTasksSection(): string { // File editing rules, test/lint requirements // "When editing files: use Read before Edit" // "When writing tests: follow existing patterns"}Tool Usage Guide
Section titled “Tool Usage Guide”function getUsingYourToolsSection(enabledTools: Set<string>): string { // Conditional sections based on which tools are available // Includes explore agent usage tips when enabled // Includes fork agent instructions when FORK_SUBAGENT is active}Output Efficiency
Section titled “Output Efficiency”Two variants — ant-internal (longer, more nuanced communication guidelines) and external (concise “get to the point” instructions):
function getOutputEfficiencySection(): string { if (process.env.USER_TYPE === 'ant') { return `# Communicating with the userWhen sending user-facing text, you're writing for a person, not logging to a console...` } return `# Output efficiencyIMPORTANT: Go straight to the point. Try the simplest approach first...`}Dynamic Sections
Section titled “Dynamic Sections”Dynamic sections are managed through a registry system (systemPromptSections.ts):
// Two types of dynamic sectionssystemPromptSection('memory', () => loadMemoryPrompt())// ↑ Cached by the section registry — value stabilizes within a session
DANGEROUS_uncachedSystemPromptSection('mcp_instructions', () => getMcpInstructionsSection(...))// ↑ Recomputed every turn — changes between turns (e.g., MCP connect/disconnect)Environment Information
Section titled “Environment Information”// Includes: CWD, date/time, git status, OS, model nameasync function computeEnvInfo( modelId: string, additionalWorkingDirectories?: string[],): Promise<string> { const [isGit, unameSR] = await Promise.all([getIsGit(), getUnameSR()]) // Builds environment string with all runtime context}Memory Prompt
Section titled “Memory Prompt”Loaded from the auto-memory system (src/memdir/memdir.ts). The memory prompt section includes MEMORY.md contents, team memory, and save/recall instructions.
Language Preference
Section titled “Language Preference”function getLanguageSection(language?: string): string | null { if (!language) return null return `IMPORTANT: Always respond in ${language}.`}MCP Server Instructions
Section titled “MCP Server Instructions”When MCP servers provide custom instructions (via client.instructions), they’re injected into the system prompt:
function getMcpInstructions(mcpClients: MCPServerConnection[]): string | null { const clientsWithInstructions = connectedClients.filter(c => c.instructions) // Returns "# MCP Server Instructions\n## server-name\n{instructions}"}Special Modes
Section titled “Special Modes”Simple Mode (--bare)
Section titled “Simple Mode (--bare)”Strips the system prompt to a bare minimum — just identity, CWD, and date:
if (isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)) { return [`You are Claude Code...\nCWD: ${getCwd()}\nDate: ${getSessionStartDate()}`]}Proactive Mode
Section titled “Proactive Mode”Autonomous agents get a radically different prompt focused on continuous self-directed work rather than human interaction:
if (proactiveModule?.isProactiveActive()) { return [ `You are an autonomous agent. Use the available tools to do useful work.`, getSystemRemindersSection(), await loadMemoryPrompt(), envInfo, // ... minimal sections ]}Coordinator Mode
Section titled “Coordinator Mode”Coordinator agents use getCoordinatorSystemPrompt() from src/coordinator/coordinatorMode.ts — a completely separate ~370-line prompt that defines the orchestration protocol (see Coordinator Pattern).
Prompt Size Budget
Section titled “Prompt Size Budget”The system prompt is a significant fraction of the context window. Key size decisions:
| Section | Relative Size | Notes |
|---|---|---|
| Coding instructions | Large | Full file-editing protocol |
| Output efficiency | Medium | Ant version is longer |
| Tool usage guide | Variable | Grows with enabled tools |
| Memory | Capped | MEMORY.md: 200 lines, 25KB max |
| MCP instructions | Variable | Depends on connected servers |
| Environment info | Small | ~200 chars |
The two-zone split ensures the largest sections (coding instructions, tool usage) are in the static zone and cached globally, while the smaller per-session sections occupy the dynamic zone.