Skillが10個を超えたあたりから、コードベースの雰囲気が変わります。状態管理が散らばり、エラー処理が場当たり的になり、設定がスキルごとにバラバラ。動くけど、触るのが怖い。
95個のSkillを運用してきた中で、この「脆さ」を潰すために効いたのは3つの設計パターンでした。型安全性、エラーハンドリング、スキル間連携。この記事ではコードベースから実例を抜き出しながら、それぞれの設計判断を掘り下げます。
この記事で学べること
- JSON契約による入出力の型保証パターン
- Auto-Retryプロトコルとフェーズ別エラーハンドリング
- 3層コンフィグマージとパイプライン設計によるスキル間連携
前提:入門編の知識
Skillsの設計思想と自作ガイド(入門編)の内容を前提にしています。SKILL.md、scripts/、references/ の構造が頭に入っていればOKです。
パターン1:JSON契約で入出力を型保証する
なぜ型が要るのか
Skillのスクリプトが返す値を、呼び出し側のSKILL.mdが正しく解釈できるか。これ、祈りで成り立っているプロジェクトが多いです。
「スクリプトの出力が変わったのに、SKILL.mdがまだ旧フォーマットを期待してる」。これ、発生頻度が高い割に原因特定に時間がかかります。
構造化エラー出力パターン
スクリプトがエラーで落ちたとき、exit codeだけだと「何がどう壊れたか」がわかりません。人間ならログを読めますが、AIエージェントはJSON構造でないと解析できない。
そこで、すべてのエラー出力をJSON形式に統一します。私たちの実装では die_json というヘルパー関数を共通ライブラリに置いています:
die_json() {
local msg="$1"
local code="${2:-1}"
# json_str: 文字列をJSONセーフにエスケープするヘルパー
echo "{\"status\":\"error\",\"error\":$(json_str "$msg"),\"exit_code\":$code}" >&2
exit "$code"
}
これでSKILL.md側は、スクリプトの成功・失敗を出力フォーマットで判定できます。「exit codeだけ見ても何が起きたかわからない」問題を根本から解消。
状態ファイルで入出力の契約を結ぶ
各スキルの状態管理スクリプトは、初期化時にJSONスキーマを確定します。audit-state.sh の init コマンドの例:
jq -n \
--arg target "$target" \
--arg scope "$scope" \
--arg focus "$focus" \
--argjson auditors "$auditors" \
'{
version: "1.0.0",
target: $target,
scope: $scope,
focus: $focus,
status: "initializing",
auditors: $auditors,
findings: [],
hotspots: [],
action_plan: []
}' > "$STATE_FILE"
ポイントは3つ:
| 設計判断 | 理由 |
|---|---|
version フィールド | スキーマ変更時の後方互換を保証 |
jq -n で生成 | 手書きJSONのタイポを排除 |
| enum バリデーション | case 文で入力値を検証してから書き込み |
bug-hunt の仮説カテゴリ検証も同じパターンです:
VALID_CATEGORIES="logic state external environment"
# カテゴリが有効かチェック(typoで1時間溶かした経験から追加)
local valid=false
for v in $VALID_CATEGORIES; do
[[ "$HYP_CATEGORY" == "$v" ]] && valid=true && break
done
[[ "$valid" == true ]] || die_json "Invalid category: $HYP_CATEGORY. Must be: $VALID_CATEGORIES"
状態ファイルで並列処理の契約を結ぶ
並列タスクの状態を1つのJSONファイルで管理し、書き込みを1プロセスに制限する設計パターンです。
たとえば、大きな機能を複数のサブタスクに分割して並列実行するケースを考えます。「認証モジュール」「API層」「テスト」をそれぞれ別のエージェントが担当する場合、誰がどのファイルを触っていいかを事前に決めないとコンフリクトします。
私たちの実装では、タスク分解スキルが flow.json という状態ファイルを生成し、各サブタスクのスコープを定義します:
{
"status": "decomposed",
"subtasks": [
{
"id": "task1",
"status": "pending",
"worktree": "/path/to/worktree",
"scope": { "files": ["src/api/auth.ts"], "description": "認証モジュール" }
}
],
"integration": { "strategy": "merge", "test_command": "npm test" }
}
実行スキルは --task-id と --flow-state を受け取り、状態ファイルから自分のスコープだけを読み出します。サブタスク間でファイルが重複しないことを契約として保証しているため、並列実行してもコンフリクトしません。
もうひとつ重要なのが、書き込み権限の制限です。状態ファイルの読み取りと書き込みを別のスクリプトに分離し、書き込みはオーケストレーター(全体を管理する親プロセス)だけが行います。並列で動くサブタスクが直接状態ファイルを更新するのはご法度。この「単一ライター設計」により、競合状態を構造的に防ぎます。
パターン2:フェーズ別Auto-Retryプロトコル
「盲目的リトライ」は害悪
スクリプトが失敗したとき、同じコマンドをもう一回叩く。これ、最悪のリカバリ戦略です。同じ入力で同じ失敗が再現するだけ。
私たちのAuto-Retryプロトコルはエラー分析を必須にしています:
失敗 → エラー分析 → 修正して再実行 (1回目)
→ まだ失敗 → 別アプローチで再実行 (2回目)
→ まだ失敗 → journal記録 → pause for intervention
フェーズの性質でリトライ戦略を変える
私たちの開発フローは8フェーズで構成されています:
- 環境構築 — worktree作成・依存インストール
- Issue分析 — 要件抽出・受入条件の確定
- 実装計画 — 設計方針・ファイル構成の決定
- 実装 — コーディング
- 検証 — テスト実行・品質チェック
- 評価 — 独立エージェントによる品質判定
- コミット — 変更の記録
- PR作成 — レビュー依頼
フェーズの性質に応じて、リトライ戦略を変えます:
| フェーズ | 失敗時の対応 | 最大リトライ | 理由 |
|---|---|---|---|
| 1-2 (環境構築・分析) | 即abort | 0 | 基盤が壊れてたら先に進んでも無駄 |
| 3 (実装計画) | エラー分析 → コンテキスト付きリトライ | 2 | 要件の曖昧さが原因なら再解釈で通ることが多い |
| 4 (実装) | エラー分析 → フィードバック付きリトライ | 2 | コンパイルエラーなら修正して再挑戦 |
| 5 (検証) | --fix 付きリトライ | 2 | テスト失敗は自動修正できるケースが多い |
| 6 (評価) | 1回リトライ → スキップ | 1 | サブプロセス起動の失敗は環境要因。品質は後のPRレビューで担保 |
| 7-8 (コミット・PR) | 1回リトライ → 手動コマンド報告 | 1 | git操作の失敗は人間の判断が必要 |
設計の勘所: リトライ回数は「そのフェーズの失敗が自動修正可能か」で決めています。実装フェーズは自動修正の余地が大きいから2回。git操作は人間の判断が要るから1回。
Evaluate-Retry Loop:品質ゲートの設計
Phase 6では、実装者とは別の視点で品質を判定する独立した評価エージェントが動きます。「自分で書いたコードを自分でレビューしても甘くなる」のと同じ理屈で、実装と評価を分離しています。評価結果に応じてどこまで戻るかを判断:
Phase 6 verdict:
"pass" → Phase 7 へ進む
"fail" + design → Phase 3 に戻る(設計からやり直し)
"fail" + implement → Phase 4 に戻る(実装だけやり直し)
「設計が悪いのか、実装が悪いのか」をAIが判断してくれるので、手戻りの粒度が最適化されます。最大5イテレーションでリミット。無限ループ回避は大事です(人間もAIも体力は有限)。
journal記録でリトライを学習資産に
すべてのリトライはjournalに記録されます:
$SKILLS_DIR/skill-retrospective/scripts/journal.sh log dev-kickoff partial \
--issue $ISSUE \
--error-category <category> \
--error-msg "<message>" \
--recovery "<what was done>" \
--recovery-turns $N \
--worktree $WORKTREE
skill-retrospective が定期的にjournalを分析し、頻出するエラーパターンを検出します。「Phase 4でTypeScriptの型エラーが多発している」とわかれば、dev-implementのプロンプトに型チェックの事前確認を追加する、といった自動改善ループが回ります。このサイクルについては失敗から学ぶAIエージェント - skill-retrospectiveの仕組みで詳しく解説しています。
パターン3:スキル間連携のアーキテクチャ
スキルが増えると「設定の統一」「直列連携」「並列協調」の3つの課題が出てきます。順番に見ていきます。
3層コンフィグマージ
95個のスキルが個別の設定ファイルを持つのは管理が破綻します。私たちは3層マージ方式で解決しました:
スキル内蔵デフォルト ← グローバル config ← プロジェクト skill-config.json
(ユーザー共通) (プロジェクト固有: 最優先)
_lib/common.sh の load_skill_config() が自動的にマージします:
load_skill_config() {
local skill_name="$1"
# Layer 1: グローバル設定
local global_cfg
global_cfg="$(_load_global_skill_config "$skill_name")"
# Layer 2: プロジェクト設定
local project_cfg="{}"
# ... プロジェクトのskill-config.jsonを検索 ...
# deep merge: プロジェクトが勝つ
echo "$global_cfg" | jq --argjson proj "$project_cfg" '. * $proj'
}
具体例:SNS投稿のデフォルト言語はグローバルで "ja" に設定し、特定プロジェクトだけ "en" に上書き。スキル側のコードは何も変えなくてOK。
// ~/.config/skills/config.json(グローバル)
{ "sns-announce": { "default_lang": "ja" } }
// <project>/skill-config.json(プロジェクト)
{ "sns-announce": { "default_lang": "en", "base_url": "https://example.com" } }
// → マージ結果
{ "sns-announce": { "default_lang": "en", "base_url": "https://example.com" } }
パイプライン設計:スキルの直列連携
SEO記事企画パイプラインは、3つのスキルが前段の出力を後段の入力として受け取る設計です:
ga-analyzer → trends-analyzer → seo-content-planner
↓ ↓ ↓
GA4レポート トレンド分析 記事提案レポート
(JSON) (JSON) (Markdown + JSON)
各スキルの出力JSONが次のスキルの入力になります。SEO企画パイプラインの記事で全体像を解説していますが、ここで注目したいのはスキル間の結合度の低さです。
trends-analyzer は ga-analyzer の出力JSONからキーワードを自動抽出しますが、手動でキーワードを渡しても動きます。パイプラインに組み込めるけど、単体でも完結する。この独立性が、スキルの再利用性を支えています。
Agent Teamパターン:並列協調の設計
複数のAIエージェントが並列で動くとき、お互いの発見を共有する仕組みが必要です。Agent Teamの記事で全体像を解説していますが、ここではスキル設計の観点から状態管理パターンを見ます。
考え方はシンプルです。「共有の状態ファイルに構造化データを追記する → 同じ箇所への複数指摘をクロスリファレンスする」。
たとえばコード監査で、セキュリティ担当・パフォーマンス担当・アーキテクチャ担当の3エージェントが並列に動くケース:
# セキュリティ担当がSQLインジェクションを発見 → 状態ファイルに追記
audit-state.sh add-finding \
--domain security --severity critical \
--location "src/api/users.ts:42" \
--title "SQLインジェクション脆弱性"
# 他の担当にクロスドメイン調査を依頼(SendMessage)
# 同じファイルに複数ドメインの指摘がある「ホットスポット」を自動検出
audit-state.sh detect-hotspots
ポイントは、同じ箇所にセキュリティ問題とパフォーマンス問題の両方が見つかった場合、単独の問題より優先度を上げること。「なぜクロスドメイン検出が有効か」というと、1つの箇所に複数の観点から問題がある場合、根本的な設計ミスの可能性が高いからです。この優先度判断を、状態ファイルのデータ構造だけで自動化しています。
補足:SKILL.mdを軽量に保つ
スキルが複雑になると、SKILL.mdが肥大化してコンテキストウィンドウを圧迫します。SKILL.mdのdescriptionはスキル一覧表示時に常にロードされるため、ここが大きいと他の処理に使えるコンテキストが減ります。
解決策は references/ への委譲です:
my-skill/
├── SKILL.md # 80行(ワークフロー概要のみ)
└── references/
├── safety-guards.md # ガード条件の詳細
├── strategy.md # 戦略の詳細
└── template.md # テンプレート
5スキルでこのリファクタリングを実施した結果、SKILL.mdの合計が 1,236行 → 650行(47%削減) に。ワークフロー制御ロジックはSKILL.mdに残し、テンプレートや詳細仕様だけを分離します。Claudeは必要なときだけreferences/を参照するので、普段のコンテキスト消費が激減します(メモリの節約は地球にやさしい...かもしれない)。
3パターンの組み合わせ:dev-flowの全体像
ここまでの3パターンが dev-flow でどう組み合わさるか、整理します:
dev-flow (オーケストレーター)
│
├── [型安全性] flow.json で並列サブタスクのスコープを契約
├── [エラーハンドリング] フェーズ別のAuto-Retryプロトコル
├── [スキル間連携] 3層コンフィグで環境差分を吸収
│
├── dev-decompose → flow.json 生成
├── dev-kickoff x N → 並列実行、flow-update.sh で状態同期
├── dev-integrate → マージ、コンフリクトの自動解決パターン
└── pr-iterate → LGTMまで改善ループ
各サブスキルは単体でも動くし、dev-flowから呼ばれても動く。この柔軟性を支えているのが、JSON契約・Auto-Retry・コンフィグマージの3パターンです。
まとめ
95スキルの運用から蒸留した3つの設計パターンを紹介しました。
- JSON契約: 構造化エラー出力で失敗理由を明確に、状態ファイルで入出力のスキーマを固定、並列処理のスコープを契約で保証
- フェーズ別Auto-Retry: 盲目的リトライを禁止し、エラー分析を挟む。フェーズの性質に応じてリトライ回数を変える
- スキル間連携: 3層コンフィグマージで設定を統一、パイプラインで直列連携、Agent Teamで並列協調
入門編で「動くSkill」を作れたら、次はこれらのパターンで「壊れにくいSkill」にする。結局のところ、2ヶ月後の自分が安心してメンテできる設計が一番の生産性向上です。
Claude Code Skills設計 完全ガイド — この記事を含む7本の記事で、Skills設計・オーケストレーション・状態管理を体系的に解説しています。



