はじめに
Claude Code、Codex、Gemini を併用していると、あとから「あの日どのツールで何を相談したか」を追いたくなる場面が増えます。
ただし実際のログは、ツールごとに保存形式も保存場所も違います。
- Codex は JSONL ベース
- Claude Code も JSONL だがイベント種別が多い
- Gemini はセッション JSON が主体
そのままでは読みづらく、日単位の振り返りにも向きません。
そこで、各ツールのログを chat_logs に集約し、さらに LLM_history 配下へ「日次概要 + LLM別詳細 Markdown」として自動整形する仕組みを作りました。
本記事では、その構成、設計意図、スクリプト、そして cron による毎日1回の自動実行までをまとめます。
完成イメージ
この構成により、日付単位で次の2種類の見方ができます。
| ファイル | 用途 |
|---|---|
YYYY-MM-DD.md |
1日の全体像をざっと確認する概要 |
YYYY-MM-DD.codex.md など |
ツール別に詳細な会話本文を読む |
システム構成
今回の仕組みは、ログ収集・状態管理・Markdown 出力の 4 層で構成しています。
| 層 | コンポーネント | 役割 |
|---|---|---|
| 元ログ |
~/.codex/sessions, ~/.claude/projects, ~/.gemini/tmp
|
各ツールの生ログを保持 |
| 差分収集 |
batch_copy_codex_diff.sh など |
元ログを chat_logs に差分コピー |
| 状態管理 |
SQLite (LLM_history/_meta/history.db) |
取り込み済みファイル、正規化済みメッセージ、重複排除キーを保持 |
| 出力 |
render_llm_history.py + LLM_history/
|
日次概要・LLM別詳細・月次一覧を生成 |
chat_logs だけでなく SQLite を間に挟んでいるのがポイントです。これにより、ログ差分収集と閲覧用 Markdown 生成を疎結合にできます。
前提環境
筆者の環境は以下のとおりです。既存の chat_logs 収集基盤の上に、本記事の Markdown 整理レイヤを追加しています。
| 項目 | 詳細 |
|---|---|
| OS | Ubuntu 24.04.4 LTS |
| CPU | Intel Core i7-12700K |
| RAM | 64 GB |
| GPU | NVIDIA GeForce RTX 3070(VRAM 8 GB) |
| 接続方式 | Windows → VS Code Remote SSH → Linux |
| 作業ルート | /home/username/work_space |
| 元ログ集約先 | chat_logs/ |
| 出力先 | LLM_history/ |
| 正規化スクリプト | tools_for_ai/codex/render_llm_history.py |
| 実行ラッパ | tools_for_ai/codex/update_llm_history.sh |
| 永続状態 | LLM_history/_meta/history.db |
| 定期実行 |
cron(毎日 04:05 UTC) |
LLM_history/ は .gitignore に追加しておくのがおすすめです。
日次ファイルが増え続けるため、未追跡ファイルのノイズを防げます。
記事内の絶対パスは、個人名を避けるため username 表記に統一しています。
仕組みの全体像
なぜ chat_logs と LLM_history を分けるのか
この構成では、あえて「集約ログ」と「閲覧用ドキュメント」を分離しています。
| 層 | 役割 | 理由 |
|---|---|---|
chat_logs |
元ログの差分保管 | 再解析や再生成の元データとして残す |
LLM_history |
人が読むための Markdown | 読みやすさを最優先に整形する |
この分離により、表示ルールを後から変えても chat_logs から再生成できます。
なぜ SQLite を挟むのか
chat_logs を毎回フルスキャンして Markdown を直接再生成することもできますが、差分収集方式との相性を考えると SQLite を挟んだ方が運用しやすくなります。
| SQLite に持たせている情報 | 役割 |
|---|---|
source_files |
どの _merged_*.jsonl を何回目のパーサで取り込んだか管理 |
messages |
正規化済みの会話本文を保持 |
message_key |
LLM ごとの重複排除キー |
これにより、次のことが可能になります。
- 変更のあった入力ファイルだけ再取り込みする
- 過去分を
--render-allで高速に再出力する - 差分コピーで同じセッションが再度流れてきても重複を抑える
なぜ「日次概要 + LLM別詳細」にしたのか
最初は1日1ファイルに全部入れる案もありましたが、会話量が多い日は長すぎて読みにくくなります。逆に LLM ごとに完全分離すると、その日の全体像が見えなくなります。
そこで折衷案として、次の形にしました。
| 見方 | 向いている用途 |
|---|---|
| 日次概要 | その日何をしていたかを横断的に把握 |
| LLM別詳細 | 同じツールでの会話だけを深く追跡 |
ログ形式の違い
各ツールはログ形式が違うため、正規化ステップが必要です。
| ツール | 集約元 | 代表ファイル | 主な抽出対象 |
|---|---|---|---|
| Codex | chat_logs/chatgpt/... |
_merged_rollouts.jsonl |
response_item の user / assistant |
| Claude Code | chat_logs/claude/... |
_merged_sessions.jsonl |
type=user/assistant の本文 |
| Gemini | chat_logs/gemini/... |
_merged_sessions.jsonl |
messages[] の user / gemini |
特に Codex は保存先ディレクトリ名が chatgpt ですが、実際には Codex ログを格納しています。これは既存運用との互換のため、そのまま使っています。
正規化でやっていること
1. 不要イベントの除外
人が読み返す用途では、すべてのイベントを残す必要はありません。進捗通知や内部指示は落としています。
| 除外対象 | 例 |
|---|---|
| system/developer 相当 |
AGENTS.md instructions, permissions instructions
|
| 進捗イベント |
queue-operation, progress, event_msg の一部 |
| 低シグナル応答 |
I will ... のような Gemini の進捗文 |
| ツール呼び出しだけの行 | ツール呼び出し: ... |
2. 重複排除
差分収集は「更新されたセッションファイルを丸ごと再コピー」する方式なので、そのまま連結して読むと重複が出ます。そこで SQLite に正規化済みメッセージを保存し、メッセージキーで重複を防ぎます。
| ツール | 重複排除キー |
|---|---|
| Codex | session_id + timestamp + role + content_hash |
| Claude Code | uuid |
| Gemini | message.id |
3. 日付判定の統一
ログは UTC タイムスタンプを持っていますが、日次ファイルは Asia/Tokyo などのローカルタイムで切った方が実運用に合います。
| 入力 | 出力 |
|---|---|
2026-03-01T15:00:11.871Z |
2026-03-02 00:00:11 JST |
この変換を統一しておくことで、深夜帯の会話が期待どおり同じ日付にまとまります。
出力ファイル構成
生成後のディレクトリは次のようになります。
LLM_history/
_meta/
history.db
index.md
2026/
2026-03/
index.md
2026-03-01.md
2026-03-01.codex.md
2026-03-01.claude.md
2026-03-01.gemini.md
| パス | 内容 |
|---|---|
LLM_history/index.md |
全月・全日一覧 |
LLM_history/YYYY/YYYY-MM/index.md |
月次一覧 |
YYYY-MM-DD.md |
日次概要 |
YYYY-MM-DD.codex.md など |
LLM別詳細 |
LLM_history/_meta/history.db |
増分管理用 SQLite |
Step 1: 差分収集スクリプトの準備
まずは各 LLM の元ログを chat_logs に取り込む必要があります。今回の環境では、すでに以下の差分収集スクリプトを使っています。
| スクリプト | 役割 |
|---|---|
/home/username/tools/batch_copy_codex_diff.sh |
Codex ログ取り込み |
/home/username/tools/batch_copy_claude_diff.sh |
Claude Code ログ取り込み |
/home/username/tools/batch_copy_gemini_diff.sh |
Gemini ログ取り込み |
これらは chat_logs/<tool>/000xx/ のような連番ディレクトリを作り、 _merged_*.jsonl を生成します。
Step 2: Markdown 生成スクリプト(render_llm_history.py)
会話本文の整形は render_llm_history.py が担当します。
スクリプトの役割
| 処理 | 内容 |
|---|---|
| ソース探索 |
chat_logs 配下の _merged_*.jsonl を列挙 |
| 形式吸収 | Codex / Claude / Gemini の本文抽出 |
| 永続化 |
SQLite に正規化済みメッセージと取り込み状態を保存 |
| 出力 | 日次概要・LLM別詳細・月次一覧・全体一覧を生成 |
| 清掃 | 消えた日付や不要ファイルを削除 |
主な実行コマンド
# 全件再生成
python3 /home/username/work_space/tools_for_ai/codex/render_llm_history.py --render-all
# 特定日だけ更新
python3 /home/username/work_space/tools_for_ai/codex/render_llm_history.py --date 2026-03-01
# タイムゾーン変更
python3 /home/username/work_space/tools_for_ai/codex/render_llm_history.py --tz Asia/Tokyo
Step 3: 実行ラッパ(update_llm_history.sh)
日常運用では、差分収集と Markdown 生成を別々に叩くより、ラッパにまとめた方が楽です。
update_llm_history.sh は次の流れで動きます。
代表的な使い方
# 収集 + 生成
/home/username/work_space/tools_for_ai/codex/update_llm_history.sh
# 既に chat_logs が更新済みなら生成だけ
/home/username/work_space/tools_for_ai/codex/update_llm_history.sh --skip-sync
Step 4: cron による日次実行
今回の環境では、差分収集ジョブがすでに 04:00 UTC に入っていたため、その5分後に LLM_history 生成を追加しました。
現在の cron 登録
# batch_copy diff jobs
0 4 * * * /home/username/tools/batch_copy_claude_diff.sh > /dev/null 2>&1
0 4 * * * /home/username/tools/batch_copy_codex_diff.sh > /dev/null 2>&1
0 4 * * * /home/username/tools/batch_copy_gemini_diff.sh > /dev/null 2>&1
# LLM_history daily render (UTC)
5 4 * * * /home/username/work_space/tools_for_ai/codex/update_llm_history.sh --skip-sync >> /home/username/work_space/tmp/update_llm_history.log 2>&1
こうした理由
| 選択 | 理由 |
|---|---|
04:05 UTC |
差分収集が終わった直後に動かしたい |
--skip-sync |
同じタイミングで二重に収集しない |
>> ...log 2>&1 |
失敗時の原因をあとから追える |
この cron 実装では CRON_TZ が使えなかったため、スケジュールは UTC で登録しています。
一方、Markdown 内の日付判定は Asia/Tokyo で行っています。
実際の見え方
生成後は、たとえば次のように読み分けられます。
| ファイル | 読み方 |
|---|---|
2026-03-01.md |
その日のセッション一覧と LLM 別リンクを見る |
2026-03-01.codex.md |
Codex の会話だけ読む |
2026-03-01.claude.md |
Claude Code の会話だけ読む |
2026-03-01.gemini.md |
Gemini の会話だけ読む |
月次一覧には次のように「概要 + 各 LLM の詳細リンク」が並びます。
| 日付 | 概要 | Codex | Claude | Gemini |
|---|---|---|---|---|
2026-03-01 |
あり | あり | あり | あり |
2026-03-02 |
あり | あり | あり | なし |
2026-03-04 |
あり | なし | なし | あり |
この形にしておくと、「今日は Gemini だけだった」「この日は Claude のみ大量に使った」といった偏りも一覧で把握できます。
運用上のポイント
1. 生成物は Git 管理しない
生成ファイルは日々増えるので、基本的には .gitignore に入れるのが無難です。
2. 表示ルールを変えたくなったら --render-all
進捗文の除外ルールやタイトル生成ルールを変えた場合は、全件再生成すれば過去分にも反映できます。
python3 /home/username/work_space/tools_for_ai/codex/render_llm_history.py --render-all
3. 将来的な拡張
| 拡張案 | 内容 |
|---|---|
| 週次サマリ | 1週間単位のインデックスを追加 |
| 全文検索 |
history.db に FTS を追加 |
| タグ抽出 | 会話からトピックを自動分類 |
| HTML 出力 | Markdown だけでなく静的サイト化 |
まとめ
今回の仕組みでは、3 種類の LLM ログを chat_logs に集約し、さらに LLM_history として人間向けの Markdown に再構成しました。
ポイントは次の3つです。
- 保存用の
chat_logsと閲覧用のLLM_historyを分離した -
日次概要 + LLM別詳細にして可読性と追跡性を両立した -
cronで毎日1回更新し、運用を手放せる形にした
「複数の AI コーディングツールを使っているが、あとから会話を振り返りづらい」と感じているなら、この構成はかなり相性が良いはずです。