個人開発で4つの技術ブログを自動エージェントに運用させていると、エージェントが書き出すメモリファイルが数ヶ月で数十件まで膨れ上がります。最初は便利でした。「この修正は前に試して失敗した」と覚えていてくれるからです。ところが件数が増えるにつれ、古い事実と新しい事実が静かに衝突し始めました。半年前に「このフラグは無効化した」と書いたメモが残ったまま、別のメモには「同じフラグを再度有効化した」と書いてある。エージェントはどちらを信じればよいのか分からなくなり、判断が鈍りました。
2026年6月の Code w/ Claude 開発者会議で発表された Managed Agents の Dreaming (リサーチプレビュー)は、まさにこの問題に正面から取り組んだ機能です。過去のセッションとメモリを定期的に見直し、パターンを抽出してエージェント自身を更新します。私自身が手作業でやっていた「メモリの棚卸し」を、エージェントの稼働の一部として組み込む発想です。Dreaming の設計思想を読み解いたうえで、それを待たずに今すぐ Anthropic SDK で作れる「自前のメモリ整理ループ」を、動くコードとともに示していきます。
メモリが増えたエージェントほど判断が鈍るという逆説
長期稼働エージェントのメモリは、放っておくと単調増加します。セッションごとに「学んだこと」を書き足していくと、半年で数十件、一年で百件を超えることも珍しくありません。
問題は件数そのものではなく、事実の鮮度がバラバラなまま並ぶ ことです。私のケースでは、同じ設定項目について時系列の異なる3つのメモが残っていて、最新だけが正しく、残り2つは既に誤りでした。エージェントは3つとも等しく「過去の学び」として読み込むため、古い指示に引きずられて、すでに直したはずの不具合を再発させたことがあります。
人間のチームであれば、ドキュメントを定期的に見直して古い記述を消します。ところがエージェントのメモリは「追記専用ログ」になりがちで、誰も剪定しません。Dreaming は、この剪定をエージェント自身に担わせるための仕組みだと理解すると腑に落ちます。
Dreaming は「セッションの記録」を「使える知識」に畳み込む
Dreaming の核心は、生のセッション履歴をそのまま貯め込むのではなく、繰り返し現れたパターンを抽出して、より一般的な知識に畳み込む 点にあります。
たとえば「APIがタイムアウトしたので指数バックオフでリトライした」というセッションが何度か続いたとします。素朴なメモリは、その都度「○月○日、タイムアウトをリトライで解決」と記録を増やします。Dreaming はこれらを横断的に見て、「このワークロードでは断続的なタイムアウトが起きるため、最初から指数バックオフを既定にすべき」という一段抽象化された方針へと書き換えます。
これは私が手作業のメモリ整理でやろうとしていたことと同じです。個別の出来事を消し、そこから得た判断基準だけを残す。違いは、Dreaming がそれを定期実行のループとして自律的に回す点にあります。
公開された数値をどう読むか — Harvey 6倍・Wisedocs 50%
Anthropic は Dreaming の効果として、Harvey がタスク完了率を約6倍に、Wisedocs がレビュー時間を50%削減したと報告しています。インパクトの大きい数字ですが、個人開発の文脈に持ち込むときは、いくつか割り引いて読む必要があります。
第一に、これらはメモリの蓄積が成果に直結するワークロード です。法務や医療文書のレビューは、過去の判断との一貫性が品質そのものなので、メモリ整理の効果が成果指標にそのまま跳ね返ります。一方で、毎回ほぼ独立したタスク(たとえば単発の翻訳)では、メモリを畳み込んでも完了率はそれほど動きません。
第二に、「6倍」はベースラインがメモリ未整理の状態であることを忘れてはいけません。私の実感でも、メモリが汚れたエージェントは目に見えて精度が落ちるので、その状態からの改善幅は大きく出ます。伸び率の大きさは、裏返せば放置したときの劣化の深さ でもあるのです。
結論として、私はこの数値を「Dreaming を入れれば6倍速くなる」ではなく、「メモリの鮮度管理を怠ると、それだけで成果が数分の一に落ちうる」という警告として読んでいます。
Dreaming を待たずに作る、自前のメモリ整理ループ
Dreaming はまだリサーチプレビューで、すべての環境ですぐ使えるわけではありません。ですが本質は「定期的にメモリを読み、重複と陳腐化を判定し、畳み込んで書き戻す」という単純なループです。これは Anthropic SDK だけで再現できます。
以下は、JSON Lines 形式で蓄積したメモリファイルを読み込み、Claude に整理させて書き戻す完全なスクリプトです。各メモリには id created text status を持たせ、整理結果を構造化出力で受け取ります。
import json
import os
from datetime import datetime, timezone
from pathlib import Path
from anthropic import Anthropic
client = Anthropic( api_key = os.environ[ "ANTHROPIC_API_KEY" ])
MEMORY_FILE = Path( "memory.jsonl" )
MODEL = "claude-sonnet-4-6"
def load_memories () -> list[ dict ]:
if not MEMORY_FILE .exists():
return []
rows = []
for line in MEMORY_FILE .read_text( encoding = "utf-8" ).splitlines():
line = line.strip()
if line:
rows.append(json.loads(line))
# status が active のものだけを整理対象にする
return [m for m in rows if m.get( "status" , "active" ) == "active" ]
CONSOLIDATION_PROMPT = """ \
あなたは長期稼働エージェントのメモリ管理担当です。
以下は時系列で蓄積された運用メモの一覧です(古い順)。
次の方針で整理し、JSON だけを返してください。
1. 同じ対象について新旧で矛盾するメモがあれば、最新を正とし古い方を stale にする
2. 個別の出来事を3件以上横断して同じ教訓が出ているなら、1件の一般化された方針に畳み込む
3. 一度きりで再現性のない記録は archive にする
4. 残すメモは原文の事実を改変しない
返却フォーマット:
{
"keep": [{"id": "...", "text": "...", "merged_from": ["id1","id2"]}],
"stale": ["id3", "id4"],
"archive": ["id5"]
}
メモ一覧:
"""
def consolidate (memories: list[ dict ]) -> dict :
listing = " \n " .join(
f '- id= { m[ "id" ] } created= { m[ "created" ] } : { m[ "text" ] } '
for m in memories
)
resp = client.messages.create(
model = MODEL ,
max_tokens = 4096 ,
messages = [{ "role" : "user" , "content" : CONSOLIDATION_PROMPT + listing}],
)
raw = resp.content[ 0 ].text
start, end = raw.find( "{" ), raw.rfind( "}" )
return json.loads(raw[start : end + 1 ])
def apply_plan (memories: list[ dict ], plan: dict ) -> list[ dict ]:
by_id = {m[ "id" ]: m for m in memories}
now = datetime.now(timezone.utc).isoformat()
result = []
demoted = set (plan.get( "stale" , [])) | set (plan.get( "archive" , []))
for item in plan.get( "keep" , []):
base = by_id.get(item[ "id" ], {})
result.append({
"id" : item[ "id" ],
"created" : base.get( "created" , now),
"updated" : now,
"text" : item[ "text" ],
"status" : "active" ,
"merged_from" : item.get( "merged_from" , []),
})
for mid in demoted:
if mid in by_id:
row = dict (by_id[mid])
row[ "status" ] = "stale" if mid in plan.get( "stale" , []) else "archived"
row[ "updated" ] = now
result.append(row)
return result
def main ():
memories = load_memories()
if len (memories) < 10 :
print ( f "active memories = { len (memories) } : 整理は不要" )
return
plan = consolidate(memories)
merged = apply_plan(memories, plan)
MEMORY_FILE .write_text(
" \n " .join(json.dumps(m, ensure_ascii = False ) for m in merged) + " \n " ,
encoding = "utf-8" ,
)
kept = sum ( 1 for m in merged if m[ "status" ] == "active" )
print ( f "整理完了: active { len (memories) } -> { kept } 件" )
if __name__ == "__main__" :
main()
ポイントは3つあります。第一に、status を active stale archived の3値で持ち、物理削除しない こと。Dreaming も同様に履歴を残す設計が安全です。判定を誤っても元に戻せます。第二に、整理対象を active だけに絞り、stale を再び読ませないこと。第三に、merged_from で畳み込みの根拠を残すことです。後から「なぜこの方針になったのか」を追跡できます。
メモリ整理プロンプトの設計 — 何を残し、何を捨てるか
整理ループの品質は、ほぼプロンプト次第です。私が何度か失敗して落ち着いた基準は、次の通りです。
残す: 再現性のある判断基準・複数回確認された事実・現在も有効な制約
stale にする: 新しいメモと矛盾する古い事実
archive にする: 一度きりの出来事・特定セッションでしか意味を持たない記録
触らない: 残すと決めたメモの「事実」部分(表現は整えてよいが内容は改変しない)
最初の頃、私は「要約して短くして」とだけ指示していました。するとモデルは気を利かせて事実そのものを書き換えてしまい、「無効化した」が「調整した」に丸められて、肝心の判断が曖昧になりました。畳み込み(generalize)と要約(summarize)は別物 だと明示するのが効きました。畳み込みは複数の事実から方針を導くこと、要約は一つの事実を短くすることで、後者をメモリに対してやってはいけません。
もう一つ重要なのは、整理の頻度です。私はセッションごとではなく、active が10件を超えたタイミングで初めて走らせています。毎回走らせると、まだ十分なパターンが溜まっていないのに無理に畳み込もうとして、かえって情報が失われます。
いつ効き、いつ逆効果になるのか
Dreaming も自前ループも、万能ではありません。私の運用では、効くワークロードと逆効果になるワークロードがはっきり分かれました。
効くのは、過去の判断との一貫性が品質に直結する 長期タスクです。同じコードベースを継続的に触る、同じ顧客対応を続ける、同じサイト群を運用する、といった場合は、メモリの鮮度が成果をそのまま左右します。Harvey や Wisedocs の事例もこの型です。
逆効果になりやすいのは、タスクが互いに独立している 場合です。毎回別のユーザーの単発リクエストを処理するようなワークロードでは、過去セッションから抽出した「パターン」が、目の前のタスクには当てはまらない先入観として働きます。私が翻訳系のエージェントで整理ループを入れたとき、過去の文体の癖を一般化しすぎて、新しい依頼の指定を無視するようになりました。このときは整理対象を「運用上の制約」だけに絞り、コンテンツ固有の判断はメモリに残さない方針に切り替えて解決しました。
判断基準を一文にすると、**「次のタスクで過去の判断を参照したいか」**です。参照したいなら畳み込む価値があり、参照したくないならそもそもメモリに残さない方が安全です。
本番で踏んだ3つの落とし穴
自前のメモリ整理ループを Dolice Labs の運用に組み込む過程で、再現性のある落とし穴を3つ踏みました。
1つ目は、整理中のメモリを別プロセスが読んでしまう競合 です。書き戻しの最中にエージェントがメモリを読むと、半分だけ更新された状態を掴みます。一時ファイルに書いてから os.replace() でアトミックに差し替えることで解消しました。write_text を直接上書きに使うと、この窓が開きます。
2つ目は、畳み込みの暴走 です。整理を重ねるうちに、モデルが「もっときれいにできる」と判断して、本来独立すべき2つの方針を1つにまとめてしまいました。結果、片方の文脈が失われました。対策として、keep の件数が前回比で30%以上減ったら整理を破棄して人間に通知する、というガードを入れました。急激な縮小は、ほぼ常に過剰な畳み込みのサインです。
3つ目は、stale の判定ミス です。新しいメモが必ずしも正しいとは限りません。一時的な実験のメモが「最新」として古い正しい方針を stale にしたことがありました。これは、メモに created だけでなく「確定事項か実験中か」のラベルを持たせ、実験中のメモは他を stale にできないルールにして防ぎました。
Dreaming がリサーチプレビューである今のうちに、こうしたガードを自前のループで作り込んでおくと、正式版に移行するときも「何を任せ、何を手元に残すか」の線引きがすでに自分の中にできています。
次の一歩
まずは、いま運用しているエージェントのメモリを一度ダンプして、active な件数と、その中に矛盾するペアがいくつあるかを数えてみてください。10件を超えていて矛盾が1組でもあれば、それはもう判断を鈍らせています。上のスクリプトの consolidate だけを、書き戻しなしの「読み取り専用モード」で走らせれば、Claude がどのメモを stale と見なすかが分かります。書き戻すのは、その判定に納得してからで十分です。
私自身、メモリの鮮度管理を運用の一部として明示的に組み込んでから、同じ不具合を二度踏むことが目に見えて減りました。エージェントを長く動かすほど、記憶を増やすことより整えることが効いてくるのだと感じています。