Consolidate agent harness course into 19 lessons

This commit is contained in:
Haoran
2026-08-04 02:25:40 +08:00
parent 2ad77cee19
commit b36dbcd84f
168 changed files with 6544 additions and 10400 deletions

View File

@@ -2,32 +2,34 @@
[English](README.md) · [中文](README.zh.md) · [日本語](README.ja.md)
s01 → ... → s13 → s14 → `s15` → [s16](../s16_autonomous_agents/) → s17 → s18 → s19 → s20 → s21
s01 → ... → s13 → s14 → `s15` → [s16](../s16_mcp_plugin/) → s17 → s18 → s19
> *「1 つの Agent だけでは扱いきれないなら、チームメイトで分担する。」* — 永続チームメイト、メッセージ配信、協調プロトコル。
> *「1 つの Agent で仕事全体を抱えきれないなら、チームメイトで分担する。」* — 永続チームメイト、共有タスクの Claim、任意の worktree、協調プロトコル。
>
> **Harness レイヤー**チーム — 複数 Agent を並行動作させながら制御を保つ
> **Harness レイヤー**Team — 複数 Agent が Lead の管理下で仕事を分担し、状態を共有する仕組み
---
## 問題
Agent にバックエンド全体のリファクタリングを頼む場合、設定読み込み、認証、テストを同時に扱うことになる。1 つの Agent 順番に処理することもできるが、時間がかかり、初期の詳細は徐々にコンテキストから抜けていく。
Agent にバックエンド全体のリファクタリングを依頼するとする。作業範囲は設定読み込み、認証、テストにまたがる。1 つの Agent でも順番に処理できるが、時間がかかり、初期の詳細は少しずつコンテキストから抜けていく。
このような仕事は並列化に向いている。しかし、通常のユーザーはチーム構成ではなく目的だけを伝える:
この仕事は並列化に向いている。ただし、ユーザーは通常、チーム構成ではなく目を伝える:
```text
このサンプルバックエンドをリファクタリングしてください。
設定読み込み、認証ロジック、テストを整理し、
既存インターフェースを保ったままテストを通してください。
設定読み込み、認証、テストを整理し、既存インターフェースを保ち、
テストが通ることを確認してください。
```
そのため Harness は、単に Agent を増やすだけでなく、次の 4 点を解決する必要がある:
Harness は、つながった 6 つの問題を扱う必要がある:
1. 並列が有効かを誰が判断し、追加 Agent の起動を誰が認するか。
2. チームメイト複数の依頼にまたがって、どう身元とコンテキストを保つか。
3. モデルに受信箱を繰り返し確認させず、結果をどう Lead へ戻すか。
4. 終了と計画承認を、どう追跡可能で強制可能なプロトコルにするか。
1. 並列作業が有効だと誰が判断し、追加 Agent を誰が認するか。
2. チームメイトは、複数の割り当てをまたいで識別子とコンテキストをどう保つか。
3. モデルに受信箱をポーリングさせず、結果を Lead へどう返すのか。
4. IDLE のチームメイトは、次の指示を待たずに ready task を引き受けられるか。
5. 並列編集が衝突し得る時、タスクはどの作業ディレクトリを使うのか。
6. shutdown と計画承認を、追跡できて実際に制約をかけるプロトコルにするにはどうするか。
---
@@ -35,24 +37,25 @@ Agent にバックエンド全体のリファクタリングを頼む場合、
![Agent Teams Overview](images/agent-teams-overview.ja.svg)
s15 は単一 Agent の Harness の外側に、Lead 管理するチームランタイムを追加する:
s15 は単一 Agent の Harness Lead 管理チームランタイムを加える:
- **Lead** はユーザーとの会話を維持し、分担案を示して確認を待つ。
- **チームメイト** は独立した Agent Loop をバックグラウンドスレッドで実行し、作業後は IDLE になる。
- **MessageBus** はファイル受信箱を通して、通常メッセージ、結果、制御イベントを運ぶ。
- **ランタイム配信** は Lead の受信箱を消費し、チームイベントを次のターンへ注入する。
- **協調プロトコル** は `type``request_id`、状態遷移で終了と計画承認を扱う
- **計画ゲート** は、必要な計画が承認されるまで `bash``write_file` を遮断する
- **Lead** はユーザーとの会話を担当し、分担案を示して確認を待つ。
- **チームメイト** は独立した Agent Loop を実行し、WORK と IDLE を行き来する。
- **MessageBus** はファイルベースの受信箱で通常メッセージ、結果、制御イベントを運ぶ。
- **ランタイム配信** は Lead の受信箱を消費し、チームイベントを次のターンへ追加する。
- **共有タスクボード** により、IDLE のチームメイトは ready task を探し、ロック下で Claim できる
- **任意の worktree** は、必要なタスクだけを別の作業ディレクトリへ紐付ける。紐付けのないタスクは通常のリポジトリディレクトリを使う
- **型付きプロトコルと計画ゲート** は shutdown と承認状態を明示し、必要な計画が承認されるまで変更系ツールを止める。
モデルはタスクを理解して分担を決める。コードは配信、ライフサイクル、プロトコル制約を担う
これらはすべて Team Harness レイヤーの一部である。タスク発見のために別の Agent Loop は要らず、worktree が別種の Agent を作るわけでもない
---
## 仕組み
### 1. Lead はチーム案を示し、確認を待つ
### 1. Lead はチーム案を示し、ユーザーの確認を待つ
チームメイト起動、コスト、並行度、ワークスペースを書き換える主体を変える。この境界を通常のツール呼び出しの中に隠してはいけない。Lead の system prompt は次のように定める:
チームメイト起動すると、コスト、並行度、ワークスペースを編集できる主体が変わる。Lead のシステムプロンプトは、その境界を明示する:
```python
"When parallel work would help, first propose a small team with clear "
@@ -60,35 +63,35 @@ s15 は単一 Agent の Harness の外側に、Lead が管理するチームラ
"spawn_teammate before the user confirms."
```
最初の依頼に対して、Lead はまず分担案だけをす:
最初の要求に対して、Lead は分担案だけをす:
```text
次の 3 方向で並行処理することを提案します
- config設定読み込み整理
- auth認証ロジックのリファクタリング
- tests回帰テスト追加
3 つの領域を並行して進めることを提案します
- config設定読み込み整理
- auth認証リファクタリング
- tests回帰テスト追加
確認後にチームメイトを起動します。
```
ユーザーが「始めてください」と返した後、Lead は `spawn_teammate` を呼。ユーザーが目を示し、Lead がチームを設計し、ユーザーが実行境界を確認する。
ユーザーが「始めてください」と返した後、Lead は `spawn_teammate` を呼べる。ユーザーが目を示し、Lead がチームを設計し、ユーザーが実行境界を確認する。
### 2. 各チームメイトは独立したループを持つ
s06 の Subagent は 1 回限りの呼び出しだが、チームメイトは永続する実行単位である
s06 の subagent は 1 回限りの呼び出しである。チームメイトは永続する実行単位
| | s06 Subagent | s15 チームメイト |
| | s06 Subagent | s15 Teammate |
|---|---|---|
| ライフサイクル | 1 回の呼び出し後に終了 | 終了要求まで `WORK → IDLE → WORK` |
| コンテキスト | 1 つのタスクだけ | 複数の依頼をまたいで保持 |
| 通信 | 1 回だけ結果を返す | メッセージを受け取りイベントを送る |
| 協調 | 一方向の委 | Lead との双方向協調 |
| ライフサイクル | 1 回の呼び出し後に終了 | shutdown まで `WORK → IDLE → WORK` |
| コンテキスト | 1 つのタスクだけ存在 | 割り当てをまたいで保持 |
| 通信 | 1 回だけ結果を返す | メッセージを受け取りイベントを送る |
| 協調 | 一方向の委 | Lead との双方向協調 |
`spawn_teammate_thread()` はチームメイトごとに system prompt、messages、ツールを作り、daemon thread でループを実行する。Lead はチームメイトの終了を待たずに、別の依頼や結果を調整できる。
`spawn_teammate_thread()`、各チームメイト専用のシステムプロンプト、messages、ツール、現在の作業ディレクトリ状態を用意し、daemon thread でループを実行する。チームメイトの作業中も Lead は調整を続けられる。`lead``agent` はランタイム識別子として予約されるが、`MessageBus` はコーディネーターの受信箱として `lead` を引き続き受け付ける。
### 3. MessageBus は通信をモデルのコンテキスト外に置く
Lead とチームメイト同じ messages 配列を共有すると、あるチームメイトのツール結果が別のチームメイトの推論へ混ざる。`MessageBus` Agent に `.mailboxes/<name>.jsonl` 受信箱を与える:
Lead とチームメイト同じ messages 配列を共有できない。共有すると、あるチームメイトのツール結果が別のチームメイトの推論へ混ざる。`MessageBus` は Agent ごと`.mailboxes/<name>.jsonl` 受信箱を用意する:
```python
class MessageBus:
@@ -102,21 +105,27 @@ class MessageBus:
"metadata": metadata or {},
}
with self._changed:
append_jsonl(self._path(to_agent), msg)
with open(self._path(to_agent), "a") as f:
f.write(json.dumps(msg) + "\n")
self._changed.notify_all()
def wait_for_messages(self, agent):
def wait_for_messages(self, agent, timeout=None):
deadline = None if timeout is None else time.monotonic() + timeout
with self._changed:
while not self.peek(agent):
self._changed.wait()
remaining = (None if deadline is None
else deadline - time.monotonic())
if remaining is not None and remaining <= 0:
return []
self._changed.wait(remaining)
return self._read_unlocked(agent)
```
ロックは複数スレッドによる受信箱ファイルの破損を防ぐ`Condition` により、IDLE のチームメイトはポーリングせずイベント到着まで待機できる。
ロックは、チームメイトによる受信箱ファイルの並行アクセスを保護する`Condition` はメッセージ到着時にチームメイトを起こし、IDLE 中の短い timeout にも使える。
### 4. 受信イベントはランタイムが自動配信する
### 4. 受信イベントはランタイムが配信する
`read_inbox()`メッセージを読み、受信箱ファイルを削除する。そのため Lead の消費入口`consume_lead_inbox()` だけにする:
`read_inbox()` は受信箱ファイルを読み取って削除するためLead の消費処理`consume_lead_inbox()` だけにする:
```python
def consume_lead_inbox():
@@ -127,33 +136,165 @@ def consume_lead_inbox():
return messages
```
メインループのイベントスレッド、新しいメッセージが届くと Lead を起こす:
メインループの隣で動くイベントスレッド、新しいメッセージの到着時に Lead を起こす:
```text
MessageBus → consume_lead_inbox
→ プロトコル状態を更新
→ [Team events] を history へ注入
→ [Team events] を history に追加
→ Lead の次ターンを開始
```
`check_inbox` はモデルのツールではない。メッセージの到着はランタイムの責務であり、モデルはコンテキストへ配信済みのイベントだけを処理する。
`check_inbox` はモデルのツールではない。メッセージの到着と消費はランタイムが担当し、モデルはコンテキストへ配信済みのイベントを処理する。
### 5. 結果と IDLE は別のイベント
### 5. 結果と IDLE は別のイベントである
チームメイトが 1 件の作業を終えると、ランタイムは次の順序で 2 つのイベントを送る:
チームメイトが 1 つの割り当てを終えると、ランタイムは 2 つのイベントを順に送る:
```text
result: "認証をリファクタリングし、関連テストが通りました。"
idle_notification: "Waiting for more work."
```
`result` は「今回の作業で何が得られたか」、`idle_notification` は「新しい仕事を受けられるか」を表す。1 つの曖昧な「done」では両者を区別できない。
`result` は「この割り当てで何ができたか」、`idle_notification` は「このチームメイトが次の仕事を受けられるか」を表す。曖昧な「完了」だけでは、両方の状態を表せない。
IDLE になったチームメイトは終了しない。通常メッセージ WORK に戻り、`shutdown_request` で終了ハンドシェイクを始める。
IDLE チームメイトは終了しない。直接メッセージか ready task を受けると WORK に戻り、`shutdown_request` を受けると段階的な shutdown handshake を始める。
### 6. 制御メッセージには型と request_id を使う
### 6. IDLE は受信箱を先に確認し、その後 ready task を探す
通常の協調は自由文でよいが、終了と承認を意図の推測に任せてはいけない。制御イベントは構造化する:
IDLE ではメッセージを優先し、その後に共有タスクボードを確認する:
```python
while True:
inbox = BUS.wait_for_messages(name, IDLE_SCAN_INTERVAL)
if inbox:
should_stop = handle_messages(inbox)
if should_stop or messages[-1]["role"] == "user":
break
continue
task = claim_next_task(name)
if task:
messages.append({
"role": "user",
"content": f"[Auto-claimed task {task.id}] {task.subject}",
})
break
```
shutdown、計画承認、Lead からの直接指示は、空き時間に見つけた仕事より先に扱う。メッセージも ready task もなければ、チームメイトは IDLE を続ける。別のチームメイトが前提タスクを完了すると、blocked task が ready になることもある。
### 7. 発見と Claim を分け、Claim はアトミックに行う
走査は候補を探すだけで、状態を変更しない:
```python
def scan_unclaimed_tasks() -> list[Task]:
return [
task for task in list_tasks()
if task.status == "pending"
and task.owner is None
and can_start(task.id)
]
```
候補一覧は一時点の snapshot にすぎない。別のチームメイトも同じタスクを見る可能性があるため、所有権の変更は `task_lock` で保護した `claim_task()` 内で行う:
```python
def claim_task(task_id: str, owner: str) -> str:
with task_lock:
task = load_task(task_id)
if task.status != "pending" or task.owner is not None:
return "Task is no longer available"
if _owner_in_progress(owner):
return "Owner must complete its current task first"
if not can_start(task_id):
return "Task is blocked"
cwd, error = task_worktree_cwd(task)
if error:
return f"Cannot claim {task_id}: {error}"
task.owner = owner
task.status = "in_progress"
save_task(task)
teammate_assignments[owner] = {"task_id": task.id, "cwd": cwd}
return f"Claimed {task.id}"
```
複数のチームメイトが同じ候補を発見しても、`in_progress` へ進められる Claim は 1 つだけである。現在のタスクを完了するまで、チームメイトは次のタスクを Claim できない。worktree の紐付けが壊れている場合、リポジトリディレクトリへ戻さず Claim を失敗させる。
### 8. Claim した仕事は同じ WORK ループを再利用する
Claim に成功すると、ランタイムはタスク ID、件名、説明をチームメイトの messages へ追加する:
```text
ready task が現れる
→ IDLE のチームメイトが発見
→ claim_task が owner と in_progress を記録
→ タスクがチームメイトの messages に入る
→ WORK
→ complete_task
→ result + idle_notification
→ IDLE
```
チームメイトは、Lead が直接割り当てた時と同じモデル呼び出し、ファイルツール、Shell、計画ゲート、結果通知、shutdown protocol を使う。タスク発見は、既存の WORK ループへの別の入口である。
### 9. タスクがツールの作業ディレクトリを選ぶ
`Task.worktree` は任意フィールドである:
```python
@dataclass
class Task:
id: str
subject: str
description: str
status: str
owner: str | None
blockedBy: list[str]
worktree: str | None = None
```
並列編集を別ディレクトリに分けたい時、Lead は worktree を作成してタスクへ紐付けられる:
```python
create_worktree(name="auth-refactor", task_id="task_1234")
```
`create_worktree` は Lead 専用ツールである。pending、owner なし、worktree 未設定のタスクを受け取り、名前、パス、ブランチ、Git registry を確認する。checkout の作成後にだけタスクへ紐付ける。Git が失敗を返しても branch や登録済み checkout が残った場合は partial operation を報告し、task は未紐付けのまま、それらを manual recovery 用に保持する。チームメイトが使うのはタスクツールとファイルツールである。
Claim 時に、解決済みのディレクトリを `teammate_assignments` へ保存する。チームメイトの `bash``read_file``write_file` wrapper は assignment からディレクトリを読む。worktree のないタスクは `WORKDIR` に解決されるため、worktree は opt-in である:
```python
cwd, error = task_worktree_cwd(task)
if not error:
teammate_assignments[owner] = {
"task_id": task.id,
"cwd": cwd,
}
```
`complete_task(task_id, owner)` は、呼び出し元が進行中タスクの owner か確認する。ランタイムが assignment を削除するのは完了に成功した時だけである。失敗時はタスクのディレクトリを維持し、チームメイトが修正して再試行できるようにする。タスクの `worktree` 紐付けは checkout を削除するまで残る。
> Worktree が分離するのは Git の作業ディレクトリとブランチであり、sandbox ではない。Shell コマンドは親プロセスに許可されたパスやリソースへアクセスできる。
### 10. Worktree のクリーンアップはデフォルトで作業を残す
モデル向けの `remove_worktree(name)` tool は、`pending` または `in_progress` のタスクに紐付いた worktree の削除を拒否する。タスク完了後も tracked、untracked、ignored file をすべて未コミットデータとして扱い、clean な checkout だけを `--force` なしで削除する。
低レベルの Python helper は、host が別途ユーザーの明示的な確認を得た場合のために `discard_changes=True` を残すが、この parameter はモデルの tool schema にはない。変更のある worktree は削除せず、user が確認できる状態で残す。どちらの削除経路でも `wt/<name>` ブランチはリポジトリに残り、upstream のない clean な local commit も保持される。削除成功後は checkout が存在しないため、タスクの worktree 紐付けを解除する。
```text
clean worktree → ディレクトリを削除し、wt/<name> ブランチは保持
changed worktree → model tool は拒否し、保持か破棄かを user が決める
pending/running task → 削除を拒否
```
タスク完了と worktree cleanup も分かれている。`complete_task` はタスク結果を記録し、Lead はその後に worktree を確認、merge、keep、remove できる。
### 11. 制御メッセージには型と request_id を使う
通常の協調には自由形式のテキストを使えるが、shutdown と承認を意図の推測に任せるべきではない。これらは構造化メッセージを使う:
![Team Protocols](images/team-protocols-overview.ja.svg)
@@ -171,22 +312,22 @@ class ProtocolState:
pending_requests: dict[str, ProtocolState] = {}
```
終了プロトコルは次の経路を通る
shutdown の流れは次の通り
```text
Lead が pending の shutdown request を作る
→ shutdown_request(request_id) チームメイトへ送
→ チームメイトが現在の手順を終える
→ shutdown_response(request_id) Lead へ返す
→ request_id で元の要求を特定する
→ pending が approved になり、チームメイトループが終了する
→ shutdown_request(request_id) チームメイトの受信箱に入
→ チームメイトが現在のステップを終える
→ shutdown_response(request_id) Lead へ戻る
→ request_id で元の request を特定する
→ pending が approved になり、チームメイトの loop が終了する
```
ID は要求と応答を対応付け、型は誤った応答による状態変更を防ぎ、状態は重複応答の適用を防ぐ。
ID は応答を 1 つの request に対応付け、型は不一致の応答による状態変更を防ぎ、status は同じ応答の二重適用を防ぐ。
### 7. 計画承認は実行も制約する
### 12. 計画承認は実行も制約する
計画プロトコルは逆方向に流れる
計画プロトコルは逆方向に進む
```text
Lead → plan_request
@@ -194,7 +335,7 @@ Lead → plan_request
Lead → plan_approval_response(request_id, approve, feedback)
```
「承認まで待つ」と伝えるだけでは確実なゲートにならない。そこでツール dispatch が計画状態を検査する:
ツール dispatch がゲートを強制する:
```python
def _run_teammate_tool(name, block, handlers):
@@ -206,32 +347,38 @@ def _run_teammate_tool(name, block, handlers):
return handlers[block.name](**block.input)
```
状態が `required``pending``rejected` の間、チームメイトはファイルを読み、計画を提出または修正できるが、Shell 実行ファイル書き込みはできない。承認応答で `approved` になった後にだけツールが解放される。
状態が `required``pending``rejected` の間、チームメイトはファイルを読み、計画を提出または修正できるが、Shell コマンドの実行ファイル書き込みはできない。承認応答で状態が `approved` になると、ツールを使えるようになる。
---
## 一連の実行例
```text
s15 >> このサンプルバックエンドリファクタリングしてください。
設定読み込み、認証、テストを整理し、
既存インターフェースを保ってテストを通してください。
s15 >> バックエンドリファクタリングを共有タスクボードに分解し、
設定、認証、テストを可能な範囲で並行実行してください。
認証には worktree を使い、既存インターフェースを保ち、
テストが通ることを確認してください。
Lead: config、auth、tests の 3 方向で並行処理することを提案します。
チームを開始しますか?
Leadconfig、auth、tests の 3 領域に分けることを提案します。
チームを起動しますか?
s15 >> 始めてください
[teammate] config spawned
[teammate] auth spawned
[teammate] tests spawned
[bus] auth → lead (result) ...
[bus] auth → lead (idle_notification) ...
[task] config created
[task] auth created → worktree auth-refactor
[task] tests created
[teammate] alice spawned
[teammate] bob spawned
[claim] alice → config (cwd: repository)
[claim] bob → auth (cwd: .worktrees/auth-refactor)
[complete] auth
[bus] bob → lead (result) ...
[bus] bob → lead (idle_notification) ...
[wake: 2 team events → new turn]
Lead: 認証の結果を受け取りました。残りの作業調整します。
Lead:認証タスクの結果を受け取りました。残りの作業調整します。
```
端末には、ユーザー要求、Lead の分担、起動、メッセージ、結果、IDLE、終了イベントが表示される。ユーザーが Lead を指したり、受信箱の確認を頼んだりする必要はない。
ターミナルには、ユーザー要求、Lead の提案、タスク状態、Claim、選択されたディレクトリ、結果、IDLE 遷移、制御イベントが表示される。ユーザーが Lead を指したり、受信箱の確認を依頼したりする必要はない。
---
@@ -239,13 +386,15 @@ Lead: 認証の結果を受け取りました。残りの作業も調整しま
| コンポーネント | s14 | s15 |
|---|---|---|
| Agent | 1 つ | 1 つの Lead + 永続チームメイト |
| ユーザーフロー | 依頼を直接実行 | チーム案を示してから起動確認 |
| 通信 | なし | ファイル受信箱 + 自動イベント配信 |
| Agent | 1 つの Agent | 1 つの Lead 永続チームメイト |
| ユーザーフロー | 要求を実行 | チーム案を示してから起動確認 |
| 通信 | なし | ファイル受信箱とランタイム配信 |
| ライフサイクル | 1 つのループ | チームメイトの `WORK / IDLE / shutdown` |
| 共有作業 | Lead の既存タスクツール | IDLE 走査とチームメイトのアトミックな Claim |
| 作業ディレクトリ | リポジトリの `WORKDIR` | デフォルトは `WORKDIR`、タスクごとに worktree を選択可能 |
| 結果通知 | 現在の Agent の出力 | `result``idle_notification` を分離 |
| 制御 | なし | 終了と計画承認プロトコル |
| 強制 | チーム制約なし | 必須計画が変更系ツールをゲート |
| 制御 | なし | 型付き shutdown と計画承認プロトコル |
| 強制 | チーム向け制約なし | 必須計画が変更系ツールをゲート |
---
@@ -256,12 +405,12 @@ cd learn-claude-code
python s15_agent_teams/code.py
```
まず通常の依頼を入力する:
通常の要求を入力する:
```text
このサンプルバックエンドリファクタリングしてください。
設定読み込み、認証ロジック、テストを整理し、
既存インターフェースを保ったままテストを通してください。
バックエンドリファクタリングを共有タスクボードへ分解し、依存関係が
許す範囲で設定、認証、テストを並行実行してください。認証には worktree
を使い、既存インターフェースを維持して、最後に結果をまとめてください。
```
Lead がチーム案を示したら、次のように返す:
@@ -270,14 +419,16 @@ Lead がチーム案を示したら、次のように返す:
始めてください
```
`spawned``result``idle_notification``plan_approval_*``shutdown_*` の各イベントと、`.mailboxes/` のファイルが生成・消費される流れを確認する。
`.tasks/``pending``in_progress``completed` と変化する様子、`.mailboxes/``result``idle_notification` を配信する様子、紐付けたタスクにだけ `.worktrees/` が作られることを確認する。直接メッセージがタスクボード走査より優先されることと、`complete_task` の失敗後もチームメイトの作業ディレクトリが変わらないことも確認できる。
---
## 次へ
s15 では、Lead が各チームメイトへ明示的に仕事を割り当てる。次のセッションでは共有タスクボードを IDLE のチームメイトに公開し、実行可能な仕事を自ら見つけて claim できるようにする。
チームランタイムは、委譲、共有タスクの Claim、任意の作業ディレクトリを扱えるようになった。ただし、ツールは今も Python コードへ直接定義している。
へ:[s16 Autonomous Agents](../s16_autonomous_agents/)
のレッスンでは、標準の発見・呼び出しプロトコルを使って外部ツールへ接続する
<!-- translation-sync: zh@v2, en@v2, ja@v2 -->
次へ:[s16 MCP Tools](../s16_mcp_plugin/)。
<!-- translation-sync: zh@v3, en@v3, ja@v3 -->