Claude Memoryツールの6コマンド実装 — 返り値とエラーの仕様
Memoryツールのview/create/str_replace/insert/delete/renameが返す文字列とエラー処理を、公式仕様に沿って実装レベルで押さえます。
Memoryツールの返り値は自分の実装が決める
Memoryツールは、Claudeが /memories 配下のファイルにメモを書き残し、次の会話で読み戻すための仕組みです。動作の主体はクライアント側にあります。Claudeは「ファイルを作りたい」「この範囲を読みたい」という操作を tool_use ブロックで要求するだけで、実際にファイルを読み書きするのは呼び出し元のアプリケーションです。
/memories は実ストレージに割り当てる接頭辞です。ユーザーごとのディレクトリでも、データベースのキーでも構いません。次の会話が同じメモリから再開されるのは、同じ tools エントリを送り、ハンドラが同じストアを返す場合です。
Memoryツールの1往復
- 1
Claudeがメモリの一覧を要求する
{"command": "view", "path": "/memories"}を含むtool_useが返ります。公式の例では、サポートチケットへの返信を頼まれたClaudeが、作業の前にこれを呼びます。 - 2
アプリが一覧を返す
ハンドラがディレクトリの中身を文字列にして、同じ
tool_use_idを持つtool_resultに入れて送り返します。 - 3
Claudeが必要なファイルを読む
一覧から関連しそうなファイルを選び、そのパスで
viewを要求します。 - 4
内容を返して会話を進める
行番号付きの内容を返すと、Claudeはそのメモを踏まえて回答を組み立てます。
リクエストへの追加は、tools 配列に {"type": "memory_20250818", "name": "memory"} を1つ加えるだけです。name は memory 固定で、入力スキーマは定義しません。Claude 4以降の全モデルで使え、ベータヘッダーも不要です。SDKのヘルパーはベータ名前空間にありますが、Memoryツール自体はベータ扱いではありません。
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-5-5",
"max_tokens": 2048,
"messages": [{"role": "user", "content": "Help me respond to this ticket."}],
"tools": [{"type": "memory_20250818", "name": "memory"}]
}'残る作業は、view / create / str_replace / insert / delete / rename の6コマンドをさばくハンドラです。SDKの用意は言語で分かれます。
- Python / C#:
BetaAbstractMemoryToolをサブクラス化 - TypeScript:
betaMemoryToolに保存先の実装を渡す - Java:
BetaMemoryToolHandlerを実装 - PHP: ハンドラのクロージャを汎用の
BetaRunnableToolで包む - Go / Ruby: ヘルパーが無く、ツール呼び出しのループも自前
ヘルパーはツールの接続口とループを引き受けます。ファイルをディスクに置くか、データベースに入れるか、暗号化するかは呼び出し元の判断です。PythonとTypeScriptには、ローカルのファイルシステムに保存する BetaLocalFilesystemMemoryTool も同梱されています。
Go / Ruby / PHPのサンプルが使うメモリ上のストアは、パス検証を省いています。本番のハンドラには、次の節のパス検証が別途必要です。
下記の返り値は、仕様というより推奨される挙動です。Claudeはツール結果のテキストをそのまま読むので、アプリケーションの都合で別の文字列を返しても構いません。システムプロンプトに自動で加わるメモリプロトコルの指示文は、最初に view でメモリディレクトリを見て、進捗を書き残すよう求めるもので、一覧やファイル内容の書式には触れていません。
| コマンド | 成功時の返り値 | 代表的なエラー |
|---|---|---|
| view | 成功時の返り値ディレクトリ一覧、または行番号付きファイル内容 | 代表的なエラーパスが存在しない |
| create | 成功時の返り値File created successfully at: {path} | 代表的なエラー既に存在する |
| str_replace | 成功時の返り値The memory file has been edited. + 編集箇所のスニペット | 代表的なエラー置換対象なし / 複数一致 |
| insert | 成功時の返り値The file {path} has been edited. | 代表的なエラー行番号が範囲外 |
| delete | 成功時の返り値Successfully deleted {path} | 代表的なエラーパスが存在しない |
| rename | 成功時の返り値Successfully renamed {old_path} to {new_path} | 代表的なエラー移動先が既に存在する |
最初に書くのはパス検証
Claudeが送るパスはただの文字列です。/memories/../../secrets.env のような値をそのまま実ファイルに結びつけると、メモリの外に出てしまいます。仕様はすべてのコマンドで全パスを検証するよう求めています。対策は、接頭辞の検証、正規形への解決、../ や ..\ の拒否、URLエンコードされた %2e%2e%2f への注意、言語標準のパス関連ユーティリティの利用です。
パス検証の順序(Pythonの場合)
- 1
文字列の形を見る
/memoriesそのものか、/memories/で始まる値だけを通します。 - 2
実パスに変換して正規化する
ルートの実ディレクトリに連結し、
Path.resolve()で..やシンボリックリンクを解決します。 - 3
ルートの配下にあるか確かめる
relative_to()でルートからの相対パスに直せなければ、範囲外として弾きます。
1手目を startswith("/memories") だけで済ませると、危うい値が通ります。Python 3.12で次の3つを試すと、すべて True でした。
/memories/../../secrets.env(..で上位へ出る)/memories_backup/x(別のディレクトリ名が接頭辞に一致する)/memories/%2e%2e%2fsecrets.env(URLエンコードされた../)
from pathlib import Path
ROOT = Path("./memory_root").resolve() # /memories の割り当て先
def resolve(p: str) -> Path:
if p != "/memories" and not p.startswith("/memories/"):
raise PermissionError(p)
real = (ROOT / p[len("/memories"):].lstrip("/")).resolve()
real.relative_to(ROOT) # ルート外なら ValueError
return realこの関数を全コマンドの入口に置き、PermissionError と ValueError を「範囲外」のエラー文字列に変換します。rename は old_path と new_path の両方に通します。
view — ディレクトリ一覧とファイル内容の返り値
view はディレクトリの中身、またはファイルの内容を返します。view_range は省略可能です。[開始行, 終了行] ならその範囲だけ、[開始行, -1] ならその行から末尾までを返します。
{
"command": "view",
"path": "/memories/notes.txt",
"view_range": [1, 10]
}対象がディレクトリなら、階層2つ分までのファイルとディレクトリをサイズ付きで一覧します。隠しファイル(. 始まり)と node_modules は除外し、サイズとパスの間はタブ区切りです。
Here're the files and directories up to 2 levels deep in /memories, excluding hidden items and node_modules:
4.0K /memories
1.5K /memories/notes.txt
2.0K /memories/progress.md空の /memories への最初の view はエラーではありません。BetaLocalFilesystemMemoryTool は、Claudeの最初の呼び出しより前にメモリのルートを作成し、一覧のヘッダーと空ディレクトリ自身の1行だけを返します。
対象がファイルなら、ヘッダーに続けて行番号付きの内容を返します。行番号の書式は細かく決まっています。
- 幅: 6文字、右詰め、空白パディング
- 区切り: 行番号と内容の間はタブ文字
- 起点: 1行目が1(0始まりではない)
- 上限: 999,999行を超えるファイルはエラー(
File {path} exceeds maximum line limit of 999,999 lines.)
Here's the content of /memories/notes.txt with line numbers:
1 Meeting notes
2 - Discussed timeline
10 - Next steps definedClaudeへのツール説明文は、view が .jpg / .jpeg / .png の画像も表示すること、16,000文字を超えるテキストは切り詰めて表示することを伝えています。画像パスへの view と、長いファイルへの範囲指定つきの追い読みが来る前提で実装します。
create / str_replace / insert — ファイルを書き換える3コマンド
create は新規ファイルを作り、成功時は File created successfully at: {path} を返します。既存パスへの create は、リファレンスではエラー(Error: File {path} already exists)です。ただしClaudeへのツール説明文は create を「作成または上書き」と書いています。つまり既存パスへの呼び出しは実際に起こり、返し方は実装が選ぶ分岐です。
既存パスへの create をどう扱うか
エラーを返す
既存ファイルの内容は変わりません。誤って消えるメモが出ない代わりに、Claudeへ返るのは「既に存在する」というエラーです。
上書きする
ツール説明文の「作成または上書き」と挙動が一致します。既存内容を失うので、バックアップやバージョン管理と組み合わせる設計が前提です。
str_replace はファイル内のテキストを置換します。new_str は省略でき、省略すると old_str が削除されるだけです。
{
"command": "str_replace",
"path": "/memories/preferences.txt",
"old_str": "Favorite color: blue",
"new_str": "Favorite color: green"
}成功時は The memory file has been edited. に続けて、編集箇所を行番号付きで抜粋した文字列を返します。エラーは3種類です。ファイルが存在しない場合、old_str が本文に一字一句一致しない場合(No replacement was performed, old_str ... did not appear verbatim in {path}.)、そして複数箇所に一致する場合です。複数一致では該当行番号を列挙し、一意にするよう促します(Multiple occurrences of old_str ... in lines: {line_numbers}. Please ensure it is unique)。パスがディレクトリを指すときは「ファイルが存在しない」エラーにします。
insert は指定行の直後にテキストを挿入します。insert_line に 0 を指定すると先頭への挿入です。
{
"command": "insert",
"path": "/memories/todo.txt",
"insert_line": 2,
"insert_text": "- Review memory tool spec\n"
}成功時は The file {path} has been edited. を返します。行番号が不正なときは Error: Invalid insert_line parameter: {insert_line}. It should be within the range of lines of the file: [0, {n_lines}] です。ディレクトリ指定は、こちらも「ファイルが存在しない」エラーで扱います。
insert_line: 2 が指すのは「2行目の直後」です。3行のファイル(1: 見出し、2: 項目A、3: 項目B)なら、新しい内容は3行目に入り、項目Bは4行目へ下がります。許される範囲の上限はファイルの行数なので、行数と同じ値を渡せば末尾への追記になります。
「存在しない」エラーの文言はコマンドごとに違う
公式の仕様には、パスが存在しないときの文言が3通りあります。同じ状況でも、Error: の接頭辞と案内文の有無がコマンドで揃っていません。
| コマンド | 存在しないパスへの返り値 |
|---|---|
| view | 存在しないパスへの返り値The path {path} does not exist. Please provide a valid path. |
| str_replace | 存在しないパスへの返り値Error: The path {path} does not exist. Please provide a valid path. |
| insert | 存在しないパスへの返り値Error: The path {path} does not exist |
| delete | 存在しないパスへの返り値Error: The path {path} does not exist |
| rename | 存在しないパスへの返り値Error: The path {old_path} does not exist(移動元) |
この表をそのまま移した実装は、view だけ Error: の接頭辞が付かない返し方になります。文字列は強制ではないので、接頭辞の有無を揃えるかどうかも実装側の判断です。揃える場合は、is_error を true にする運用(後述)を前提にすると、文言の差を気にする必要が薄れます。
delete / rename — 削除と移動の返り値
delete はファイルまたはディレクトリを削除します。成功時は Successfully deleted {path}、存在しないパスなら Error: The path {path} does not exist です。対象がディレクトリなら、中身ごと再帰的に消します。
rename はファイルまたはディレクトリの名前変更と移動を行い、ディレクトリを渡した場合はディレクトリ自体の名前を変えます。
{
"command": "rename",
"old_path": "/memories/draft.txt",
"new_path": "/memories/final.txt"
}成功時は Successfully renamed {old_path} to {new_path} を返します。エラーは移動元が存在しない場合と、移動先が既に存在する場合です。後者は上書きせずに Error: The destination {new_path} already exists を返すのが仕様です。
実際に動かした出力
先ほどの resolve() に、create / insert / str_replace / delete を足した80行ほどのPythonハンドラを、空のメモリルートに対して動かしました(Python 3.12.2)。成功・既存パス・複数一致などのメッセージは上で挙げた仕様の文字列です。ルートの削除拒否と範囲外のメッセージは仕様に文言が無いため、自前の文言にしています。
python3 handler.py ./memory_rootcreate /memories/todo.txt -> File created successfully at: /memories/todo.txt
create /memories/todo.txt -> Error: File /memories/todo.txt already exists
insert /memories/todo.txt -> The file /memories/todo.txt has been edited.
insert /memories/todo.txt -> Error: Invalid `insert_line` parameter: 9. It should be within the range of lines of the file: [0, 4]
str_replace /memories/todo.txt -> No replacement was performed. Multiple occurrences of old_str `- ` in lines: [2, 3, 4]. Please ensure it is unique
str_replace /memories/todo.txt -> The memory file has been edited.
str_replace /memories/nope.txt -> Error: The path /memories/nope.txt does not exist. Please provide a valid path.
delete /memories -> Error: Cannot delete the /memories directory itself
delete /memories/../../secrets.env -> Error: The path /memories/../../secrets.env is outside /memories
delete /memories_backup/x -> Error: The path /memories_backup/x is outside /memories
delete /memories/%2e%2e%2fsecrets.env -> Error: The path /memories/%2e%2e%2fsecrets.env does not exist
delete /memories/todo.txt -> Successfully deleted /memories/todo.txt読みどころは3か所です。1つ目は4行目で、insert_line: 9 のエラーに出た上限が 4 なことです。直前の insert で3行のファイルが4行になっており、上限は挿入後の現在の行数に追従します。
2つ目は5行目です。- は3行に一致し、行番号 [2, 3, 4] がそのまま返ります。続く6行目では、- new のように一意に絞った old_str なら置換が通りました。
3つ目は %2e%2e%2f の行です。デコードを挟まないハンドラでは、この値はただのファイル名として扱われ、「存在しない」エラーで終わります。パスをURLデコードしてから使う構成では、デコード後の文字列に対して resolve() を通す形になります。
運用で決めること
エラーの返し方は、メッセージ文字列だけでなく is_error フラグとセットです。tool_result の is_error を true にして、メッセージを content に入れます。Text editorツールのエラー処理と同様のパターンです。
{
"type": "tool_result",
"tool_use_id": "toolu_01C4D5E6F7G8H9I0J1K2L3M4",
"content": "Error: The path /memories/notes.txt does not exist",
"is_error": true
}ファイルサイズは追跡して上限を設けます。view が返す文字数にキャップをかけ、残りはClaudeに view_range で読み進めさせる形が、公式の挙げる方法です。本記事の view の書式は上限を含めていないので、長いファイルを扱うなら返却前に切り詰める処理を足します。
機微情報について、Claudeは通常、メモリへの書き込みを断ります。確実に防ぎたいなら、ハンドラが書き込む前に機微情報を取り除く検証を挟みます。使われなくなったファイルは、長期間アクセスがないものを定期的に削除します。
メモリの散らかりには、プロンプトで補強する手があります。メモリプロトコルは自動で付くので、自分で書く必要はありません。それでもClaudeが似たファイルを乱立させるなら、注意書きをプロンプトに足せます。趣旨は、メモリの内容を最新で整理された状態に保つこと、不要なファイルは改名か削除をすること、必要がなければ新しいファイルを作らないことです。書き込む内容を特定のトピックに絞る指示も有効です。
長い会話との併用では、役割が分かれます。コンテキスト編集はクライアント側で特定のツール結果を消し、コンパクションは会話がコンテキストウィンドウの上限に近づくとサーバー側で全体を要約します。要約をまたいで残したい情報の置き場がメモリです。
複数セッションにまたがる開発には、公式のパターンがあります。最初のセッションで進捗ログ・機能チェックリスト・起動スクリプトへの参照をメモリに用意します。以降のセッションはそれを読んで始め、終わる前に進捗ログを更新します。詳しくはMemoryツールをマルチセッションで運用するパターンにあります。
まとめ
ツール定義は1行で済みますが、6コマンド分のハンドラとパス検証は自前で書く部分です。返り値の書式は公式リファレンスに沿わせるのが近道で、ルートの保護とサイズの上限、機微情報の検証はハンドラ側のガードとして持たせます。
ツール呼び出しループの組み立ては、Tool RunnerでAnthropic APIのツール呼び出しループを自動化するが扱っています。