Compound Engineering:AIエージェントがバグから学ぶ方法
AIエージェントでメモリリークをデバッグし、その学びを蓄積して将来のエージェントが同じ過ちを繰り返さないようにする。
LLMエージェントには記憶がない。毎回のセッションはゼロからスタートする。今日Claudeにバグ修正を依頼しても、明日のセッションでは何を学んだか知る由もない。
Everyはcompound engineeringを開発した—エージェントとコーディングするためのワークフロー:brainstorm、plan、work、review、compound。「compound」ステップが重要で、問題を解決した後にドキュメント化して、将来のエージェントが参照できるようにする。
エージェントはそのドキュメントをどう見つけるか?これはコンテキストエンジニアリングだ—LLMに明示的に指し示す必要がある。私たちはCLAUDE.mdとAGENTS.mdに参照を追加している。これらはコンテキストウィンドウの早い段階で現れる。Vercelの研究もこれを裏付けている:情報が明示的に存在すれば、エージェントはそれを使う。何かを調べるかどうかを判断させると、しばしば調べない。
私たちはClaude CodeでEveryのcompound-engineering-pluginを使っている。実際のバグでどう機能したかを紹介する。
問題
長時間使用するとアプリが遅くなる。リフレッシュすると一時的に直る。典型的なリソースリーク。
Brainstormフェーズ
人間レベルの問いから始める:実際に何を解決しようとしているのか?症状は曖昧だった—「アプリが遅くなる」。レンダリングかもしれないし、ネットワークかもしれないし、メモリかもしれない。
話し合った:時間とともに遅くなる、リフレッシュで一時的に直る、異なるページで発生する。これは特定のコンポーネントのバグではなく、リソースの蓄積を示していた。
Planフェーズ
エージェントがコードベースと外部ドキュメントを調査した。問題は同期レイヤーにあった—Electric SQL、PostgresデータをServer-Sent Events(SSE)経由でクライアントにストリーミングする。
- Electric SQLのshapeサブスクリプションが永続的なSSE接続を作成していることを発見
- Electric SQLのドキュメントを検索—SSE接続は自動的に閉じない
- クリーンアップされないまま蓄積するリトライタイムアウトロジックを発見
重要な発見:ログイン、ナビゲート、ログアウト、再ログイン—元の接続は残ったまま。倍になる。
計画:すべてのコレクションのクリーンアップ関数を作成し、ログアウトに連結し、リトライ状態をクリアする。
Workフェーズ
計画が明確になれば、実装は簡潔:
export async function cleanupAllCollections(): Promise<void> {
await Promise.allSettled(
collections
.filter((c) => c !== null)
.map((c) => c.cleanup())
);
// シングルトンをリセットして次回アクセス時に新規作成
_quizzesCollection = null;
// ...
}
ログアウトに連結:
const logout = useCallback(async () => {
await cleanupAllCollections(); // SSE接続を閉じる
clearRetryState(); // 保留中のタイムアウトをキャンセル
queryClient.clear(); // キャッシュデータをクリア
await authClient.signOut();
}, [queryClient]);
Reviewフェーズ
複数のサブエージェントが異なる角度からコードを分析する:
- シンプルさ:これは最小限の修正か?過剰設計していないか?
- エッジケース:クリーンアップが途中で失敗したら?セッション期限切れは?
- パターン:コードベースの既存の規約に従っているか?
レビューで一つ指摘があった:一つのコレクションが失敗してもクリーンアップが継続するよう、Promise.allではなくPromise.allSettledが必要だった。
Compoundフェーズ
バグを修正した。次は、将来のエージェントが同じ調査を繰り返さないようにドキュメント化する。
- 何が起きたか:SSE接続が自動で閉じず、メモリが蓄積した
- どう修正したか:ログアウトに連結するクリーンアップ関数
- どう検証するか:DevToolsのNetworkタブで「EventStream」をフィルタ—ログアウトで接続が閉じるべき。ヒープスナップショットはベースラインに戻るべき。
- どう予防するか:新しいElectric SQL機能を追加する際のチェックリスト
そしてCLAUDE.mdとAGENTS.mdに参照を追加:
## Electric SQL
- Memory management: `docs/solutions/electric-sql-collection-cleanup.md`
- Prevention checklist: `docs/strategies/electric-sql-memory-leak-prevention.md`
次にエージェントがElectric SQLのコードに触れるとき、これらの参照を見て計画中にドキュメントを検索する。SSE接続のライフサイクルを理解している。ログアウト時のクリーンアップを確認することを知っている。すでに行った調査を繰り返さない。
なぜこれが機能するか
エージェントは毎回ゼロから始まるが、リポジトリはそうではない。学びはドキュメントに蓄積される。エージェント設定ファイルがそのドキュメントを指し示す。バグ修正のたびに、将来のエージェントが検索できる知識ベースが増える。
人間の開発者はこれを自然にやっている—過去のバグを覚えているか、古いメモを掘り起こす。LLMエージェントは記憶できない。だから記憶を構築してあげる必要がある。