
How Claude Code Organizes System Prompts and Context
After reading the claude-code source, I found it does not rely on a simple system prompt to nudge the model toward correct tool selection. Instead, it uses a layered control surface to shape behavior in advance.
After reading the claude-code source code, my core takeaway is that it does not rely on a simple system prompt to "remind the model to pick the right tools." Instead, it uses a layered control surface to shape behavior in advance.
This surface has at least six layers.
Layer 1: Static System Prompt
The default prompt is assembled in src/constants/prompts.ts:445-578. It includes a dedicated # Using your tools section that explicitly states "use dedicated tools instead of Bash" and provides global rules for reading files, editing files, searching, and parallel tool calls.
Layer 2: Separation of User Context and System Context
src/context.ts:116-188 only produces context data. The actual injection happens in src/utils/api.ts:437-474. Here, systemContext is appended to the system prompt, while userContext is wrapped as a synthetic user message inside <system-reminder>.
This matters. Repo instructions, memory, dates, and similar info are fed by "semantic role," not dumped into a single string.
Layer 3: Tool-Specific Prompts and Descriptions
Before tool schemas are sent to the model, src/utils/api.ts:119-266 renders each tool's prompt() result into the API description. Tool selection relies mainly on each tool's affordance definition, not on guessing from the top-level prompt.
Typical examples:
src/tools/GrepTool/prompt.ts:6-17src/tools/FileReadTool/prompt.ts:27-48src/tools/BashTool/prompt.ts:42-160
Layer 4: Reminders and Attachments
These are not added just once at session start. They are injected:
- At the initial user input stage:
src/utils/processUserInput/processUserInput.ts:495-513 - Again before the next turn after tool execution:
src/query.ts:1541-1593
These attachments are eventually converted into <system-reminder> messages visible to the model in src/utils/messages.ts:3660-4290. This layer dynamically reminds the model about plan mode, auto mode, todo/task, skills, deferred tools, MCP instructions, compaction, etc.
Layer 5: Tool Visibility Itself
src/utils/toolSearch.ts:154-198 and src/services/api/claude.ts:1105-1233 do not just tell the model "which tool to use." They directly decide which tools are visible at this moment, which to defer, and which to surface via ToolSearch. This is the strongest prior—tools the model cannot see will not be selected.
Layer 6: Structural and Cache Stability
src/constants/prompts.ts:115 defines SYSTEM_PROMPT_DYNAMIC_BOUNDARY. src/utils/api.ts:321-435 splits static and dynamic prompt blocks. src/constants/systemPromptSections.ts:17-57 handles section-level caching. src/utils/toolSchemaCache.ts:3-8 handles tool schema caching.
A finer layer is src/utils/messages.ts:1808-1950, which re-wraps, smooshes, and relocates <system-reminder> to prevent the context topology itself from inducing bad patterns.
The Philosophy Behind This Context Construction
Treat "tool selection" as a continuous control loop, not a one-time prompt.
- Stable rules go in the static prompt; volatile info goes into reminders/deltas so dynamic content does not pollute the entire prompt prefix
- Tool descriptions are the primary routing surface; the top-level system prompt is just the general rule
- Dynamic capability changes should be communicated via "incremental reminders" or "capability list changes," not by rewriting the full prompt every turn
- Optimize not just token cost but behavioral stability. Claude Code treats prompt bytes as architectural assets, not string concatenation artifacts
- System-injected info is explicitly tagged with
<system-reminder>and specially handled during message normalization to prevent the model from learning it as a regular user turn pattern
Comparison: Common Simplified Approaches
Many agent systems still construct context at the "single-string system prompt + repo context concatenation + tool modelDescription" layer, lacking a true layered context system.
Typical symptoms:
- Concatenating fixed system prompt with
repoContext.contentinto one string - Having
stableandvolatileconcepts as labels without driving different injection strategies - Provider layer only accepts a single
systemPrompt?: string, keeping the system layer as a "flat string" model - Tool selection relies on two places: a few general rules in the app layer + each tool's
MODEL_DESCRIPTION
What is missing is not "more copy," but an independent reminder plane and request-time context assembly.
Upgrade Recommendations
If you are building a similar agent system, consider these directions:
Architecture
- Stop modeling context as a final string; move to a provider-neutral
PromptEnvelope - Complete the
ContextSectionidea: addchannel(distinguishing system, user-reminder, post-tool-reminder), keepstability, addcadenceorinjectionPolicy - Let
AgentSessionperform context assembly on each provider request, rather than having the app layer pre-concatenate a full string
Context Usage
- The
CLAUDE.mdpath is better suited for a stable repo-instruction channel cwd/repoRoot/envfits into a session-level system sectioncurrent-date,git snapshotare better suited for volatile reminders, not mixed with durable rules in one string
Tool Layer
- Do not hardcode global "which tool to use" rules; generate them from the tool registry
- Let tool definitions expose explicit routing metadata such as
preferredOver,fallbackOnly,primaryUseCases,counterExamples
Reminder Strategy
- Error recovery reminder: after consecutive tool argument failures, give the model a correction reminder
- Capability change reminder: when adding MCP / dynamic tools, use delta hints rather than rewriting the full prompt
- Mode reminder: if introducing plan mode / auto mode later, reuse the same mechanism
In one sentence: Claude Code's strength is not longer prompts, but separating "static rules, dynamic reminders, tool affordances, tool visibility, and context structural stability" into different layers. To upgrade, stop adding sentences to buildSystemPrompt() and move context construction from "string concatenation" to "layered assembly."


