LogoSteve
  • Blog
  • About
How Claude Code Organizes System Prompts and Context
2026/04/06

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-17
  • src/tools/FileReadTool/prompt.ts:27-48
  • src/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.content into one string
  • Having stable and volatile concepts 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 ContextSection idea: add channel (distinguishing system, user-reminder, post-tool-reminder), keep stability, add cadence or injectionPolicy
  • Let AgentSession perform context assembly on each provider request, rather than having the app layer pre-concatenate a full string

Context Usage

  • The CLAUDE.md path is better suited for a stable repo-instruction channel
  • cwd/repoRoot/env fits into a session-level system section
  • current-date, git snapshot are 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."

All Posts

Author

avatar for Steve
Steve

Categories

  • Agent
Layer 1: Static System PromptLayer 2: Separation of User Context and System ContextLayer 3: Tool-Specific Prompts and DescriptionsLayer 4: Reminders and AttachmentsLayer 5: Tool Visibility ItselfLayer 6: Structural and Cache StabilityThe Philosophy Behind This Context ConstructionComparison: Common Simplified ApproachesUpgrade Recommendations

More Posts

How I Think About Agent Memory
Agent

How I Think About Agent Memory

I prefer agent memory on demand, not by default. The real difficulty is deciding what to keep, when to keep it, and how to stop memory from turning into noise.

avatar for Steve
Steve
2026/03/30
Agent vs Harnessed Agent
Agent

Agent vs Harnessed Agent

Claude Code is great for interactive exploration. Once you need long-running, recoverable, auditable agent execution, code-level control becomes much harder to avoid.

avatar for Steve
Steve
2026/03/30
How Claude Code Optimizes Tool Calling
Agent

How Claude Code Optimizes Tool Calling

After reading the claude-code source code, I found that its tool calling optimization relies not on a single trick, but on multiple layers of mechanisms working together.

avatar for Steve
Steve
2026/04/05
LogoSteve

Steve's Blog

© 2026 Steve