SpyBara
Go Premium

agent-sdk/modifying-system-prompts.md 2026-09-27 23:59 UTC to 2026-09-28 13:57 UTC

This page contains 106 additions and 0 deletions.

2026
Wed 9 22:58 Thu 10 23:00 Sun 13 21:00 Wed 16 22:58 Fri 18 23:58 Tue 22 23:59 Mon 28 13:57

Modifying system prompts

Choose between the claude_code preset and a custom system prompt, and customize behavior with CLAUDE.md, output styles, append, or a fully custom prompt.

System prompts define Claude's behavior, capabilities, and response style. Start from the claude_code preset for CLI or IDE-like coding tools where a human watches and steers the work. Write your own prompt for agents with a different surface, identity, or permission model.

How system prompts work

A system prompt is the initial instruction set that shapes how Claude behaves throughout a conversation. The Agent SDK has three starting points for it:

  • Minimal default: when you don't set systemPrompt in TypeScript or system_prompt in Python, the SDK uses a minimal prompt that covers tool calling but omits the rest of the claude_code preset's content, including its security and safety instructions and its context about the working directory and environment. This differs from claude -p, which uses the Claude Code system prompt by default. If you're migrating from the CLI and want matching behavior, set the claude_code preset.
  • claude_code preset: the system prompt that the Claude Code CLI uses, with tool usage instructions, security and safety instructions, and context about the working directory and environment. Set systemPrompt: { type: "preset", preset: "claude_code" } in TypeScript or system_prompt={"type": "preset", "preset": "claude_code"} in Python, optionally with append to add your own instructions on the end.
  • Custom string: a prompt you write yourself. The SDK sends only what you provide.

Decide on a starting point

The deciding factor is how closely your agent resembles Claude Code: a coding agent operating in a repository, with a human watching streaming output and steering the work. The further your product is from that, the more you'll want to write your own prompt.

You're building Use What you get
A CLI or IDE-like coding tool where a human watches and steers, and Claude Code's defaults are what you want claude_code preset The Claude Code prompt, including tool guidance, safety rules, and environment context
The same kind of tool, plus product-specific rules like coding standards, output format, or domain context claude_code preset with append Everything above, with your instructions added after the preset. Nothing is removed, so this is the lowest-risk customization
An agent with a different surface, identity, or permission model, or a non-coding agent Custom prompt string Only what you write. You take responsibility for replacing the tool guidance and safety instructions your agent still needs
A thin tool-calling loop with no agent persona, where you supply all behavior in the user prompt No systemPrompt option The minimal default: tool-calling support and nothing else

"Different from Claude Code" usually means one of the following:

  • Different surface: the output isn't read in a terminal by the person who triggered it. Chat UIs, structured-output consumers, and non-coding automation each need a prompt that matches how their output is rendered and reviewed. Unattended coding automation, like a CI job that fixes lint errors or reviews diffs, still fits the preset because the work itself is what the preset is written for.
  • Different identity: the agent shouldn't present itself as Claude Code. A support bot, a data-analysis assistant, or any domain-specific agent needs its own name, scope, and persona.
  • Different permission model: the agent runs autonomously without a human approving each step, or operates on a narrow set of resources. Claude Code's prompt assumes a human is in the loop with access to a full toolset.
  • Non-coding tasks: most of Claude Code's prompt is coding guidance. For research, content, or operations agents, that guidance competes with the instructions you actually need.

The comparison table shows what each customization method preserves.

Customize agent behavior

append and a custom prompt string each change the system prompt directly, and an output style changes the instructions Claude Code gives Claude for every response. CLAUDE.md takes a different path: the SDK reads it and injects its content into the conversation as project context, so it shapes behavior alongside whichever system prompt you choose. Skills, hooks, and permissions also shape behavior outside the system prompt and are covered on their own pages.

CLAUDE.md files for project-level instructions

CLAUDE.md files give Claude persistent project context and instructions. The SDK injects their content into the conversation and leaves the system prompt untouched, so they work with any system prompt configuration. For what to put in CLAUDE.md, where to place it, and how to write effective instructions, see When to add to CLAUDE.md and the rest of How Claude remembers your project. This section covers what's specific to the SDK: how CLAUDE.md loads.

The SDK reads CLAUDE.md when the matching setting source is enabled: 'project' loads CLAUDE.md or .claude/CLAUDE.md from the working directory, and 'user' loads ~/.claude/CLAUDE.md. Default query() options enable both sources, so CLAUDE.md loads automatically. If you set settingSources in TypeScript or setting_sources in Python explicitly, include the sources you need. CLAUDE.md loading is controlled by setting sources, not by the claude_code preset.

Load CLAUDE.md with the SDK

To load CLAUDE.md, set settingSources to include the level where you keep your CLAUDE.md. The example below loads a project-level CLAUDE.md alongside the claude_code preset, so Claude has both the coding-agent prompt and your project's conventions:

import { query } from "@anthropic-ai/claude-agent-sdk";

const messages = [];

for await (const message of query({
prompt: "Add a new React component for user profiles",
options: {
systemPrompt: {
type: "preset",
preset: "claude_code" // Use Claude Code's system prompt
},
settingSources: ["project"] // Loads CLAUDE.md from project
}
})) {
messages.push(message);
}

// Now Claude has access to your project guidelines from CLAUDE.md

When you run either example, the SDK streams messages as Claude works: a system init message, assistant messages, user messages carrying tool results, and a final result message with the session outcome.

CLAUDE.md is persistent across all sessions in a project, shared with your team through git, and discovered automatically without code changes. It is not loaded if you pass an empty settingSources array.

Output styles for persistent configurations

Output styles are saved sets of instructions that change Claude's role, tone, and output format. They're stored as markdown files and can be reused across sessions and projects.

Create an output style

An output style is a markdown file with frontmatter for metadata, followed by the prompt content. Save it to ~/.claude/output-styles/ for a user-level style available in every project, or .claude/output-styles/ in your repository for a project-level style you can commit and share with your team.

A custom output style leaves the claude_code preset's software engineering instructions out and uses your own. To keep them and layer your instructions on top, set keep-coding-instructions: true in the frontmatter. Those instructions are only in Claude Code's full system prompt, so the setting has no effect in a session on the shorter system prompt, which you pin on or off with CLAUDE_CODE_SIMPLE_SYSTEM_PROMPT. Keep them when your agent is still doing software engineering work. Leave them out when you're replacing the role entirely.

The example below defines a code-review persona that keeps the coding instructions, since reviewing code still benefits from Claude Code's security and code-quality guidance. Save it as ~/.claude/output-styles/code-reviewer.md to make it available across projects:

---
name: Code Reviewer
description: Thorough code review assistant
keep-coding-instructions: true
---

You are an expert code reviewer.

For every code submission:
1. Check for bugs and security issues
2. Evaluate performance
3. Suggest improvements
4. Rate code quality (1-10)

Activate an output style

Once created, activate output styles via:

  • CLI: run /output-style <style>, for example /output-style concise, or run /config and select one. The /output-style command requires Claude Code v2.1.269 or later.

  • Settings: set outputStyle in .claude/settings.local.json

  • TypeScript SDK: set outputStyle inside the inline settings object passed to query(), or point settings at a settings file that sets it. outputStyle is not a top-level Options field:

    const options = { settings: { outputStyle: "Explanatory" } };
    

In the Python SDK, set outputStyle through the settings option, which takes a JSON string such as '{"outputStyle": "Explanatory"}' or a path to a settings file that sets it.

Note for SDK users: Output styles are loaded when you include settingSources: ['user'] or settingSources: ['project'] (TypeScript) / setting_sources=["user"] or setting_sources=["project"] (Python) in your options.

Append to the claude_code preset

You can use the Claude Code preset with an append property to add your custom instructions while preserving all built-in functionality.

import { query } from "@anthropic-ai/claude-agent-sdk";

const messages = [];

for await (const message of query({
prompt: "Help me write a Python function to calculate fibonacci numbers",
options: {
systemPrompt: {
type: "preset",
preset: "claude_code",
append: "Always include detailed docstrings and type hints in Python code."
}
}
})) {
messages.push(message);
if (message.type === "assistant") {
console.log(message.message.content);
}
}

Improve prompt caching across users and machines

By default, two sessions that use the same claude_code preset and append text still cannot share a prompt cache entry if they run from different working directories. This is because the preset embeds per-session context in the system prompt ahead of your append text: the working directory, whether it's a git repository, the platform, the active shell, the OS version, and auto memory paths. Any difference in that context produces a different system prompt and a cache miss. CLAUDE.md content doesn't affect the system prompt cache because the SDK injects it into the conversation, not the system prompt.

To make the system prompt identical across sessions, set excludeDynamicSections: true in TypeScript or "exclude_dynamic_sections": True in Python. The per-session context moves into the first user message, leaving only the static preset and your append text in the system prompt so identical configurations share a cache entry across users and machines.

The following example pairs a shared append block with excludeDynamicSections so a fleet of agents running from different directories can reuse the same cached system prompt:

import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
prompt: "Triage the open issues in this repo",
options: {
systemPrompt: {
type: "preset",
preset: "claude_code",
append: "You operate Acme's internal triage workflow. Label issues by component and severity.",
excludeDynamicSections: true
}
}
})) {
// ...
}

Tradeoffs: the working directory, the git-repo flag, the platform, the active shell, the OS version, and auto memory paths still reach Claude, but as part of the first user message rather than the system prompt. Instructions in the user message carry marginally less weight than the same text in the system prompt, so Claude may rely on them less strongly when reasoning about the current directory or auto memory paths. Enable this option when cross-session cache reuse matters more than maximally authoritative environment context.

For the equivalent flag in non-interactive CLI mode, see --exclude-dynamic-system-prompt-sections.

Custom system prompts

You can provide a custom string as systemPrompt to replace the default entirely with your own instructions.

import { query } from "@anthropic-ai/claude-agent-sdk";

const customPrompt = `You are a Python coding specialist.
Follow these guidelines:
- Write clean, well-documented code
- Use type hints for all functions
- Include comprehensive docstrings
- Prefer functional programming patterns when appropriate
- Always explain your code choices`;

const messages = [];

for await (const message of query({
prompt: "Create a data processing pipeline",
options: {
systemPrompt: customPrompt
}
})) {
messages.push(message);
if (message.type === "assistant") {
console.log(message.message.content);
}
}

In Python, load a large custom prompt from a file with system_prompt={"type": "file", "path": "..."} instead of passing it as a string. The Python SDK passes a string prompt as one command-line argument to the CLI subprocess, so a prompt that exceeds the OS argument-length limit fails at process spawn before any API request is sent. On Linux the error is Argument list too long. See SystemPromptFile for the platform thresholds and the Windows behavior.

Cache the static part of a custom prompt

In the TypeScript SDK, you can pass a custom prompt as an array of strings instead of one string, with the SYSTEM_PROMPT_DYNAMIC_BOUNDARY marker between the static part and the rest. Use this when your prompt combines instructions that are the same on every request with context that changes per request, such as the customer or ticket the agent is handling. When you pass both parts as one string, a change to the per-request part changes the whole system prompt, so the static instructions miss the cache too. The array form isn't available in the Python SDK; ClaudeAgentOptions lists the forms system_prompt accepts.

To split the prompt, import SYSTEM_PROMPT_DYNAMIC_BOUNDARY from @anthropic-ai/claude-agent-sdk and pass it as its own array element between the two parts. The SDK sends the strings before the marker as one text block and the strings after it as a second block, each with its own cache breakpoint. In the example below, a support agent loads its triage instructions from a file and receives details about one ticket on each request, so the instructions stay cached while the ticket details change:

import { readFile } from "node:fs/promises";
import { query, SYSTEM_PROMPT_DYNAMIC_BOUNDARY } from "@anthropic-ai/claude-agent-sdk";

// Identical on every request
const instructions = await readFile("triage-instructions.md", "utf8");
// Different on every request
const ticketContext = "Customer plan: Enterprise. Other open tickets from this customer: 3.";

for await (const message of query({
  prompt: "Triage ticket 4821",
  options: {
    systemPrompt: [instructions, SYSTEM_PROMPT_DYNAMIC_BOUNDARY, ticketContext]
  }
})) {
  // ...
}

Track cache tokens describes the cache_creation_input_tokens and cache_read_input_tokens fields on each result message.

The SDK assembles the blocks from the array as follows:

  • The SDK joins the strings on each side of the marker with a blank line between them and removes the marker itself, so the marker text doesn't reach Claude.
  • If you include the marker more than once, the first one is the split and the SDK removes the others.
  • If you leave the marker out, the SDK joins all the strings into one block, the same as passing one string.

With the CLI's --system-prompt or --system-prompt-file flags, the prompt is one string, so there is no array to carry the marker. Include a line containing only __SYSTEM_PROMPT_DYNAMIC_BOUNDARY__ between the static and per-request parts instead. Claude Code splits the prompt at the first such line into the same two blocks and removes that line. Requires Claude Code v2.1.275 or later.

In the SDK, prefer the array form, which carries the boundary without a marker line.

Change the prompt of an existing session

By default, if you pass a different append or custom prompt when you return to a session with resume or continue, Claude doesn't see it on the next turn. Claude Code records the system prompt on a session's first request and reuses that record until the session is compacted. The new text takes effect after that compaction, or in a new session.

Update Claude's instructions mid-session

If the instructions you put in the system prompt need to change while a session is running, for example because your user switched the agent to a read-only mode or edited its configuration in your app, send the new instructions in the conversation instead of changing systemPrompt:

  • In your next message: include the new instructions in the next user message you send.
  • From a hook: return additionalContext from a UserPromptSubmit or PostToolUse hook callback, written as a factual statement such as "The workspace is now read-only". The SDK inserts the text into the conversation at the point where the hook fired, so the recorded prompt stays unchanged.

Turn recording off while you iterate on wording

While you iterate on prompt wording and want each edit to reach a session you resume, set snapshot to false on the object form of the system prompt. Claude Code then rebuilds the prompt on every request. The field is available on the preset and custom forms of systemPrompt in TypeScript and of system_prompt in Python, and requires @anthropic-ai/claude-agent-sdk v0.3.257 or later, or claude-agent-sdk v0.2.153 or later.

Keep recording on in production. With recording off, a different append or custom prompt on a resumed session reaches Claude on the next turn, and that request can't reuse the session's prompt cache. Where the API enforces preserved thinking, Claude also loses its thinking from earlier turns.

Outside of cloud sessions, if you start Claude Code in bare mode by passing --bare through extraArgs or setting CLAUDE_CODE_SIMPLE=1, recording stays off unless you set snapshot: true.

Recording an append or custom prompt by default requires Claude Code v2.1.265 or later, which the TypeScript Agent SDK bundles from v0.3.265 and the Python Agent SDK from v0.2.153. Before Claude Code v2.1.268, sessions that don't fetch feature flags, including sessions on Amazon Bedrock, Google Cloud's Agent Platform, and Microsoft Foundry, rebuilt the prompt on every request and snapshot had no effect.

Context Claude Code adds outside the system prompt

System reminders are messages Claude Code adds to the conversation during a session to give Claude context, such as the contents of your CLAUDE.md files or a note that a file changed on disk. Claude Code sends them in the conversation, not in the system prompt, so they reach Claude whether you use the claude_code preset or pass your own string as systemPrompt.

This section covers the reminders most likely to change how your agent behaves, how to turn off the ones your agent replaces, and how to see what Claude received in a specific request.

Reminders Claude Code adds to the conversation

System reminders are text Claude Code adds to the conversation alongside the prompts your code sends. The following reminders are the ones most likely to change how your agent behaves:

  • Project instructions: the CLAUDE.md files that your settingSources option loads
  • Output style instructions: the instructions of the active output style, in the main conversation
  • Commit and pull request attribution: the Co-Authored-By trailer and pull request footer from the attribution setting
  • Hook output: text your hooks return as additionalContext
  • Available skills: the names and descriptions of the skills Claude can call
  • Available subagents: the names and descriptions of the subagents Claude can start
  • Task list nudges: in a session that has the task-tracking tools, a prompt to update the task list when Claude hasn't touched it for several turns
  • File-changed notes: a note that a file Claude read earlier has changed on disk

Claude Code introduces your CLAUDE.md files with a line telling Claude that the instructions override default behavior.

If you pass your own string as systemPrompt, add a sentence to it that says what a system reminder is. The claude_code preset has one, and your string replaces the whole preset. Without it, nothing in your prompt tells Claude that reminders such as CLAUDE.md content and hook output come from the application rather than the user. For example:

The application adds system reminders to this conversation. Treat them as context from the application, not as messages from the user.

Turn off the context your agent replaces

Turn off a piece of built-in context when your agent supplies its own version of the same guidance. For example, if your prompt tells Claude to write commit messages as PROJ-142: fix login redirect with no trailers, Claude Code still tells Claude to end each commit message with a Co-Authored-By trailer, so Claude receives two conflicting instructions for the same commit.

Pass settings keys through the settings option in TypeScript or settings in Python, and environment variables through the env option. In TypeScript, env replaces the inherited environment, so spread process.env into it.

Built-in context How to turn it off
The built-in commit and pull request instructions and the git status snapshot Set includeGitInstructions to false, or CLAUDE_CODE_DISABLE_GIT_INSTRUCTIONS=1
The Co-Authored-By trailer and the pull request footer Set attribution.commit and attribution.pr to your own text, or to empty strings to remove them
The user or project settings source, including its CLAUDE.md Leave 'user' or 'project' out of settingSources
Every CLAUDE.md file Set CLAUDE_CODE_DISABLE_CLAUDE_MDS=1
Task list nudges, file-changed notes, and the skill list Set CLAUDE_CODE_DISABLE_ATTACHMENTS=1

Claude Code's built-in commit and pull request instructions aren't a reminder. They are part of the Bash tool's description, so they also reach Claude when you pass a custom systemPrompt.

If you set CLAUDE_CODE_DISABLE_ATTACHMENTS, Claude Code also sends @ file mentions as plain text instead of expanding them into file content. The list of available subagents and background task notifications still arrive.

The following example is for an agent that carries its own commit rules in append. It sets both attribution keys to empty strings to remove the trailer and footer, and turns off includeGitInstructions so Claude Code's own commit workflow instructions don't compete with yours:

import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
prompt: "Commit the staged changes for ticket PROJ-142",
options: {
systemPrompt: {
type: "preset",
preset: "claude_code",
append: "Write commit messages as: <ticket id>: <summary>. Add no trailers."
},
settings: {
includeGitInstructions: false,
attribution: { commit: "", pr: "" }
},
allowedTools: ["Bash(git *)"]
}
})) {
if (message.type === "result") console.log(message.subtype);
}

To confirm the change, run the example in a repository with staged changes and check the new commit with git log -1. The message ends without a Co-Authored-By trailer.

See what Claude received

The SDK message stream doesn't include system reminders, so reading the messages your code receives won't show you what Claude saw. To see them, log the requests Claude Code sends:

  • Raw request logging: set OTEL_LOG_RAW_API_BODIES to file:<dir>. Claude Code writes each request body to that directory.
  • A gateway you control: point ANTHROPIC_BASE_URL at a proxy that logs request bodies.

In a logged request, look in the messages array. A reminder appears inside a user message wrapped in <system-reminder> tags or, on some models, as a separate message with the system role.

Compare the four approaches

The four customization methods differ in where they live, how they're shared, and what they preserve from the claude_code preset.

Feature CLAUDE.md Output Styles systemPrompt with append Custom systemPrompt
Persistence Per-project file Saved as files Session only Session only
Reusability Per-project Across projects Code duplication Code duplication
Management On filesystem CLI + files In code In code
Default tools Preserved Preserved Preserved Lost (unless included)
Built-in safety Maintained Maintained Maintained Must be added
Environment context Automatic Automatic Automatic Must be provided
Customization level Additions only Replace or extend default Additions only Complete control
Version control With project Yes With code With code
Scope Project-specific User or project Code session Code session

"With append" means using systemPrompt: { type: "preset", preset: "claude_code", append: "..." } in TypeScript or system_prompt={"type": "preset", "preset": "claude_code", "append": "..."} in Python. CLAUDE.md doesn't change the system prompt itself: the SDK injects its content into the conversation as project context.

Combine approaches

The approaches compose. A persistent output style or CLAUDE.md sets the long-lived behavior, and append layers session-specific instructions on top without touching the saved configuration.

Combine an output style with session-specific additions

The example below assumes a Code Reviewer output style is already active. The append block layers session-specific focus areas on top of the persona, so a single review session can prioritize OAuth and token storage without changing the saved output style:

import { query } from "@anthropic-ai/claude-agent-sdk";

// Assuming "Code Reviewer" output style is active (via /config or settings)
// Add session-specific focus areas
const messages = [];

for await (const message of query({
prompt: "Review this authentication module",
options: {
systemPrompt: {
type: "preset",
preset: "claude_code",
append: `
For this review, prioritize:
- OAuth 2.0 compliance
- Token storage security
- Session management
`
}
}
})) {
messages.push(message);
}

See also

  • Output styles: create, manage, and share output styles for the CLI, including the file format and storage locations
  • How Claude remembers your project: what to put in CLAUDE.md, where to place it, and how to write effective project instructions
  • TypeScript SDK reference: the full Options type, including systemPrompt, settingSources, and settings
  • Python SDK reference: the full ClaudeAgentOptions type, including system_prompt and setting_sources
  • Settings: the settings.json reference, including where output styles and other configuration are stored