はじめに
Claude Codeに慣れてくると、「これはSkillゲーだ」と悟るタイミングがあります。日々の業務をSkills化できると、Claude Codeの成果物の質が上がりますし、コンテキストを渡す労力も大幅に削減されます。
ただ、よほど簡単な業務でない限り、完璧なSkillを一発で作ることは難しいです。そして仕事のやり方も不変ではありません。つまりSkillは1回作って終わりではなく、常に面倒を見続ける必要があります。気づいたら毎日Skillを弄っている気がします。流石に辛い。
何か良いソリューションはないものかと探していると、いくつか見つかりました。例えば、Anthropicが公式で出しているSkill Creatorは、評価と改善のループを自律的に回してSkillをブラッシュアップする仕組みを持っています。他にも、Skillを自動的・自律的に作成・更新する手法やツールがいろいろとありそうです。
その中で目を引いたのが、Everything Claude Code(ECC)の「Continuous Learning」という仕組みです。ECCはClaude Codeの拡張フレームワークで、スキル、フック、エージェント等を体系的に管理してClaude Codeの能力を拡張します。
Continuous Learningは、Claude Codeのセッション中の行動パターンを自動的に観察・学習し、再利用可能な知識として蓄積する機能です。現在はv2(v2.1)で、「Instinct-based architecture」というアーキテクチャで構成されています。
Skillの手動メンテナンスから解放してくれる可能性を感じたので、その内部の仕組みを詳しく調べてみました。
Continuous Learningの概要
まず全体像を掴んでから、各ステップの詳細に入ります。
Instinctとは
Continuous Learningの核となるのが「Instinct(インスティンクト)」と呼ばれる学習単位です。セッション中に検出されたパターンをYAML+Markdownの小さなファイルとして保存したもので、1つのトリガーと1つのアクションで構成される最小単位の学習データです。
できること / できないこと
できること:
- Claude Codeのセッションからユーザーの行動パターンを学習し、Skill/Command/Agentを生成する
- 学習したパターン(Instinct)をプロジェクト間で共有・再利用する
できないこと:
- 完全な自律化 -- Instinctの生成までは自動だが、Skill/Command/Agentの作成にはユーザーが
/evolve --generateを手動実行する必要がある - 既存のSkill/Command/Agentの更新 -- evolveは毎回Instinctから完全に再生成するため、既存ファイルの内容を読んでマージする仕組みはない
- 中長期的な時系列分析 -- 解析は直近のobservations.jsonl(最大500行)に対するバッチ処理であり、過去のアーカイブや既存InstinctのConfidenceとの統合は保証されない
バージョン変遷
| 機能 | v1 | v2 | v2.1 |
|---|---|---|---|
| 観察タイミング | セッション終了時のみ | ツール呼び出し毎 | 同左 |
| 観察信頼性 | スキル依存(50〜80%) | Hooks(100%) | 同左 |
| スコープ管理 | なし | なし | プロジェクト単位で分離 |
| クロスプロジェクト汚染 | あり | あり | 防止 |
v1からv2への最大の変更点は、観察をスキル(確率的)からHooks(決定論的・100%)に移行したことです。
全体フロー
処理は大きく3つのステップに分かれます。
| ステップ | 内容 | 自動/手動 |
|---|---|---|
| 1. 観察データの記録 | ツール呼び出しのたびにHooksがobservations.jsonlに記録する | 自動 |
| 2. Instinctの生成 | 一定量溜まるとobserverがパターンを検出し、Instinctとして保存する | 自動 |
| 3. Command/Skill/Agentへの昇格 |
/evolve --generate を手動実行し、蓄積されたInstinctから生成する |
手動 |
登場人物
| 名前 | 種別 | 役割 |
|---|---|---|
| continuous-learning-v2 | Skill | observe.sh・コマンド群・スクリプト群を含むスキルパッケージ本体 |
| observe.sh | Hooks | ツール呼び出しごとに起動し、observations.jsonlに記録する |
| observations.jsonl | 中間データ | ツール呼び出しの記録(入力・出力・タイムスタンプ) |
| observer | Subagent | observations.jsonlを解析してInstinctを生成する |
| Instinct | 中間データ | 1つのトリガーと1つのアクションで構成される最小単位の学習データ |
/evolve |
コマンド | InstinctをクラスタリングしてSkill/Command/Agentへの昇格を提案する |
以下、各ステップの詳細を見ていきます。
ステップ1: 観察データの記録(Hooks → observations.jsonl)
Hookの登録
observe.sh が、ツール呼び出しのたびに起動され、観察データをobservations.jsonlに追記します。settings.jsonに以下のHookを登録することで有効化されます。
{
"hooks": {
"PreToolUse": [{
"matcher": "*",
"hooks": [{"type": "command", "command": "~/.claude/skills/continuous-learning-v2/hooks/observe.sh pre"}]
}],
"PostToolUse": [{
"matcher": "*",
"hooks": [{"type": "command", "command": "~/.claude/skills/continuous-learning-v2/hooks/observe.sh post"}]
}]
}
}
matcher: "*" なので、すべてのツール呼び出しが対象になります。
observe.shの処理フロー
ツール呼び出しのたびに observe.sh が起動され、stdinからJSONを受け取り、シークレットを除去してobservations.jsonlに1行追記します。
ツール呼び出し → observe.sh が起動
↓
1. stdinからJSONを読み込む
2. 自動化セッションフィルタリング(5層)→ 該当したら終了
3. プロジェクト検出([detect-project.sh](https://github.com/affaan-m/everything-claude-code/blob/main/skills/continuous-learning-v2/scripts/detect-project.sh)でgitリモートURLのハッシュからプロジェクトIDを生成)
4. JSONをPythonでパース・シークレット除去
5. observations.jsonl に1行追記
6. バックグラウンドオブザーバーを起動(未起動なら)
7. 20回に1回 SIGUSR1 でオブザーバーに通知
PreToolUseとPostToolUseの両方で起動されるため、1回のツール呼び出しで2行追加されます。preでは「何をしようとしたか(入力)」、postでは「何が起きたか(出力)」を記録します。後段のobserverが「意図」と「結果」の両方を見てパターンを分析するために、両方が必要です。
tool_start: Edit で class → def に変えようとした
tool_complete: 成功
tool_start: ユーザーが def → class に戻した
↓
instinct: 「このプロジェクトはclassスタイルを好む」
自動化セッションのフィルタリング
observer自身もツールを呼ぶため、そのまま記録するとノイズになりループにもなります。人間が操作しているセッションだけを観察するために5層のフィルタで弾きます。
| Layer | チェック内容 | 防ぐもの |
|---|---|---|
| 1 |
CLAUDE_CODE_ENTRYPOINT が cli / sdk-ts 以外はスキップ |
自動化ツールからの起動 |
| 2 |
ECC_HOOK_PROFILE=minimal ならスキップ |
自動化セッション(協調的宣言) |
| 3 |
ECC_SKIP_OBSERVE=1 ならスキップ |
observer自身のツール呼び出し |
| 4 | JSONに agent_id フィールドがあればスキップ |
サブエージェント |
| 5 |
cwd が observer-sessions 等を含むならスキップ |
Layer3・4の漏れへの最後の砦 |
observations.jsonlの中身
{"timestamp":"2026-03-29T08:00:00Z","event":"tool_start","tool":"Edit","session":"abc123","project_id":"a1b2c3d4e5f6","project_name":"my-project","input":"{...}"}
{"timestamp":"2026-03-29T08:00:01Z","event":"tool_complete","tool":"Edit","session":"abc123","project_id":"a1b2c3d4e5f6","project_name":"my-project","output":"File updated successfully"}
肥大化を防ぐ仕組みもあります。10MBを超えると observations.archive/ にローテーションされ、アーカイブは30日で自動削除されます。
記録と解析のループ
observe.sh と observer-loop.sh は独立したプロセスとして連携します。
- observe.sh(記録係)-- ツール呼び出しのたびに起動・終了する短命なプロセス。observations.jsonl への追記と、observer-loop.sh への通知だけを行う
- observer-loop.sh(解析係)-- セッション中ずっとバックグラウンドで常駐するプロセス。observe.sh からの通知(SIGUSR1)または5分タイマーをトリガーに解析を実行する
observe.sh が初回起動時に start-observer.sh 経由で observer-loop.sh を立ち上げ、以降はツール呼び出しごとに observations.jsonl にデータを溜めつつ、定期的に observer-loop.sh が解析してInstinctを生成するサイクルになっています。
ステップ2: Instinctの生成(observer → Instinct)
observerの起動
observer-loop.sh がトリガーされると、以下の流れでInstinctが生成されます。
- observations.jsonl の直近500行をtmpファイルに書き出す
-
analyze_observations()がobserverを起動する - observerがtmpファイルをReadし、4種類のパターンを探す
- 3回以上観察されたパターンがあれば、observerがWriteツールでInstinctファイルを直接書き出す
- 解析完了後、observations.jsonl はアーカイブに移動されリセットされる
2重起動や暴走を防ぐために3つのガードが用意されています。
# 1. 再入防止: すでに解析中なら即スキップ
if [ "$ANALYZING" -eq 1 ]; then return; fi
# 2. クールダウン: 前回から60秒未満ならスキップ
if [ "$elapsed" -lt 60 ]; then return; fi
# 3. タイムアウト: Haikuが120秒で終わらなければ強制終了
observerの正体
observerは --allowedTools "Read,Write" しか持たないHaikuベースのサブエージェントです。「データを読む → パターンを検出する → Instinctファイルを書く」の3ステップ以外のことはできません。
ECC_SKIP_OBSERVE=1 ECC_HOOK_PROFILE=minimal \
claude --model haiku --max-turns 10 --print \
--allowedTools "Read,Write"
observer-loop.sh がobserverを起動する際、4ブロック構成のプロンプトをヒアドキュメントで渡します。
- 動作モードの指示 -- 「非インタラクティブモード。確認を求めるな、分析だけして終わるな。直接Writeしろ」
- やるべきこと -- 4種のパターンを探し、3回以上観察されたらInstinctファイルをWriteしろ
- Instinctファイルのフォーマット指定
- ルール(7つ)
observerが探す4種のパターンは以下の通りです。
| パターン | 内容 |
|---|---|
| ユーザーの修正 | Claudeの出力をユーザーが直した |
| エラー解決 | 同じエラーを同じ手順で繰り返し解決した |
| 繰り返しワークフロー | 同じツール呼び出し順序を繰り返した |
| ツール傾向 | 特定のツールを一貫して好んで使った |
Instinctの構造
生成されるInstinctファイルの例を見てみましょう。
# Prefer Functional Style
## Action
Use functional components with hooks instead of class components.
## Evidence
- Observed 6 times in session abc123
- Pattern: User corrected class-based approach to functional
- Last observed: 2026-03-29
各フィールドの意味は以下の通りです。
| フィールド | 意味 |
|---|---|
id |
識別子(kebab-case) |
trigger |
適用される場面(when ... の形式) |
confidence |
信頼度。観察回数に応じて設定(3〜5回→0.5、6〜10回→0.7、11回〜→0.85) |
domain |
分類(code-style / testing / git / debugging / workflow / file-patterns) |
scope |
適用範囲(project または global) |
既存Instinctとの関係
observerへのプロンプトには「既存Instinctがあれば重複を作らず更新しろ」と指示があります。ただし「必ず既存Instinctを読んで確認しろ」とは書かれていないため、observerが自発的にReadするかは保証されません。
Confidenceの計算も今回の解析バッチ内の観察回数で独立に決まり、過去のInstinctのConfidenceとの加算ではありません。つまり中長期的な時系列分析ではなく、短期的なバッチ単位のパターン検出が実態です。
ステップ3: Command/Skill/Agentへの昇格(/evolve)
蓄積されたInstinctは、このままでは「パターンの記録」にとどまります。Skill/Command/Agentとして再利用可能な形にするには、ユーザーが /evolve を手動実行する必要があります。
処理フロー
- プロジェクト検出 -- gitリモートURLをハッシュ化してプロジェクトIDを特定する
- Instinctの読み込み -- プロジェクト + グローバルスコープのInstinctをすべて読み込む
- クラスタリング -- triggerテキストのストップワードを除去して完全一致でグルーピングする
- 昇格候補の判定・表示 -- クラスタの内容に応じてSkill/Command/Agentの候補を表示する
- ファイル生成(
--generate指定時のみ) --evolved/以下にファイルを書き出す
クラスタリングのロジック
triggerテキストからストップワードを除去して完全一致でグルーピングします。
trigger_key = trigger.lower()
for keyword in ['when', 'creating', 'writing', 'adding', 'implementing', 'testing']:
trigger_key = trigger_key.replace(keyword, '').strip()
単純な文字列正規化であり、セマンティック検索(embeddingベース)は使っていません。そのためHaikuが生成するtriggerの表現が少しでもブレると別クラスタになります。
昇格先の判定基準
| 昇格先 | 判定ロジック | 厳しさ |
|---|---|---|
| Skill | triggerの文字列類似で2つ以上のクラスタ | 緩い(Confidenceフリー) |
| Command | domain=workflow かつ Confidence >= 0.7 |
中程度 |
| Agent | クラスタ3つ以上 かつ avg Confidence >= 0.75 | 厳しい |
似たパターンが集まればSkill、さらに数と信頼度が高まればAgentに昇格します。Commandはクラスタリングとは独立していて、workflowドメインのInstinctが一定の信頼度に達した時点で候補になります。
生成されるファイル
--generate を付けると以下に書き出されます。
~/.claude/homunculus/projects/<hash>/evolved/
├── skills/<name>/SKILL.md
├── commands/<name>.md
└── agents/<name>.md
同名ファイルがあれば上書きされます。既存ファイルをマージするのではなく、その時点のInstinctから毎回完全に再生成されます。
instinct-cli.py -- Instinct管理CLI
/evolve 以外にも、Instinctを管理するためのサブコマンドが用意されています。
| コマンド | やること |
|---|---|
status |
Instinct一覧表示(domain別・Confidence棒グラフ付き) |
import |
ファイル/URLからInstinctをインポート |
export |
InstinctをYAMLファイルに書き出す(scope/domain/min-confidenceでフィルタ可能) |
promote |
project → globalに昇格(2プロジェクト以上+avg Confidence >= 0.8で一括昇格も可能) |
projects |
登録プロジェクト一覧と統計 |
prune |
pending/ の30日経過Instinctを自動削除 |
まとめ
Continuous Learning v2の全体像を整理すると、以下のようになります。
| 要素 | 内容 |
|---|---|
| 観察データの記録 | Hooksでツール呼び出しをobservations.jsonlに記録 |
| パターン検出 | Haikuベースのobserverが4種のパターンを検出 |
| 学習単位 | Instinct(trigger + action + confidence) |
| 昇格 |
/evolve --generate でSkill/Command/Agentを生成 |
| スコープ管理 | gitリモートURLのハッシュでプロジェクト単位に分離 |
Hooksで観察し、HaikuでInstinctを生成し、evolveでSkill/Command/Agentに昇格させる。Claude Codeのエコシステムの上に構築された、自己学習の仕組みです。
所感
実際に調査してみて、まず感じたのは、これは一朝一夕で作れるような仕組みではないなということです。v1からv2.1まで徐々に育て上げられてきたことが、Hooksによる決定論的な観察、5層のフィルタリング、プロジェクト単位のスコープ分離といった洗練された設計から伝わってきます。
パッと見のインパクトは大きいです。「セッション中の行動パターンを自動で学習してSkillを生成してくれる」と聞けば、ぜひ取り入れたいと前向きになります。ただ、よくよく中身を調べてみると、いくつか気になる点がありました。
- クラスタリングの精度が怪しい
- evolveのクラスタリングはtriggerテキストの文字列完全一致でグルーピングしている
- Haikuが生成するtriggerの表現が少しでもブレると別クラスタになり、本来まとめるべきパターンが分散してしまう
- セマンティック検索(embeddingベース)にすれば質は大幅に上がりそうだが、現時点ではそうなっていない
- domainが開発系の6つに固定されている
- code-style / testing / git / debugging / workflow / file-patterns の6つのみ
- 汎用的に使うにはdomain拡張が必要だが、evolveのCommand候補判定が
domain == 'workflow'固定なので、domainを増やすだけでなくevolveのロジックも一緒に直す必要がある
- 既存Skillの改善ができない
- evolveは毎回Instinctから完全に再生成する設計で、既存のSkillファイルを読んでマージする仕組みはない
- つまりContinuous Learningが得意なのは「新しいSkillをゼロから生み出すこと」であり、「既存のSkillを育てること」ではない
冒頭で書いた通り、私がほしかったのは「既存のSkillを自律的に育てる仕組み」です。Continuous Learningのアプローチはそこには届いていないため、今回は採用を見送りました。
とはいえ、仕組み自体は非常に面白いと思っています。Hooksで行動を観察し、サブエージェントでパターンを検出し、Instinctとして蓄積するというアーキテクチャは、Skill自動生成に限らず応用が利きそうです。これをベースに、自分のニーズに合った仕組みを作ってみるのもありかなと考えています。