【Claude Code 連載 第86回】システムプロンプトの変更(agent-sdk/modifying-system-prompts)

スポンサーリンク
【Claude Code 連載 第86回】システムプロンプトの変更(agent-sdk/modifying-system-prompts) 用語解説
【Claude Code 連載 第86回】システムプロンプトの変更(agent-sdk/modifying-system-prompts)
この記事は約35分で読めます。
よっしー
よっしー

こんにちは。よっしーです(^^)

スポンサーリンク

背景

この連載では、Claude Codeの公式ドキュメントを1ページずつ読み解いていきます。公式ドキュメントは情報が網羅されている分、「結局どの機能を、どんな場面で使えばいいのか」は自分で考える必要があり、読むのに意外と時間がかかります。そこで、私が実務で使うために読み込んだ内容を「使う場面→実例コード」の順に整理して残していくことにしました。専門家の解説というより、一次情報を読んだ記録です。推測や動作を確認していない部分には、その都度そう書きます。

公式ドキュメント:https://code.claude.com/docs/ja/agent-sdk/modifying-system-prompts

1. 一言でいうと

システムプロンプトは、会話の最初から最後まで Claude の振る舞いを方向づける「一番上の指示」です。Agent SDK では、それを「何も指定しない(最小限)」「Claude Code と同じものを使う」「Claude Code のものに書き足す」「自分で全部書く」の中から選べます。このページは、その選び方と、CLAUDE.md・出力スタイルを含めた4つのカスタマイズ方法の違いをまとめたものです。

最も大事な事実を先に書いておきます。SDK で何も指定しないと、Claude Code のシステムプロンプトは使われません。 ツールを呼ぶための最小限のプロンプトだけになり、安全のための指示や、作業ディレクトリ・環境の情報は入りません。CLI の claude -p は既定で Claude Code のシステムプロンプトを使うので、ここが CLI と SDK の大きな違いです。


2. どういう場面で役立つか

シーン1:CLI で動かしていた自動化を SDK に移す

claude -p で動かしていたコードレビューの自動化を、SDK のアプリに書き直すとします。何も考えずに移すと、上の違いのせいで Claude の振る舞いが変わります。claude_code プリセットを指定すれば、CLI と同じシステムプロンプトで動きます。

シーン2:自社のコーディング規約を足す

「Python には必ず型ヒントと docstring を付ける」のような規約を、Claude Code の振る舞いはそのままに足したい場合は、プリセットに append で書き足します。何も削らないので、最も危険の少ないカスタマイズです。

シーン3:コーディング以外のエージェントを作る

社内の問い合わせ対応ボットやデータ分析のアシスタントは、「Claude Code」として振る舞うべきではありません。独自の名前・担当範囲・人格を持たせるには、システムプロンプトを自分で全部書きます。

不要・向かないケース

  • セッションの途中で指示を変えたい:システムプロンプトは最初のリクエストで記録され、同じセッションでは使い回されます。途中の変更は、次のユーザーメッセージかフックの additionalContext で伝えます(後述)。
  • 絶対に守らせたい禁止事項:システムプロンプトは指示であって保証ではありません。確実に止めたい操作は、権限やフックで止めます(第72・80・82回)。
  • プロジェクトの規約を全セッションで共有したいだけ:コードを書き換えなくても、CLAUDE.md に書けば済みます(ただし SDK で読み込まれる条件があります。後述)。

3. コードと仕組みの解説

原文の fenced コードブロックは13本です。どれも内容が違い、同じ例の再掲がないので、全数を引用します。Python と TypeScript の対訳は両方載せます。コードは原文から機械的に抜き出したもので、入れ子に由来する先頭インデントを外した以外は変えていません。

3-0. バージョン要件

機能ごとに必要なバージョンが分かれています。

機能必要バージョン
excludeDynamicSections(動的な情報をシステムプロンプトから外す)TypeScript SDK v0.2.98 以降/Python SDK v0.1.58 以降
append やカスタムプロンプトを既定で記録するClaude Code v2.1.265 以降(TypeScript SDK v0.3.265、Python SDK v0.2.153 から同梱)
機能フラグを取得しないセッション(Amazon Bedrock・Google Cloud の Agent Platform・Microsoft Foundry など)でも記録が効く(それ以前は毎回プロンプトを作り直し、snapshot は効果なし)Claude Code v2.1.268 以降
CLI の /output-style コマンドClaude Code v2.1.269 以降
CLI のプロンプト内の __SYSTEM_PROMPT_DYNAMIC_BOUNDARY__ 行による分割Claude Code v2.1.275 以降
snapshot: false(記録をオフにする)TypeScript SDK v0.3.257 以降/Python SDK v0.2.153 以降

3-1. 3つの出発点と選び方

Agent SDK のシステムプロンプトには、3つの出発点があります。

出発点指定のしかた中身
最小限の既定systemPrompt(Python は system_prompt)を指定しないツール呼び出しをカバーする最小限のプロンプト。claude_code プリセットの残り(安全のための指示、作業ディレクトリと環境の情報など)は入らない
claude_code プリセットTypeScript:systemPrompt: { type: "preset", preset: "claude_code" }/Python:system_prompt={"type": "preset", "preset": "claude_code"}Claude Code の CLI が使うシステムプロンプト。ツールの使い方、安全のための指示、作業ディレクトリと環境の情報を含む。append で末尾に指示を足せる
カスタム文字列自分で書いた文字列を渡す渡したものだけが送られる

冒頭で書いたとおり、最小限の既定は claude -p の既定とは違います。CLI から移行して同じ振る舞いにしたいなら、claude_code プリセットを指定します。

どれを選ぶかの判断基準は「作るエージェントが Claude Code にどれだけ似ているか」です。 Claude Code は、リポジトリの中で動くコーディングエージェントで、人間がストリーミングされる出力を見ながら作業を導きます。作る製品がこの姿から遠いほど、自分でプロンプトを書く必要が大きくなります。

作るもの使うもの得られるもの
人間が見ながら導く、CLI や IDE のようなコーディングツール。Claude Code の既定がほしいclaude_code プリセットClaude Code のプロンプト(ツールの案内、安全のルール、環境の情報)
同じ種類のツールに、コーディング規約・出力形式・業務知識などの製品固有のルールを足したものclaude_code プリセット+append上の全部に、プリセットの後ろに足した指示。何も削らないので最も危険の少ないカスタマイズ
画面・アイデンティティ・権限の考え方が違うエージェント、またはコーディング以外のエージェントカスタムの文字列書いたものだけ。必要なツールの案内と安全の指示を自分で用意する責任がある
ツール呼び出しのループが薄く、人格を持たせず、振る舞いは全てユーザーのプロンプトで与えるsystemPrompt を指定しない最小限の既定(ツール呼び出しの対応だけ)

「Claude Code と違う」とは、通常次のどれかです。

  • 画面が違う:出力を、それを頼んだ人がターミナルで読むわけではない。チャット画面、構造化された出力を受け取るプログラム、コーディング以外の自動化は、出力がどう表示され、どう確認されるかに合ったプロンプトが必要です。一方、CI でリントエラーを直す、差分をレビューする、といった無人のコーディング自動化は、作業そのものがプリセットの想定どおりなので、プリセットが合います。
  • アイデンティティが違う:エージェントが「Claude Code」として名乗るべきではない。サポートボット、データ分析のアシスタント、業務特化のエージェントには、独自の名前・担当範囲・人格が必要です(第76回のブランドの規定とも関係します)。
  • 権限の考え方が違う:人間が1歩ずつ承認せずに自律的に動く、または限られた資源だけを扱う。Claude Code のプロンプトは、人間がループの中にいて、全てのツールを使えることを前提にしています。
  • コーディング以外の作業:Claude Code のプロンプトの大半はコーディングの案内です。調査、コンテンツ制作、運用のエージェントでは、その案内が本当に必要な指示とぶつかります。

「安全のための指示が無い」ことの意味。 最小限の既定やカスタム文字列では、プリセットに含まれる安全のための指示が入りません。ただし、これはシステムプロンプトの中の指示の話で、第80回の権限の評価や第82回のフックといった仕組みによる制御は、システムプロンプトの選択とは関係なく働きます。確実に止めたいことは仕組みで止める、という第72回の原則はここでも同じです(この整理は筆者)。

3-2. 振る舞いを変える4つの方法

append とカスタム文字列は、システムプロンプトそのものを変えます。出力スタイルは、Claude Code が毎回の応答で Claude に与える指示を変えます。CLAUDE.md だけは経路が違い、SDK が読み込んだ内容を「プロジェクトの情報」として会話に差し込むので、選んだシステムプロンプトと並んで振る舞いを形づくります。スキル、フック、権限もシステムプロンプトの外で振る舞いを形づくりますが、それぞれ別のページの範囲です。

CLAUDE.md(プロジェクト単位の指示)

CLAUDE.md は、プロジェクトについての情報と指示を Claude に持続的に渡すファイルです。SDK はその内容を会話に差し込み、システムプロンプトには触れないので、どのシステムプロンプトの設定とも組み合わせられます。何を書くか、どこに置くか、効く書き方はメモリのページ(第7回)の範囲で、このページは SDK での読み込み方だけを扱います。

SDK が CLAUDE.md を読むのは、対応する設定ソースが有効なときです。

設定ソース読み込むファイル
'project'作業ディレクトリの CLAUDE.md か .claude/CLAUDE.md
'user'~/.claude/CLAUDE.md

既定の query() オプションでは両方が有効なので、CLAUDE.md は自動で読み込まれます。settingSources(Python は setting_sources)を明示的に指定する場合は、必要なソースを含めます。CLAUDE.md の読み込みは設定ソースで決まり、claude_code プリセットを使うかどうかとは関係ありません。 カスタム文字列のプロンプトでも、設定ソースが有効なら CLAUDE.md は読まれます。空の settingSources 配列を渡すと読まれません(第79回のマルチテナント分離の話と同じ)。

次の例は、claude_code プリセットと一緒にプロジェクトの CLAUDE.md を読み込み、Claude がコーディングエージェントのプロンプトとプロジェクトの規約の両方を持つようにします。

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
import asyncio

from claude_agent_sdk import query, ClaudeAgentOptions

messages = []


async def main():
    async for message in query(
        prompt="Add a new React component for user profiles",
        options=ClaudeAgentOptions(
            system_prompt={
                "type": "preset",
                "preset": "claude_code",  # Use Claude Code's system prompt
            },
            setting_sources=["project"],  # Loads CLAUDE.md from project
        ),
    ):
        messages.append(message)


asyncio.run(main())

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

どちらを実行しても、SDK は Claude の作業に合わせてメッセージを流します。システムの初期化メッセージ、アシスタントのメッセージ、ツール結果を含むユーザーメッセージ、最後にセッションの結果を含む結果メッセージです。

CLAUDE.md はプロジェクトの全セッションで持続し、git でチームと共有され、コードを変えなくても自動で見つかります。この例は settingSources を ["project"] に絞っているので、~/.claude/CLAUDE.md(ユーザー単位)は読まれません。個人の設定をエージェントに混ぜたくない場合に有効な絞り方です(筆者の補足)。

出力スタイル(保存しておく振る舞いの設定)

出力スタイルは、Claude の役割・口調・出力の形を変える指示を、Markdown ファイルとして保存しておくものです。セッションやプロジェクトをまたいで使い回せます。

ファイルは、メタデータの frontmatter の後にプロンプトの本文が続く形です。置き場所は2つあります。

  • ~/.claude/output-styles/:全プロジェクトで使えるユーザー単位のスタイル
  • .claude/output-styles/:リポジトリに置いてチームで共有するプロジェクト単位のスタイル

独自の出力スタイルは、claude_code プリセットのソフトウェアエンジニアリングの指示を外し、代わりに自分の指示を使います。 エンジニアリングの指示を残して上に重ねたいなら、frontmatter に keep-coding-instructions: true を書きます。目安は、エージェントがまだソフトウェア開発の作業をするなら残す、役割そのものを置き換えるなら外す、です。なお、この指示は Claude Code の完全なシステムプロンプトにしか無いので、CLAUDE_CODE_SIMPLE_SYSTEM_PROMPT で短いシステムプロンプトに固定したセッションでは、この設定は影響しません。

次の例は、コーディングの指示を残したコードレビュー担当の人格です。コードレビューは Claude Code のセキュリティやコード品質の案内が引き続き役立つので、keep-coding-instructions: true にしています。~/.claude/output-styles/code-reviewer.md として保存すると、全プロジェクトで使えます。

---
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)

有効にする方法は次のとおりです。

場所方法
CLI/output-style <style> を実行する(例:/output-style concise)か、/config でスタイルを選ぶ。/output-style は v2.1.269 以降
設定ファイル.claude/settings.local.json に outputStyle を書く
TypeScript SDKquery() に渡すインラインの settings オブジェクトの中に outputStyle を書くか、settings で outputStyle を書いた設定ファイルを指す。outputStyle は Options のトップレベルのフィールドではない
Python SDKsettings オプションに、JSON の文字列(例:'{"outputStyle": "Explanatory"}')か、outputStyle を書いた設定ファイルのパスを渡す

TypeScript の書き方は次のとおりです。

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

settings の渡し方が言語で違う点に注意してください。TypeScript はオブジェクトを渡せますが、Python は JSON の文字列かファイルのパスです。

SDK で出力スタイルが読み込まれるのは、設定ソースに 'user' か 'project' を含めたときです(TypeScript:settingSources: ['user'] など、Python:setting_sources=["user"] など)。CLAUDE.md と同じく、settingSources を空にすると出力スタイルも読まれません。

claude_code プリセットに書き足す(append)

プリセットに append を付けると、組み込みの機能を全て残したまま、独自の指示を足せます。

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);
  }
}
import asyncio

from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage

messages = []


async def main():
    async for message in query(
        prompt="Help me write a Python function to calculate fibonacci numbers",
        options=ClaudeAgentOptions(
            system_prompt={
                "type": "preset",
                "preset": "claude_code",
                "append": "Always include detailed docstrings and type hints in Python code.",
            }
        ),
    ):
        messages.append(message)
        if isinstance(message, AssistantMessage):
            print(message.content)


asyncio.run(main())

例では「Python のコードには必ず詳しい docstring と型ヒントを付ける」という指示を足しています。アシスタントのメッセージの中身の読み方は、TypeScript が message.message.content、Python が message.content です(第77回の二重構造)。

カスタムシステムプロンプト

systemPrompt にカスタムの文字列を渡すと、既定のプロンプトを自分の指示で完全に置き換えられます。

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);
  }
}
import asyncio

from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage

custom_prompt = """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"""

messages = []


async def main():
    async for message in query(
        prompt="Create a data processing pipeline",
        options=ClaudeAgentOptions(system_prompt=custom_prompt),
    ):
        messages.append(message)
        if isinstance(message, AssistantMessage):
            print(message.content)


asyncio.run(main())

例は「Python コーディングの専門家」としての指針を5項目並べています。このプロンプトには、プリセットにあったツールの使い方や安全のための指示、作業ディレクトリや環境の情報が入っていません。必要なら自分で書き足す必要があります(3-4 の比較表)。

Python で長いプロンプトを渡すときの落とし穴。 Python SDK は、文字列のプロンプトを CLI のサブプロセスへの1つのコマンドライン引数として渡します。そのため、OS の引数の長さの上限を超えるプロンプトは、API にリクエストを送る前のプロセス起動の段階で失敗します。Linux では Argument list too long というエラーになります。大きなプロンプトは、文字列の代わりに system_prompt={"type": "file", "path": "..."} でファイルから読み込みます。各プラットフォームの上限と Windows での挙動は、Python リファレンスの SystemPromptFile に回されています。

3-3. プロンプトキャッシュを効かせる

システムプロンプトは毎回のリクエストの先頭に付くので、同じ内容ならプロンプトキャッシュが効き、費用と待ち時間が減ります。このページには、キャッシュを効かせるための工夫が2つあります。

プリセットの動的な部分を外す(excludeDynamicSections)

既定では、同じ claude_code プリセットと同じ append の文章を使う2つのセッションでも、作業ディレクトリが違えばキャッシュを共有できません。プリセットは append の文章より前に、セッションごとの情報をシステムプロンプトに埋め込むからです。埋め込まれるのは次の情報です。

  • 作業ディレクトリ
  • git リポジトリかどうか
  • プラットフォーム
  • 使っているシェル
  • OS のバージョン
  • 自動メモリのパス

これらの違いがシステムプロンプトの違いになり、キャッシュが外れます。一方、CLAUDE.md の内容はシステムプロンプトに影響しません。SDK がそれをシステムプロンプトではなく会話に差し込むからです。

セッションをまたいでシステムプロンプトを同一にするには、TypeScript では excludeDynamicSections: true、Python では "exclude_dynamic_sections": True を指定します。セッションごとの情報は最初のユーザーメッセージに移り、システムプロンプトには固定のプリセットと append の文章だけが残るので、同じ設定ならユーザーやマシンをまたいでキャッシュを共有できます。

この設定はプリセットのオブジェクト形式でだけ指定できます。カスタムプロンプトを渡した場合、SDK はこの設定を無視します(カスタムプロンプトのキャッシュは次の項の方法を使います)。

次の例は、共通の append と excludeDynamicSections を組み合わせ、違うディレクトリで動くたくさんのエージェントが、同じキャッシュ済みのシステムプロンプトを使い回せるようにします。

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
    }
  }
})) {
  // ...
}
import asyncio

from claude_agent_sdk import query, ClaudeAgentOptions


async def main():
    async for message in query(
        prompt="Triage the open issues in this repo",
        options=ClaudeAgentOptions(
            system_prompt={
                "type": "preset",
                "preset": "claude_code",
                "append": "You operate Acme's internal triage workflow. Label issues by component and severity.",
                "exclude_dynamic_sections": True,
            },
        ),
    ):
        ...


asyncio.run(main())

引き換えになるもの。 作業ディレクトリなどの情報は引き続き Claude に届きますが、システムプロンプトではなく最初のユーザーメッセージの一部として届きます。ユーザーメッセージの指示は、システムプロンプトの同じ文章よりわずかに重みが低いので、Claude が現在のディレクトリや自動メモリのパスをもとに考えるとき、それらに頼る度合いが下がります。セッションをまたぐキャッシュの再利用が、情報の権威の高さより大事な場合に有効にします。

非対話の CLI モードで同じことをするフラグは --exclude-dynamic-system-prompt-sections です(CLI リファレンスの範囲)。

カスタムプロンプトの固定部分をキャッシュする(TypeScript のみ)

TypeScript SDK では、カスタムプロンプトを1つの文字列ではなく文字列の配列として渡し、固定の部分と残りの部分の間に SYSTEM_PROMPT_DYNAMIC_BOUNDARY という目印を置けます。毎回同じ指示と、リクエストごとに変わる情報(今扱っている顧客やチケットなど)を組み合わせるプロンプトで使います。

両方を1つの文字列にまとめると、変わる部分が変わるたびにシステムプロンプト全体が変わるので、固定の指示までキャッシュが外れます。配列にして目印で分ければ、固定の部分だけはキャッシュが効きます。この配列の形は Python SDK にはありません(Python の system_prompt が受け付ける形は ClaudeAgentOptions のリファレンスにあります)。

分割が効く環境は限られます。 SDK がプロンプトを分割するのは、Claude API を直接呼ぶときか、Claude Platform on AWS で動かすときだけです。原文は、Amazon Bedrock、Google Cloud の Agent Platform、Microsoft Foundry、LLM ゲートウェイなどでは、CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1 を設定するたびに、SDK がプロンプト全体を1つのブロックとして送ると書いています。この一文は日本語訳では条件の範囲が読み取りにくいのですが、「分割するのは直接の API と Claude Platform on AWS のときだけ」という前半の限定から、それ以外の環境と、同変数を設定したときは、いずれも1つの文字列を渡したのと同じになると読むのが自然です(筆者の解釈)。

使い方は、@anthropic-ai/claude-agent-sdk から SYSTEM_PROMPT_DYNAMIC_BOUNDARY を読み込み、2つの部分の間の配列要素として渡すだけです。SDK は目印より前の文字列を1つのテキストブロックとして、後ろの文字列を2つ目のブロックとして送り、それぞれに独立したキャッシュの区切り(ブレークポイント)を付けます。次の例は、サポートのエージェントがトリアージの指示をファイルから読み込み、リクエストごとに1件のチケットの詳細を受け取ります。指示はキャッシュされたまま、チケットの詳細だけが変わります。

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]
  }
})) {
  // ...
}

キャッシュが効いているかは、各結果メッセージの cache_creation_input_tokens と cache_read_input_tokens で確かめられます(費用の追跡のページの範囲)。

SDK が配列からブロックを組み立てる規則は次のとおりです。

  • 目印の両側の文字列は、それぞれ空行を挟んで結合され、目印自体は取り除かれる。目印の文字は Claude には届かない
  • 目印を複数入れた場合、最初のものが区切りになり、他は取り除かれる
  • 目印を入れない場合、全ての文字列が1つのブロックに結合される(1つの文字列を渡したのと同じ)

CLI の --system-prompt や --system-prompt-file では、プロンプトは1つの文字列なので配列は使えません。代わりに、固定の部分とリクエストごとの部分の間に、__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__ だけを書いた行を入れます。Claude Code は最初のそのような行でプロンプトを同じ2つのブロックに分け、その行を取り除きます(v2.1.275 以降)。SDK では、目印の行を書かずに境界を表せる配列の形が勧められています。

3-4. 既存のセッションのプロンプトを変える

既定では、resume や continue でセッションに戻るときに別の append やカスタムプロンプトを渡しても、Claude は次のターンでそれを見ません。 Claude Code は、セッションの最初のリクエストでシステムプロンプトを記録し、セッションがコンパクション(要約)されるまでその記録を使い回すからです。新しい文章が効くのは、次のコンパクションの後か、新しいセッションからです。

セッションの途中で指示を変える

ユーザーがエージェントを読み取り専用のモードに切り替えた、アプリで設定を編集した、などの理由でセッション中に指示を変える必要があるときは、systemPrompt を変えるのではなく、会話の中で新しい指示を送ります。

  • 次のメッセージで:次に送るユーザーメッセージに新しい指示を入れる。
  • フックから:UserPromptSubmit か PostToolUse のフックのコールバックから additionalContext を返す。「ワークスペースは読み取り専用になりました」のように、事実を述べる形で書く。SDK はフックが発火した時点で会話に文章を差し込むので、記録されたプロンプトは変わらない。

第82回で、systemMessage はユーザー向け、additionalContext はモデル向けだと整理しました。ここはその additionalContext の典型的な使いどころです。ただし、3-3 で見たとおり、ユーザーメッセージ側の指示はシステムプロンプトより重みがわずかに低い、という点は同じです。読み取り専用のように確実に守らせたい切り替えは、指示と合わせて権限モードやフックで実際に書き込みを止めておくのが安全です(第80回で見たとおり権限モードはセッション中に切り替えられます。この組み合わせの提案は筆者)。

文面を練っている間は記録をオフにする

プロンプトの文面を何度も直しながら試していて、直すたびに再開したセッションにも反映させたい場合は、システムプロンプトのオブジェクト形式で snapshot を false にします。Claude Code は毎回のリクエストでプロンプトを作り直します。このフィールドは、TypeScript の systemPrompt のプリセット形式とカスタム形式、Python の system_prompt で使えます(バージョンは 3-0)。

本番では記録をオンのままにします。 記録がオフだと、再開したセッションに渡した別の append やカスタムプロンプトが次のターンで Claude に届く代わりに、次の不利益があります。

  • そのリクエストはセッションのプロンプトキャッシュを使い回せない
  • API が preserved thinking(思考の保持)を強制する場合、Claude は前のターンの思考も失う

ベアモードでは既定で記録がオフです。 クラウドセッションの外で、extraArgs で --bare を渡すか CLAUDE_CODE_SIMPLE=1 を設定してベアモード(第75回)で起動すると、snapshot: true を指定しない限り記録はオフのままです。

3-5. 4つの方法の比較

4つのカスタマイズ方法は、置き場所、共有のしかた、claude_code プリセットから何を残すかが違います。原文の比較表をそのまま示します。

項目CLAUDE.md出力スタイルsystemPrompt に appendカスタム systemPrompt
持続性プロジェクトごとのファイルファイルとして保存セッションのみセッションのみ
再利用性プロジェクトごとプロジェクト全体コードの重複コードの重複
管理ファイルシステム上CLI+ファイルコード内コード内
既定のツール保持保持保持失われる(含めない限り)
組み込みのセキュリティ維持維持維持追加する必要がある
環境の情報自動自動自動提供する必要がある
カスタマイズの度合い追加のみ既定を置き換え追加のみ完全に制御
バージョン管理プロジェクトと共にはいコードと共にコードと共に
範囲プロジェクト固有ユーザーまたはプロジェクトコードのセッションコードのセッション

「append」は、TypeScript では systemPrompt: { type: "preset", preset: "claude_code", append: "..." }、Python では system_prompt={"type": "preset", "preset": "claude_code", "append": "..."} を指します。CLAUDE.md はシステムプロンプト自体を変えず、SDK がその内容をプロジェクトの情報として会話に差し込みます。

表の読み方で2点補足します(いずれも筆者の指摘)。

  • 「既定のツール:失われる」の行は、ツールそのものが使えなくなるのか、プリセットに含まれるツールの使い方の案内が無くなるのかが、表だけでは読み取れません。3-1 の選び方の表は、カスタム文字列について「必要なツールの案内と安全の指示を自分で用意する責任がある」と書いているので、主に案内のことと読めます。ツールを使えるかどうかは、第83回の tools オプションや第80回の権限で決まります。
  • 出力スタイルは「既定を置き換え」ですが、3-2 で見たとおり、keep-coding-instructions: true にすればエンジニアリングの指示を残せます。

3-6. 方法を組み合わせる

これらの方法は組み合わせられます。出力スタイルや CLAUDE.md で長く使う振る舞いを決めておき、append でそのセッションだけの指示を、保存した設定に触れずに上に重ねる、という使い方です。

次の例は、3-2 の「Code Reviewer」出力スタイルがすでに有効になっている前提です。append でセッション固有の重点項目を人格の上に重ね、保存した出力スタイルを変えずに、1回のレビューだけ OAuth とトークンの保存を優先させます。

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

// "Code Reviewer" 出力スタイルがアクティブであると仮定(/config または settings 経由)
// セッション固有のフォーカス領域を追加
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);
}
import asyncio

from claude_agent_sdk import query, ClaudeAgentOptions

# "Code Reviewer" 出力スタイルがアクティブであると仮定(/config または settings 経由)
# セッション固有のフォーカス領域を追加
messages = []


async def main():
    async for message in query(
        prompt="Review this authentication module",
        options=ClaudeAgentOptions(
            system_prompt={
                "type": "preset",
                "preset": "claude_code",
                "append": """
                For this review, prioritize:
                - OAuth 2.0 compliance
                - Token storage security
                - Session management
                """,
            }
        ),
    ):
        messages.append(message)


asyncio.run(main())

コードのコメントにあるとおり、出力スタイルは /config か settings で有効にしておく前提です。SDK から使うなら、3-2 で見たように設定ソースに 'user' か 'project' が含まれている必要があります。この例のオプションには settingSources の指定が無いので、既定の(両方とも有効な)状態で出力スタイルが読み込まれることになります。


4. まとめ

  • SDK のシステムプロンプトの出発点は3つ:最小限の既定(何も指定しない)、claude_code プリセット、カスタム文字列。
  • 何も指定しないと Claude Code のプロンプトは使われず、安全のための指示も環境の情報も入らない。claude -p の既定とは違うので、CLI から移すならプリセットを指定する。
  • 選び方は「Claude Code にどれだけ似ているか」。規約を足すだけならプリセット+appendが最も安全。画面・アイデンティティ・権限の考え方・作業内容が違うならカスタム文字列で、ツールの案内と安全の指示は自分で用意する。
  • CLAUDE.md はシステムプロンプトを変えず会話に差し込まれる。読み込みは設定ソース(project/user)で決まり、プリセットとは無関係。出力スタイルも設定ソース次第。
  • 出力スタイルはエンジニアリングの指示を外すが、keep-coding-instructions: true で残せる。SDK では TypeScript は settings オブジェクト、Python は JSON 文字列かファイルパスで outputStyle を指定する。
  • Python で長いプロンプトを文字列で渡すと Argument list too long で起動に失敗する。{"type": "file", "path": ...} を使う。
  • キャッシュを効かせるには、プリセットでは excludeDynamicSections(環境の情報を最初のユーザーメッセージへ移す。重みはわずかに下がる)、TypeScript のカスタムプロンプトでは SYSTEM_PROMPT_DYNAMIC_BOUNDARY で固定部分を分ける(直接の API と Claude Platform on AWS でのみ分割される)。
  • システムプロンプトはセッションの最初に記録され、再開しても次のコンパクションまで変わらない。途中の変更は次のメッセージかフックの additionalContext で。文面を練るときだけ snapshot: false、本番はオン。ベアモードでは既定でオフ。

次回予告

次回は 「セッション」(agent-sdk/sessions) を取り上げる予定です。

今回、システムプロンプトは「セッションの最初に記録され、resume や continue で戻っても使い回される」と見ました。次回は、その resume・continue を含め、SDK で作ったエージェントの会話をどう続け、分岐させ、保存するのかを見ていきます。第68回で扱った CLI のセッションと比べながら、アプリの中で会話の履歴を扱う方法が分かります。

※テーマは変更になる場合があります。


よっしー
よっしー

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。

それでは、また明日お会いしましょう(^^)

コメント

タイトルとURLをコピーしました