Skip to content

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.

The system prompt is split into two zones by a boundary marker:

src/constants/prompts.ts
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 --> P

Everything 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 main entry point is getSystemPrompt() in src/constants/prompts.ts:

// src/constants/prompts.ts — simplified structure
export 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.

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
}
function getSimpleDoingTasksSection(): string {
// File editing rules, test/lint requirements
// "When editing files: use Read before Edit"
// "When writing tests: follow existing patterns"
}
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
}

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 user
When sending user-facing text, you're writing for a person, not logging to a console...`
}
return `# Output efficiency
IMPORTANT: Go straight to the point. Try the simplest approach first...`
}

Dynamic sections are managed through a registry system (systemPromptSections.ts):

// Two types of dynamic sections
systemPromptSection('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)
// Includes: CWD, date/time, git status, OS, model name
async function computeEnvInfo(
modelId: string,
additionalWorkingDirectories?: string[],
): Promise<string> {
const [isGit, unameSR] = await Promise.all([getIsGit(), getUnameSR()])
// Builds environment string with all runtime context
}

Loaded from the auto-memory system (src/memdir/memdir.ts). The memory prompt section includes MEMORY.md contents, team memory, and save/recall instructions.

function getLanguageSection(language?: string): string | null {
if (!language) return null
return `IMPORTANT: Always respond in ${language}.`
}

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}"
}

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()}`]
}

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 agents use getCoordinatorSystemPrompt() from src/coordinator/coordinatorMode.ts — a completely separate ~370-line prompt that defines the orchestration protocol (see Coordinator Pattern).

The system prompt is a significant fraction of the context window. Key size decisions:

SectionRelative SizeNotes
Coding instructionsLargeFull file-editing protocol
Output efficiencyMediumAnt version is longer
Tool usage guideVariableGrows with enabled tools
MemoryCappedMEMORY.md: 200 lines, 25KB max
MCP instructionsVariableDepends on connected servers
Environment infoSmall~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.