Files
analysis_claude_code/s07_skill_loading/README.ja.md
gui-yue 1baf1aca5a Follow up PR #265: refine chapters, diagrams, and add S20 (#283)
* feat: s01-s14 docs quality overhaul — tool pipeline, single-agent, knowledge & resilience

Rewrite code.py and README (zh/en/ja) for s01-s14, each chapter building
incrementally on the previous. Key fixes across chapters:

- s01-s04: agent loop, tool dispatch, permission pipeline, hooks
- s05-s08: todo write, subagent, skill loading, context compact
- s09-s11: memory system, system prompt assembly, error recovery
- s12-s14: task graph, background tasks, cron scheduler

All chapters CC source-verified. Code inherits fixes forward (PROMPT_SECTIONS,
json.dumps cache, real-state context, can_start dep protection, etc.).

* feat: s15-s19 docs quality overhaul — multi-agent platform: teams, protocols, autonomy, worktree, MCP tools

Rewrite code.py and README (zh/en/ja) for s15-s19, the multi-agent platform
chapters. Each chapter inherits all previous fixes and adds one mechanism:

- s15: agent teams (TeamCreate, teammate threads, shared task list)
- s16: team protocols (plan approval, shutdown handshake, consume_inbox)
- s17: autonomous agents (idle polling, auto-claim, consume_lead_inbox)
- s18: worktree isolation (git worktree, bind_task, cwd switching, safety)
- s19: MCP tools (MCPClient, normalize_mcp_name, assemble_tool_pool, no cache)

All appendix source code references verified against CC source. Config priority
corrected: claude.ai < plugin < user < project < local.

* fix: 5 regressions across s05-s19 — glob safety, todo validation, memory extraction, protocol types, dep crash

- s05-s09: glob results now filter with is_relative_to(WORKDIR) (inherited from s02)
- s06-s08: todo_write validates content/status required fields (inherited from s05)
- s09: extract_memories uses pre-compression snapshot instead of compacted messages
- s16: submit_plan docstring clarifies protocol-only (not code-level gate)
- s17-s19: match_response restores type mismatch validation (from s16)
- s17-s19: claim_task deps list handles missing dep files without crashing

* fix: s12 Todo V2 logic reversal, s14/s15 cron range validation, s18/s19 worktree name validation

- s12 README (zh/en/ja): fix Todo V2 direction — interactive defaults to Task,
  non-interactive/SDK defaults to TodoWrite. Fix env var name to
  CLAUDE_CODE_ENABLE_TASKS (not TODO_V2).
- s14/s15: add _validate_cron_field with per-field range checks (minute 0-59,
  hour 0-23, dom 1-31, month 1-12, dow 0-6), step > 0, range lo <= hi.
  Replace old try/except validation that only caught exceptions.
- s18/s19: add validate_worktree_name() to remove_worktree and keep_worktree,
  not just create_worktree.

* fix: align s16-s19 teaching tool consistency

* fix pr265 chapter diagrams

* Add comprehensive s20 harness chapter

* Fix chapter smoke test regressions

* Clarify README tutorial track transition

---------

Co-authored-by: Haoran <bill-billion@outlook.com>
2026-05-20 21:45:38 +08:00

10 KiB
Raw Blame History

s07: Skill Loading — 必要なときにだけ読み込む

中文 · English · 日本語

s01 → s02 → s03 → s04 → s05 → s06 → s07s08 → s09 → ... → s20

"Load when needed, don't stuff the prompt" — tool_result で注入、system prompt には詰め込まない。

Harness レイヤー: 知識 — 必要に応じて読み込み、コンテキストに詰め込まない。


課題

プロジェクトには React コンポーネント仕様、SQL スタイルガイド、API 設計ドキュメントがある。Agent にこれらの仕様を自動的に守らせたい。最も直接的な方法 — すべて system prompt に詰め込む:

SYSTEM = (
    f"You are a coding agent. "
    + open("docs/react-style.md").read()       # 2000 行
    + open("docs/sql-style.md").read()         # 1500 行
    + open("docs/api-design.md").read()        # 3000 行
)

6500 行の system prompt。Agent は LLM を呼び出すたびにこれらのドキュメントを運ぶ — CSS の色を変えるときも SQL クエリを修正するときも。99% の内容が現在のタスクと無関係で、トークンを無駄に消費する。


ソリューション

Skill Overview

前章の最小フック構造、todo_write、サブ Agent を維持し、本章は新規の load_skill ツールに注目する。起動時にスキルカタログを SYSTEM prompt に注入し、実行時に完全な内容を読み込むツールを登録する。使ったときだけトークンを消費。

2 層設計:

場所 タイミング コスト
1. カタログ system prompt 起動時に注入harness が skills/ をスキャン) ~100 トークン/スキル、毎ターン携帯
2. 内容 tool_result Agent が load_skill を呼び出したとき ~2000 トークン/スキル、オンデマンド

ディスパッチ機構は変わらず、load_skillTOOL_HANDLERS[block.name] を通じて自動的にディスパッチされる。


仕組み

skills/ ディレクトリ、スキルごとに 1 つのサブディレクトリ、それぞれに SKILL.md ファイルを含む:

skills/
  agent-builder/SKILL.md
  code-review/SKILL.md
  mcp-builder/SKILL.md
  pdf/SKILL.md

第 1 層:起動時にカタログを注入harness は起動時に _scan_skills() を呼び出して skills/ ディレクトリをスキャンし、各 SKILL.md の YAML frontmatternamedescription)を解析して SKILL_REGISTRY 辞書に格納する。list_skills() はレジストリからカタログを生成し、SYSTEM prompt に注入する。Agent は毎ターン「どのスキルが利用可能か」を確認できる。追加の API 呼び出しは不要:

SKILL_REGISTRY: dict[str, dict] = {}

def _scan_skills():
    if not SKILLS_DIR.exists():
        return
    for d in sorted(SKILLS_DIR.iterdir()):
        if not d.is_dir():
            continue
        manifest = d / "SKILL.md"
        if manifest.exists():
            raw = manifest.read_text()
            meta, body = _parse_frontmatter(raw)
            name = meta.get("name", d.name)
            desc = meta.get("description", raw.split("\n")[0].lstrip("#").strip())
            SKILL_REGISTRY[name] = {"name": name, "description": desc, "content": raw}

_scan_skills()  # runs once at startup

def list_skills() -> str:
    return "\n".join(f"- **{s['name']}**: {s['description']}" for s in SKILL_REGISTRY.values())

def build_system() -> str:
    catalog = list_skills()
    return (
        f"You are a coding agent at {WORKDIR}. "
        f"Skills available:\n{catalog}\n"
        "Use load_skill to get full details when needed."
    )

SYSTEM = build_system()

第 2 層load_skillAgent が「SQL スタイルガイドが必要」と判断し、load_skill("sql-style") を呼び出す。レジストリを通じて検索し、ファイルパスを経由しないため、パストラバーサルのリスクがない。内容は tool_result を通じて注入される:

def load_skill(name: str) -> str:
    skill = SKILL_REGISTRY.get(name)
    if not skill:
        return f"Skill not found: {name}"
    return skill["content"]

重要な違い:スキル内容は system prompt の一部ではなく、ツール結果として現在の messages に入る。後続の呼び出しでは履歴とともに携帯され、コンテキスト圧縮、切り捨て、またはセッション終了まで保持される。これは s08 の compact と自然に接続するオンデマンド読み込みで「運ぶべきでないものは運ばない」を解決し、compact が「捨てるべきものをどう捨てるか」を解決する。


s06 からの変更点

コンポーネント 変更前 (s06) 変更後 (s07)
ツール数 7 (bash, read, write, edit, glob, todo_write, task) 8 (+load_skill)
知識読み込み なし 2 層:起動時カタログ注入 SYSTEM + 実行時 load_skill
SYSTEM プロンプト 静的文字列 起動時に skills/ をスキャンしてカタログ注入
スキルレジストリ なし SKILL_REGISTRY起動時に充填、パストラバーサル防止
ループ 変更なし 変更なし(スキルツールは自動ディスパッチ)

試してみよう

cd learn-claude-code
python s07_skill_loading/code.py

以下のプロンプトを試してみよう:

  1. What skills are available?
  2. Load the code-review skill and follow its instructions
  3. I need to do a code review -- load the relevant skill first

観察のポイントAgent は SYSTEM 内のカタログから利用可能なスキルを知っているか? 完全な手順が必要なときに [HOOK] load_skill が表示されるか? 読み込んだスキルの説明を使って回答しているか?


次へ

オンデマンド読み込みで「運ぶべきでないものは運ばない」問題は解決した。しかし別の問題が待っているAgent が 30 分連続で作業すると、messages リストが中間プロセスで埋め尽くされる。古い tool_result、期限切れのファイル内容、コンテキストを占領しているが価値を生まない。

→ s08 Context Compact4 層圧縮戦略。安価な層を先に実行、高価な層を後に実行。

CC ソースコードを深掘り

以下は CC ソースコード loadSkillsDir.tsSkillTool.tsbundledSkills.tscommands.ts の分析に基づく。

一、スキルソースskills/ ディレクトリだけではない

教育版はすべてのスキルが skills/ ディレクトリにあると想定している。CC は実際に複数のファイルに分散したソースから読み込む:loadSkillsDir.ts は user/project/--add-dir ディレクトリと legacy commands.claude/commands/)を担当、bundledSkills.ts は組み込みスキル、SkillTool.ts は MCP リモートスキル、commands.ts はコマンド集約を担当。タイプには managed/policy skills、user skills~/.claude/skills/、project skills.claude/skills/)、--add-dir skills、legacy commands、dynamic skills、conditional skillspaths frontmatter を持ち、ファイルパスでアクティベート、bundled skills、plugin skills、MCP skills が含まれる。

二、SKILL.md Frontmatter の一般的なフィールド

CC の SKILL.md YAML frontmatter は parseSkillFrontmatterFields()loadSkillsDir.ts)で解析される。一般的なフィールド:

フィールド 用途
name / description 表示名と説明
when_to_use モデルにいつ呼び出すかを指導
allowed-tools スキルが使用可能なツールの自動許可リスト
context inline(デフォルト)または fork(サブ Agent として実行)
model モデルオーバーライドhaiku/sonnet/opus/inherit
hooks スキルレベルのフック設定
paths 条件付きアクティベーションの glob パターン
user-invocable ユーザーが /name で呼び出し可能

完全なフィールドリストはバージョンによって変動する。上記は教育版に関連するコアフィールドのみ。

三、2 層読み込みの正確な実装

  1. カタログ(起動時)getSkillDirCommands() がディレクトリをスキャン → メタデータのみを含む Command オブジェクトとして登録。getSkillListingAttachments() がスキルリストを添付ファイルとしてフォーマット、コンテキストウィンドウの ~1% を予算とする(上限 8000 文字)。
  2. 読み込み(呼び出し時):モデルが Skill ツールを呼び出す(入力フィールドは skill + オプションの args、教育版は name を使用)→ getPromptForCommand() が完全な SKILL.md 内容を展開 → SkillTool が返す tool_result の表示テキストは "Launching skill: {name}" のみ、実際のスキル内容は newMessages を通じて注入される。教育版では両者を「tool_result を通じて注入」として簡略化している。

教育版の単純化は意図的

  • 複数ファイル・複数ソース → 1 つの skills/ ディレクトリ2 層読み込みの核心概念を示すのに十分
  • 複数の frontmatter フィールド → name/description のみ解析:解析の複雑さを削減
  • forked skillscontext: 'fork')→ 省略:教学版では inline skill loading のみ展開する
  • Skill ツールの入力 skill+args → 教育版は name を使用:追加の引数解析の複雑さを回避