mirror of
https://github.com/shareAI-lab/analysis_claude_code.git
synced 2026-09-20 12:13:38 +08:00
refactor: streamline the course to 17 lessons
This commit is contained in:
@@ -1,17 +1,18 @@
|
||||
# s07: Skill Loading — 必要なときにだけ読み込む
|
||||
# s07: Skill Loading — 必要なときにスキルを読み込む
|
||||
|
||||
[English](README.md) · [中文](README.zh.md) · [日本語](README.ja.md)
|
||||
|
||||
s01 → s02 → s03 → s04 → s05 → s06 → `s07` → [s08](../s08_context_compact/) → s09 → ... → s18 → s19
|
||||
> *"Load when needed, don't stuff the prompt"* — tool_result で注入、system prompt には詰め込まない。
|
||||
s01 → s02 → s03 → s04 → s05 → s06 → `s07` → [s08](../s08_context_compact/) → s09 → ... → s16 → s17
|
||||
|
||||
> system prompt にはスキルカタログを入れ、`load_skill` は完全な `SKILL.md` を返す。
|
||||
>
|
||||
> **Harness レイヤー**: 知識 — 必要に応じて読み込み、コンテキストに詰め込まない。
|
||||
> **Harness レイヤー**:知識の読み込み — 利用可能なスキルをモデルに示し、名前で内容を読み込む。
|
||||
|
||||
---
|
||||
|
||||
## 課題
|
||||
|
||||
あるプロジェクトに React コンポーネント仕様、SQL スタイルガイド、API 設計ドキュメントがあるとする。開発中、Agent にこれらの規約を守らせたい。最も直接的な方法は、すべてを system prompt に入れることだ:
|
||||
あるプロジェクトに React コンポーネント仕様、SQL スタイルガイド、API 設計ドキュメントがあるとする。開発中に Agent へこれらの規約を守らせたい場合、最も直接的な方法は、すべてを system prompt に入れることだ:
|
||||
|
||||
```python
|
||||
SYSTEM = (
|
||||
@@ -22,7 +23,7 @@ SYSTEM = (
|
||||
)
|
||||
```
|
||||
|
||||
これで LLM を呼び出すたびに 3 つの文書すべてが渡される。現在のタスクで使うのが 1 つだけでも、残りの 2 つがコンテキストを占める。
|
||||
この方法で Agent はすべての規約を読めるが、3 つの文書すべてが system prompt に固定され、現在のタスクに必要な文書だけを選べない。LLM を呼び出すたびに、3 つの文書の全文がモデルへ送られる。タスクが React コンポーネントの変更だけなら、必要なのは React コンポーネント仕様だけである。無関係な SQL スタイルガイドと API 設計ドキュメントも入力 token とコンテキストウィンドウを使うため、コード、会話、tool result に使える領域が減る。
|
||||
|
||||
---
|
||||
|
||||
@@ -30,24 +31,20 @@ SYSTEM = (
|
||||
|
||||

|
||||
|
||||
前章の最小フック構造、`todo_write`、サブ Agent を維持し、本章は新規の `load_skill` ツールに注目する。起動時にスキルカタログを SYSTEM prompt に注入し、実行時に完全な内容を読み込むツールを登録する。使ったときだけトークンを消費。
|
||||
起動時に `SkillLoader` が `skills/*/SKILL.md` を走査し、YAML frontmatter の `name` と `description` を読み取って、カタログを system prompt に追加する。完全な指示が必要になると、モデルは `load_skill(name)` を呼ぶ。返された `SKILL.md` は `tool_result` としてメッセージリストへ追加される。
|
||||
|
||||
2 層設計:
|
||||
|
||||
| 層 | 場所 | タイミング | コスト |
|
||||
|---|------|-----------|--------|
|
||||
| 1. カタログ | system prompt | 起動時に注入(harness が skills/ をスキャン) | ~100 トークン/スキル、毎ターン携帯 |
|
||||
| 2. 内容 | tool_result | Agent が load_skill を呼び出したとき。SKILL.md は、必要に応じて read_file/bash で追加リソースへアクセスするための手がかりになる | ~2000 トークン/スキル、オンデマンド |
|
||||
|
||||
ディスパッチ機構は変わらず、`load_skill` は `TOOL_HANDLERS[block.name]` を通じて自動的にディスパッチされる。
|
||||
| 内容 | モデル入力での位置 | 追加時点 |
|
||||
|------|--------------------|----------|
|
||||
| スキル名と説明 | system prompt | 起動時 |
|
||||
| 完全な `SKILL.md` | `tool_result` | `load_skill` 呼び出し時 |
|
||||
|
||||
---
|
||||
|
||||
## 仕組み
|
||||
|
||||
**skills/ ディレクトリ**、スキルごとに 1 つのサブディレクトリ、それぞれに `SKILL.md` ファイルを含む:
|
||||
各スキルは `SKILL.md` を持つディレクトリである:
|
||||
|
||||
```
|
||||
```text
|
||||
skills/
|
||||
agent-builder/SKILL.md
|
||||
code-review/SKILL.md
|
||||
@@ -55,64 +52,58 @@ skills/
|
||||
pdf/SKILL.md
|
||||
```
|
||||
|
||||
**第 1 層:起動時にカタログを注入**:harness は起動時に `_scan_skills()` を呼び出して skills/ ディレクトリをスキャンし、各 SKILL.md の YAML frontmatter(`name`、`description`)を解析して `SKILL_REGISTRY` 辞書に格納する。`list_skills()` はレジストリからカタログを生成し、SYSTEM prompt に注入する。Agent は毎ターン「どのスキルが利用可能か」を確認できる。追加の API 呼び出しは不要:
|
||||
### スキルを走査する
|
||||
|
||||
```python
|
||||
SKILL_REGISTRY: dict[str, dict] = {}
|
||||
class SkillLoader:
|
||||
def scan(self):
|
||||
self.skills.clear()
|
||||
for manifest in sorted(self.skills_dir.glob("*/SKILL.md")):
|
||||
content = manifest.read_text()
|
||||
metadata, body = self.parse_frontmatter(content)
|
||||
name = str(metadata.get("name") or manifest.parent.name).strip()
|
||||
description = metadata.get("description") or body.splitlines()[0]
|
||||
description = " ".join(str(description).lstrip("# ").split())
|
||||
self.skills[name] = {
|
||||
"name": name,
|
||||
"description": description,
|
||||
"content": content,
|
||||
}
|
||||
```
|
||||
|
||||
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}
|
||||
`catalog()` は名前と説明だけを返す:
|
||||
|
||||
_scan_skills() # runs once at startup
|
||||
```text
|
||||
- code-review: Perform thorough code reviews...
|
||||
- pdf: Process PDF files...
|
||||
```
|
||||
|
||||
def list_skills() -> str:
|
||||
return "\n".join(f"- **{s['name']}**: {s['description']}" for s in SKILL_REGISTRY.values())
|
||||
### system prompt を組み立てる
|
||||
|
||||
def build_system() -> str:
|
||||
catalog = list_skills()
|
||||
```python
|
||||
def build_system_prompt() -> str:
|
||||
return (
|
||||
f"You are a coding agent at {WORKDIR}. "
|
||||
f"Skills available:\n{catalog}\n"
|
||||
"Use load_skill to get full details when needed."
|
||||
f"You are a coding agent at {WORKDIR}. Use tools to solve tasks. "
|
||||
"Act, don't explain.\n\n"
|
||||
f"Skills available:\n{SKILL_LOADER.catalog()}\n\n"
|
||||
"Use load_skill to read the full instructions when a skill applies."
|
||||
)
|
||||
|
||||
SYSTEM = build_system()
|
||||
```
|
||||
|
||||
**第 2 層:load_skill**:Agent が「SQL スタイルガイドが必要」と判断し、`load_skill("sql-style")` を呼び出す。レジストリを通じて検索し、ファイルパスを経由しないため、パストラバーサルのリスクがない。SKILL.md の内容は `tool_result` を通じて注入され、既存の file および bash ツールを通じて、参照される `references/`、`scripts/`、`assets/` へのその後のアクセスも含められる。
|
||||
固定された Agent の指示と、起動時に見つかったスキルカタログをこの関数で組み合わせる。
|
||||
|
||||
### 完全な内容を読み込む
|
||||
|
||||
```python
|
||||
def load_skill(name: str) -> str:
|
||||
skill = SKILL_REGISTRY.get(name)
|
||||
if not skill:
|
||||
return f"Skill not found: {name}"
|
||||
return skill["content"]
|
||||
def load(self, name: str) -> str:
|
||||
skill = self.skills.get(name)
|
||||
if skill:
|
||||
return skill["content"]
|
||||
available = ", ".join(self.skills) or "none"
|
||||
return f"Error: Unknown skill '{name}'. Available: {available}"
|
||||
```
|
||||
|
||||
重要な違い:スキル内容は 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。SKILL.md がその後のリソースアクセスを案内できる |
|
||||
| SYSTEM プロンプト | 静的文字列 | 起動時に skills/ をスキャンしてカタログ注入 |
|
||||
| スキルレジストリ | なし | SKILL_REGISTRY(起動時に充填、パストラバーサル防止) |
|
||||
| ループ | 変更なし | 変更なし(スキルツールは自動ディスパッチ) |
|
||||
`name` は起動時に作られたレジストリの検索に使われ、ファイルパスとして解釈されない。ツールが返ると、既存の Agent Loop が内容を新しい `tool_result` メッセージとして追加する。
|
||||
|
||||
---
|
||||
|
||||
@@ -123,21 +114,21 @@ cd learn-claude-code
|
||||
python s07_skill_loading/code.py
|
||||
```
|
||||
|
||||
以下のプロンプトを試してみよう:
|
||||
以下の prompt を試す:
|
||||
|
||||
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`
|
||||
3. `Review README.md and load the relevant skill first`
|
||||
|
||||
観察のポイント:Agent は SYSTEM 内のカタログから利用可能なスキルを知っているか? 完全な手順が必要なときに `[HOOK] load_skill` が表示されるか? 読み込んだスキルの説明を使って回答しているか?
|
||||
system prompt にカタログだけが入り、`load_skill` の呼び出し後に完全な `SKILL.md` が現れることを確認する。
|
||||
|
||||
---
|
||||
|
||||
## 次へ
|
||||
|
||||
オンデマンド読み込みで「運ぶべきでないものは運ばない」問題は解決した。しかし別の問題が待っている:Agent が 30 分連続で作業すると、messages リストが中間プロセスで埋め尽くされる。古い tool_result、期限切れのファイル内容、コンテキストを占領しているが価値を生まない。
|
||||
ツール呼び出しが増えると、`messages[]` には以前のファイル内容やツール結果が残る。
|
||||
|
||||
→ s08 Context Compact:4 層圧縮戦略。安価な層を先に実行、高価な層を後に実行。
|
||||
s08 Context Compact → 過去のメッセージを短くし、後続の呼び出しで使えるコンテキストを確保する。
|
||||
|
||||
|
||||
<!-- translation-sync: zh@v2, en@v2, ja@v2 -->
|
||||
<!-- translation-sync: zh@v6, en@v6, ja@v6 -->
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
# s07: Skill Loading — Load Only When Needed
|
||||
# s07: Skill Loading — Load Skills When Needed
|
||||
|
||||
[English](README.md) · [中文](README.zh.md) · [日本語](README.ja.md)
|
||||
|
||||
s01 → s02 → s03 → s04 → s05 → s06 → `s07` → [s08](../s08_context_compact/) → s09 → ... → s18 → s19
|
||||
> *"Load when needed, don't stuff the prompt"* — Inject via tool_result, not system prompt.
|
||||
s01 → s02 → s03 → s04 → s05 → s06 → `s07` → [s08](../s08_context_compact/) → s09 → ... → s16 → s17
|
||||
|
||||
> The system prompt contains the skill catalog; `load_skill` returns the full `SKILL.md`.
|
||||
>
|
||||
> **Harness Layer**: Knowledge — load on demand, don't fill the context.
|
||||
> **Harness Layer**: Knowledge loading — show the model which skills exist, then load one by name.
|
||||
|
||||
---
|
||||
|
||||
## The Problem
|
||||
|
||||
Suppose a project has a React component specification, a SQL style guide, and an API design document. We want the Agent to follow these rules during development. The most direct approach is to put all of them into the system prompt:
|
||||
Suppose a project has a React component specification, a SQL style guide, and an API design document. We want the Agent to follow these rules during development, so the most direct approach is to put all of them into the system prompt:
|
||||
|
||||
```python
|
||||
SYSTEM = (
|
||||
@@ -22,7 +23,7 @@ SYSTEM = (
|
||||
)
|
||||
```
|
||||
|
||||
Every LLM call now carries all three documents. Even when a task uses only one of them, the other two still occupy context.
|
||||
This approach lets the Agent read every specification, but it fixes all three documents in the system prompt instead of selecting only the one needed for the current task. Every LLM call sends the full text of all three documents to the model. When the task only changes React components, only the React specification is relevant; the SQL style guide and API design document still consume input tokens and context-window space that could hold code, conversation, and tool results.
|
||||
|
||||
---
|
||||
|
||||
@@ -30,24 +31,20 @@ Every LLM call now carries all three documents. Even when a task uses only one o
|
||||
|
||||

|
||||
|
||||
The minimal hook structure, `todo_write`, and sub-Agent from the previous chapter are preserved. This chapter focuses on the new `load_skill` tool. At startup, inject the skill catalog into the SYSTEM prompt; at runtime, register one more tool to load full content, spending tokens only when used.
|
||||
At startup, `SkillLoader` scans `skills/*/SKILL.md`, reads `name` and `description` from YAML frontmatter, and adds that catalog to the system prompt. When the model needs the full instructions, it calls `load_skill(name)`; the returned `SKILL.md` is appended to the message list as a `tool_result`.
|
||||
|
||||
Two-level design:
|
||||
|
||||
| Level | Location | Timing | Cost |
|
||||
|-------|----------|--------|------|
|
||||
| 1. Catalog | system prompt | Injected at startup (harness scans skills/) | ~100 tokens/skill, carried every turn |
|
||||
| 2. Content | tool_result | When Agent calls load_skill; SKILL.md can guide later read_file/bash access to extra resources | ~2000 tokens/skill, on demand |
|
||||
|
||||
The dispatch mechanism is unchanged, `load_skill` auto-dispatches via `TOOL_HANDLERS[block.name]`.
|
||||
| Content | Model input | Added |
|
||||
|---------|-------------|-------|
|
||||
| Skill name and description | system prompt | At startup |
|
||||
| Full `SKILL.md` | `tool_result` | When `load_skill` is called |
|
||||
|
||||
---
|
||||
|
||||
## How It Works
|
||||
|
||||
**skills/ directory**, one subdirectory per skill, each containing a `SKILL.md` file:
|
||||
Each skill is a directory containing `SKILL.md`:
|
||||
|
||||
```
|
||||
```text
|
||||
skills/
|
||||
agent-builder/SKILL.md
|
||||
code-review/SKILL.md
|
||||
@@ -55,64 +52,58 @@ skills/
|
||||
pdf/SKILL.md
|
||||
```
|
||||
|
||||
**Level 1: Inject catalog at startup**: the harness calls `_scan_skills()` at startup to scan the skills/ directory, parsing each SKILL.md's YAML frontmatter (`name`, `description`) into a `SKILL_REGISTRY` dictionary. `list_skills()` generates the catalog from the registry, injected into the SYSTEM prompt. The Agent sees "which skills I have available" every turn, with no extra API calls:
|
||||
### Scan Skills
|
||||
|
||||
```python
|
||||
SKILL_REGISTRY: dict[str, dict] = {}
|
||||
class SkillLoader:
|
||||
def scan(self):
|
||||
self.skills.clear()
|
||||
for manifest in sorted(self.skills_dir.glob("*/SKILL.md")):
|
||||
content = manifest.read_text()
|
||||
metadata, body = self.parse_frontmatter(content)
|
||||
name = str(metadata.get("name") or manifest.parent.name).strip()
|
||||
description = metadata.get("description") or body.splitlines()[0]
|
||||
description = " ".join(str(description).lstrip("# ").split())
|
||||
self.skills[name] = {
|
||||
"name": name,
|
||||
"description": description,
|
||||
"content": content,
|
||||
}
|
||||
```
|
||||
|
||||
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}
|
||||
`catalog()` returns only names and descriptions:
|
||||
|
||||
_scan_skills() # runs once at startup
|
||||
```text
|
||||
- code-review: Perform thorough code reviews...
|
||||
- pdf: Process PDF files...
|
||||
```
|
||||
|
||||
def list_skills() -> str:
|
||||
return "\n".join(f"- **{s['name']}**: {s['description']}" for s in SKILL_REGISTRY.values())
|
||||
### Build the System Prompt
|
||||
|
||||
def build_system() -> str:
|
||||
catalog = list_skills()
|
||||
```python
|
||||
def build_system_prompt() -> str:
|
||||
return (
|
||||
f"You are a coding agent at {WORKDIR}. "
|
||||
f"Skills available:\n{catalog}\n"
|
||||
"Use load_skill to get full details when needed."
|
||||
f"You are a coding agent at {WORKDIR}. Use tools to solve tasks. "
|
||||
"Act, don't explain.\n\n"
|
||||
f"Skills available:\n{SKILL_LOADER.catalog()}\n\n"
|
||||
"Use load_skill to read the full instructions when a skill applies."
|
||||
)
|
||||
|
||||
SYSTEM = build_system()
|
||||
```
|
||||
|
||||
**Level 2: load_skill**: the Agent decides "I need the SQL style guide" and calls `load_skill("sql-style")`. Lookup goes through the registry, not file paths, eliminating path traversal risk. The SKILL.md content is injected via `tool_result`, and can include later access to referenced `references/`, `scripts/`, or `assets/` through the existing file and bash tools.
|
||||
This function combines the fixed Agent instructions with the catalog found at startup.
|
||||
|
||||
### Load Full Content
|
||||
|
||||
```python
|
||||
def load_skill(name: str) -> str:
|
||||
skill = SKILL_REGISTRY.get(name)
|
||||
if not skill:
|
||||
return f"Skill not found: {name}"
|
||||
return skill["content"]
|
||||
def load(self, name: str) -> str:
|
||||
skill = self.skills.get(name)
|
||||
if skill:
|
||||
return skill["content"]
|
||||
available = ", ".join(self.skills) or "none"
|
||||
return f"Error: Unknown skill '{name}'. Available: {available}"
|
||||
```
|
||||
|
||||
The key distinction: skill content is not part of the system prompt. It enters the current messages as a tool result. Subsequent calls carry it along with the history until context compaction, truncation, or session end. This naturally connects to s08's compact: on-demand loading solves "don't carry what you shouldn't", compact solves "how to drop what you should."
|
||||
|
||||
---
|
||||
|
||||
## Changes from s06
|
||||
|
||||
| Component | Before (s06) | After (s07) |
|
||||
|-----------|-------------|-------------|
|
||||
| Tool count | 7 (bash, read, write, edit, glob, todo_write, task) | 8 (+load_skill) |
|
||||
| Knowledge loading | None | Two-level: startup catalog in SYSTEM + runtime load_skill; SKILL.md may guide later resource access |
|
||||
| SYSTEM prompt | Static string | Startup scan of skills/ injects catalog |
|
||||
| Skill registry | None | SKILL_REGISTRY (populated at startup, prevents path traversal) |
|
||||
| Loop | Unchanged | Unchanged (skill tool auto-dispatches) |
|
||||
`name` looks up the startup registry; it is not interpreted as a file path. After the tool returns, the existing Agent Loop appends its content as a new `tool_result` message.
|
||||
|
||||
---
|
||||
|
||||
@@ -127,17 +118,17 @@ Try these prompts:
|
||||
|
||||
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`
|
||||
3. `Review README.md and load the relevant skill first`
|
||||
|
||||
What to watch for: Does the Agent know available skills from the SYSTEM catalog? Does `[HOOK] load_skill` appear when full instructions are needed? Does the answer use the loaded skill's instructions?
|
||||
Check that the system prompt contains only the catalog and that the full `SKILL.md` appears after `load_skill` is called.
|
||||
|
||||
---
|
||||
|
||||
## What's Next
|
||||
|
||||
On-demand loading solved "don't carry what you shouldn't." But another problem looms: after the Agent works for 30 minutes, the messages list fills up with intermediate process. Old tool_results, stale file contents, occupying context but adding no value.
|
||||
As tool calls accumulate, `messages[]` retains earlier file contents and tool results.
|
||||
|
||||
→ s08 Context Compact: A four-layer compaction strategy. Cheap layers run first, expensive layers run last.
|
||||
→ s08 Context Compact: shorten earlier messages and keep context available for later calls.
|
||||
|
||||
|
||||
<!-- translation-sync: zh@v2, en@v2, ja@v2 -->
|
||||
<!-- translation-sync: zh@v6, en@v6, ja@v6 -->
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
# s07: Skill Loading — 用到的时候才加载
|
||||
# s07: Skill Loading — 用到时再加载
|
||||
|
||||
[English](README.md) · [中文](README.zh.md) · [日本語](README.ja.md)
|
||||
|
||||
s01 → s02 → s03 → s04 → s05 → s06 → `s07` → [s08](../s08_context_compact/) → s09 → ... → s18 → s19
|
||||
> *"用到时再加载, 别全塞 prompt 里"* — 通过 tool_result 注入, 不塞 system prompt。
|
||||
s01 → s02 → s03 → s04 → s05 → s06 → `s07` → [s08](../s08_context_compact/) → s09 → ... → s16 → s17
|
||||
|
||||
> system prompt 保存技能目录;`load_skill` 返回完整的 `SKILL.md`。
|
||||
>
|
||||
> **Harness 层**: 知识 — 按需加载, 不堆满上下文。
|
||||
> **Harness 层**:知识加载 — 让模型先知道有哪些技能,再按名称读取内容。
|
||||
|
||||
---
|
||||
|
||||
## 问题
|
||||
|
||||
假设某个项目有一套 React 组件规范、一份 SQL 风格指南和一份 API 设计文档。我们希望 Agent 在开发过程中遵守这些规范。最直接的做法,是把它们全部放进 system prompt:
|
||||
假设某个项目有一套 React 组件规范、一份 SQL 风格指南和一份 API 设计文档。我们希望 Agent 在开发过程中遵守这些规范,最直接的做法就是把它们全部放进 system prompt:
|
||||
|
||||
```python
|
||||
SYSTEM = (
|
||||
@@ -22,7 +23,7 @@ SYSTEM = (
|
||||
)
|
||||
```
|
||||
|
||||
这样,每次调用 LLM 都会携带三份完整文档。即使当前任务只涉及其中一份,另外两份仍会占用上下文。
|
||||
这种做法能让 Agent 读到所有规范,但问题在于,三份文档被固定放进了 system prompt,无法根据当前任务只选择需要的那一份。每次调用 LLM 时,三份文档的全文都会一起发送给模型。当前任务只修改 React 组件时,实际需要的只有 React 组件规范;SQL 风格指南和 API 设计文档与任务无关,却仍然占用输入 token 和上下文窗口,留给代码、对话和工具结果的空间也会变少。
|
||||
|
||||
---
|
||||
|
||||
@@ -30,24 +31,20 @@ SYSTEM = (
|
||||
|
||||

|
||||
|
||||
保留上一章的最小 hook 结构、`todo_write` 和子 Agent,本章重点转向新增的 `load_skill` 工具。启动时把技能目录注入 SYSTEM prompt,运行时多注册一个工具加载完整内容,用到才花 token。
|
||||
启动时,`SkillLoader` 扫描 `skills/*/SKILL.md`,读取 YAML frontmatter 中的 `name` 和 `description`,并把这份目录加入 system prompt。模型需要完整说明时,调用 `load_skill(name)`;返回的 `SKILL.md` 作为 `tool_result` 追加到消息列表。
|
||||
|
||||
两层设计:
|
||||
|
||||
| 层 | 位置 | 时机 | 代价 |
|
||||
|---|------|------|------|
|
||||
| 1. 目录 | system prompt | 启动时注入(harness 扫描 skills/) | ~100 tokens/skill,每轮都带 |
|
||||
| 2. 内容 | tool_result | Agent 调用 load_skill 时;SKILL.md 可指引后续的 read_file/bash 调用,用于按需访问额外资源 | ~2000 tokens/skill,按需 |
|
||||
|
||||
dispatch 机制不变,load_skill 通过 `TOOL_HANDLERS[block.name]` 分发。
|
||||
| 内容 | 进入模型的位置 | 何时加入 |
|
||||
|------|----------------|----------|
|
||||
| 技能名称和描述 | system prompt | 启动时 |
|
||||
| 完整 `SKILL.md` | `tool_result` | 调用 `load_skill` 时 |
|
||||
|
||||
---
|
||||
|
||||
## 工作原理
|
||||
|
||||
**skills/ 目录**,每个技能一个子目录,包含 `SKILL.md` 文件:
|
||||
每个技能是一个包含 `SKILL.md` 的目录:
|
||||
|
||||
```
|
||||
```text
|
||||
skills/
|
||||
agent-builder/SKILL.md
|
||||
code-review/SKILL.md
|
||||
@@ -55,64 +52,58 @@ skills/
|
||||
pdf/SKILL.md
|
||||
```
|
||||
|
||||
**第一级:启动时注入目录**:harness 启动时调用 `_scan_skills()` 扫描 skills/ 目录,解析每个 SKILL.md 的 YAML frontmatter(`name`、`description`),存入 `SKILL_REGISTRY` 字典。`list_skills()` 从注册表生成目录,注入 SYSTEM prompt。Agent 每轮都能看到"我有哪些技能可用",不花额外 API 调用:
|
||||
### 扫描技能
|
||||
|
||||
```python
|
||||
SKILL_REGISTRY: dict[str, dict] = {}
|
||||
class SkillLoader:
|
||||
def scan(self):
|
||||
self.skills.clear()
|
||||
for manifest in sorted(self.skills_dir.glob("*/SKILL.md")):
|
||||
content = manifest.read_text()
|
||||
metadata, body = self.parse_frontmatter(content)
|
||||
name = str(metadata.get("name") or manifest.parent.name).strip()
|
||||
description = metadata.get("description") or body.splitlines()[0]
|
||||
description = " ".join(str(description).lstrip("# ").split())
|
||||
self.skills[name] = {
|
||||
"name": name,
|
||||
"description": description,
|
||||
"content": content,
|
||||
}
|
||||
```
|
||||
|
||||
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}
|
||||
`catalog()` 只输出名称和描述:
|
||||
|
||||
_scan_skills() # runs once at startup
|
||||
```text
|
||||
- code-review: Perform thorough code reviews...
|
||||
- pdf: Process PDF files...
|
||||
```
|
||||
|
||||
def list_skills() -> str:
|
||||
return "\n".join(f"- **{s['name']}**: {s['description']}" for s in SKILL_REGISTRY.values())
|
||||
### 组装 system prompt
|
||||
|
||||
def build_system() -> str:
|
||||
catalog = list_skills()
|
||||
```python
|
||||
def build_system_prompt() -> str:
|
||||
return (
|
||||
f"You are a coding agent at {WORKDIR}. "
|
||||
f"Skills available:\n{catalog}\n"
|
||||
"Use load_skill to get full details when needed."
|
||||
f"You are a coding agent at {WORKDIR}. Use tools to solve tasks. "
|
||||
"Act, don't explain.\n\n"
|
||||
f"Skills available:\n{SKILL_LOADER.catalog()}\n\n"
|
||||
"Use load_skill to read the full instructions when a skill applies."
|
||||
)
|
||||
|
||||
SYSTEM = build_system()
|
||||
```
|
||||
|
||||
**第二级:load_skill**:Agent 决定"我需要 SQL 风格指南",调用 `load_skill("sql-style")`。通过注册表查找,不走文件路径,没有路径遍历风险。SKILL.md 内容通过 `tool_result` 注入,并可通过现有的 file 和 bash 工具进一步访问引用的 `references/`、`scripts/` 或 `assets/`。
|
||||
固定的 Agent 指令和扫描得到的技能目录在这里组成实际传给模型的 system prompt。
|
||||
|
||||
### 加载完整内容
|
||||
|
||||
```python
|
||||
def load_skill(name: str) -> str:
|
||||
skill = SKILL_REGISTRY.get(name)
|
||||
if not skill:
|
||||
return f"Skill not found: {name}"
|
||||
return skill["content"]
|
||||
def load(self, name: str) -> str:
|
||||
skill = self.skills.get(name)
|
||||
if skill:
|
||||
return skill["content"]
|
||||
available = ", ".join(self.skills) or "none"
|
||||
return f"Error: Unknown skill '{name}'. Available: {available}"
|
||||
```
|
||||
|
||||
关键区别:技能内容不是 system prompt 的一部分,它作为一次工具结果进入当前 messages。后续调用会随历史一起携带,直到上下文压缩、截断或会话结束。这和 s08 的 compact 自然衔接:按需加载解决了"不该提前带的不要带",compact 解决"该丢的怎么丢"。
|
||||
|
||||
---
|
||||
|
||||
## 相对 s06 的变更
|
||||
|
||||
| 组件 | 之前 (s06) | 之后 (s07) |
|
||||
|------|-----------|-----------|
|
||||
| 工具数量 | 7 (bash, read, write, edit, glob, todo_write, task) | 8 (+load_skill) |
|
||||
| 知识加载 | 无 | 两级:启动时目录注入 SYSTEM + 运行时 load_skill;SKILL.md 可指引后续资源访问 |
|
||||
| SYSTEM 提示 | 静态字符串 | 启动时扫描 skills/ 注入目录 |
|
||||
| 技能注册表 | 无 | SKILL_REGISTRY(启动时填充,防路径遍历) |
|
||||
| 循环 | 不变 | 不变(skill 工具自动分发) |
|
||||
`name` 用于查询启动时建立的注册表,不会被当作文件路径。工具返回后,原有 Agent Loop 会把内容作为新的 `tool_result` 消息追加。
|
||||
|
||||
---
|
||||
|
||||
@@ -127,17 +118,17 @@ 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`
|
||||
3. `Review README.md and load the relevant skill first`
|
||||
|
||||
观察重点:Agent 是否直接从 SYSTEM 里的目录知道有哪些技能?需要完整规范时是否出现 `[HOOK] load_skill`?加载后回答是否使用了对应 skill 的说明?
|
||||
观察 system prompt 中是否只有技能目录,以及调用 `load_skill` 后是否出现完整的 `SKILL.md` 内容。
|
||||
|
||||
---
|
||||
|
||||
## 接下来
|
||||
|
||||
按需加载解决了"不该带的不要带"。但另一个问题来了:Agent 连续工作 30 分钟后,messages 列表塞满了中间过程。旧的 tool_result、过时的文件内容,占着上下文但不产生价值。
|
||||
随着工具调用增加,`messages[]` 会积累较早的文件内容和工具结果。
|
||||
|
||||
s08 Context Compact → 四层压缩策略。便宜的先跑,贵的后跑。
|
||||
s08 Context Compact → 缩短较早的消息,为后续调用保留上下文空间。
|
||||
|
||||
|
||||
<!-- translation-sync: zh@v2, en@v2, ja@v2 -->
|
||||
<!-- translation-sync: zh@v6, en@v6, ja@v6 -->
|
||||
|
||||
@@ -1,38 +1,36 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
s07: Skill Loading — two-level on-demand knowledge injection.
|
||||
s07_skill_loading.py - Skill Loading
|
||||
|
||||
Layer 1 (cheap, always present):
|
||||
SYSTEM prompt includes skill names + one-line descriptions (~100 tokens/skill)
|
||||
"Skills available: agent-builder, code-review, mcp-builder, pdf"
|
||||
The system prompt contains a catalog of skill names and descriptions.
|
||||
The model loads the full SKILL.md only when it calls load_skill.
|
||||
|
||||
Layer 2 (expensive, on demand):
|
||||
Agent calls load_skill("code-review") → full SKILL.md content
|
||||
injected via tool_result (~2000 tokens/skill)
|
||||
skills/ Startup
|
||||
+------------------+ +------------------+
|
||||
| code-review/ | ----> | SkillLoader |
|
||||
| SKILL.md | | name + summary |
|
||||
| pdf/ | +--------+---------+
|
||||
| SKILL.md | |
|
||||
+------------------+ v
|
||||
system prompt catalog
|
||||
|
||||
skills/
|
||||
agent-builder/SKILL.md
|
||||
code-review/SKILL.md
|
||||
mcp-builder/SKILL.md
|
||||
pdf/SKILL.md
|
||||
|
||||
Changes from s06:
|
||||
+ build_system() — scan skills/ dir at startup, inject catalog into SYSTEM
|
||||
+ load_skill(name) — return full SKILL.md content via tool_result
|
||||
+ SKILLS_DIR config
|
||||
Loop unchanged: load_skill auto-dispatches via TOOL_HANDLERS.
|
||||
|
||||
Run: python s07_skill_loading/code.py
|
||||
Needs: pip install anthropic python-dotenv pyyaml + ANTHROPIC_API_KEY in .env
|
||||
LLM -- load_skill(name) --> full SKILL.md
|
||||
^ |
|
||||
+--------- tool_result --------+
|
||||
"""
|
||||
|
||||
import ast, json, os, subprocess
|
||||
import os
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
try:
|
||||
import readline
|
||||
readline.parse_and_bind('set bind-tty-special-chars off')
|
||||
readline.parse_and_bind('set input-meta on')
|
||||
readline.parse_and_bind('set output-meta on')
|
||||
readline.parse_and_bind('set convert-meta off')
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
@@ -47,108 +45,116 @@ WORKDIR = Path.cwd()
|
||||
SKILLS_DIR = WORKDIR / "skills"
|
||||
client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))
|
||||
MODEL = os.environ["MODEL_ID"]
|
||||
CURRENT_TODOS: list[dict] = []
|
||||
|
||||
# s07: Skill catalog scan (used by build_system below)
|
||||
def _parse_frontmatter(text: str) -> tuple[dict, str]:
|
||||
"""Parse YAML frontmatter from SKILL.md. Returns (meta, body)."""
|
||||
if not text.startswith("---"):
|
||||
return {}, text
|
||||
parts = text.split("---", 2)
|
||||
if len(parts) < 3:
|
||||
return {}, text
|
||||
try:
|
||||
meta = yaml.safe_load(parts[1]) or {}
|
||||
except yaml.YAMLError:
|
||||
meta = {}
|
||||
return meta, parts[2].strip()
|
||||
|
||||
# Build skill registry at startup (used for safe lookup in load_skill)
|
||||
SKILL_REGISTRY: dict[str, dict] = {}
|
||||
# -- Skill catalog --
|
||||
|
||||
def _scan_skills():
|
||||
"""Scan skills/ dir, populate SKILL_REGISTRY with name/description/content."""
|
||||
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}
|
||||
class SkillLoader:
|
||||
def __init__(self, skills_dir: Path):
|
||||
self.skills_dir = skills_dir
|
||||
self.skills: dict[str, dict[str, str]] = {}
|
||||
self.scan()
|
||||
|
||||
_scan_skills()
|
||||
@staticmethod
|
||||
def parse_frontmatter(text: str) -> tuple[dict, str]:
|
||||
if not text.startswith("---"):
|
||||
return {}, text
|
||||
parts = text.split("---", 2)
|
||||
if len(parts) < 3:
|
||||
return {}, text
|
||||
try:
|
||||
metadata = yaml.safe_load(parts[1]) or {}
|
||||
except yaml.YAMLError:
|
||||
metadata = {}
|
||||
if not isinstance(metadata, dict):
|
||||
metadata = {}
|
||||
return metadata, parts[2].lstrip()
|
||||
|
||||
def list_skills() -> str:
|
||||
"""List all skills (name + one-line description)."""
|
||||
if not SKILL_REGISTRY:
|
||||
return "(no skills found)"
|
||||
return "\n".join(f"- **{s['name']}**: {s['description']}" for s in SKILL_REGISTRY.values())
|
||||
def scan(self):
|
||||
self.skills.clear()
|
||||
if not self.skills_dir.exists():
|
||||
return
|
||||
|
||||
# s07: SYSTEM includes skill catalog (cheap — just names + descriptions)
|
||||
def build_system() -> str:
|
||||
"""Build SYSTEM prompt with skill catalog injected at startup."""
|
||||
catalog = list_skills()
|
||||
for manifest in sorted(self.skills_dir.glob("*/SKILL.md")):
|
||||
content = manifest.read_text()
|
||||
metadata, body = self.parse_frontmatter(content)
|
||||
name = str(metadata.get("name") or manifest.parent.name).strip()
|
||||
description = metadata.get("description") or body.splitlines()[0]
|
||||
description = " ".join(str(description).lstrip("# ").split())
|
||||
self.skills[name] = {
|
||||
"name": name,
|
||||
"description": description,
|
||||
"content": content,
|
||||
}
|
||||
|
||||
def catalog(self) -> str:
|
||||
if not self.skills:
|
||||
return "(no skills found)"
|
||||
return "\n".join(
|
||||
f"- {skill['name']}: {skill['description']}"
|
||||
for skill in self.skills.values()
|
||||
)
|
||||
|
||||
def load(self, name: str) -> str:
|
||||
skill = self.skills.get(name)
|
||||
if skill:
|
||||
return skill["content"]
|
||||
available = ", ".join(self.skills) or "none"
|
||||
return f"Error: Unknown skill '{name}'. Available: {available}"
|
||||
|
||||
|
||||
SKILL_LOADER = SkillLoader(SKILLS_DIR)
|
||||
|
||||
|
||||
def build_system_prompt() -> str:
|
||||
return (
|
||||
f"You are a coding agent at {WORKDIR}. "
|
||||
f"Skills available:\n{catalog}\n"
|
||||
"Use load_skill to get full details when needed."
|
||||
f"You are a coding agent at {WORKDIR}. Use tools to solve tasks. "
|
||||
"Act, don't explain.\n\n"
|
||||
f"Skills available:\n{SKILL_LOADER.catalog()}\n\n"
|
||||
"Use load_skill to read the full instructions when a skill applies."
|
||||
)
|
||||
|
||||
SYSTEM = build_system()
|
||||
|
||||
# s07: subagent gets its own system prompt — no skill loading, no task
|
||||
SUB_SYSTEM = (
|
||||
f"You are a coding agent at {WORKDIR}. "
|
||||
"Complete the task you were given, then return a concise summary. "
|
||||
"Do not delegate further."
|
||||
)
|
||||
SYSTEM = build_system_prompt()
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# FROM s02-s06 (unchanged): Tool Implementations
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
def safe_path(p: str) -> Path:
|
||||
path = (WORKDIR / p).resolve()
|
||||
if not path.is_relative_to(WORKDIR):
|
||||
raise ValueError(f"Path escapes workspace: {p}")
|
||||
return path
|
||||
# -- Tools --
|
||||
|
||||
def run_bash(command: str) -> str:
|
||||
try:
|
||||
r = subprocess.run(command, shell=True, cwd=WORKDIR,
|
||||
capture_output=True, text=True, timeout=120)
|
||||
out = (r.stdout + r.stderr).strip()
|
||||
return out[:50000] if out else "(no output)"
|
||||
result = subprocess.run(
|
||||
command, shell=True, cwd=WORKDIR,
|
||||
capture_output=True, text=True, timeout=120,
|
||||
)
|
||||
output = (result.stdout + result.stderr).strip()
|
||||
return output[:50000] if output else "(no output)"
|
||||
except subprocess.TimeoutExpired:
|
||||
return "Error: Timeout (120s)"
|
||||
|
||||
|
||||
def run_read(path: str, limit: int | None = None) -> str:
|
||||
try:
|
||||
lines = safe_path(path).read_text().splitlines()
|
||||
lines = (WORKDIR / path).resolve().read_text().splitlines()
|
||||
if limit and limit < len(lines):
|
||||
lines = lines[:limit] + [f"... ({len(lines) - limit} more lines)"]
|
||||
return "\n".join(lines)
|
||||
except Exception as e:
|
||||
return f"Error: {e}"
|
||||
|
||||
|
||||
def run_write(path: str, content: str) -> str:
|
||||
try:
|
||||
file_path = safe_path(path)
|
||||
file_path = (WORKDIR / path).resolve()
|
||||
file_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
file_path.write_text(content)
|
||||
return f"Wrote {len(content)} bytes to {path}"
|
||||
except Exception as e:
|
||||
return f"Error: {e}"
|
||||
|
||||
|
||||
def run_edit(path: str, old_text: str, new_text: str) -> str:
|
||||
try:
|
||||
file_path = safe_path(path)
|
||||
file_path = (WORKDIR / path).resolve()
|
||||
text = file_path.read_text()
|
||||
if old_text not in text:
|
||||
return f"Error: text not found in {path}"
|
||||
@@ -157,126 +163,18 @@ def run_edit(path: str, old_text: str, new_text: str) -> str:
|
||||
except Exception as e:
|
||||
return f"Error: {e}"
|
||||
|
||||
|
||||
def run_glob(pattern: str) -> str:
|
||||
import glob as g
|
||||
import glob
|
||||
try:
|
||||
results = []
|
||||
for match in g.glob(pattern, root_dir=WORKDIR):
|
||||
matches = []
|
||||
for match in glob.glob(pattern, root_dir=WORKDIR):
|
||||
if (WORKDIR / match).resolve().is_relative_to(WORKDIR):
|
||||
results.append(match)
|
||||
return "\n".join(results) if results else "(no matches)"
|
||||
matches.append(match)
|
||||
return "\n".join(matches) if matches else "(no matches)"
|
||||
except Exception as e:
|
||||
return f"Error: {e}"
|
||||
|
||||
def _normalize_todos(todos):
|
||||
if isinstance(todos, str):
|
||||
try:
|
||||
todos = json.loads(todos)
|
||||
except json.JSONDecodeError:
|
||||
try:
|
||||
todos = ast.literal_eval(todos)
|
||||
except (SyntaxError, ValueError):
|
||||
return None, "Error: todos must be a list or JSON array string"
|
||||
if not isinstance(todos, list):
|
||||
return None, "Error: todos must be a list"
|
||||
for i, t in enumerate(todos):
|
||||
if not isinstance(t, dict):
|
||||
return None, f"Error: todos[{i}] must be an object"
|
||||
if "content" not in t or "status" not in t:
|
||||
return None, f"Error: todos[{i}] missing 'content' or 'status'"
|
||||
if t["status"] not in ("pending", "in_progress", "completed"):
|
||||
return None, f"Error: todos[{i}] has invalid status '{t['status']}'"
|
||||
return todos, None
|
||||
|
||||
def run_todo_write(todos: list) -> str:
|
||||
global CURRENT_TODOS
|
||||
todos, error = _normalize_todos(todos)
|
||||
if error:
|
||||
return error
|
||||
CURRENT_TODOS = todos
|
||||
lines = ["\n\033[33m## Current Tasks\033[0m"]
|
||||
for t in CURRENT_TODOS:
|
||||
icon = {"pending": " ", "in_progress": "\033[36m▸\033[0m", "completed": "\033[32m✓\033[0m"}[t["status"]]
|
||||
lines.append(f" [{icon}] {t['content']}")
|
||||
print("\n".join(lines))
|
||||
return f"Updated {len(CURRENT_TODOS)} tasks"
|
||||
|
||||
def extract_text(content) -> str:
|
||||
if not isinstance(content, list):
|
||||
return str(content)
|
||||
return "\n".join(getattr(b, "text", "") for b in content if getattr(b, "type", None) == "text")
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# FROM s06 (unchanged): Subagent
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
SUB_TOOLS = [
|
||||
{"name": "bash", "description": "Run a shell command.",
|
||||
"input_schema": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}},
|
||||
{"name": "read_file", "description": "Read file contents.",
|
||||
"input_schema": {"type": "object", "properties": {"path": {"type": "string"}}, "required": ["path"]}},
|
||||
{"name": "write_file", "description": "Write content to a file.",
|
||||
"input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}},
|
||||
{"name": "edit_file", "description": "Replace exact text in a file once.",
|
||||
"input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "old_text": {"type": "string"}, "new_text": {"type": "string"}}, "required": ["path", "old_text", "new_text"]}},
|
||||
{"name": "glob", "description": "Find files matching a glob pattern.",
|
||||
"input_schema": {"type": "object", "properties": {"pattern": {"type": "string"}}, "required": ["pattern"]}},
|
||||
]
|
||||
SUB_HANDLERS = {"bash": run_bash, "read_file": run_read, "write_file": run_write,
|
||||
"edit_file": run_edit, "glob": run_glob}
|
||||
|
||||
def spawn_subagent(description: str) -> str:
|
||||
print(f"\n\033[35m[Subagent spawned]\033[0m")
|
||||
messages = [{"role": "user", "content": description}]
|
||||
for _ in range(30):
|
||||
response = client.messages.create(model=MODEL, system=SUB_SYSTEM,
|
||||
messages=messages, tools=SUB_TOOLS, max_tokens=8000)
|
||||
messages.append({"role": "assistant", "content": response.content})
|
||||
if response.stop_reason != "tool_use":
|
||||
break
|
||||
results = []
|
||||
for block in response.content:
|
||||
if block.type == "tool_use":
|
||||
blocked = trigger_hooks("PreToolUse", block)
|
||||
if blocked:
|
||||
results.append({"type": "tool_result", "tool_use_id": block.id,
|
||||
"content": str(blocked)})
|
||||
continue
|
||||
handler = SUB_HANDLERS.get(block.name)
|
||||
output = handler(**block.input) if handler else f"Unknown: {block.name}"
|
||||
trigger_hooks("PostToolUse", block, output)
|
||||
print(f" \033[90m[sub] {block.name}: {str(output)[:100]}\033[0m")
|
||||
results.append({"type": "tool_result", "tool_use_id": block.id, "content": output})
|
||||
messages.append({"role": "user", "content": results})
|
||||
result = extract_text(messages[-1]["content"])
|
||||
if not result:
|
||||
for msg in reversed(messages):
|
||||
if msg["role"] == "assistant":
|
||||
result = extract_text(msg["content"])
|
||||
if result:
|
||||
break
|
||||
if not result:
|
||||
result = "Subagent stopped after 30 turns without final answer."
|
||||
print(f"\033[35m[Subagent done]\033[0m")
|
||||
return result
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# NEW in s07: load_skill — runtime full content loading
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
def load_skill(name: str) -> str:
|
||||
"""Load full skill content. Lookup via registry — no path traversal."""
|
||||
skill = SKILL_REGISTRY.get(name)
|
||||
if not skill:
|
||||
return f"Skill not found: {name}"
|
||||
return skill["content"]
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# Tool Registry — all tools from s02-s07
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
TOOLS = [
|
||||
{"name": "bash", "description": "Run a shell command.",
|
||||
@@ -289,31 +187,29 @@ TOOLS = [
|
||||
"input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "old_text": {"type": "string"}, "new_text": {"type": "string"}}, "required": ["path", "old_text", "new_text"]}},
|
||||
{"name": "glob", "description": "Find files matching a glob pattern.",
|
||||
"input_schema": {"type": "object", "properties": {"pattern": {"type": "string"}}, "required": ["pattern"]}},
|
||||
{"name": "todo_write", "description": "Create and manage a task list for your current coding session.",
|
||||
"input_schema": {"type": "object", "properties": {"todos": {"type": "array", "items": {"type": "object", "properties": {"content": {"type": "string"}, "status": {"type": "string", "enum": ["pending", "in_progress", "completed"]}}, "required": ["content", "status"]}}}, "required": ["todos"]}},
|
||||
{"name": "task", "description": "Launch a subagent to handle a complex subtask. Returns only the final conclusion.",
|
||||
"input_schema": {"type": "object", "properties": {"description": {"type": "string"}}, "required": ["description"]}},
|
||||
# s07: skill tool (catalog is already in SYSTEM prompt, this loads full content)
|
||||
{"name": "load_skill", "description": "Load the full content of a skill by name.",
|
||||
{"name": "load_skill", "description": "Load the full SKILL.md content by skill name.",
|
||||
"input_schema": {"type": "object", "properties": {"name": {"type": "string"}}, "required": ["name"]}},
|
||||
]
|
||||
|
||||
TOOL_HANDLERS = {
|
||||
"bash": run_bash, "read_file": run_read, "write_file": run_write,
|
||||
"edit_file": run_edit, "glob": run_glob, "todo_write": run_todo_write,
|
||||
"task": spawn_subagent, "load_skill": load_skill,
|
||||
"bash": run_bash,
|
||||
"read_file": run_read,
|
||||
"write_file": run_write,
|
||||
"edit_file": run_edit,
|
||||
"glob": run_glob,
|
||||
"load_skill": SKILL_LOADER.load,
|
||||
}
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# FROM s04 (unchanged): Hook System
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# -- Hooks --
|
||||
|
||||
HOOKS = {"UserPromptSubmit": [], "PreToolUse": [], "PostToolUse": [], "Stop": []}
|
||||
|
||||
|
||||
def register_hook(event: str, callback):
|
||||
HOOKS[event].append(callback)
|
||||
|
||||
|
||||
def trigger_hooks(event: str, *args):
|
||||
for callback in HOOKS[event]:
|
||||
result = callback(*args)
|
||||
@@ -321,52 +217,104 @@ def trigger_hooks(event: str, *args):
|
||||
return result
|
||||
return None
|
||||
|
||||
|
||||
DENY_LIST = ["rm -rf /", "sudo", "shutdown", "reboot", "mkfs", "dd if="]
|
||||
DESTRUCTIVE = ["rm ", "> /etc/", "chmod 777"]
|
||||
|
||||
|
||||
def permission_hook(block):
|
||||
"""PreToolUse: block denied operations and ask about risky ones."""
|
||||
if block.name == "bash":
|
||||
for p in DENY_LIST:
|
||||
if p in block.input.get("command", ""):
|
||||
print(f"\n\033[31m⛔ Blocked: '{p}'\033[0m")
|
||||
return "Permission denied"
|
||||
command = block.input.get("command", "")
|
||||
for pattern in DENY_LIST:
|
||||
if pattern in command:
|
||||
print(f"\n\033[31m[blocked] '{pattern}'\033[0m")
|
||||
return "Permission denied by deny list"
|
||||
for keyword in DESTRUCTIVE:
|
||||
if keyword in command:
|
||||
print("\n\033[33m[permission] Potentially destructive command\033[0m")
|
||||
print(f" Tool: {block.name}({block.input})")
|
||||
choice = input(" Allow? [y/N] ").strip().lower()
|
||||
if choice not in ("y", "yes"):
|
||||
return "Permission denied by user"
|
||||
|
||||
if block.name in ("read_file", "write_file", "edit_file"):
|
||||
path = block.input.get("path", "")
|
||||
if not (WORKDIR / path).resolve().is_relative_to(WORKDIR):
|
||||
print("\n\033[33m[permission] Access outside workspace\033[0m")
|
||||
print(f" Tool: {block.name}({block.input})")
|
||||
choice = input(" Allow? [y/N] ").strip().lower()
|
||||
if choice not in ("y", "yes"):
|
||||
return "Permission denied by user"
|
||||
return None
|
||||
|
||||
|
||||
def log_hook(block):
|
||||
print(f"\033[90m[HOOK] {block.name}\033[0m")
|
||||
"""PreToolUse: log every tool call."""
|
||||
args_preview = str(list(block.input.values())[:2])[:60]
|
||||
print(f"\033[90m[HOOK] {block.name}({args_preview})\033[0m")
|
||||
return None
|
||||
|
||||
|
||||
def large_output_hook(block, output):
|
||||
"""PostToolUse: warn on large output."""
|
||||
if len(str(output)) > 100000:
|
||||
print(f"\033[33m[HOOK] Large output from {block.name}: {len(str(output))} chars\033[0m")
|
||||
return None
|
||||
|
||||
|
||||
def context_inject_hook(query: str):
|
||||
"""UserPromptSubmit: log the working directory."""
|
||||
print(f"\033[90m[HOOK] UserPromptSubmit: working in {WORKDIR}\033[0m")
|
||||
return None
|
||||
|
||||
|
||||
def summary_hook(messages: list):
|
||||
tool_count = sum(1 for m in messages
|
||||
for b in (m.get("content") if isinstance(m.get("content"), list) else [])
|
||||
if isinstance(b, dict) and b.get("type") == "tool_result")
|
||||
"""Stop: print the number of tool results in this message list."""
|
||||
tool_count = sum(
|
||||
1
|
||||
for message in messages
|
||||
for block in (
|
||||
message.get("content")
|
||||
if isinstance(message.get("content"), list)
|
||||
else []
|
||||
)
|
||||
if isinstance(block, dict) and block.get("type") == "tool_result"
|
||||
)
|
||||
print(f"\033[90m[HOOK] Stop: session used {tool_count} tool calls\033[0m")
|
||||
return None
|
||||
|
||||
|
||||
register_hook("UserPromptSubmit", context_inject_hook)
|
||||
register_hook("PreToolUse", permission_hook)
|
||||
register_hook("PreToolUse", log_hook)
|
||||
register_hook("PostToolUse", large_output_hook)
|
||||
register_hook("Stop", summary_hook)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# agent_loop — same as s05-s06 + nag reminder
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
def execute_tool(block) -> str:
|
||||
blocked = trigger_hooks("PreToolUse", block)
|
||||
if blocked:
|
||||
return str(blocked)
|
||||
|
||||
handler = TOOL_HANDLERS.get(block.name)
|
||||
try:
|
||||
output = handler(**block.input) if handler else f"Unknown: {block.name}"
|
||||
except Exception as e:
|
||||
output = f"Error: {e}"
|
||||
|
||||
trigger_hooks("PostToolUse", block, output)
|
||||
return str(output)
|
||||
|
||||
|
||||
def agent_loop(messages: list):
|
||||
rounds_since_todo = 0
|
||||
while True:
|
||||
if rounds_since_todo >= 3 and messages:
|
||||
messages.append({"role": "user",
|
||||
"content": "<reminder>Update your todos.</reminder>"})
|
||||
rounds_since_todo = 0
|
||||
|
||||
response = client.messages.create(
|
||||
model=MODEL, system=SYSTEM, messages=messages,
|
||||
tools=TOOLS, max_tokens=8000,
|
||||
model=MODEL,
|
||||
system=SYSTEM,
|
||||
messages=messages,
|
||||
tools=TOOLS,
|
||||
max_tokens=8000,
|
||||
)
|
||||
messages.append({"role": "assistant", "content": response.content})
|
||||
|
||||
@@ -377,35 +325,22 @@ def agent_loop(messages: list):
|
||||
continue
|
||||
return
|
||||
|
||||
rounds_since_todo += 1
|
||||
results = []
|
||||
for block in response.content:
|
||||
if block.type != "tool_use":
|
||||
continue
|
||||
|
||||
blocked = trigger_hooks("PreToolUse", block)
|
||||
if blocked:
|
||||
results.append({"type": "tool_result", "tool_use_id": block.id,
|
||||
"content": str(blocked)})
|
||||
continue
|
||||
|
||||
handler = TOOL_HANDLERS.get(block.name)
|
||||
output = handler(**block.input) if handler else f"Unknown: {block.name}"
|
||||
|
||||
trigger_hooks("PostToolUse", block, output)
|
||||
|
||||
if block.name == "todo_write":
|
||||
rounds_since_todo = 0
|
||||
|
||||
results.append({"type": "tool_result", "tool_use_id": block.id,
|
||||
"content": output})
|
||||
|
||||
output = execute_tool(block)
|
||||
results.append({
|
||||
"type": "tool_result",
|
||||
"tool_use_id": block.id,
|
||||
"content": output,
|
||||
})
|
||||
messages.append({"role": "user", "content": results})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("s07: Skill Loading — catalog in SYSTEM, content on demand")
|
||||
print("Type a question, press Enter. Type q to quit.\n")
|
||||
print("s07: Skill Loading - catalog first, full content on demand")
|
||||
print("Enter a question, press Enter to send. Type q to quit.\n")
|
||||
|
||||
history = []
|
||||
while True:
|
||||
|
||||
@@ -1,110 +1,55 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 380" font-family="system-ui, -apple-system, sans-serif">
|
||||
<defs>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#555"/>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="7" markerHeight="7" orient="auto">
|
||||
<path d="M0 0 L10 5 L0 10 Z" fill="#2563eb"/>
|
||||
</marker>
|
||||
<marker id="arrow-blue" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#2563eb"/>
|
||||
</marker>
|
||||
<marker id="arrow-green" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#16a34a"/>
|
||||
</marker>
|
||||
<marker id="arrow-amber" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#d97706"/>
|
||||
</marker>
|
||||
<linearGradient id="header" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0%" stop-color="#1e3a5f"/>
|
||||
<stop offset="100%" stop-color="#2563eb"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="800" height="380" rx="8" fill="#fafbfc"/>
|
||||
<rect width="800" height="48" rx="8" fill="#1e3a5f"/>
|
||||
<rect y="40" width="800" height="8" fill="#1e3a5f"/>
|
||||
<text x="400" y="31" fill="#fff" font-size="16" font-weight="700" text-anchor="middle">Skill Loading — Catalog in system prompt, full content in tool_result</text>
|
||||
|
||||
<!-- Background -->
|
||||
<rect width="800" height="380" fill="#fafbfc" rx="8"/>
|
||||
<text x="32" y="78" fill="#475569" font-size="11" font-weight="700">Startup</text>
|
||||
<rect x="32" y="90" width="120" height="54" rx="6" fill="#fff" stroke="#64748b"/>
|
||||
<text x="92" y="112" fill="#1e293b" font-size="11" font-weight="700" text-anchor="middle">skills/</text>
|
||||
<text x="92" y="130" fill="#64748b" font-size="9" text-anchor="middle">*/SKILL.md</text>
|
||||
<line x1="152" y1="117" x2="195" y2="117" stroke="#2563eb" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<rect x="198" y="90" width="130" height="54" rx="6" fill="#eff6ff" stroke="#2563eb"/>
|
||||
<text x="263" y="112" fill="#1e3a5f" font-size="11" font-weight="700" text-anchor="middle">SkillLoader.scan</text>
|
||||
<text x="263" y="130" fill="#64748b" font-size="9" text-anchor="middle">parse frontmatter</text>
|
||||
<line x1="328" y1="117" x2="371" y2="117" stroke="#2563eb" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<rect x="374" y="90" width="130" height="54" rx="6" fill="#eff6ff" stroke="#2563eb"/>
|
||||
<text x="439" y="112" fill="#1e3a5f" font-size="11" font-weight="700" text-anchor="middle">Skill catalog</text>
|
||||
<text x="439" y="130" fill="#64748b" font-size="9" text-anchor="middle">name + description</text>
|
||||
<line x1="504" y1="117" x2="547" y2="117" stroke="#2563eb" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<rect x="550" y="90" width="218" height="54" rx="6" fill="#ecfdf5" stroke="#059669"/>
|
||||
<text x="659" y="112" fill="#065f46" font-size="11" font-weight="700" text-anchor="middle">system prompt</text>
|
||||
<text x="659" y="130" fill="#64748b" font-size="9" text-anchor="middle">base instructions + catalog</text>
|
||||
|
||||
<!-- Title -->
|
||||
<rect x="0" y="0" width="800" height="48" fill="url(#header)" rx="8"/>
|
||||
<rect x="0" y="40" width="800" height="8" fill="url(#header)"/>
|
||||
<text x="400" y="31" fill="#fff" font-size="16" font-weight="700" text-anchor="middle">Skill Loading — catalog at startup, content on demand</text>
|
||||
<text x="32" y="190" fill="#475569" font-size="11" font-weight="700">Runtime</text>
|
||||
<rect x="32" y="204" width="90" height="48" rx="6" fill="#fff" stroke="#2563eb"/>
|
||||
<text x="77" y="233" fill="#1e293b" font-size="11" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<line x1="122" y1="228" x2="149" y2="228" stroke="#2563eb" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<rect x="152" y="204" width="110" height="48" rx="6" fill="#eff6ff" stroke="#2563eb"/>
|
||||
<text x="207" y="224" fill="#1e3a5f" font-size="10" font-weight="700" text-anchor="middle">load_skill</text>
|
||||
<text x="207" y="240" fill="#64748b" font-size="9" text-anchor="middle">name</text>
|
||||
<line x1="262" y1="228" x2="289" y2="228" stroke="#2563eb" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<rect x="292" y="204" width="100" height="48" rx="6" fill="#fff" stroke="#64748b"/>
|
||||
<text x="342" y="224" fill="#1e293b" font-size="10" font-weight="700" text-anchor="middle">Registry</text>
|
||||
<text x="342" y="240" fill="#64748b" font-size="9" text-anchor="middle">lookup by name</text>
|
||||
<line x1="392" y1="228" x2="419" y2="228" stroke="#2563eb" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<rect x="422" y="204" width="110" height="48" rx="6" fill="#fff" stroke="#64748b"/>
|
||||
<text x="477" y="224" fill="#1e293b" font-size="10" font-weight="700" text-anchor="middle">Full content</text>
|
||||
<text x="477" y="240" fill="#64748b" font-size="9" text-anchor="middle">SKILL.md</text>
|
||||
<line x1="532" y1="228" x2="559" y2="228" stroke="#2563eb" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<rect x="562" y="204" width="100" height="48" rx="6" fill="#ecfdf5" stroke="#059669"/>
|
||||
<text x="612" y="224" fill="#065f46" font-size="10" font-weight="700" text-anchor="middle">tool_result</text>
|
||||
<text x="612" y="240" fill="#64748b" font-size="9" text-anchor="middle">returned to model</text>
|
||||
<line x1="662" y1="228" x2="689" y2="228" stroke="#2563eb" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<rect x="692" y="204" width="76" height="48" rx="6" fill="#fff" stroke="#2563eb"/>
|
||||
<text x="730" y="224" fill="#1e293b" font-size="10" font-weight="700" text-anchor="middle">messages[]</text>
|
||||
<text x="730" y="240" fill="#64748b" font-size="9" text-anchor="middle">append</text>
|
||||
|
||||
<!-- ===== History preserved ===== -->
|
||||
<text x="50" y="96" fill="#94a3b8" font-size="11" font-weight="600">History preserved</text>
|
||||
|
||||
<!-- messages[] -->
|
||||
<rect x="40" y="108" width="110" height="44" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="95" y="135" fill="#1e3a5f" font-size="12" font-weight="600" text-anchor="middle">messages[]</text>
|
||||
|
||||
<!-- → LLM -->
|
||||
<line x1="150" y1="130" x2="198" y2="130" stroke="#2563eb" stroke-width="2" marker-end="url(#arrow-blue)"/>
|
||||
|
||||
<!-- LLM -->
|
||||
<rect x="200" y="106" width="110" height="48" rx="8" fill="#fff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="255" y="128" fill="#1e3a5f" font-size="13" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="255" y="146" fill="#64748b" font-size="9" text-anchor="middle">stop_reason=tool_use?</text>
|
||||
|
||||
<!-- No → return -->
|
||||
<line x1="255" y1="154" x2="255" y2="178" stroke="#2563eb" stroke-width="2" marker-end="url(#arrow-blue)"/>
|
||||
<text x="268" y="172" fill="#2563eb" font-size="9" font-weight="600">No</text>
|
||||
<rect x="200" y="180" width="110" height="28" rx="14" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="255" y="198" fill="#1e3a5f" font-size="11" font-weight="600" text-anchor="middle">Return result</text>
|
||||
|
||||
<!-- Yes → PreToolUse -->
|
||||
<line x1="310" y1="130" x2="348" y2="130" stroke="#555" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<text x="325" y="122" fill="#d97706" font-size="9" font-weight="600">Yes</text>
|
||||
|
||||
<!-- PreToolUse (s04) -->
|
||||
<rect x="350" y="108" width="90" height="44" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="395" y="128" fill="#1e3a5f" font-size="9" font-weight="600" text-anchor="middle">trigger_hooks</text>
|
||||
<text x="395" y="142" fill="#64748b" font-size="8" text-anchor="middle">PreToolUse</text>
|
||||
|
||||
<!-- → TOOL_HANDLERS -->
|
||||
<line x1="440" y1="130" x2="488" y2="130" stroke="#555" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- ===== TOOL_HANDLERS ===== -->
|
||||
<rect x="490" y="88" width="120" height="130" rx="10" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="550" y="108" fill="#1e3a5f" font-size="10" font-weight="700" text-anchor="middle">TOOL_HANDLERS</text>
|
||||
|
||||
<!-- s06 tools -->
|
||||
<rect x="500" y="116" width="100" height="18" rx="3" fill="#fff" stroke="#2563eb" stroke-width="0.8"/>
|
||||
<text x="550" y="129" fill="#1e3a5f" font-size="8" text-anchor="middle">bash · read · write</text>
|
||||
<rect x="500" y="138" width="100" height="18" rx="3" fill="#fff" stroke="#2563eb" stroke-width="0.8"/>
|
||||
<text x="550" y="151" fill="#1e3a5f" font-size="8" text-anchor="middle">edit · glob · todo</text>
|
||||
<rect x="500" y="160" width="100" height="18" rx="3" fill="#fff" stroke="#2563eb" stroke-width="0.8"/>
|
||||
<text x="550" y="173" fill="#1e3a5f" font-size="8" text-anchor="middle">task (subagent)</text>
|
||||
|
||||
<rect x="500" y="186" width="100" height="22" rx="3" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
|
||||
<text x="550" y="201" fill="#166534" font-size="9" font-weight="700" text-anchor="middle">load_skill</text>
|
||||
|
||||
<!-- ===== Loop back ===== -->
|
||||
<path d="M 550 218 L 550 270 L 95 270 L 95 152" fill="none" stroke="#555" stroke-width="2" marker-end="url(#arrow)" stroke-dasharray="6,3"/>
|
||||
<text x="320" y="290" fill="#64748b" font-size="10" text-anchor="middle">Results appended to messages[], loop continues</text>
|
||||
|
||||
<!-- ===== s07 two-level injection labels (right side) ===== -->
|
||||
|
||||
<text x="710" y="220" fill="#16a34a" font-size="11" font-weight="700" text-anchor="middle">s07 new</text>
|
||||
|
||||
<!-- ① Startup SYSTEM injection -->
|
||||
<rect x="640" y="86" width="140" height="56" rx="8" fill="#f0fdf4" stroke="#16a34a" stroke-width="1.5"/>
|
||||
<text x="710" y="106" fill="#166534" font-size="10" font-weight="700" text-anchor="middle">① build_system()</text>
|
||||
<text x="710" y="120" fill="#64748b" font-size="8" text-anchor="middle">Scan skills/ first line at startup</text>
|
||||
<text x="710" y="134" fill="#166534" font-size="8" font-weight="600" text-anchor="middle">→ inject SYSTEM prompt</text>
|
||||
|
||||
<!-- ② Runtime load_skill → tool_result -->
|
||||
<rect x="640" y="148" width="140" height="56" rx="8" fill="#f0fdf4" stroke="#16a34a" stroke-width="1.5"/>
|
||||
<text x="710" y="168" fill="#166534" font-size="10" font-weight="700" text-anchor="middle">② load_skill(name)</text>
|
||||
<text x="710" y="182" fill="#64748b" font-size="8" text-anchor="middle">Read full SKILL.md at runtime</text>
|
||||
<text x="710" y="196" fill="#166534" font-size="8" font-weight="600" text-anchor="middle">→ inject tool_result</text>
|
||||
|
||||
<!-- ① → LLM (top connection) -->
|
||||
<path d="M 710 86 L 710 68 L 255 68 L 255 106" fill="none" stroke="#16a34a" stroke-width="1.5" stroke-dasharray="4,3" marker-end="url(#arrow-green)"/>
|
||||
<text x="480" y="64" fill="#166534" font-size="7" font-weight="600">SYSTEM has skill catalog, carried every turn</text>
|
||||
|
||||
<!-- ② → load_skill (short connection) -->
|
||||
<line x1="640" y1="176" x2="600" y2="197" stroke="#16a34a" stroke-width="1.5" stroke-dasharray="4,3"/>
|
||||
|
||||
<!-- ===== Legend ===== -->
|
||||
<rect x="60" y="308" width="680" height="44" rx="6" fill="#f1f5f9"/>
|
||||
<rect x="80" y="322" width="12" height="12" rx="2" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
|
||||
<text x="100" y="332" fill="#334155" font-size="10">History preserved (loop, hooks, TODO, subagent — unchanged)</text>
|
||||
<rect x="80" y="338" width="12" height="12" rx="2" fill="#dcfce7" stroke="#16a34a" stroke-width="1"/>
|
||||
<text x="100" y="348" fill="#334155" font-size="10">s07 new (startup catalog in SYSTEM + load_skill tool)</text>
|
||||
<path d="M730 252 V316 H77 V252" fill="none" stroke="#2563eb" stroke-width="2" stroke-dasharray="6 4" marker-end="url(#arrow)"/>
|
||||
<text x="403" y="309" fill="#475569" font-size="10" text-anchor="middle">The next LLM call receives the updated messages[]</text>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 4.7 KiB |
@@ -1,110 +1,55 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 380" font-family="system-ui, -apple-system, sans-serif">
|
||||
<defs>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#555"/>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="7" markerHeight="7" orient="auto">
|
||||
<path d="M0 0 L10 5 L0 10 Z" fill="#2563eb"/>
|
||||
</marker>
|
||||
<marker id="arrow-blue" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#2563eb"/>
|
||||
</marker>
|
||||
<marker id="arrow-green" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#16a34a"/>
|
||||
</marker>
|
||||
<marker id="arrow-amber" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#d97706"/>
|
||||
</marker>
|
||||
<linearGradient id="header" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0%" stop-color="#1e3a5f"/>
|
||||
<stop offset="100%" stop-color="#2563eb"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="800" height="380" rx="8" fill="#fafbfc"/>
|
||||
<rect width="800" height="48" rx="8" fill="#1e3a5f"/>
|
||||
<rect y="40" width="800" height="8" fill="#1e3a5f"/>
|
||||
<text x="400" y="31" fill="#fff" font-size="16" font-weight="700" text-anchor="middle">Skill Loading — カタログは system prompt、完全な内容は tool_result</text>
|
||||
|
||||
<!-- 背景 -->
|
||||
<rect width="800" height="380" fill="#fafbfc" rx="8"/>
|
||||
<text x="32" y="78" fill="#475569" font-size="11" font-weight="700">起動時</text>
|
||||
<rect x="32" y="90" width="120" height="54" rx="6" fill="#fff" stroke="#64748b"/>
|
||||
<text x="92" y="112" fill="#1e293b" font-size="11" font-weight="700" text-anchor="middle">skills/</text>
|
||||
<text x="92" y="130" fill="#64748b" font-size="9" text-anchor="middle">*/SKILL.md</text>
|
||||
<line x1="152" y1="117" x2="195" y2="117" stroke="#2563eb" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<rect x="198" y="90" width="130" height="54" rx="6" fill="#eff6ff" stroke="#2563eb"/>
|
||||
<text x="263" y="112" fill="#1e3a5f" font-size="11" font-weight="700" text-anchor="middle">SkillLoader.scan</text>
|
||||
<text x="263" y="130" fill="#64748b" font-size="9" text-anchor="middle">frontmatter を解析</text>
|
||||
<line x1="328" y1="117" x2="371" y2="117" stroke="#2563eb" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<rect x="374" y="90" width="130" height="54" rx="6" fill="#eff6ff" stroke="#2563eb"/>
|
||||
<text x="439" y="112" fill="#1e3a5f" font-size="11" font-weight="700" text-anchor="middle">スキルカタログ</text>
|
||||
<text x="439" y="130" fill="#64748b" font-size="9" text-anchor="middle">name + description</text>
|
||||
<line x1="504" y1="117" x2="547" y2="117" stroke="#2563eb" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<rect x="550" y="90" width="218" height="54" rx="6" fill="#ecfdf5" stroke="#059669"/>
|
||||
<text x="659" y="112" fill="#065f46" font-size="11" font-weight="700" text-anchor="middle">system prompt</text>
|
||||
<text x="659" y="130" fill="#64748b" font-size="9" text-anchor="middle">基本指示 + カタログ</text>
|
||||
|
||||
<!-- タイトル -->
|
||||
<rect x="0" y="0" width="800" height="48" fill="url(#header)" rx="8"/>
|
||||
<rect x="0" y="40" width="800" height="8" fill="url(#header)"/>
|
||||
<text x="400" y="31" fill="#fff" font-size="16" font-weight="700" text-anchor="middle">Skill Loading — 起動時にカタログ注入、実行時にオンデマンド読み込み</text>
|
||||
<text x="32" y="190" fill="#475569" font-size="11" font-weight="700">実行時</text>
|
||||
<rect x="32" y="204" width="90" height="48" rx="6" fill="#fff" stroke="#2563eb"/>
|
||||
<text x="77" y="233" fill="#1e293b" font-size="11" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<line x1="122" y1="228" x2="149" y2="228" stroke="#2563eb" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<rect x="152" y="204" width="110" height="48" rx="6" fill="#eff6ff" stroke="#2563eb"/>
|
||||
<text x="207" y="224" fill="#1e3a5f" font-size="10" font-weight="700" text-anchor="middle">load_skill</text>
|
||||
<text x="207" y="240" fill="#64748b" font-size="9" text-anchor="middle">name</text>
|
||||
<line x1="262" y1="228" x2="289" y2="228" stroke="#2563eb" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<rect x="292" y="204" width="100" height="48" rx="6" fill="#fff" stroke="#64748b"/>
|
||||
<text x="342" y="224" fill="#1e293b" font-size="10" font-weight="700" text-anchor="middle">レジストリ</text>
|
||||
<text x="342" y="240" fill="#64748b" font-size="9" text-anchor="middle">名前で検索</text>
|
||||
<line x1="392" y1="228" x2="419" y2="228" stroke="#2563eb" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<rect x="422" y="204" width="110" height="48" rx="6" fill="#fff" stroke="#64748b"/>
|
||||
<text x="477" y="224" fill="#1e293b" font-size="10" font-weight="700" text-anchor="middle">完全な内容</text>
|
||||
<text x="477" y="240" fill="#64748b" font-size="9" text-anchor="middle">SKILL.md</text>
|
||||
<line x1="532" y1="228" x2="559" y2="228" stroke="#2563eb" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<rect x="562" y="204" width="100" height="48" rx="6" fill="#ecfdf5" stroke="#059669"/>
|
||||
<text x="612" y="224" fill="#065f46" font-size="10" font-weight="700" text-anchor="middle">tool_result</text>
|
||||
<text x="612" y="240" fill="#64748b" font-size="9" text-anchor="middle">モデルへ返す</text>
|
||||
<line x1="662" y1="228" x2="689" y2="228" stroke="#2563eb" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<rect x="692" y="204" width="76" height="48" rx="6" fill="#fff" stroke="#2563eb"/>
|
||||
<text x="730" y="224" fill="#1e293b" font-size="10" font-weight="700" text-anchor="middle">messages[]</text>
|
||||
<text x="730" y="240" fill="#64748b" font-size="9" text-anchor="middle">追加</text>
|
||||
|
||||
<!-- ===== 過去章を保持 ===== -->
|
||||
<text x="50" y="96" fill="#94a3b8" font-size="11" font-weight="600">過去章を保持</text>
|
||||
|
||||
<!-- messages[] -->
|
||||
<rect x="40" y="108" width="110" height="44" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="95" y="135" fill="#1e3a5f" font-size="12" font-weight="600" text-anchor="middle">messages[]</text>
|
||||
|
||||
<!-- → LLM -->
|
||||
<line x1="150" y1="130" x2="198" y2="130" stroke="#2563eb" stroke-width="2" marker-end="url(#arrow-blue)"/>
|
||||
|
||||
<!-- LLM -->
|
||||
<rect x="200" y="106" width="110" height="48" rx="8" fill="#fff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="255" y="128" fill="#1e3a5f" font-size="13" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="255" y="146" fill="#64748b" font-size="9" text-anchor="middle">stop_reason=tool_use?</text>
|
||||
|
||||
<!-- No → 戻る -->
|
||||
<line x1="255" y1="154" x2="255" y2="178" stroke="#2563eb" stroke-width="2" marker-end="url(#arrow-blue)"/>
|
||||
<text x="268" y="172" fill="#2563eb" font-size="9" font-weight="600">No</text>
|
||||
<rect x="200" y="180" width="110" height="28" rx="14" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="255" y="198" fill="#1e3a5f" font-size="11" font-weight="600" text-anchor="middle">結果を返す</text>
|
||||
|
||||
<!-- Yes → PreToolUse -->
|
||||
<line x1="310" y1="130" x2="348" y2="130" stroke="#555" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<text x="325" y="122" fill="#d97706" font-size="9" font-weight="600">Yes</text>
|
||||
|
||||
<!-- PreToolUse (s04) -->
|
||||
<rect x="350" y="108" width="90" height="44" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="395" y="128" fill="#1e3a5f" font-size="9" font-weight="600" text-anchor="middle">trigger_hooks</text>
|
||||
<text x="395" y="142" fill="#64748b" font-size="8" text-anchor="middle">PreToolUse</text>
|
||||
|
||||
<!-- → TOOL_HANDLERS -->
|
||||
<line x1="440" y1="130" x2="488" y2="130" stroke="#555" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- ===== TOOL_HANDLERS ===== -->
|
||||
<rect x="490" y="88" width="120" height="130" rx="10" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="550" y="108" fill="#1e3a5f" font-size="10" font-weight="700" text-anchor="middle">TOOL_HANDLERS</text>
|
||||
|
||||
<!-- s06 ツール -->
|
||||
<rect x="500" y="116" width="100" height="18" rx="3" fill="#fff" stroke="#2563eb" stroke-width="0.8"/>
|
||||
<text x="550" y="129" fill="#1e3a5f" font-size="8" text-anchor="middle">bash · read · write</text>
|
||||
<rect x="500" y="138" width="100" height="18" rx="3" fill="#fff" stroke="#2563eb" stroke-width="0.8"/>
|
||||
<text x="550" y="151" fill="#1e3a5f" font-size="8" text-anchor="middle">edit · glob · todo</text>
|
||||
<rect x="500" y="160" width="100" height="18" rx="3" fill="#fff" stroke="#2563eb" stroke-width="0.8"/>
|
||||
<text x="550" y="173" fill="#1e3a5f" font-size="8" text-anchor="middle">task (subagent)</text>
|
||||
|
||||
<rect x="500" y="186" width="100" height="22" rx="3" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
|
||||
<text x="550" y="201" fill="#166534" font-size="9" font-weight="700" text-anchor="middle">load_skill</text>
|
||||
|
||||
<!-- ===== ループバック ===== -->
|
||||
<path d="M 550 218 L 550 270 L 95 270 L 95 152" fill="none" stroke="#555" stroke-width="2" marker-end="url(#arrow)" stroke-dasharray="6,3"/>
|
||||
<text x="320" y="290" fill="#64748b" font-size="10" text-anchor="middle">結果を messages[] に追加、ループ継続</text>
|
||||
|
||||
<!-- ===== s07 2 層注入ラベル(右側) ===== -->
|
||||
|
||||
<text x="710" y="220" fill="#16a34a" font-size="11" font-weight="700" text-anchor="middle">s07 新規</text>
|
||||
|
||||
<!-- ① 起動時 SYSTEM 注入 -->
|
||||
<rect x="640" y="86" width="140" height="56" rx="8" fill="#f0fdf4" stroke="#16a34a" stroke-width="1.5"/>
|
||||
<text x="710" y="106" fill="#166534" font-size="10" font-weight="700" text-anchor="middle">① build_system()</text>
|
||||
<text x="710" y="120" fill="#64748b" font-size="8" text-anchor="middle">起動時に skills/ の 1 行目をスキャン</text>
|
||||
<text x="710" y="134" fill="#166534" font-size="8" font-weight="600" text-anchor="middle">→ SYSTEM プロンプトに注入</text>
|
||||
|
||||
<!-- ② 実行時 load_skill → tool_result -->
|
||||
<rect x="640" y="148" width="140" height="56" rx="8" fill="#f0fdf4" stroke="#16a34a" stroke-width="1.5"/>
|
||||
<text x="710" y="168" fill="#166534" font-size="10" font-weight="700" text-anchor="middle">② load_skill(name)</text>
|
||||
<text x="710" y="182" fill="#64748b" font-size="8" text-anchor="middle">実行時に完全な SKILL.md を読み取り</text>
|
||||
<text x="710" y="196" fill="#166534" font-size="8" font-weight="600" text-anchor="middle">→ tool_result に注入</text>
|
||||
|
||||
<!-- ① → LLM(上部接続) -->
|
||||
<path d="M 710 86 L 710 68 L 255 68 L 255 106" fill="none" stroke="#16a34a" stroke-width="1.5" stroke-dasharray="4,3" marker-end="url(#arrow-green)"/>
|
||||
<text x="480" y="64" fill="#166534" font-size="7" font-weight="600">SYSTEM にスキルカタログ、毎ターン携帯</text>
|
||||
|
||||
<!-- ② → load_skill(短接続) -->
|
||||
<line x1="640" y1="176" x2="600" y2="197" stroke="#16a34a" stroke-width="1.5" stroke-dasharray="4,3"/>
|
||||
|
||||
<!-- ===== 凡例 ===== -->
|
||||
<rect x="60" y="308" width="680" height="44" rx="6" fill="#f1f5f9"/>
|
||||
<rect x="80" y="322" width="12" height="12" rx="2" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
|
||||
<text x="100" y="332" fill="#334155" font-size="10">過去章を保持(ループ、フック、TODO、サブ Agent — 変更なし)</text>
|
||||
<rect x="80" y="338" width="12" height="12" rx="2" fill="#dcfce7" stroke="#16a34a" stroke-width="1"/>
|
||||
<text x="100" y="348" fill="#334155" font-size="10">s07 新規(起動時カタログ注入 SYSTEM + load_skill ツール)</text>
|
||||
<path d="M730 252 V316 H77 V252" fill="none" stroke="#2563eb" stroke-width="2" stroke-dasharray="6 4" marker-end="url(#arrow)"/>
|
||||
<text x="403" y="309" fill="#475569" font-size="10" text-anchor="middle">次の LLM 呼び出しは更新後の messages[] を受け取る</text>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 4.7 KiB |
@@ -1,110 +1,55 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 380" font-family="system-ui, -apple-system, sans-serif">
|
||||
<defs>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#555"/>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="7" markerHeight="7" orient="auto">
|
||||
<path d="M0 0 L10 5 L0 10 Z" fill="#2563eb"/>
|
||||
</marker>
|
||||
<marker id="arrow-blue" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#2563eb"/>
|
||||
</marker>
|
||||
<marker id="arrow-green" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#16a34a"/>
|
||||
</marker>
|
||||
<marker id="arrow-amber" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#d97706"/>
|
||||
</marker>
|
||||
<linearGradient id="header" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0%" stop-color="#1e3a5f"/>
|
||||
<stop offset="100%" stop-color="#2563eb"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="800" height="380" rx="8" fill="#fafbfc"/>
|
||||
<rect width="800" height="48" rx="8" fill="#1e3a5f"/>
|
||||
<rect y="40" width="800" height="8" fill="#1e3a5f"/>
|
||||
<text x="400" y="31" fill="#fff" font-size="16" font-weight="700" text-anchor="middle">Skill Loading — 目录进入 system prompt,完整内容进入 tool_result</text>
|
||||
|
||||
<!-- 背景 -->
|
||||
<rect width="800" height="380" fill="#fafbfc" rx="8"/>
|
||||
<text x="32" y="78" fill="#475569" font-size="11" font-weight="700">启动时</text>
|
||||
<rect x="32" y="90" width="120" height="54" rx="6" fill="#fff" stroke="#64748b"/>
|
||||
<text x="92" y="112" fill="#1e293b" font-size="11" font-weight="700" text-anchor="middle">skills/</text>
|
||||
<text x="92" y="130" fill="#64748b" font-size="9" text-anchor="middle">*/SKILL.md</text>
|
||||
<line x1="152" y1="117" x2="195" y2="117" stroke="#2563eb" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<rect x="198" y="90" width="130" height="54" rx="6" fill="#eff6ff" stroke="#2563eb"/>
|
||||
<text x="263" y="112" fill="#1e3a5f" font-size="11" font-weight="700" text-anchor="middle">SkillLoader.scan</text>
|
||||
<text x="263" y="130" fill="#64748b" font-size="9" text-anchor="middle">解析 frontmatter</text>
|
||||
<line x1="328" y1="117" x2="371" y2="117" stroke="#2563eb" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<rect x="374" y="90" width="130" height="54" rx="6" fill="#eff6ff" stroke="#2563eb"/>
|
||||
<text x="439" y="112" fill="#1e3a5f" font-size="11" font-weight="700" text-anchor="middle">技能目录</text>
|
||||
<text x="439" y="130" fill="#64748b" font-size="9" text-anchor="middle">name + description</text>
|
||||
<line x1="504" y1="117" x2="547" y2="117" stroke="#2563eb" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<rect x="550" y="90" width="218" height="54" rx="6" fill="#ecfdf5" stroke="#059669"/>
|
||||
<text x="659" y="112" fill="#065f46" font-size="11" font-weight="700" text-anchor="middle">system prompt</text>
|
||||
<text x="659" y="130" fill="#64748b" font-size="9" text-anchor="middle">基础指令 + 技能目录</text>
|
||||
|
||||
<!-- 标题 -->
|
||||
<rect x="0" y="0" width="800" height="48" fill="url(#header)" rx="8"/>
|
||||
<rect x="0" y="40" width="800" height="8" fill="url(#header)"/>
|
||||
<text x="400" y="31" fill="#fff" font-size="16" font-weight="700" text-anchor="middle">Skill Loading — 启动时注入目录,运行时按需加载内容</text>
|
||||
<text x="32" y="190" fill="#475569" font-size="11" font-weight="700">运行时</text>
|
||||
<rect x="32" y="204" width="90" height="48" rx="6" fill="#fff" stroke="#2563eb"/>
|
||||
<text x="77" y="233" fill="#1e293b" font-size="11" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<line x1="122" y1="228" x2="149" y2="228" stroke="#2563eb" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<rect x="152" y="204" width="110" height="48" rx="6" fill="#eff6ff" stroke="#2563eb"/>
|
||||
<text x="207" y="224" fill="#1e3a5f" font-size="10" font-weight="700" text-anchor="middle">load_skill</text>
|
||||
<text x="207" y="240" fill="#64748b" font-size="9" text-anchor="middle">name</text>
|
||||
<line x1="262" y1="228" x2="289" y2="228" stroke="#2563eb" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<rect x="292" y="204" width="100" height="48" rx="6" fill="#fff" stroke="#64748b"/>
|
||||
<text x="342" y="224" fill="#1e293b" font-size="10" font-weight="700" text-anchor="middle">注册表查询</text>
|
||||
<text x="342" y="240" fill="#64748b" font-size="9" text-anchor="middle">按名称</text>
|
||||
<line x1="392" y1="228" x2="419" y2="228" stroke="#2563eb" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<rect x="422" y="204" width="110" height="48" rx="6" fill="#fff" stroke="#64748b"/>
|
||||
<text x="477" y="224" fill="#1e293b" font-size="10" font-weight="700" text-anchor="middle">完整内容</text>
|
||||
<text x="477" y="240" fill="#64748b" font-size="9" text-anchor="middle">SKILL.md</text>
|
||||
<line x1="532" y1="228" x2="559" y2="228" stroke="#2563eb" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<rect x="562" y="204" width="100" height="48" rx="6" fill="#ecfdf5" stroke="#059669"/>
|
||||
<text x="612" y="224" fill="#065f46" font-size="10" font-weight="700" text-anchor="middle">tool_result</text>
|
||||
<text x="612" y="240" fill="#64748b" font-size="9" text-anchor="middle">返回模型</text>
|
||||
<line x1="662" y1="228" x2="689" y2="228" stroke="#2563eb" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<rect x="692" y="204" width="76" height="48" rx="6" fill="#fff" stroke="#2563eb"/>
|
||||
<text x="730" y="224" fill="#1e293b" font-size="10" font-weight="700" text-anchor="middle">messages[]</text>
|
||||
<text x="730" y="240" fill="#64748b" font-size="9" text-anchor="middle">追加</text>
|
||||
|
||||
<!-- ===== 历史章节保留 ===== -->
|
||||
<text x="50" y="96" fill="#94a3b8" font-size="11" font-weight="600">历史章节保留</text>
|
||||
|
||||
<!-- messages[] -->
|
||||
<rect x="40" y="108" width="110" height="44" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="95" y="135" fill="#1e3a5f" font-size="12" font-weight="600" text-anchor="middle">messages[]</text>
|
||||
|
||||
<!-- → LLM -->
|
||||
<line x1="150" y1="130" x2="198" y2="130" stroke="#2563eb" stroke-width="2" marker-end="url(#arrow-blue)"/>
|
||||
|
||||
<!-- LLM -->
|
||||
<rect x="200" y="106" width="110" height="48" rx="8" fill="#fff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="255" y="128" fill="#1e3a5f" font-size="13" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="255" y="146" fill="#64748b" font-size="9" text-anchor="middle">stop_reason=tool_use?</text>
|
||||
|
||||
<!-- 否 → 返回 -->
|
||||
<line x1="255" y1="154" x2="255" y2="178" stroke="#2563eb" stroke-width="2" marker-end="url(#arrow-blue)"/>
|
||||
<text x="268" y="172" fill="#2563eb" font-size="9" font-weight="600">否</text>
|
||||
<rect x="200" y="180" width="110" height="28" rx="14" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="255" y="198" fill="#1e3a5f" font-size="11" font-weight="600" text-anchor="middle">返回结果</text>
|
||||
|
||||
<!-- 是 → PreToolUse -->
|
||||
<line x1="310" y1="130" x2="348" y2="130" stroke="#555" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<text x="325" y="122" fill="#d97706" font-size="9" font-weight="600">是</text>
|
||||
|
||||
<!-- PreToolUse (s04) -->
|
||||
<rect x="350" y="108" width="90" height="44" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="395" y="128" fill="#1e3a5f" font-size="9" font-weight="600" text-anchor="middle">trigger_hooks</text>
|
||||
<text x="395" y="142" fill="#64748b" font-size="8" text-anchor="middle">PreToolUse</text>
|
||||
|
||||
<!-- → TOOL_HANDLERS -->
|
||||
<line x1="440" y1="130" x2="488" y2="130" stroke="#555" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- ===== TOOL_HANDLERS ===== -->
|
||||
<rect x="490" y="88" width="120" height="130" rx="10" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="550" y="108" fill="#1e3a5f" font-size="10" font-weight="700" text-anchor="middle">TOOL_HANDLERS</text>
|
||||
|
||||
<!-- s06 工具 -->
|
||||
<rect x="500" y="116" width="100" height="18" rx="3" fill="#fff" stroke="#2563eb" stroke-width="0.8"/>
|
||||
<text x="550" y="129" fill="#1e3a5f" font-size="8" text-anchor="middle">bash · read · write</text>
|
||||
<rect x="500" y="138" width="100" height="18" rx="3" fill="#fff" stroke="#2563eb" stroke-width="0.8"/>
|
||||
<text x="550" y="151" fill="#1e3a5f" font-size="8" text-anchor="middle">edit · glob · todo</text>
|
||||
<rect x="500" y="160" width="100" height="18" rx="3" fill="#fff" stroke="#2563eb" stroke-width="0.8"/>
|
||||
<text x="550" y="173" fill="#1e3a5f" font-size="8" text-anchor="middle">task (subagent)</text>
|
||||
|
||||
<rect x="500" y="186" width="100" height="22" rx="3" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
|
||||
<text x="550" y="201" fill="#166534" font-size="9" font-weight="700" text-anchor="middle">load_skill</text>
|
||||
|
||||
<!-- ===== 回环 ===== -->
|
||||
<path d="M 550 218 L 550 270 L 95 270 L 95 152" fill="none" stroke="#555" stroke-width="2" marker-end="url(#arrow)" stroke-dasharray="6,3"/>
|
||||
<text x="320" y="290" fill="#64748b" font-size="10" text-anchor="middle">结果追加到 messages[],循环继续</text>
|
||||
|
||||
<!-- ===== s07 两级注入标注(右侧) ===== -->
|
||||
|
||||
<text x="710" y="220" fill="#16a34a" font-size="11" font-weight="700" text-anchor="middle">s07 新增</text>
|
||||
|
||||
<!-- ① 启动时注入 SYSTEM -->
|
||||
<rect x="640" y="86" width="140" height="56" rx="8" fill="#f0fdf4" stroke="#16a34a" stroke-width="1.5"/>
|
||||
<text x="710" y="106" fill="#166534" font-size="10" font-weight="700" text-anchor="middle">① build_system()</text>
|
||||
<text x="710" y="120" fill="#64748b" font-size="8" text-anchor="middle">启动时扫描 skills/ 第一行</text>
|
||||
<text x="710" y="134" fill="#166534" font-size="8" font-weight="600" text-anchor="middle">→ 注入 SYSTEM prompt</text>
|
||||
|
||||
<!-- ② 运行时 load_skill → tool_result -->
|
||||
<rect x="640" y="148" width="140" height="56" rx="8" fill="#f0fdf4" stroke="#16a34a" stroke-width="1.5"/>
|
||||
<text x="710" y="168" fill="#166534" font-size="10" font-weight="700" text-anchor="middle">② load_skill(name)</text>
|
||||
<text x="710" y="182" fill="#64748b" font-size="8" text-anchor="middle">运行时读完整 SKILL.md</text>
|
||||
<text x="710" y="196" fill="#166534" font-size="8" font-weight="600" text-anchor="middle">→ 注入 tool_result</text>
|
||||
|
||||
<!-- ① → LLM(顶部连线) -->
|
||||
<path d="M 710 86 L 710 68 L 255 68 L 255 106" fill="none" stroke="#16a34a" stroke-width="1.5" stroke-dasharray="4,3" marker-end="url(#arrow-green)"/>
|
||||
<text x="480" y="64" fill="#166534" font-size="7" font-weight="600">SYSTEM 含技能目录,每轮都带</text>
|
||||
|
||||
<!-- ② → load_skill(短连接线) -->
|
||||
<line x1="640" y1="176" x2="600" y2="197" stroke="#16a34a" stroke-width="1.5" stroke-dasharray="4,3"/>
|
||||
|
||||
<!-- ===== 图例 ===== -->
|
||||
<rect x="60" y="308" width="680" height="44" rx="6" fill="#f1f5f9"/>
|
||||
<rect x="80" y="322" width="12" height="12" rx="2" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
|
||||
<text x="100" y="332" fill="#334155" font-size="10">历史章节保留(循环、钩子、TODO、subagent — 完全不变)</text>
|
||||
<rect x="80" y="338" width="12" height="12" rx="2" fill="#dcfce7" stroke="#16a34a" stroke-width="1"/>
|
||||
<text x="100" y="348" fill="#334155" font-size="10">s07 新增(启动时目录注入 SYSTEM + load_skill 工具)</text>
|
||||
<path d="M730 252 V316 H77 V252" fill="none" stroke="#2563eb" stroke-width="2" stroke-dasharray="6 4" marker-end="url(#arrow)"/>
|
||||
<text x="403" y="309" fill="#475569" font-size="10" text-anchor="middle">下一次 LLM 调用继续使用更新后的 messages[]</text>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 4.7 KiB |
Reference in New Issue
Block a user