Agent SDKのシステムプロンプトをカスタマイズする4つのアプローチ
CLAUDE.md・Output Styles・append・カスタムプロンプトの4つを比較し、Claude Codeとの違いの大きさに応じた選び方とプロンプトキャッシュの注意点をまとめます。
Agent SDKのシステムプロンプトには3つの出発点がある
Agent SDKで query() を呼ぶとき、システムプロンプト(会話全体でClaudeの振る舞いを決める最初の指示)の出発点は3つあります。claude -p の既定はClaude Codeのシステムプロンプトそのものですが、SDKは指定がなければ別の出発点から始まります。CLIと同じ挙動にしたいときは、指定の仕方を意識する必要があります。
3つの出発点
最小の既定
systemPrompt(Pythonはsystem_prompt)を指定しない場合です。ツール呼び出しに必要な最低限の指示だけが入る最小プロンプトで、セキュリティ・安全性の指示は入りません。claude_codeプリセット
systemPrompt: { type: "preset", preset: "claude_code" }と書くと、Claude Code CLIと同じツール利用ガイドと安全指示が使われます。Pythonはsystem_prompt={"type": "preset", "preset": "claude_code"}です。自作の文字列
SDKは渡した内容だけを送ります。ツールの使い方も安全面の指示も、必要なら自分で書きます。
選ぶ基準は「自分のエージェントがどれだけClaude Codeに近いか」です。人間が画面を見ながらストリーミング出力を確認し、作業を操縦するコーディングツールならプリセット、遠いほど自作プロンプトが向きます。
Claude Codeと違うと言える4つの目安
Claude Codeと「違う」と判断する材料は主に4つです。
- 利用形態が違う: 出力を、起動した本人がターミナルで読まない。チャットUI・構造化出力の消費者・人が介在しない自動化が当たります。ただしlintエラーを直すCIジョブのような無人のコーディング自動化は、作業内容自体がプリセットの想定どおりなのでプリセットのままで合います
- アイデンティティが違う: エージェントがClaude Codeを名乗ってはいけない。サポートbot、データ分析アシスタント、ドメイン特化のエージェントは、固有の名前・守備範囲・人格が要ります
- 権限モデルが違う: 人間が各ステップを承認せず自律的に動く、あるいは扱うリソースが狭い範囲に限られる。Claude Codeのプロンプトは、人間が介在して全ツールを使える前提で書かれています
- コーディング以外のタスク: プリセットの大半はコーディング向けの指示で、リサーチ・文章生成・運用系のエージェントでは本当に必要な指示と競合します
4つの手段の比較表
システムプロンプトを直接変えるのは append とカスタム文字列の2つで、Output Stylesは応答のたびにClaude Codeが渡す指示を変えます。そこにCLAUDE.mdを加えた4つを並べて比べます。CLAUDE.mdだけは経路が違い、システムプロンプトではなく会話の中に注入されます。そのため、どのシステムプロンプト設定を選んでいてもCLAUDE.mdは並行して効きます。
| 観点 | CLAUDE.md | Output Styles | append | カスタムsystemPrompt |
|---|---|---|---|---|
| 永続性 | CLAUDE.mdプロジェクトファイル単位 | Output Stylesファイルとして保存 | appendセッション限り | カスタムsystemPromptセッション限り |
| 再利用性 | CLAUDE.mdプロジェクト単位 | Output Styles複数プロジェクトで共有可 | appendコードの複製が必要 | カスタムsystemPromptコードの複製が必要 |
| 管理場所 | CLAUDE.mdファイルシステム | Output StylesCLIとファイル | appendコード内 | カスタムsystemPromptコード内 |
| 既定ツール | CLAUDE.md維持される | Output Styles維持される | append維持される | カスタムsystemPrompt含めない限り失われる |
| 安全指示 | CLAUDE.md維持される | Output Styles維持される | append維持される | カスタムsystemPrompt自分で追加が必要 |
| 変えられる範囲 | CLAUDE.md追記のみ | Output Styles既定の置き換えも追記も可 | append追記のみ | カスタムsystemPrompt完全に制御 |
append の正式な書き方は systemPrompt: { type: "preset", preset: "claude_code", append: "..." }(Pythonは対応するdict)です。
CLAUDE.mdでプロジェクトの前提を渡す
CLAUDE.mdはプロジェクトに紐づく永続的なコンテキストで、gitでチームと共有でき、コード変更なしに自動検出されます。SDK側では settingSources(Pythonは setting_sources)に読み込みたいレベルを含めると有効になります。'project' はカレントディレクトリの CLAUDE.md / .claude/CLAUDE.md、'user' は ~/.claude/CLAUDE.md を読み込みます。
既定では project / user の両方が有効です。明示的に指定した場合は、そこに挙げたレベルだけが読み込まれます(settingSources: ["project"] なら ~/.claude/CLAUDE.md 側は読まれません)。空配列を渡すと、CLAUDE.mdは読み込まれません。読み込みを決めるのは settingSources で、claude_code プリセットではありません。
import { query } from "@anthropic-ai/claude-agent-sdk";
for await (const message of query({
prompt: "Add a new React component for user profiles",
options: {
systemPrompt: { type: "preset", preset: "claude_code" },
settingSources: ["project"]
}
})) {
// ...
}CLAUDE.mdの中身をどう設計するかは、システムプロンプトの選択とは独立した話です。書くべき内容や置き場所はClaude CodeのCLAUDE.mdを実用に引き上げる10のパターンにまとめています。
Output Stylesで永続的な人格を作る
Output Stylesは、Claudeの役割・口調・出力形式を変える指示をMarkdownファイルとして保存し、セッションやプロジェクトをまたいで再利用する仕組みです。~/.claude/output-styles/ に置けば全プロジェクトで、.claude/output-styles/ に置けばそのリポジトリのチームで共有できます。
既定では claude_code プリセットのソフトウェアエンジニアリング指示を除いて、独自の内容に置き換わります。残したまま上乗せしたい場合は、frontmatterに keep-coding-instructions: true を指定します。コードレビュー担当のように、セキュリティやコード品質の指針がまだ役立つ役割なら残す側です。役割を丸ごと入れ替えるなら外します。
---
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有効化の経路はTypeScriptとPythonで形が違います。
- TypeScript:
query()のsettingsオブジェクトにoutputStyleを入れます。outputStyleはOptionsのトップレベルのフィールドではありません - Python:
settingsオプションにJSON文字列('{"outputStyle": "Explanatory"}')か、設定ファイルのパスを渡します
const options = { settings: { outputStyle: "Explanatory" } };どちらの言語でも、settingSources に 'user' か 'project' のどちらかを含めないとOutput Styleは読み込まれません。環境変数 CLAUDE_CODE_SIMPLE_SYSTEM_PROMPT で短縮版システムプロンプトに切り替えているセッションでは、keep-coding-instructions: true は効きません。ソフトウェアエンジニアリング指示は完全版にしか含まれず、残す対象がないためです。
CLIからの切り替えは /output-style <名前>(Claude Code v2.1.269以降)か /config で、.claude/settings.local.json の outputStyle に書く方法もあります。手順はClaude Code output styleの切り替えは/configで行うで扱っています。
appendでclaude_codeプリセットに追記する
claude_code プリセットの内容をすべて残したまま、独自の指示だけを末尾に足したいときは append を使います。組み込み機能を何も削らないため、4つの中で最もリスクの低いカスタマイズです。
options: {
systemPrompt: {
type: "preset",
preset: "claude_code",
append: "Always include detailed docstrings and type hints in Python code."
}
}プロンプトキャッシュが共有されないとき
同じ claude_code プリセットと同じ append を使っていても、2つのセッションがプロンプトキャッシュを共有できないことがあります。原因は、プリセットが自動メモリー(auto memory)の保存場所をシステムプロンプトの append より前に埋め込むことです。この場所は既定で ~/.claude/projects/ 配下の、リポジトリのディスク上のパスにちなんだ絶対パスになります。ユーザー・マシン・チェックアウトごとに値が変わるため、システムプロンプトの中身も変わります。
一方で、CLAUDE.mdの内容や、作業ディレクトリ・プラットフォーム・シェル・OSバージョンといった環境情報は、システムプロンプトではなく会話側で渡されます。これらはキャッシュに影響しません。
SDK v0.3.288の型定義では、excludeDynamicSections のコメントが「作業ディレクトリ、自動メモリーのパス」を取り除く対象に挙げています。別の箇所のコメントには、git状態も挙がっています。公式ページは自動メモリーの場所が原因だと書き、型定義は作業ディレクトリも含めています。説明が食い違う以上、どこまでがキャッシュを分けるかは実際のキャッシュトークンの数字で見るのが確実です。
excludeDynamicSections: true(Pythonは "exclude_dynamic_sections": True)を設定すると、ユーザーごとの文脈が最初のユーザーメッセージ側に移ります。システムプロンプトには静的なプリセットと append だけが残るので、同じ構成のセッションがユーザーやマシンをまたいで同じキャッシュエントリを共有できます。
CLIの非対話モードにも同等のフラグ --exclude-dynamic-system-prompt-sections があります。
要件はSDKのバージョンです。TypeScriptは @anthropic-ai/claude-agent-sdk v0.2.98以降、Pythonは claude-agent-sdk v0.1.58以降で使えます。プリセットをオブジェクトで指定した場合にだけ有効で、カスタム文字列では無視されます。
systemPrompt: {
type: "preset",
preset: "claude_code",
append: "You operate Acme's internal triage workflow. Label issues by component and severity.",
excludeDynamicSections: true
}代償は、ユーザーメッセージ内の指示がシステムプロンプト内の同じ文よりわずかに軽く扱われることです。移るのは少なくとも自動メモリーの場所で、自動メモリーの節ごと移ることもあります。そのため、自動メモリーに関する指示への従い方が揺れる可能性があります。多数のエージェントを別々のマシンで動かし、キャッシュ再利用の価値が大きい構成向けの設定です。何をセッション間で持ち越し何を持ち越さないかという判断は、Managed Agentsのメモリ管理ベストプラクティスでも別の切り口から扱っています。
完全カスタムプロンプトに置き換える
systemPrompt に文字列を渡すと、既定のプロンプトを完全に自分の指示だけに置き換えられます。この経路を選ぶと、既定で付いてくるツール利用ガイドと安全指示は失われ、必要なものは自分で明記する責任が生じます。制御は最大ですが、準備すべきことも最も多い選択肢です。
置き換えても残るものがあります。CLAUDE.mdやフックの出力、利用可能なスキルの一覧といった「システムリマインダー」は会話側に足されるので、プリセットの有無に関係なくClaudeに届きます。ところがプリセットには「リマインダーとは何か」を説明する一文が入っていて、自作文字列はそれごと消えます。そのままだと、CLAUDE.mdの内容やフックの出力が利用者の発言ではなくアプリケーションからの文脈だと、プロンプトのどこにも書かれません。次のような一文を足します。
The application adds system reminders to this conversation. Treat them as context from the application, not as messages from the user.Pythonで大きなカスタムプロンプトを文字列のまま渡すと、CLIサブプロセスへの起動引数になる都合で、OSの引数長の上限を超えて起動に失敗することがあります(Linuxでは Argument list too long)。長いプロンプトは system_prompt={"type": "file", "path": "..."} でファイルから読み込みます。
固定部分と変動部分でキャッシュを分ける(TypeScript)
TypeScript SDKでは、カスタムプロンプトを文字列の配列で渡せます。毎回同じ指示と、リクエストごとに変わる文脈(対応中の顧客やチケットなど)の間に SYSTEM_PROMPT_DYNAMIC_BOUNDARY を置くと、境界の前後が別々のブロックになり、それぞれにキャッシュ位置が付きます。1つの文字列で渡すと、変動部分が変わるたびに固定部分もキャッシュを外します。
import { readFile } from "node:fs/promises";
import { query, SYSTEM_PROMPT_DYNAMIC_BOUNDARY } from "@anthropic-ai/claude-agent-sdk";
const instructions = await readFile("triage-instructions.md", "utf8"); // 毎回同じ
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]
}
})) {
// ...
}SDK v0.3.288では、この定数は "__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__" という文字列として定義され、systemPrompt の型は string | string[] と、type: 'custom' や type: 'preset' のオブジェクトの組み合わせです。境界が複数あれば最初の1つで分割され、境界がなければ全部が1つのブロックになります。Python SDKには配列形式がありません。
CLIの --system-prompt と --system-prompt-file では、__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__ だけを書いた行が境界になります。こちらはClaude Code v2.1.275以降が必要です。
この分割が効くのは、Claude APIを直接呼ぶ場合とClaude Platform on AWSで動かす場合です。Amazon Bedrock・Google CloudのAgent Platform・Microsoft Foundry・LLMゲートウェイ経由では、プロンプトは1ブロックで送られます。CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1 のときも同じです。
再開したセッションで指示が変わらないとき
resume や continue でセッションに戻るとき、別の append やカスタムプロンプトを渡しても、次のターンでClaudeはそれを見ません。Claude Codeはセッションの最初のリクエストでシステムプロンプトを記録し、コンパクションが起きるまでその記録を使い回すためです。新しい文面が効くのは、コンパクションの後か、新しいセッションです。
この仕様を前提に、実行中に指示を変えたい場合(利用者が読み取り専用モードに切り替えた、など)は、システムプロンプトを触らず会話に足します。
- 次のユーザーメッセージに含める: 新しい指示をそのまま書きます
- フックから返す:
UserPromptSubmitやPostToolUseのフックコールバックがadditionalContextを返します。「ワークスペースは読み取り専用になりました」のような事実の文で書きます
プロンプトの文面を試行錯誤している間だけは、systemPrompt のオブジェクト形式で snapshot: false にできます。リクエストのたびにプロンプトが作り直されるので、再開したセッションにも変更が届きます。SDK v0.3.288の型定義では、preset 形式と custom 形式の両方に snapshot?: boolean があります。使えるのは @anthropic-ai/claude-agent-sdk v0.3.257以降、Pythonは claude-agent-sdk v0.2.153以降です。
本番では記録を残したままにします。オフにすると、再開したセッションはそのセッションのプロンプトキャッシュを使えなくなります。プリザーブドシンキング(過去ターンの思考の保持)をAPIが強制する環境では、過去のターンの思考も失われます。クラウドセッション以外で --bare で起動した場合や CLAUDE_CODE_SIMPLE=1 のときは、snapshot: true を指定しない限り記録はオフです。
append やカスタムプロンプトを既定で記録するには、Claude Code v2.1.265以降が必要です(TypeScript SDK v0.3.265、Python SDK v0.2.153から同梱)。v2.1.268より前は、機能フラグを取得しないセッション(Amazon Bedrock・Google CloudのAgent Platform・Microsoft Foundry)でプロンプトが毎リクエスト作り直され、snapshot が効きませんでした。
プリセットのままコミット規則を差し替える
自作のコミット規則を append に書いても、Claude Codeは各コミットメッセージの末尾に Co-Authored-By を付けるよう指示します。たとえば「PROJ-142: fix login redirect の形式で、トレーラーなし」と書いても、同じコミットに2つの矛盾した指示が届きます。エージェントが自前のガイダンスを持つ部分は、組み込み側を切ります。
| 組み込みの文脈 | 切り方 |
|---|---|
| コミット・PRの指示とgit状態のスナップショット | 切り方includeGitInstructions を false、または CLAUDE_CODE_DISABLE_GIT_INSTRUCTIONS=1 |
Co-Authored-By とPRのフッター | 切り方attribution.commit / attribution.pr を自前の文面か空文字列に |
| すべてのCLAUDE.md | 切り方CLAUDE_CODE_DISABLE_CLAUDE_MDS=1 |
| タスクリストの催促・ファイル変更の通知・スキル一覧 | 切り方CLAUDE_CODE_DISABLE_ATTACHMENTS=1 |
CLAUDE_CODE_DISABLE_ATTACHMENTS を設定すると、@ によるファイル指定も、内容の展開をやめて文字のまま送られます。コミット・PRの組み込み指示はリマインダーではなくBashツールの説明の一部なので、カスタムの systemPrompt を渡しても届きます。
TypeScriptでは settings オプションで設定キーを、env オプションで環境変数を渡します。env は継承された環境変数を置き換えるので、process.env を展開して渡します。
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);
}ステージ済みの変更があるリポジトリで実行し、git log -1 で新しいコミットを見ると、Co-Authored-By が付いていないことを確かめられます。
Claudeに実際に届いた内容を見る
SDKのメッセージストリームにはシステムリマインダーが含まれません。コードが受け取るメッセージを読んでも、Claudeが見たものは分かりません。送信されたリクエストのほうを記録します。
リクエストの中身を見る
- 1
記録先を指定する
環境変数
OTEL_LOG_RAW_API_BODIESにfile:<ディレクトリ>を設定します。Claude Codeがリクエスト本文を1件ずつそのディレクトリに書きます。自分で用意したプロキシにログを取らせたいときは、ANTHROPIC_BASE_URLをそのプロキシに向けます。 - 2
エージェントを実行する
確認したい
systemPromptと設定でquery()を動かします。 - 3
messages配列を探す
リマインダーは、
system-reminderタグで囲まれたユーザーメッセージとして入っています。モデルによっては、systemロールの別メッセージです。
使い分け早見表
| 作っているもの | 使う手法 |
|---|---|
| 人間が操縦するCLI・IDE系のコーディングツールで、Claude Codeの既定でよい | 使う手法claude_code プリセット |
| 同種のツールに、コーディング規約や出力形式などプロダクト固有のルールを足したい | 使う手法claude_code プリセット + append |
| 利用形態・アイデンティティ・権限モデルが異なるエージェント、非コーディング系のエージェント | 使う手法カスタムプロンプト文字列 |
| ユーザープロンプトにすべての振る舞いを書き、ペルソナを持たない薄いツール呼び出しループ | 使う手法systemPrompt を指定しない(最小既定) |
アプローチは組み合わせられる
これらの手段は排他的ではありません。Output StylesやCLAUDE.mdで長期的な振る舞いを決め、その上に append でセッション固有の指示を重ねるという組み合わせが典型です。たとえばCode ReviewerというOutput Styleを有効にした状態で、そのセッションだけ「OAuth 2.0への準拠を優先してレビューして」という append を足せば、保存済みの設定自体は変えずに一時的な重点だけを切り替えられます。
まとめ
自作のカスタムプロンプトに踏み切ると、ツール利用と安全の指示、システムリマインダーの一文、コミット規則のような組み込みの文脈の扱いを、すべて自分で決めることになります。プリセットのままで足りるなら、append が最もリスクの低い入口です。