Claude Code に「確か前に〇〇って言ってたよね」って言ってほしい
はじめに
こんにちは、FLINTERS の野崎です。
Claude Code を日常的に使っていると、こういう瞬間がよくあります。
「前にこのバグ直したよなー、何だっけ」
「先週決めた方針、どっかに書いたはず」
このときエージェント側は、過去のセッションのことを覚えていません。Claude Code には session jsonl という詳細な作業ログが残っているし、公式の memory 機能もありますが、どちらも「ふと思い出す」のニュアンスとは少し違うものです。
ログは網羅的だけど、こちらが引っ張りに行かないと出てこない。memory は方針や好みは保持してくれるけど、「先週の作業の文脈」といったエピソード単位の想起は範囲外。
人間でもそうですよね。日記を引っ張り出さないと出てこない記憶もあれば、何かの拍子にふっと出てくる記憶もある。後者の感じをエージェントハーネス側で構造的に成立させたい、というのがこの記事で紹介する memory-recall 機能のスタートでした。
今回は できるだけ Claude Code 自体の機能だけで完結させることを目標にしました。具体的には hook / skill / slash command / scheduled task の組み合わせだけで作る、ということです。理由は単純で、外部の常駐デーモンや別エコシステムを足すと、最初は便利でも長期的にクリーンアップが辛くなるから。Claude Code 内の構成要素だけで組めば、不要になった日に skill / hook を消せばそれで終わります。
実際こう動きます
たとえば私が「ハッカソン参加しようかな」とだけ打った時の、Claude Code の応答がこちらです。

過去に登録したTODOを拾ってくれている例
私は事前に「前にこの話したよね」とは何も伝えていません。それでも Claude Code 側が、過去に登録していたTODO(「ハッカソン開始時に google/skills を project-local に繋ぐ手順を確認しておく」) を勝手に思い出して、「確かそのまま放置してた気がするんで、参加決めるならその辺の準備時間も込みで考えると良さそう」と持ち出してくれます。
この記事では、その実装の メンタルモデル、設計判断、試行錯誤についてまとめています。
何を作ったか — 3 層 + 2 流路 + 入口コマンド
ファイルが何個も絡む構成なので、まず一枚絵で全体像を出します。
ざっくり言うと、scheduled task で毎日要約を作ってもらい、UserPromptSubmit hook で検索する、という流れです。検索のためのキーワードは、プロンプトを形態素解析にかけて拾う、というシンプルな仕組みでやっています。
- 同期流路 が「想起する側」 — プロンプトが来た瞬間に過去の蓄積をキーワードで引いて、関連しそうな項目をコンテキストに流し込む。
- 非同期流路 が「定着させる側」 — その日のセッションログを要約・脱機密化して、長期記憶側に書き戻す。
両方が揃うと「今日の作業が、明日にはキーワードでふっと出てくる」という感覚が成立します。
Layer の話
3 つの層に分けています。
| 層 | 中身 | 性格 |
|---|---|---|
| L1 | beads (TODO 管理) | 「今日メモした思いつき」「忘れちゃダメな TODO」 |
| L2 | Obsidian (ノート) | 概念ノート + 過去のセッション要約 |
| L3 | session jsonl | Claude Code が書いている全 turn の生ログ |
L2 のうち「過去のセッション要約」のほうは、3 ヶ月以上経つと検索対象から自動で外れる ようにしてあります。古い記憶が雑音として混じるのを避けるためで、先ほどの図とは別のscheduled taskがクリーンアップを担当しています。
私はもともと beads と Obsidian を併用していて、思いつきや知見はそこに書き溜めていました。今回の機能はその既存ストアを recall 対象にしただけで、新しい入れ物を増やしてはいません。私が beads + Obsidian に分けているのは、Obsidian の双方向リンク機能が好きだから、というだけです。全部 1 個に集約しても、検索対象を絞れば同じように動きます。
要約のフォーマット
要約ファイルを作成するときに、エージェントに渡しているテンプレートを載せておきます。要約結果を後でキーワード検索しやすい形に揃えるためのプロンプトで、これを少しいじるだけで「自分の作業の振り返り」用の要約フォーマットとしても普通に使えるはずなので、興味があればコピペして使ってみてください。
## title
30 文字以内・1 行で、そのセッションの主題を体言止めで書く。
最初の prompt をそのまま引用するのは避け、セッション全体を通しての本質的なテーマを書く。
## 何をしてた
3〜5 個の短い箇条書きで、セッションの進行が追える形に書く。
1 行 1 フェーズ・1 アクションで、ダラダラ書かない。
## 決まったこと
このセッションで「新たに確定」した変更や決定のみを列挙する。
- 含める: 値の確定 (例: cap を 5 に変更)、採否 (A 採用 / B 不採用)、命名、期日、設定の追加・削除
- 含めない (= 「何をしてた」側に書く): 既存事実の「確認」、観察、分析の結論、学習成果、議論したが未確定のもの
- 該当なしなら `- (none)` の 1 行だけ書く。
## 触ったファイル
session 内で実際に Read / Edit / Write したファイルのパスを箇条書きで。
推測のパスを作らない (= tool-use 履歴から実際に拾えるものだけ)。
## 添付ファイル・URL
session に登場した添付や URL を `- <full path or url> — 1 行の内容説明` の形式で。
パスや URL は短縮しない。該当なしなら `- (none)` の 1 行だけ。
## 出てきた話題
そのセッションで議論された主題を、名詞ベースの wikilink 風キーワードで 3〜5 個。
`[[キーワード1]] [[キーワード2]]` の形式で書く。
設計のポイントは 3 つあります。
1. 「何をしてた」と「決まったこと」を分けていること
エージェントはうまく書こうとして、「議論した時点の論点」を「決まったこと」として書いてしまいがちです。「議論した」と「確定した」が混ざると要約全体が膨れて、後で読んだ時に何が決まったのか分からなくなってしまいます。これをセクションで分離することで、片側に閉じ込めています。
2. 該当なしなら - (none)を必ず書かせていること。
セクションごと消されてしまうと、後で集計したり grep したりする時に「内容がない」ことが分からない。「ない」を明示してもらう方が下流の処理がラクです。
3. [[wikilink]] 形式のキーワードを末尾に必ず置いていること。
Obsidian の双方向リンク記法なので、要約自体が同じ話題を別のセッションで議論したファイルへの導線になり、芋づる式に過去の議論を辿ることができます。
設計判断 — Claude Code 機能だけで組み立てる
冒頭でも書いたとおり、できるだけ Claude Code 自体の機能だけで完結させたかったので、それを実現するために通した判断を4つ並べます。
1. 非同期処理は macOS native cron ではなく Claude Code の scheduled task
要約のタスクは毎日走らせる必要があるので、最初は素直に macOS の launchd / cron を使う案もありましたが、やめました。
理由はクリーンアップ時の重さです。launchd の plist を ~/Library/LaunchAgents/ に置くと、後で「やっぱり要らないな」と思っても、launchctl unload してファイル消して、と複数ステップの撤去が必要です。
代わりに Claude Code の scheduled task で動かしました。これなら管理画面 1 つで一覧が見えて、不要になったらそこから消すだけ。何より、ハーネスのライフサイクルが Claude Code に閉じている安心感があります。
2. 形態素解析は DI で差し替え可能に
keyword 抽出の心臓部分は形態素解析器ですが、これをハードコードせず、 regex / sudachi / mecab / kagome から実行環境に合わせて選べるようにしました。第一選択は sudachi です。
具体的には、形態素解析処理を「標準入力からプロンプトを受け取り、標準出力にキーワードを返す独立したプロセス」として定義しています。これにより、呼び出し側のスクリプト(Recall処理)からはどの解析器を使っているかを意識する必要がありません。
ちなみに「なぜ sudachi なのか」と聞かれると、正直に言うと 最初に手が出たのが sudachi だった くらいの理由です。mecab でも kagome でも要件は満たせるはずで、「これじゃないとダメ」という強い差別化要素があったわけではない。一度動いて運用に乗ってからは、切り替える動機もないので継続している、というのが実際のところです。
ただ、バックエンドを DI にしておいたおかげで「動いてる時に変えない」が安心して言える状態になっているのは大きいです。後ろに書く実装時のつまづきの章で出てきますが、別のバックエンドが必要になった瞬間にすぐに切り替えられました。
3. Embedding / RAG にしていない
「キーワード検索じゃ文脈拾えないよね、embedding もしようよ。」というのは、実装中に何度も自問しましたが、今回は入れませんでした。
理由は シンプルに保ちたかった から、の一言に尽きます。
- ripgrep + jq で完結する今の構成なら、何がヒットしたか / なぜヒットしたか がログを見れば 1 行で分かる
- embedding を入れるとベクターDBの運用も必要になってしまう
- そして、現状のキーワード想起でもかなり「ふっと思い出す」感は出ている。検索精度の足りなさを エージェント 側の判断で吸収できている
設計の重みでいうと、軽い案で同等の効果が出ているうちは、重い案を主推奨にしないスタンスです。本当に keyword では拾えない場面が積み重なってきたら検討しようかと思っています。
4. ストアを強制しない
これは設計というよりスタンスに近いんですが、書き込み先を 1 つに強制しない ことにしています。recall hook は複数の入れ物を引きにいけるようになっていて、ユーザーは好きなところに書けばいい。
これも「Claude Code に閉じる」と同じ思想で、特定のソフトウェアにロックインしないのが大事だと思っています。ストアのパスは環境変数で外出ししているので、別のローカルツールに乗り換えたい時も差し替えるだけで動きます。
副次効果 — 何度も思い出される内容は、自然と要約にも乗る
設計判断とは少し別のレイヤなんですが、運用してみて気付いた 副産物的な良さ があるので一段だけ書いておきます。
想起された記憶がコンテキストに流れた時、そのセッションの中で
- LLM が応答の中で当該のログや内容に触れる
- 続きを読みにいく
- 「あれの話だよね」と言及する
といった形で session jsonl に記録されます。そして翌日の要約用 routine において、そのセッションが要約され新しい長期記憶ファイルとして書き出されるので、何度も想起された記憶は新しい要約にも自然に乗ることになります。最初にその話題が出たファイルが古くて検索対象から除外されてしまったとしても、参照された痕跡が新しい要約に組み込まれて、また検索で引けるようになります。
これにより、副産物的に重要な記憶は残り続けるというのが成立しています。ログの要約時に「重要だから何度も触れたんだろう」というのを拾ってくれているような感じで、結果的に複雑なスコアリングロジックを組んだりせずに重要な記憶を残すことができました。
100%保証されるわけではないので、あくまで「副産物として、本当に必要なものは生き残ってくれている感がある」くらいの話です。
これは設計したというより、あとから気付いた性質なので、設計判断と試行錯誤の間に挟む形で書きました。
実装中の試行錯誤
設計判断は綺麗に並べられますが、実際にはいろいろやらかしました。設計時 / 実装時 / 運用時、それぞれの段階で違う系統のつまづきがあったので代表例を 4 つ。
1. (設計時) session jsonl を直接 grep する案を捨てた
最初に思いつくのは「ユーザーのプロンプトと session jsonl を直接マッチさせれば過去の経緯が引けるんじゃない?」という案でした。これは捨てました。
理由は 2 つ。
- 読みづらい。一回の作業が数 MB に膨らむことがあり、grep ヒット周辺を抜粋しても LLM が「結局何があったんだっけ」を理解しづらい。生のツール呼び出しが大量に並んだ生のログから意味を拾うのは効率が悪い。
- 遅い。日次で増えていく jsonl 群を毎回のプロンプト送信時に走査すると応答が遅くなりすぎてしまう。
なので、session jsonl は生データ、要約された長期記憶が想起対象という分業に倒しました。
これがハマって、過去の作業を Obsidian で人間が読み返せる副産物まで生まれました。当初は エージェントのためだけの仕組みのつもりだったのが、自分自身の「先週何やってたっけ」にも効くようになっています。
2. (実装時) tokenize の locale バグと、stop-words の運用ループ
最初はキーワード抽出を正規表現でやっていました。具体的には、日本語のカタカナを [ァ-ヿ] で切ろうとしたのですが、macOS の特定のロケール設定下だとこの範囲表現が壊れて、[ ] を含むトークンが拾われたり、逆に正しいカタカナが落ちてしまったりする現象が出ました。
これを sudachi (品詞フィルタ付きの形態素解析器) に切り替えました。設計判断 §2 で書いた DI のおかげで、バックエンドの切り替えだけで済みました。「DI にしておいてよかった」と思った瞬間でした。
切り替えで「汎用名詞・助動詞・英語の冠詞」が自動で除外されるようになり、ノイズがかなり減りました。ただ、形態素解析だけだと「次」「処理」「ファイル」みたいな汎用語はキーワード に混じったままで、これらが想起のノイズ源になります。そこで、除外単語のリストファイルに手で追加していく運用にしています。
何を追加するかの判断材料は検索実行時のログです。「やたら検索でヒットしているけど中身が薄いキーワード」を炙り出して 除外単語のリストに追加する。やってみると面白くて、自分が無自覚に多用していた汎用語が炙り出されてきます。
除外単語のリストの中身は、初期に網羅的に書いた内容よりも、運用しながら育ってきた単語の方が多くなっています。これもロギングを最初に仕込んでおいて良かった一例で、最初から完璧を目指さなくていい、というのは精神的にも楽でした。
3. (運用時) ノイズの削減
動いてはいるけど質が低い、という段階に入ってからは「どのプロンプトでどんな検索結果が出ているか」を観測するために、recall hookの発火履歴をファイルに残すことにしました。
これによって、
- 過小セッション (= 要約してもほとんど中身がない) を 要約前段 で弾く pre-filter
- LLM が空っぽの要約を返した場合に 要約後段 で弾く post-filter
を入れました。前者は「3 ターンで終わったセッション」みたいなのを切る判定、後者は「エージェントが要約を諦めて空応答を返してしまったケース」を捨てる判定です。両方とも、観測してから「あ、これ多すぎだな」と気付いて入れたフィルターで、最初から設計できたものではありません。観測ログを最初に仕込んでおいたのが効いた 一例です。
4. (運用時) 5 時間制限の 43% を 1 回で食ったやらかし
これはちょっと笑い話なんですが、最初に要約のscheduled taskを回した時、Claude Code の 5時間利用制限 の 43%を1 回で食いつぶしてしまった ことがありました。「ヤバすぎる」と思ってログを確認したら、要約対象の中に サブエージェントのセッションや、別の routine が走ったセッションのログ までごっそり含まれていて、それを全部丁寧に要約しようとしていたのです。
対処自体は単純で、要約対象から「scheduled task や routine、セッション内で呼び出されたサブエージェント等のセッションログ」をフィルターで除外しただけです。日次運用してみないと気付かないタイプの罠で、設計時には完全に見落としていました。「scheduled taskで動いているエージェントが、自分のログを生成して、それを自分が要約しに行く」という再帰構造に、設計段階では気付けませんでした。
FAQ
Q. Claude Code の session ログとどう違うの?
ひとことで言うと 「録画」 vs 「想起」 です。
- session jsonl = 録画。何が起きたかを全て保存するログ。resume / debug / 監査が用途。生のままだと大きすぎてコンテキストに入れられない (機密・読みづらさ)。
- memory-recall = 想起。生のログを要約 + 脱機密化 + 整理して、別ファイルにしたものを検索で引く。
人間の記憶と並べてみると、毎日の会話において全ての発言を覚えているわけではなく、代わりに「あんな話をしたよな」というのを頭の中で要約して持っているのではないかと思います。何かの拍子にキーワードで引っ張り出しているというのと同じ階層分けです。
Q. 公式の memory 機能 (https://code.claude.com/docs/ja/memory) と何が違うの?
これも役割が違います。
- 公式 memory =
「指示・嗜好の事前の注入」「指示・嗜好の保持」 [1]。CLAUDE.mdのようなファイルで、「~するときは X してね」「コードコメントは英語で」といった方針 を毎セッションに反映させる仕組み。 - memory-recall = 「過去の作業コンテキストの想起」。「先週バグ直したやつ何だっけ」「あの設計議論どこで決めた」みたいな エピソード単位 の引き戻し。
人間の側でもこの 2 つは違う種類の記憶で、
- 「会議は 10 分前に入る」みたいな 手続き的・方針的な記憶
- 「先週の月曜の朝、A さんとあの議論したな」みたいな エピソード的な記憶
は脳の中でも別系統だと言われています (ここで深入りはしません)。LLM のハーネスでも、この 2 系統は別々に持てたほうが見通しがいい、というのが実装してみての感想です。CLAUDE.md で全部やろうとすると、日々増えるエピソードでファイルが肥大していくし、recall hook で全部やろうとすると、固定方針まで毎ターン keyword 検索される負荷がかかってしまいます。
ちなみに 実装制約 の差もあります。公式 memory は cloud で動くので、ローカルファイル (jsonl, Obsidian の markdown 群) に直接アクセスできない。memory-recall はローカル (Desktop scheduled task + shell script + jq + ripgrep) で完結しているので、自分のローカル資産にそのまま手が届きます。
(2026-05-12 追記) すみません勘違いしてました。 公式 memory も自動メモリ (~/.claude/projects/<project>/memory/) は ローカルファイル保存 でした (公式 docs の「ストレージの場所」 参照、「ファイルはマシン間またはクラウド環境全体で共有されません」と明記されています)。
実装層で本当に違うのは、memory機能の「プロジェクトごとに別ディレクトリで管理されていて、起動時に読まれるのは MEMORY.md の冒頭 200 行 / 25KB だけ」 という設計の方です。memory-recall はその境界の外で beads inbox / Obsidian vault を プロジェクト横断で検索対象として束ねる構成になっているので、棲み分けが成立しているという整理が正確でした。
残っている課題 — 同セッション内の dedup
このブログを書き終えてから気付いたものがあります。UserPromptSubmit hook は 毎 prompt で検索を回している だけで、セッション単位での状態を持っていません。
つまり、1ターン目の検索でヒットした内容と2ターン目の検索でヒットした内容に重複があっても、両方ともコンテキストに注入されてしまいます。1ターン目の検索結果は会話履歴として残るので、コンテキスト内に同じ内容が二重に積まれていってしまうという構造です。
人間の記憶であれば「あ、これさっき思い出したやつだ」と気付いてスキップする能力はありますが、現在のmemory-recall機能にはそれがありません。session id ベースの「直近 N ターンで注入済み」という状態を持ってスキップできたら良いとも思ったのですが、
- 同じ記憶でも、ターンごとに違う文脈で再ヒットしたら出すべき
- そもそもプロンプトキャッシュがあるのでそこまでやる必要あるか?
という両方の考えがあって、まだ手をつけられていません。実際この記事を書いている最中に発覚した課題で、書き出してみないと気付かないものだなと思いました。
おわりに
書いてみて感じたのは、「LLM の記憶を作る」と言うとどうしても embedding → vector store → RAG という流れに乗りがちだけど、Claude Code の hook と skill と scheduled task と shell script だけ でも用途を絞れば結構いい感じに動く、ということでした。
ハーネス自体が育っていく感覚もあって、ロギングを最初に入れておいたのが効きました。「なんか質が悪い」という感覚を棄却せずに、その場でログを眺めて「あー、このキーワードが頻出すぎるな」と打ち手を考えられました。
人間の記憶の話に戻ると、私たちも会話の内容を全部詳細に思い出せるわけじゃなくて、要約された痕跡をキーワードで引いているような感じがあります。完全な録画を持つ必要はなくて、後で引ける形に整えておけばいい。そういう構造を素朴に Claude Code 内の機能だけで組んだ、という記事でした。
似たことをやっている方がいたらぜひ実装の話を聞きたいです。
-
(2026-05-12 追記) 訂正: 当初「事前の注入」 と書きましたが、 公式 memory の 自動メモリ には トピックファイルのオンデマンド読み込み機構 があり、 「Recalled memory」 という公式用語まで存在します。 機構面では事前注入 (
CLAUDE.md/MEMORY.md冒頭) と想起 (トピックファイル) のハイブリッドが正確です。 ただし「ユーザープロンプトの keyword に連動して都度引く」 機構ではないため、 役割分担 (方針保持 vs エピソード想起) としての本記事の主旨は変わりません。 ↩︎
Discussion
はじめまして、記事を拝読させていただきました。
Claude Codeの正規機能(hook / skill / scheduled task)だけで閉じる設計判断が地味にありがたくて、
Hermes Agentには同種の機能(FTS5 session search + memory nudge)が標準搭載されてはいるのですが、
4月以降AnthropicがOAuth経由のサードパーティアクセスに
サーバーサイドバリデーションを追加していて、Claudeをバックエンドにする場合は
APIキー(従量課金)が必要な状況のようです。
hookとscheduled taskは正規APIサーフェスなので、この種の締め付けと無縁なのが強いですね。
claude.aiのメモリ機能も似たことをやっていますが、
あちらは「プロンプトが来る前に事前に載せておく」方式なので、
おばあちゃんが「あんたこれ好きでしょ」と毎回同じお菓子を買ってくる感じになりがちです。
記事の方式はプロンプトが来てからキーワードで引くので、
「そういえばあれ」と文脈に合ったものが出てくる。
FAQで書かれていた「手続き的記憶とエピソード記憶は別系統」が実感としてもその通りで、
両方必要だけど役割が違うというのは使っていても感じます。
副次効果の「何度も想起された記憶が新しい要約に乗る」も、明示的なスコアリングなしで重要度の自然淘汰が成立しているのが面白いです。
お読みいただきありがとうございました。