スキルを作れば作るほど、壊れるスキルが増えた
Claude Codeのスキルを10個ほど作ってきた。記事投稿、壁打ち、ツイート生成、情報収集、画像生成。最初は「スキルさえ作ればAIが全部やってくれる」と思っていた。
現実は違った。同じスキルを実行しても、毎回微妙に違う結果が返ってくる。フロントマターの変換で余計なフィールドが入ったり、ファイルの保存先が変わったり。AIが「良かれと思って」やった判断が、再現性を壊す。
この問題に何度もハマった末にたどり着いた原則がある。
「一つのスキルの中で、決定論的な処理と非決定論的な処理を見極めろ。決定論的な部分はシェルスクリプトに逃がせ。」
決定論的 / 非決定論的とは何か
ここでの定義はシンプルだ。
- 決定論的: 同じ入力に対して、毎回同じ出力を返すべき処理
- 非決定論的: 文脈や状況によって判断が変わる処理
例を挙げる。
| 処理 | 性質 | 理由 |
|---|---|---|
| Zennのフロントマターをqiita形式に変換する | 決定論的 |
topics: [A, B] → tags: [{name: A}, {name: B}] は機械的な変換 |
| 記事のタイトルを考える | 非決定論的 | 読者層・文脈・トーンによって最適解が変わる |
published: true → ignorePublish: false の変換 |
決定論的 | 真偽値の反転。判断の余地がない |
| 記事の構成を決める | 非決定論的 | テーマの深さ、読者の前提知識に依存する |
この区別は直感的にわかるはずだ。問題は、スキルを作るときにこの2つを混ぜてしまうことにある。
混ぜるとなぜ壊れるか
自分が作った「記事投稿スキル(article-publish)」を例にする。
このスキルの仕事は、Zenn形式で書いた記事をQiita形式に変換して公開することだ。やりたいことを分解するとこうなる:
- Zennのフロントマター(
topics,published,emoji)をQiitaのフロントマター(tags,ignorePublish)に変換する - 変換したファイルを
public/に配置する - git pushでGitHub Actionsを発火させる
1〜3はすべて決定論的だ。入力が同じなら出力は常に同じであるべきだし、途中でAIが「このタグ構成より、こっちの方がいいかも」と判断する余地はない。
では、これをSKILL.mdにテキストで書いたらどうなるか。
## 変換ルール
- topics配列をtags形式に変換してください
- publishedの値を反転してignorePublishに設定してください
- タグは最大5個、初心者タグを含めてください
AIはこの指示を「だいたい」守る。だが「だいたい」では困る。タグの順序が変わる。クォートの有無が揺れる。余計な属性が追加される。毎回同じ結果を出すべき処理をAIの解釈に委ねているのが問題だ。
シェルに逃がすとどうなるか
同じ処理をシェルスクリプトにするとこうなる(実際のコードを簡略化したもの):
# topics配列 → Qiita tags形式に変換(最大5タグに制限)
TOPICS=$(echo "$FRONTMATTER" | grep '^topics:' | sed 's/^topics: *//')
CLEANED=$(echo "$TOPICS" | tr -d '[]"' | tr ',' '\n' | sed 's/^ *//;s/ *$//')
while IFS= read -r tag; do
[[ -z "$tag" ]] && continue
TAG_LIST+=("$tag")
done <<< "$CLEANED"
# published → ignorePublish(反転)
if [[ "$PUBLISHED" == "true" ]]; then
IGNORE_PUBLISH="false"
else
IGNORE_PUBLISH="true"
fi
このスクリプトは何度実行しても同じ結果を返す。AIの気分に左右されない。テストもできる。デバッグも楽だ。
決定論的な処理をシェルに逃がした瞬間、スキルの信頼性が跳ね上がる。
では、スキル側(SKILL.md)には何を書くのか
シェルに逃がせない処理がある。それが非決定論的な部分だ。
article-publishスキルのSKILL.mdには、こういうことが書いてある:
- 投稿時間の戦略: 「平日朝8:00が最適。月曜7〜8時がいいね獲得率が高い」— これはデータに基づく判断指針であり、状況に応じて変わりうる
- 画像の使い分け: 「Mermaidで済むものはMermaidで書く。図解が必要ならimage-gen-codeを使う」— 何が適切かは記事の内容次第
- トーン: 「フラットな語り口。先輩エンジニアが話す感じ」— AIが文脈に応じて調整すべき領域
これらは「毎回同じ結果を出す」必要がない。むしろ文脈に応じた判断をAIに任せた方がいい。SKILL.mdは、その判断のためのリファレンス(参照情報)を提供する場所だ。
公式ドキュメントも同じことを言っている
Anthropicの公式スキル設計ガイドでは、「自由度の設定(degrees of freedom)」という概念でこの考え方を体系化している。
| 自由度 | 用途 | 具体例 |
|---|---|---|
| 低い(シェルスクリプト) | 操作が壊れやすく、一貫性が重要 | データベースマイグレーション、フォーマット変換 |
| 中程度(パラメータ付きスクリプト) | パターンはあるが設定で挙動が変わる | レポート生成、テンプレート適用 |
| 高い(テキスト指示) | 複数のアプローチが有効で、文脈判断が必要 | コードレビュー、構成の提案 |
さらに公式は明確にこう述べている:
"Prefer scripts for deterministic operations"(決定論的な操作にはスクリプトを優先せよ)
これは「シェルでできることはシェルでやれ」と同義だ。公式がスキルのベストプラクティスとして、決定論的な処理をスクリプトに分離することを推奨している。
また、公式は自由度の概念をわかりやすい比喩で説明している:
- 崖に挟まれた狭い橋: 安全な道は一つしかない。具体的なガードレールと正確な指示を出す(低い自由度)
- 障害物のない広い野原: 多くの道が成功に繋がる。大まかな方向を示してAIに任せる(高い自由度)
まとめ: スキル設計の判断フロー
スキルの中で「これをどう実装するか」迷ったとき、この判断フローが使える:
この処理は、同じ入力に対して毎回同じ出力を返すべきか?
│
├─ YES → シェルスクリプトに書く
│ 理由: 再現性が保証される。テスト可能。デバッグしやすい
│
└─ NO → SKILL.md / リファレンスに書く
理由: 文脈依存の判断はAIの得意分野。参照情報を充実させる
これだけだ。シンプルだが、これを意識するだけでスキルの信頼性は大きく変わる。
シェルに逃がすもう一つの利点は、テストの粒度が変わることだ。スキル全体をE2Eで動かして確認するのは重い。だがシェルスクリプトなら、そのスクリプト単体で入力を与えて出力を検証できる。バグが出たときも「スキルの指示が悪いのか、スクリプトのロジックが悪いのか」を切り分けられる。決定論的な処理をシェルに分離することは、テスタビリティの確保でもある。
シェルでできることは、シェルでやれ。AIには、AIにしかできない判断を任せろ。