こんにちは!
組織でぼっちエンジニアとして開発環境を改善するために日々奮起しています。
今日はClaude Codeで自己改善するエージェントを構築した話です。
Claude Code、便利ですけど、素のままだとセッションをまたいで学びが引き継がれないんですよね。
同じミスを別セッションでまた繰り返す。これを毎回指示するのは面倒。
ちなみに標準の /memory(自動メモリ)でも Claude は勝手に覚えてくれます。
ただ何を残すかの取捨選択が効きにくいのが難点。放っておくと的外れなメモも溜まり、正本が濁っていく。
そこで今回は「蓄積は自動、正本への昇格は人間が承認してから」という形にしたかった。
これを Harness Engineering の考え方を使って、
セッション終了時に自動で振り返り→教訓を蓄積→人間の承認で正本に昇格する仕組みを、
Python 標準ライブラリだけで作りました。
結論
Stop フックでトランスクリプトを解析し、「重い作業」か「ユーザー訂正あり」のときだけ Claude に振り返りをさせて .learnings/ に教訓ドラフトを自動保存する。
永続化ファイルへの反映は /promote で人間が承認してから。
やったことは3つだけ。
- Stop フック(Python スクリプト1本、約200行)
-
.learnings/ディレクトリ(教訓ドラフトの置き場) -
/promoteスキル(昇格用)
外部サービスもライブラリも不要です。
Harness Engineering とは
「AIエージェントに仕事を任せるとき、どこまで自律させて、どこで人間が介入するか」を設計する考え方です。フルオートでもなく、毎ステップ確認でもない。ガードレール(harness)を敷いて、その中で自律的に動かすのがポイント。
今回の自己改善ループでは、こう適用しました。
| Harness の原則 | この仕組みでの実装 |
|---|---|
| 最小権限 | フックは Read Only(トランスクリプトを読むだけ)。正本の書き換え権限は /promote 経由でのみ |
| 構造化出力 | 教訓ドラフトは frontmatter 付き Markdown。type / status で機械的に分類・フィルタ |
| フィードバックループ上限 | 振り返りはセッションにつき1回限り。マーカーファイルで多重発火を防止 |
| フェイルオープン | フックは何があっても exit 0。セッション体験を壊さない |
内容
アーキテクチャ
セッション終了
↓
Stop フック発火 → トランスクリプト(JSONL)を解析
↓
「重い作業(ツール15回以上)」or「ユーザー訂正あり」?
↓ No → 何もせず終了
↓ Yes
decision: block を返す → Claude が文脈を保ったまま振り返り
↓
.learnings/ に教訓ドラフトを保存 → 終了
↓
(後日)/promote → 承認されたものだけ memory 等に昇格
settings.json への登録
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "python3 \"$HOME/.claude/hooks/self-improve/stop_hook.py\"",
"timeout": 30
}
]
}
]
}
}
判定ロジック(何を振り返り対象にするか)
トランスクリプトは JSONL で、各行に type(assistant / user)と content が入っています。
# ツール呼び出しを数える
if record_type == "assistant" and isinstance(content, list):
for block in content:
if isinstance(block, dict) and block.get("type") == "tool_use":
tool_calls += 1
ツール15回以上 = 何かしら調査や実装をしたセッション。15回未満はだいたい「ちょっとした質問」なので振り返る価値が薄い。この閾値は config.json で調整できるようにしています。
無限ループ防止(一番ハマったところ)
Stop フックで block → Claude が続行 → また止まる → またフックが発火 → 無限ループ。これを2重ガードで防いでいます。
ガード1: Claude Code は block 後の再 Stop で stop_hook_active: true を渡してくれる。これで即 return。
if data.get("stop_hook_active"):
return
ガード2: セッションIDのマーカーファイルで「振り返り済み」を記録。保険です。
marker = os.path.join(STATE_DIR, "%s.done" % session_id)
if marker and os.path.exists(marker):
return
ユーザー訂正の検出(ハーネス注入テキストの罠)
トランスクリプトの user レコードには、人間の発話だけでなく <system-reminder> やスキル展開テキストも混ざります。
最初はこれを区別できず、ハーネスのテキストに「違う」等のキーワードがあると誤検出していました。
def iter_user_texts(content):
"""人間の発話らしいテキストだけ取り出す"""
for text in texts:
stripped = text.lstrip()
if not stripped or stripped.startswith("<"):
continue # system-reminder 等を除外
if len(text) > MAX_HUMAN_TEXT_CHARS:
continue # スキル展開等の長文を除外
yield text
振り返り指示のチューニング
block の reason に渡す文字列が Claude への指示になります。ここのプロンプト設計が地味に重要でした。
- 「学びがあれば書いて」→ 保守的すぎて何も書かない。 エラーを解決しても「既知の対処なので学びなし」と判定される
- 「必ず書いて」→ 質問セッションでも無理やりひねり出す
最終的にこのフレーミングが効きました。
自分にとって既知の対処でも、次のセッションの Claude は知らない可能性があるため、ほぼ常に記録に値する
教訓ドラフトの形式
構造化出力の原則に従い、frontmatter で分類できるようにしています。
---
date: 2026-07-07
session: 8104b7b9-...
project: myproject
type: discovery # mistake / correction / technique / discovery
status: draft # draft → promoted or archived
---
# 要件壁打ちは既存システムのコードを読んでから
## 何があった
要件ページの前提と実際のコードが食い違っていた。
## 学び
方針を固める前に既存リポジトリを読む。
## 昇格案
記事ネタ向き。
/promote で承認制の昇格
/promote と打つと .learnings/ のドラフトをレビューし、各ドラフトの昇格先を提案します。
| 昇格先 | 向いているもの |
|---|---|
| memory | プロジェクトの教訓・罠(個人用) |
| CLAUDE.md | チーム共有ルール(git コミット対象) |
| 新規スキル | 再利用可能な手順 |
| 記事ネタ帳 | Qiita 等の記事の種 |
| archive | 不要なもの |
人間が OK するまで正本には一切触らない。 .learnings/ はあくまで提案箱です。
フェイルオープン
Harness の原則として、フックはどんなエラーでも exit 0 で黙って通す。
if __name__ == "__main__":
try:
main()
except Exception as e:
log_error("fatal: %r" % e)
sys.exit(0)
トランスクリプトが読めない、JSON パースに失敗した、等の時は振り返りをスキップするだけ。エラーはログに落として後で見れば十分。セッション体験を壊すほうがはるかに害が大きい。
まとめ
| やったこと | ポイント |
|---|---|
| Stop フックでトランスクリプト解析 | 「重い作業 or 訂正あり」だけ振り返り。軽いセッションは素通り |
decision: block で振り返り指示 |
Claude がセッション文脈を持ったまま教訓を書く |
| 無限ループ防止 |
stop_hook_active + マーカーファイルの2重ガード |
.learnings/ に構造化ドラフト |
frontmatter で分類、/promote まで正本に触らない |
| フェイルオープン | 何があっても exit 0。セッションを壊さない |
世の中の「AIに学ばせる」系は「自動ログ型」と「スキル自動蒸留型」の2パターンがありますが、今回はその中間。蓄積は自動、昇格は承認制。Harness の考え方で「勝手に賢くなってほしいけど、勝手に変なことを覚えてほしくない」のバランスを取りました。
なかなか手をつけられていないharnessに今回挑戦してみてまだ改善できそうなので
今後も情報収集して試していきます![]()