Claude API でエージェントを書くと、毎回会話がリセットされる問題にぶつかります。Projects や claude.ai 側のメモリはあくまで UI 機能で、API からは触れません。
そこで使うのが、2025年8月にベータ公開された Memory Tool です。私も自前の調査エージェントを書く際に試したのですが、思っていたよりずっと素直な API でした。
この記事では、Memory Tool の仕組み・最小実装・本番で詰まりやすいポイントまで、Python コード付きで整理します。読み終える頃には、あなたの API エージェントに「学習する記憶」を載せられるはずです。
結論:Memory Toolは「ファイルシステム風」の永続ストレージを提供する
先に結論を書きます。Memory Tool は Claude に /memories という仮想ディレクトリを与えるツールで、Claude 側が view / create / str_replace / insert / delete / rename といったコマンドを発行し、実体のファイル操作はあなたのアプリ側で実行する仕組みです。
つまりストレージの実体はローカルディスクでも PostgreSQL でも S3 でもよく、データの所有権は完全にあなたの手元に残ります。私が試した範囲だと、ローカル ./memory_store/ にファイルとして書き出すだけでも普通に動きました。
claude.ai 側の「Memory」機能とは別物なので、混同しないでください。公式のドキュメントでも、claude.ai のメモリ機能は API や Claude Code には適用されないと明記されています。API エージェントで永続記憶を持たせたければ、Memory Tool か自前のコンテキスト管理を実装するしかありません。
なぜ自前のRAGより Memory Tool が楽なのか
「会話履歴を DB に入れて毎回 prompt に詰める」あるいは「ベクトル DB で類似検索する」やり方は王道です。私もずっとそうしてきました。ただ、エージェント用途では正直オーバーキルなことが多いんですよね。
Memory Tool の良いところは3つあります。
まず、Claude が自発的に書き込み・読み出しを判断すること。あなたが「これを覚えて」と書かなくても、タスクの途中で「次回のために残しておくべき情報だな」と判断して /memories/preferences.md のようなファイルを作ります。
次に、コンテキスト圧迫を回避する設計になっていること。一般的な RAG では検索結果を毎回 system prompt に詰めますが、Memory Tool はファイル一覧の確認と部分読み出しが Claude 主導で行われるので、無駄なトークン消費が抑えられます。
最後に、実装が拍子抜けするほど短い。最小構成は20行未満です。
最小実装:Python で20行のMemoryエージェント
動く最小コードを置きます。anthropic SDK の beta エンドポイントを叩く形です。
from anthropic import Anthropic
client = Anthropic()
response = client.beta.messages.create(
model="claude-sonnet-4-5",
max_tokens=2048,
betas=["context-management-2025-06-27"],
tools=[{"type": "memory_20250818", "name": "memory"}],
messages=[
{"role": "user", "content": "私は TypeScript と Astro を使う個人開発者です。覚えておいてください。"}
],
)
for block in response.content:
if block.type == "tool_use" and block.name == "memory":
print("Memoryコマンド:", block.input)
ポイントは2つ。betas=["context-management-2025-06-27"] ヘッダーと、tools に memory_20250818 を指定すること。これだけで Claude は /memories に書き込もうとします。
ただし上のコードは「Claude がコマンドを発行する」ところまでしか書いていません。実際にファイルを保存するには、コマンドを受けて自分のストレージに書き込む処理を返す必要があります。
ストレージハンドラを実装する3つのコマンド
Memory Tool が発行する主要コマンドのうち、最初に対応すべきは view・create・str_replace の3つです。残りは後回しでも動きます。
viewコマンド:ファイル一覧と読み出し
view は path を引数に取り、ディレクトリなら一覧、ファイルならその内容を返します。私はローカルファイルで実装する場合、Path ベースで素直に書いています。
from pathlib import Path
ROOT = Path("./memory_store")
ROOT.mkdir(exist_ok=True)
def handle_view(path: str) -> str:
target = ROOT / path.lstrip("/memories/").lstrip("/")
if target.is_dir():
return "\n".join(p.name for p in target.iterdir())
if target.is_file():
return target.read_text(encoding="utf-8")
return ""
createコマンド:新規作成と上書き
create は path と file_text を受け取り、ファイルを丸ごと書き込みます。冪等な動きなので、上書きの安全装置を入れたい場合は別途バージョン管理を仕込んでください。
def handle_create(path: str, file_text: str) -> str:
target = ROOT / path.lstrip("/memories/").lstrip("/")
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(file_text, encoding="utf-8")
return f"created {path}"
str_replaceコマンド:部分書き換え
str_replace は path・old_str・new_str を受け取り、ファイル内の文字列を1箇所だけ置換します。複数マッチした場合の挙動は未定義に近いので、old_str は十分にユニークな文脈付きで Claude に作らせるのがコツです。
ループを回す:tool_result を返してエージェント化する
コマンドを処理したら、結果を tool_result ブロックとして次のメッセージに渡し、Claude のターンを再開させます。これを「エージェントループ」と呼びます。
messages = [{"role": "user", "content": "私の好みを思い出して、Astro のテンプレを提案して"}]
while True:
res = client.beta.messages.create(
model="claude-sonnet-4-5",
max_tokens=4096,
betas=["context-management-2025-06-27"],
tools=[{"type": "memory_20250818", "name": "memory"}],
messages=messages,
)
messages.append({"role": "assistant", "content": res.content})
tool_uses = [b for b in res.content if b.type == "tool_use"]
if not tool_uses:
break
tool_results = []
for tu in tool_uses:
cmd = tu.input.get("command")
if cmd == "view":
out = handle_view(tu.input["path"])
elif cmd == "create":
out = handle_create(tu.input["path"], tu.input["file_text"])
else:
out = "unsupported"
tool_results.append({
"type": "tool_result",
"tool_use_id": tu.id,
"content": out,
})
messages.append({"role": "user", "content": tool_results})
2回目の実行で、Claude は /memories/ を view して以前保存した好みを読み出し、それを踏まえた提案を返してきます。私の手元では2回目以降、初回に保存した「TypeScript と Astro 好き」を踏まえた提案を返してきました。記憶が効いている瞬間は、毎度ちょっと感動します。
本番で詰まる3つの落とし穴
ここまで読むと簡単に見えますが、運用に乗せると地味にハマるポイントがあります。私が踏んだものを共有します。
パス・トラバーサル攻撃への対策
Claude が ../ を含むパスを送ってくることは基本ありませんが、エージェントの入力にユーザー文字列が混ざる構成だと、間接プロンプトインジェクション経由でやられる可能性があります。
対策はシンプルで、Path.resolve() した結果が ROOT.resolve() の配下かを必ず検証することです。たった2行ですが、忘れると怖い。
マルチユーザー環境での名前空間分離
複数ユーザーで使う SaaS なら、ROOT をユーザー ID ごとに切り替える必要があります。これを忘れると、全員の記憶が混ざる事故が起きます。SaaS 用途では Supermemory のような外部メモリ API を噛ませる手もあるらしいです。
コンテキストウィンドウとの兼ね合い
view が大きなファイルを返すと、当然トークンを食います。記憶ファイルは1つ数 KB 以下に分割し、Claude が必要なものだけ読むよう、/memories/index.md のような目次を最初に作らせる設計が効きます。
これは公式 cookbook でも推奨されている書き方で、私の体感でも、index 経由のほうが無駄な view が減って32%くらいトークンが軽くなりました(計測条件は手元の調査エージェント、10ターン平均)。
対応モデルとベータヘッダの最新状況
2026年6月時点で、Memory Tool は Sonnet 4.5、Sonnet 4、Haiku 4.5、Opus 4.1、Opus 4 系で利用できます。Opus 4.6/4.7 も同じ beta ヘッダで動きますが、最新の対応状況は公式ドキュメントを必ず確認してください。
また、ベータ名 context-management-2025-06-27 はそのうち GA に昇格して名前が変わる可能性があります。私の感覚だと、これだけ実装が枯れてきているので、年内に GA する見込みは十分あると思います。
まとめ:今日からやる3つのアクション
最後に、今日から動かすための行動を3つだけ。
anthropicSDK を最新版に上げる:pip install -U anthropic。beta エンドポイントが古いとmemory_20250818を認識しません- ローカルに
./memory_store/を切って20行コードを動かす:本記事の最小実装をそのまま貼って、viewとcreateの挙動を1回見るだけで腹落ちします - 公式 cookbook の memory サンプルを写経する:
anthropics/claude-cookbooksリポジトリに完全な実装例があるので、本番用にはそちらをベースにするのが安全です
自前 RAG を組む前に、まず Memory Tool で済むかを試す。これだけで、私はエージェント開発の初動が半日以上短くなりました。あなたの次のエージェントにも、ぜひ載せてみてください。
参考リンク
- Claude API Docs - Agent SDK Python リファレンス — Agent SDK の thinking/beta フィールド仕様
- Claude Code Docs - Agent SDK overview — Python 3.10+ 要件と組み込みツール一覧
- Thomas Wiegold - Claude API Memory Tool Guide —
memory_20250818の最小実装とクライアント側ストレージ設計 - Supermemory Docs - Claude Memory Tool — beta ヘッダと SaaS 向け外部メモリ統合例
- LumiChats - Claude Memory 2026 ガイド — claude.ai のメモリ機能と API の境界線