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:
241
s15_integrated_harness/README.ja.md
Normal file
241
s15_integrated_harness/README.ja.md
Normal file
@@ -0,0 +1,241 @@
|
||||
# s15: Integrated Harness — 多くの仕組みを 1 つのループへ
|
||||
|
||||
[English](README.md) · [中文](README.zh.md) · [日本語](README.ja.md)
|
||||
|
||||
s01 → ... → s13 → [s14](../s14_mcp_plugin/) → `s15` → [s16](../s16_workflow_runtime/) → s17
|
||||
|
||||
> *"仕組みは多い、ループは 1 つ"* — tools、permissions、memory、tasks、teams、plugins はすべて同じ `while True` に接続される。
|
||||
>
|
||||
> **Harness レイヤー**: 統合 — この例で実際に使う仕組みを 1 つの実行可能なシステムへまとめる。
|
||||
|
||||
---
|
||||
|
||||
## 問題
|
||||
|
||||
前の章では、異なる仕組みをそれぞれ独立した実行例に置いた。本章では、統合ランタイムに必要な仕組みを接続する。
|
||||
|
||||
長時間動く coding agent には、同時に次のものが必要になる:
|
||||
|
||||
- tool dispatch と permission boundary
|
||||
- hook extension point
|
||||
- todo plan と task graph
|
||||
- skill、memory、runtime system prompt assembly
|
||||
- compaction と error recovery
|
||||
- background task と cron scheduling
|
||||
- team、protocol、IDLE task claiming
|
||||
- task-bound worktree
|
||||
- MCP external tool integration
|
||||
|
||||
S15 は新しい独立 mechanism を追加する章ではない。既存の mechanism が model loop のどこに入り、そこで生じた event が同じ conversation にどう戻るかを示す。
|
||||
|
||||
---
|
||||
|
||||
## 解決策
|
||||
|
||||

|
||||
|
||||
S15 は新しい mechanism を追加せず、前章までの component を同じ harness に統合する:
|
||||
|
||||
```text
|
||||
user input
|
||||
→ UserPromptSubmit hooks
|
||||
→ cron/background notification injection
|
||||
→ context compact
|
||||
→ memory + skills + MCP state で system prompt を組み立てる
|
||||
→ LLM
|
||||
→ has tool_use block?
|
||||
no → Stop hooks → return
|
||||
yes → PreToolUse hooks + permission
|
||||
→ TOOL_HANDLERS / MCP handlers / background dispatch
|
||||
→ PostToolUse hooks
|
||||
→ tool_result / task_notification を messages へ戻す
|
||||
→ next round
|
||||
```
|
||||
|
||||
loop 自体は同じ構造のままだ。model を呼び、response に `tool_use` block があるかを見て、tool を実行し、結果を `messages` に戻す。tool 実行を続けるかどうかは、実際の `tool_use` block の有無で決まる。
|
||||
|
||||
---
|
||||
|
||||
## 各 Component の位置
|
||||
|
||||
| 位置 | Component | 役割 |
|
||||
|------|-----------|------|
|
||||
| user input 周辺 | `UserPromptSubmit` hooks | user input の記録、注入、監査 |
|
||||
| LLM 前 | cron queue | scheduled prompt を `messages` へ注入 |
|
||||
| LLM 前 | background notifications | 完了した background work を `<task_notification>` として注入 |
|
||||
| LLM 前 | compaction pipeline | 大きな出力を予算化し、履歴を切り、古い tool_result を圧縮し、必要なら要約 |
|
||||
| LLM 前 | memory / skills / MCP state | current capabilities と long-term context を system prompt に組み込む |
|
||||
| LLM call | error recovery | 429/529 retry、`max_tokens` escalation、prompt-too-long compact |
|
||||
| tool 実行前 | `PreToolUse` hooks + permission | 危険な command、範囲外 write、destructive MCP tool を止める |
|
||||
| tool dispatch | `assemble_tool_pool` | built-in tools と dynamic MCP tools を組み立てる |
|
||||
| tool 実行中 | background dispatch | 明示指定された bash work を daemon thread に移し、placeholder result を返す |
|
||||
| tool 実行後 | `PostToolUse` hooks | large-output warning、log、後処理 |
|
||||
| loop へ戻る | tool_result | 1 つの `tool_use` に 1 つの `tool_result`、そして次の model round |
|
||||
| tool_use がない round / stop 時 | `Stop` hooks | 統計、cleanup、audit |
|
||||
|
||||
---
|
||||
|
||||
## code.py に含まれるもの
|
||||
|
||||
### Tools と Dispatch
|
||||
|
||||
built-in tool pool には 25 個の tool がある:
|
||||
|
||||
```text
|
||||
bash, read_file, write_file, edit_file, glob
|
||||
todo_write, task, load_skill, compact
|
||||
create_task, list_tasks, get_task, claim_task, complete_task
|
||||
schedule_cron, list_crons, cancel_cron
|
||||
spawn_teammate, list_teammates, send_message
|
||||
request_shutdown, request_plan, review_plan
|
||||
create_worktree
|
||||
connect_mcp
|
||||
```
|
||||
|
||||
`assemble_tool_pool()` は毎 round で次を組み立てる:
|
||||
|
||||
```text
|
||||
BUILTIN_TOOLS + connected MCP tools
|
||||
BUILTIN_HANDLERS + mcp__server__tool handlers
|
||||
```
|
||||
|
||||
`connect_mcp("docs")` のあと、次の round では `mcp__docs__search` のような tool が出現する。
|
||||
|
||||
### Permission と Hooks
|
||||
|
||||
permission は tool 実行行に直接埋め込まない。`PreToolUse` hook として扱う:
|
||||
|
||||
```python
|
||||
blocked = trigger_hooks("PreToolUse", block)
|
||||
if blocked:
|
||||
results.append(tool_result(block.id, blocked))
|
||||
continue
|
||||
```
|
||||
|
||||
これにより permission、logging、audit が同じ hook point に接続できる。Lead、one-shot subagent、teammate の tool はすべて先に `PreToolUse` を通り、許可された call は handler 実行後に `PostToolUse` を通る。
|
||||
|
||||
permission 判定では、MCP server 自身の description を authorization の根拠にしない。host が既知の read-only call の exact allowlist を持ち、それ以外の MCP tool は user に確認する。file tool が `WORKDIR` の外へ出る場合は拒否し、すべての bash command は実行前に確認する。interactive approval を開けるのは foreground user turn だけで、asynchronous turn は main CLI と stdin を奪い合わず fail closed する。
|
||||
|
||||
### Plan と Task
|
||||
|
||||
S15 には 2 層の plan がある:
|
||||
|
||||
- `todo_write`: current session 用の軽量 plan。メモリに保持。
|
||||
- task graph: cross-session、dependency-aware、claimable な task file。`.tasks/task_*.json` に保存。
|
||||
|
||||
前者は単独 agent の drift を防ぐ。後者は team coordination の土台になる。
|
||||
|
||||
目的は近いが実装は別である。`todo_write` は現在のセッションのチェックリスト全体を置き換え、task record は安定 ID と個別のライフサイクル更新を持つ。次節の独立した `task` ツールは「隔離 subagent を一度派遣する」意味であり、Task System ではない。
|
||||
|
||||
### Subagent と Team
|
||||
|
||||
S15 には 2 種類の delegation がある:
|
||||
|
||||
- `task`: one-shot subagent。独立した `messages[]` を使い、中間 context を捨て、final summary だけ返す。
|
||||
- `spawn_teammate`: persistent teammate thread。ready `task_id` を渡すと、runtime は thread 開始前に Claim する。省略した場合、teammate は IDLE で後続 Task を待てる。assignment がない teammate は file tool と Shell tool を使えない。固定の tool round 上限なしで `WORK → result → IDLE` を続け、model または dispatch の失敗は `error` を送り、thread cleanup は未完了 assignment を task board へ戻す。model call の前には毎回 inbox を読み、direct message や shutdown request が連続する tool-use round の後ろで待ち続けないようにする。idle 中はまず `MessageBus` を待ち、timeout 後だけ ready task を scan して最大 1 件を atomic に claim する。
|
||||
|
||||
Lead は teammate を起動した後、model loop 内で status を繰り返し確認せず、現在の turn を終了する。Lead の受信箱に team event が入ると runtime が次の turn を開始する。
|
||||
|
||||
one-shot subagent は context isolation を解決する。persistent teammate は長期並列協作を解決する。
|
||||
|
||||
### Memory、Skills、Prompt
|
||||
|
||||
S15 は s09 の Memory runtime をそのまま再利用する。model call の前に `.memory/MEMORY.md` catalog を読み、現在の request に関係する record を選び、その本文を `assemble_system_prompt(context)` へ渡す。turn の終了後は `extract_memories()` が後の session でも使える情報を保存し、新しい record が増えた場合は `consolidate_memories()` を続けて実行する。
|
||||
|
||||
同じ system prompt には identity、tool guidance、workspace、skills catalog、connected MCP servers も入る。skills は catalog だけを置き、全文は `load_skill(name)` で必要な時に読む。
|
||||
|
||||
### Compaction と Recovery
|
||||
|
||||
LLM call の前に compaction pipeline を走らせる:
|
||||
|
||||
```text
|
||||
tool_result_budget → snip_compact → micro_compact → compact_history
|
||||
```
|
||||
|
||||
model call は recovery で包む:
|
||||
|
||||
- 429: exponential backoff retry
|
||||
- 529: exponential backoff、連続失敗時は fallback model へ切替可能
|
||||
- `max_tokens`: max tokens を上げ、その後 continuation を要求
|
||||
- prompt too long: reactive compact 後に retry
|
||||
|
||||
### Background と Cron
|
||||
|
||||
bash call が `run_in_background=true` を指定すると、main loop は command の終了を待たず placeholder を返す:
|
||||
|
||||
```text
|
||||
should_run_background → start_background_task → placeholder tool_result
|
||||
background done → task_notification → next round injects messages
|
||||
```
|
||||
|
||||
background path に入るのは明示的に指定された bash call だけである。command の非ゼロ終了や worker の例外は `failed` notification になる。各 Shell command は独立した process group で動き、command の終了、または Agent が通常経路や `SIGTERM` で終了する時に元の group を停止する。別の session を作った process はその group から離れられる。
|
||||
|
||||
cron scheduler は daemon thread として動き、1 秒ごとに確認する。durable な一回限り job は、先に `pending_delivery` として永続化してから queue へ入れ、その prompt を含む model call が成功するまで保持する。呼び出し失敗時と restart 後には再び queue に入るため、配信は at-least-once である。CLI は `cron_queue`、Lead inbox、終了した background work を監視し、どの event からでも Agent を 1 turn 自動で起動する。
|
||||
|
||||
### Worktree と MCP
|
||||
|
||||
s13 から継承した task-scoped worktree は working directory を管理する:
|
||||
|
||||
- pending かつ unowned の task は main workspace のままでもよく、`create_worktree(name, task_id)` で別々の branch と directory に紐付けることもできる
|
||||
- 作成前に task、name、path、branch、Git registry を検証する。Git command が失敗した後も registry と branch state を照合し、部分的に作成された checkout は未紐付けのまま manual recovery 用に保持する
|
||||
- idle teammate は ready task を 1 つ atomic に claim し、assignment は `task_id` と effective `cwd` の両方を保持する
|
||||
- Lead は ready `task_id` を `spawn_teammate` に直接渡すこともでき、Claim 成功後にだけ thread が開始する
|
||||
- teammate のすべての file tool はその `cwd` を使い、task owner だけが complete できる。assignment は current model turn の終了まで保持する
|
||||
- 削除は host 側の `remove_worktree()` helper に残し、モデルからは呼べない。user または host が task ownership、assignment lease、background work、Git state を先に確認し、破壊的な削除には別途 user confirmation を必要とする
|
||||
|
||||
worktree は tool の default working directory を変更して working copy を分離するだけで、sandbox ではない。process group cleanup は別の session を作った process を封じ込められないため、削除は host-owned のままにする。
|
||||
|
||||
Task の Claim または release は assignment version を変え、古い plan approval を無効にする。通常の `send_message` は text を配信するだけで、Task identity も plan state も変えない。
|
||||
|
||||
MCP は external capability を担当する:
|
||||
|
||||
- `connect_mcp(name)` が mock server に接続する
|
||||
- `assemble_tool_pool()` が MCP tools を tool pool に組み立て、正規化後の名前衝突を拒否する
|
||||
- tool name は `mcp__server__tool` 形式に統一する
|
||||
|
||||
---
|
||||
|
||||
## s14 からの変化
|
||||
|
||||
| Scope | s14 MCP | s15 Integrated Harness |
|
||||
|-------|---------|-------------------------|
|
||||
| built-in tools | 6 | 25 |
|
||||
| external tools | 接続済み MCP tools | 同じ dynamic MCP path と host policy |
|
||||
| local mechanisms | S04 tools、hooks、permission、MCP | todo、subagent、skills、compaction、memory、task graph、background bash、cron、teams、worktrees |
|
||||
| event sources | user input と tool results | user input、tool results、cron prompts、background notifications、team events |
|
||||
|
||||
---
|
||||
|
||||
## 試す
|
||||
|
||||
```sh
|
||||
cd learn-claude-code
|
||||
python s15_integrated_harness/code.py
|
||||
```
|
||||
|
||||
試す prompt:
|
||||
|
||||
1. `このリポジトリを調べ、重要な Python ファイルを教えてください。`
|
||||
2. `接続済みのドキュメントから agent loop の説明を探してください。`
|
||||
3. `認証モジュールとログインページを隔離した worktree で並行してリファクタリングし、編集前にそれぞれのプランを見せてください。`
|
||||
4. `3 分後に会議を知らせてください。`
|
||||
5. `依存関係をバックグラウンドでインストールしながら README.md を読んでください。`
|
||||
|
||||
見るポイント:
|
||||
|
||||
- tool call の前に hooks/permission を通るか
|
||||
- `connect_mcp` 後の次 round で MCP tool が出るか
|
||||
- `run_in_background=true` の bash call が background placeholder を返すか
|
||||
- cron が時刻到達時に自動で reminder を返すか
|
||||
- teammate が plan を提出し、approval 前に停止するか
|
||||
- idle teammate が ready task を 1 つだけ atomic に claim するか
|
||||
- teammate のすべての file tool が claimed task の `cwd` へ切り替わるか
|
||||
- complete 後も同じ turn の間は task `cwd` を保ち、IDLE で assignment を解除するか
|
||||
|
||||
---
|
||||
|
||||
## 次へ
|
||||
|
||||
[s16 Workflow Runtime](../s16_workflow_runtime/) は、この host に `Workflow` tool を追加する。Workflow は固定された orchestration path を code に置き、進行状況を記録して同じ run を再開できるようにする。
|
||||
|
||||
<!-- translation-sync: zh@v13, en@v13, ja@v13 -->
|
||||
241
s15_integrated_harness/README.md
Normal file
241
s15_integrated_harness/README.md
Normal file
@@ -0,0 +1,241 @@
|
||||
# s15: Integrated Harness — Many Mechanisms, One Loop
|
||||
|
||||
[English](README.md) · [中文](README.zh.md) · [日本語](README.ja.md)
|
||||
|
||||
s01 → ... → s13 → [s14](../s14_mcp_plugin/) → `s15` → [s16](../s16_workflow_runtime/) → s17
|
||||
|
||||
> *"Many mechanisms, one loop"* — tools, permissions, memory, tasks, teams, and plugins all hang off the same `while True`.
|
||||
>
|
||||
> **Harness layer**: Integration — put the mechanisms used by this example into one runnable system.
|
||||
|
||||
---
|
||||
|
||||
## Problem
|
||||
|
||||
The earlier chapters keep separate mechanisms in separate runnable examples. This chapter connects the mechanisms needed by the integrated runtime.
|
||||
|
||||
A long-running coding agent needs all of these at once:
|
||||
|
||||
- tool dispatch and permission boundaries
|
||||
- hook extension points
|
||||
- todo planning and task graphs
|
||||
- skills, memory, and runtime system prompt assembly
|
||||
- compaction and error recovery
|
||||
- background tasks and cron scheduling
|
||||
- teams, protocols, and IDLE task claiming
|
||||
- task-bound worktrees
|
||||
- MCP external tool integration
|
||||
|
||||
S15 does not introduce another isolated mechanism. It shows where the existing mechanisms enter the model loop and how their events return to the same conversation.
|
||||
|
||||
---
|
||||
|
||||
## Solution
|
||||
|
||||

|
||||
|
||||
S15 does not introduce a new mechanism. It connects the components from the earlier chapters in one integrated harness:
|
||||
|
||||
```text
|
||||
user input
|
||||
→ UserPromptSubmit hooks
|
||||
→ cron/background notification injection
|
||||
→ context compact
|
||||
→ memory + skills + MCP state assemble the system prompt
|
||||
→ LLM
|
||||
→ has tool_use block?
|
||||
no → Stop hooks → return
|
||||
yes → PreToolUse hooks + permission
|
||||
→ TOOL_HANDLERS / MCP handlers / background dispatch
|
||||
→ PostToolUse hooks
|
||||
→ tool_result / task_notification back to messages
|
||||
→ next round
|
||||
```
|
||||
|
||||
The loop keeps the same structure: call the model, check whether the response contains a `tool_use` block, execute tools, and append results to `messages`. The presence of a `tool_use` block decides whether tool execution continues.
|
||||
|
||||
---
|
||||
|
||||
## Where Each Component Sits
|
||||
|
||||
| Position | Component | Role |
|
||||
|----------|-----------|------|
|
||||
| Around user input | `UserPromptSubmit` hooks | Log, inject, or audit user input |
|
||||
| Before LLM | cron queue | Inject scheduled prompts into `messages` |
|
||||
| Before LLM | background notifications | Inject completed background work as `<task_notification>` |
|
||||
| Before LLM | compaction pipeline | Budget large outputs, trim history, compact old tool results, summarize when needed |
|
||||
| Before LLM | memory / skills / MCP state | Assemble the system prompt so the model sees current capabilities and long-term context |
|
||||
| LLM call | error recovery | Retry 429/529, escalate `max_tokens`, compact on prompt-too-long |
|
||||
| Before tool execution | `PreToolUse` hooks + permission | Block dangerous commands, out-of-bounds writes, destructive MCP tools |
|
||||
| Tool dispatch | `assemble_tool_pool` | Assemble built-in tools and dynamic MCP tools |
|
||||
| During tool execution | background dispatch | Move explicitly marked bash work into a daemon thread and return a placeholder result |
|
||||
| After tool execution | `PostToolUse` hooks | Large-output warnings, logs, post-processing |
|
||||
| Back to loop | tool_result | One `tool_result` per `tool_use`, then the next model round |
|
||||
| No tool_use this round / on stop | `Stop` hooks | Stats, cleanup, audit |
|
||||
|
||||
---
|
||||
|
||||
## What code.py Contains
|
||||
|
||||
### Tools and Dispatch
|
||||
|
||||
The built-in tool pool contains 25 tools:
|
||||
|
||||
```text
|
||||
bash, read_file, write_file, edit_file, glob
|
||||
todo_write, task, load_skill, compact
|
||||
create_task, list_tasks, get_task, claim_task, complete_task
|
||||
schedule_cron, list_crons, cancel_cron
|
||||
spawn_teammate, list_teammates, send_message
|
||||
request_shutdown, request_plan, review_plan
|
||||
create_worktree
|
||||
connect_mcp
|
||||
```
|
||||
|
||||
`assemble_tool_pool()` assembles these every round:
|
||||
|
||||
```text
|
||||
BUILTIN_TOOLS + connected MCP tools
|
||||
BUILTIN_HANDLERS + mcp__server__tool handlers
|
||||
```
|
||||
|
||||
After `connect_mcp("docs")`, the next round exposes tools like `mcp__docs__search`.
|
||||
|
||||
### Permissions and Hooks
|
||||
|
||||
Permission is not hardcoded into the tool execution line. It is a `PreToolUse` hook:
|
||||
|
||||
```python
|
||||
blocked = trigger_hooks("PreToolUse", block)
|
||||
if blocked:
|
||||
results.append(tool_result(block.id, blocked))
|
||||
continue
|
||||
```
|
||||
|
||||
That means permission, logging, and audit logic all attach to the same hook point. Lead tools, one-shot subagent tools, and teammate tools all pass through `PreToolUse`; an allowed call then runs `PostToolUse` after its handler.
|
||||
|
||||
The policy does not trust an MCP server's own description as authorization. The host owns a small exact allowlist for known read-only calls; every other MCP tool asks the user. File tools are denied outside `WORKDIR`, and every bash command asks before execution. Only the foreground user turn may open an interactive approval prompt; asynchronous turns fail closed instead of competing with the main CLI for stdin.
|
||||
|
||||
### Planning and Tasks
|
||||
|
||||
S15 keeps two planning layers:
|
||||
|
||||
- `todo_write`: lightweight plan for the current session, kept in memory
|
||||
- task graph: cross-session, dependency-aware, claimable task files under `.tasks/task_*.json`
|
||||
|
||||
The first keeps a single agent from drifting. The second supports team coordination.
|
||||
|
||||
They share an intent, not an implementation: `todo_write` replaces one session checklist, while task records have stable IDs and individual lifecycle updates. The separate `task` tool below means "dispatch one isolated subagent"; it is not the Task System.
|
||||
|
||||
### Subagents and Teams
|
||||
|
||||
S15 has two kinds of delegation:
|
||||
|
||||
- `task`: one-shot subagent. It uses an isolated `messages[]`, discards intermediate context, and returns only a final summary.
|
||||
- `spawn_teammate`: persistent teammate thread. When given a ready `task_id`, the runtime claims it before the thread starts; without one, the teammate can wait in IDLE for later work. A teammate without an assignment cannot use file or Shell tools. It follows `WORK → result → IDLE` without a fixed tool-round cap; model or dispatch failures emit an `error`, and thread cleanup releases an unfinished assignment back to the task board. It drains its inbox before every model call, so direct messages and shutdown requests cannot wait behind an unbroken tool-use sequence. While idle it waits for `MessageBus` delivery first, then scans ready tasks only after the wait times out and atomically claims at most one.
|
||||
|
||||
After spawning a teammate, Lead ends the current turn instead of repeatedly querying its status inside the model loop. A team event in Lead's mailbox makes the runtime start the next turn.
|
||||
|
||||
One-shot subagents solve context isolation. Persistent teammates solve long-running parallel collaboration.
|
||||
|
||||
### Memory, Skills, and Prompt
|
||||
|
||||
S15 reuses the s09 memory runtime directly. Before each model call, it reads the `.memory/MEMORY.md` catalog, selects records relevant to the current request, and passes their contents to `assemble_system_prompt(context)`. At the end of the turn, `extract_memories()` keeps information that can help in later sessions; when new records are stored, `consolidate_memories()` runs next.
|
||||
|
||||
The same system prompt also includes identity, tool guidance, the workspace, the skills catalog, and connected MCP servers. Skills contribute only their catalog; `load_skill(name)` loads full content on demand.
|
||||
|
||||
### Compaction and Recovery
|
||||
|
||||
Before the LLM call, S15 runs the compaction pipeline:
|
||||
|
||||
```text
|
||||
tool_result_budget → snip_compact → micro_compact → compact_history
|
||||
```
|
||||
|
||||
The model call is wrapped with recovery:
|
||||
|
||||
- 429: exponential backoff retry
|
||||
- 529: exponential backoff, optionally switch to fallback model after repeated failures
|
||||
- `max_tokens`: raise max tokens, then request continuation
|
||||
- prompt too long: reactive compact and retry
|
||||
|
||||
### Background and Cron
|
||||
|
||||
When a bash call sets `run_in_background=true`, the main loop returns a placeholder without waiting for the command:
|
||||
|
||||
```text
|
||||
should_run_background → start_background_task → placeholder tool_result
|
||||
background done → task_notification → next round injects messages
|
||||
```
|
||||
|
||||
Only explicitly marked bash calls enter the background path. A non-zero exit or worker exception produces a `failed` notification. Each shell runs in its own process group, which the runtime stops when the command or Agent process ends through the normal or `SIGTERM` path. A process that creates another session can leave that group.
|
||||
|
||||
The cron scheduler runs as a daemon thread and checks once per second. A durable one-shot job is persisted as `pending_delivery` before entering the queue and remains there until the model call containing its prompt succeeds; a failed call restores it to the queue, and a restart queues it again. Delivery is therefore at-least-once. The CLI watches `cron_queue`, Lead's inbox, and terminal background work; any of them can wake one automatic agent turn.
|
||||
|
||||
### Worktree and MCP
|
||||
|
||||
The task-scoped worktree behavior inherited from s13 manages working directories:
|
||||
|
||||
- a pending, unowned task may remain in the main workspace or be bound by `create_worktree(name, task_id)` to a separate branch and directory
|
||||
- creation prevalidates the task, name, path, branch, and Git registry; a failed Git command is reconciled against the registry and branch state, and any partial checkout remains unbound and preserved for manual recovery
|
||||
- an idle teammate atomically claims one ready task; the assignment records both `task_id` and its effective `cwd`
|
||||
- Lead can also pass a ready `task_id` to `spawn_teammate`; the thread starts only after the claim succeeds
|
||||
- all teammate file tools use that `cwd`; only the owning teammate can complete the task, and the assignment stays selected until that model turn ends
|
||||
- removal stays in the host-side `remove_worktree()` helper. The model cannot call it. The user or host first checks task ownership, assignment leases, background work, and Git state; destructive removal requires separate user confirmation
|
||||
|
||||
The worktree changes tool default directories. It separates working copies; it is not a sandbox, and process-group cleanup does not contain a process that starts another session. This is why deletion remains host-owned.
|
||||
|
||||
Claiming or releasing a Task changes the assignment version and invalidates an old plan approval. An ordinary `send_message` only delivers text; it changes neither the Task identity nor the plan state.
|
||||
|
||||
MCP owns external capability:
|
||||
|
||||
- `connect_mcp(name)` connects a mock server
|
||||
- `assemble_tool_pool()` assembles MCP tools and rejects normalized name collisions
|
||||
- tool names use `mcp__server__tool`
|
||||
|
||||
---
|
||||
|
||||
## Changes from s14
|
||||
|
||||
| Scope | s14 MCP | s15 Integrated Harness |
|
||||
|-------|---------|-------------------------|
|
||||
| built-in tools | 6 | 25 |
|
||||
| external tools | connected MCP tools | the same dynamic MCP path and host policy |
|
||||
| local mechanisms | S04 tools, hooks, permission, MCP | todo, subagent, skills, compaction, memory, task graph, background bash, cron, teams, and worktrees |
|
||||
| event sources | user input and tool results | user input, tool results, cron prompts, background notifications, and team events |
|
||||
|
||||
---
|
||||
|
||||
## Try It
|
||||
|
||||
```sh
|
||||
cd learn-claude-code
|
||||
python s15_integrated_harness/code.py
|
||||
```
|
||||
|
||||
Try:
|
||||
|
||||
1. `Inspect this repository and tell me which Python files matter most.`
|
||||
2. `Search the connected documentation for agent loop guidance.`
|
||||
3. `Refactor the authentication module and login page in parallel in separate worktrees. Show me each plan before editing.`
|
||||
4. `Remind me about the meeting in 3 minutes.`
|
||||
5. `Install the dependencies in the background while you read README.md.`
|
||||
|
||||
Watch for:
|
||||
|
||||
- whether each tool call passes through hooks/permission
|
||||
- whether MCP tools appear on the next round after `connect_mcp`
|
||||
- whether a bash call with `run_in_background=true` returns a background placeholder
|
||||
- whether cron automatically reminds you when the time arrives
|
||||
- whether teammates submit plans and pause before approval
|
||||
- whether an idle teammate atomically claims only one ready task
|
||||
- whether every teammate file tool switches to the claimed task's `cwd`
|
||||
- whether completion keeps the task `cwd` through the rest of the turn and releases it at IDLE
|
||||
|
||||
---
|
||||
|
||||
## Next
|
||||
|
||||
[s16 Workflow Runtime](../s16_workflow_runtime/) adds a `Workflow` tool to this host. A workflow keeps a fixed orchestration path in code and records progress so the same run can resume.
|
||||
|
||||
<!-- translation-sync: zh@v13, en@v13, ja@v13 -->
|
||||
241
s15_integrated_harness/README.zh.md
Normal file
241
s15_integrated_harness/README.zh.md
Normal file
@@ -0,0 +1,241 @@
|
||||
# s15: Agent Harness 集成 — 多种机制,一个循环
|
||||
|
||||
[English](README.md) · [中文](README.zh.md) · [日本語](README.ja.md)
|
||||
|
||||
s01 → ... → s13 → [s14](../s14_mcp_plugin/) → `s15` → [s16](../s16_workflow_runtime/) → s17
|
||||
|
||||
> *"机制很多,循环一个"* — 工具、权限、记忆、任务、团队、插件都挂在同一个 while True 上。
|
||||
>
|
||||
> **Harness 层**: 集成 — 把本章示例实际使用的机制放进同一个可运行系统。
|
||||
|
||||
---
|
||||
|
||||
## 问题
|
||||
|
||||
前面的章节把不同机制放在各自独立的示例中。本章把集成运行时需要的机制接到一起。
|
||||
|
||||
一个能长期工作的 coding agent 需要同时拥有:
|
||||
|
||||
- 工具分发和权限边界
|
||||
- hooks 扩展点
|
||||
- todo 计划和任务图
|
||||
- 技能、记忆、系统 prompt 组装
|
||||
- 压缩和错误恢复
|
||||
- 后台任务和 cron 调度
|
||||
- 团队、协议和 idle 任务认领
|
||||
- 任务绑定的 worktree
|
||||
- MCP 外部工具接入
|
||||
|
||||
S15 不再引入一个独立机制,而是展示现有机制从哪里进入模型循环,以及它们产生的事件如何回到同一段对话。
|
||||
|
||||
---
|
||||
|
||||
## 解决方案
|
||||
|
||||

|
||||
|
||||
S15 不再引入新机制,而是把前面各章的组件集成到同一个 harness:
|
||||
|
||||
```text
|
||||
用户输入
|
||||
→ UserPromptSubmit hooks
|
||||
→ cron/background 通知注入
|
||||
→ context compact
|
||||
→ memory + skills + MCP 状态组装 system prompt
|
||||
→ LLM
|
||||
→ has tool_use block?
|
||||
否 → Stop hooks → 返回
|
||||
是 → PreToolUse hooks + permission
|
||||
→ TOOL_HANDLERS / MCP handlers / background dispatch
|
||||
→ PostToolUse hooks
|
||||
→ tool_result / task_notification 回 messages
|
||||
→ 下一轮
|
||||
```
|
||||
|
||||
循环仍是同一个结构:调用模型,检查响应里是否出现 `tool_use` block,执行工具,再把结果追加回 `messages`。是否继续工具轮,由响应中有没有实际的 `tool_use` block 决定。
|
||||
|
||||
---
|
||||
|
||||
## 组件在循环中的位置
|
||||
|
||||
| 位置 | 组件 | 作用 |
|
||||
|------|------|------|
|
||||
| 用户输入前后 | `UserPromptSubmit` hooks | 记录、注入、审计用户输入 |
|
||||
| LLM 前 | cron queue | 把定时触发的 prompt 注入 `messages` |
|
||||
| LLM 前 | background notifications | 后台任务完成后以 `<task_notification>` 注入 |
|
||||
| LLM 前 | compaction pipeline | 先压大输出,再裁历史,再压旧 tool_result,必要时摘要 |
|
||||
| LLM 前 | memory / skills / MCP state | 组装 system prompt,让模型看到当前能力和长期上下文 |
|
||||
| LLM 调用 | error recovery | 429/529 重试,`max_tokens` 升级,prompt too long 触发 reactive compact |
|
||||
| 工具执行前 | `PreToolUse` hooks + permission | 拦截危险命令、写越界、破坏性 MCP 工具 |
|
||||
| 工具分发 | `assemble_tool_pool` | 组装内置工具和 MCP 动态工具 |
|
||||
| 工具执行时 | background dispatch | 显式标记的 bash 操作放入 daemon thread,主循环先返回占位结果 |
|
||||
| 工具执行后 | `PostToolUse` hooks | 大输出告警、日志等后处理 |
|
||||
| 返回循环 | tool_result | 每个 `tool_use` 对应一个 `tool_result`,再回到下一轮 |
|
||||
| 本轮没有 tool_use / 停止时 | `Stop` hooks | 统计、清理、审计 |
|
||||
|
||||
---
|
||||
|
||||
## code.py 包含什么
|
||||
|
||||
### 工具与分发
|
||||
|
||||
内置工具池包含 25 个工具:
|
||||
|
||||
```text
|
||||
bash, read_file, write_file, edit_file, glob
|
||||
todo_write, task, load_skill, compact
|
||||
create_task, list_tasks, get_task, claim_task, complete_task
|
||||
schedule_cron, list_crons, cancel_cron
|
||||
spawn_teammate, list_teammates, send_message
|
||||
request_shutdown, request_plan, review_plan
|
||||
create_worktree
|
||||
connect_mcp
|
||||
```
|
||||
|
||||
`assemble_tool_pool()` 每轮组装:
|
||||
|
||||
```text
|
||||
BUILTIN_TOOLS + connected MCP tools
|
||||
BUILTIN_HANDLERS + mcp__server__tool handlers
|
||||
```
|
||||
|
||||
所以 `connect_mcp("docs")` 后,下一轮工具池里会出现 `mcp__docs__search`。
|
||||
|
||||
### 权限和 hooks
|
||||
|
||||
权限不写死在工具执行行里,而是作为 `PreToolUse` hook:
|
||||
|
||||
```python
|
||||
blocked = trigger_hooks("PreToolUse", block)
|
||||
if blocked:
|
||||
results.append(tool_result(block.id, blocked))
|
||||
continue
|
||||
```
|
||||
|
||||
这样 permission、log、审计都可以挂在同一个 hook 点上。Lead、一次性 subagent 和队友的工具都会先经过 `PreToolUse`;允许执行的调用会在 handler 返回后触发 `PostToolUse`。
|
||||
|
||||
权限判断不会把 MCP server 自己写的 description 当成授权依据。宿主维护一组精确的已知只读工具名单,其他 MCP 工具都要询问用户。文件工具越过 `WORKDIR` 会直接拒绝,每条 bash 命令执行前都会询问。只有前台用户轮次可以弹出交互确认;异步轮次直接拒绝需要确认的操作,不和主 CLI 争抢输入。
|
||||
|
||||
### 计划与任务
|
||||
|
||||
S15 同时保留两层计划:
|
||||
|
||||
- `todo_write`:当前会话内的轻量计划,保存在内存中
|
||||
- task graph:跨会话、可依赖、可认领的任务文件,写入 `.tasks/task_*.json`
|
||||
|
||||
前者帮助单个 Agent 不漂移;后者支撑团队协作。
|
||||
|
||||
两者目标相近,但实现不同:`todo_write` 整表替换当前会话清单,task record 则有稳定 ID 和单条生命周期更新。下面单独出现的 `task` 工具表示“一次性派发隔离 subagent”,不是 Task System。
|
||||
|
||||
### 子 agent 与团队
|
||||
|
||||
S15 有两种 delegation:
|
||||
|
||||
- `task`:一次性 subagent。独立 `messages[]`,中间过程丢弃,只返回最终摘要。
|
||||
- `spawn_teammate`:持久队友线程。传入 ready `task_id` 时,运行时会在线程启动前完成认领;不传时,队友可以在 IDLE 中等待后续任务。没有 assignment 的队友不能使用文件或 Shell 工具。它按 `WORK → result → IDLE` 运行,不设固定的工具轮数上限;模型或分发失败会发出 `error`,线程清理会把未完成 assignment 释放回任务板。每次调用模型前都会先读取收件箱,因此直接消息和关机请求不会被连续的 tool-use 轮次饿死。idle 时先等待 `MessageBus` 消息,只在超时后扫描就绪 task,并以原子操作最多认领一个。
|
||||
|
||||
Lead 启动队友后结束当前轮次,不在模型循环里反复查询状态。队友事件进入 Lead 收件箱后,运行时会自动唤醒下一轮。
|
||||
|
||||
一次性 subagent 解决“上下文隔离”;持久队友解决“长期并行协作”。
|
||||
|
||||
### 记忆、技能和 prompt
|
||||
|
||||
S15 直接复用 s09 的 Memory runtime。每轮调用模型前,它读取 `.memory/MEMORY.md` 目录,根据当前请求选择相关记录,再把选中的正文交给 `assemble_system_prompt(context)`。本轮结束后,`extract_memories()` 提取可跨会话使用的信息;有新增记录时再运行 `consolidate_memories()`。
|
||||
|
||||
同一份 system prompt 还会加入身份、工具说明、workspace、skills catalog 和已连接的 MCP server。技能只放目录,完整内容通过 `load_skill(name)` 按需加载。
|
||||
|
||||
### 压缩和恢复
|
||||
|
||||
LLM 前先跑压缩管线:
|
||||
|
||||
```text
|
||||
tool_result_budget → snip_compact → micro_compact → compact_history
|
||||
```
|
||||
|
||||
调用模型时再包一层恢复:
|
||||
|
||||
- 429:指数退避重试
|
||||
- 529:指数退避,连续失败可切 fallback model
|
||||
- `max_tokens`:先提高 max_tokens,再要求 continuation
|
||||
- prompt too long:reactive compact 后重试
|
||||
|
||||
### 后台和 cron
|
||||
|
||||
bash 调用设置 `run_in_background=true` 后,主循环不再等待命令结束,而是先返回占位结果:
|
||||
|
||||
```text
|
||||
should_run_background → start_background_task → placeholder tool_result
|
||||
后台完成 → task_notification → 下一轮注入 messages
|
||||
```
|
||||
|
||||
只有显式标记的 bash 调用会进入后台路径。命令非零退出或 worker 抛出异常时会发出 `failed` 通知。每条 Shell 命令都在独立进程组中运行;命令结束,或 Agent 经正常路径、`SIGTERM` 退出时,运行时会停止原进程组。另建 session 的进程可以离开这个进程组。
|
||||
|
||||
cron 调度器独立 daemon thread 每秒检查一次。durable 的一次性任务会先持久化为 `pending_delivery`,再进入队列,并保留到包含该 prompt 的模型调用成功;调用失败会放回队列,重启后也会再次入队,因此交付语义是至少一次。CLI 同时监听 `cron_queue`、Lead 收件箱和已经结束的后台任务,任一事件都能自动唤醒一轮 Agent。
|
||||
|
||||
### worktree 与 MCP
|
||||
|
||||
从 s13 继承的任务级 worktree 机制负责管理任务工作目录:
|
||||
|
||||
- pending 且未被认领的 task 可以留在主工作区,也可以通过 `create_worktree(name, task_id)` 绑定独立分支和目录
|
||||
- 创建前会校验 task、名称、路径、分支和 Git registry;Git 命令失败后还会核对 registry 和分支状态,任何部分创建的 checkout 都保持未绑定并保留供人工恢复
|
||||
- idle 队友以原子操作认领一个就绪 task,assignment 同时记录 `task_id` 和有效 `cwd`
|
||||
- Lead 也可以把 ready `task_id` 直接传给 `spawn_teammate`,认领成功后才启动线程
|
||||
- 队友所有文件工具都使用该 `cwd`;只有 task owner 能完成任务,assignment 会保留到当前模型轮次结束
|
||||
- 移除保留在宿主侧的 `remove_worktree()` 函数中,模型不能调用。用户或宿主先检查任务所有权、assignment lease、后台工作和 Git 状态;破坏性移除需要另行取得用户确认
|
||||
|
||||
worktree 只改变工具的默认工作目录,用于分离 working copy,并不是安全沙箱。进程组清理也无法约束另建 session 的进程,因此删除保留为宿主操作。
|
||||
|
||||
认领或释放 task 会改变 assignment version,使旧的 plan approval 失效;普通 `send_message` 只传递消息,不会改变 task identity 或 plan 状态。
|
||||
|
||||
MCP 负责外部能力:
|
||||
|
||||
- `connect_mcp(name)` 连接 mock server
|
||||
- `assemble_tool_pool()` 把 MCP 工具组装进工具池,并拒绝规范化后的名称冲突
|
||||
- 工具名统一为 `mcp__server__tool`
|
||||
|
||||
---
|
||||
|
||||
## 相对 s14 的变化
|
||||
|
||||
| 范围 | s14 MCP | s15 Integrated Harness |
|
||||
|------|---------|-------------------------|
|
||||
| 内置工具 | 6 个 | 25 个 |
|
||||
| 外部工具 | 已连接的 MCP 工具 | 沿用同一套动态 MCP 路径和宿主策略 |
|
||||
| 本地机制 | S04 工具、hooks、权限和 MCP | todo、subagent、skills、compaction、memory、task graph、后台 bash、cron、teams 和 worktrees |
|
||||
| 事件来源 | 用户输入和工具结果 | 用户输入、工具结果、cron prompt、后台通知和 team events |
|
||||
|
||||
---
|
||||
|
||||
## 试一下
|
||||
|
||||
```sh
|
||||
cd learn-claude-code
|
||||
python s15_integrated_harness/code.py
|
||||
```
|
||||
|
||||
可以试:
|
||||
|
||||
1. `检查这个仓库,告诉我哪些 Python 文件最重要。`
|
||||
2. `从已连接的文档中查一下 agent loop 的相关说明。`
|
||||
3. `请在独立的 worktree 中并行重构认证模块和登录页,修改前先把各自的计划给我看。`
|
||||
4. `3 分钟后提醒我开会。`
|
||||
5. `在后台安装依赖,同时继续阅读 README.md。`
|
||||
|
||||
观察重点:
|
||||
|
||||
- 工具调用前是否经过 hooks/permission
|
||||
- `connect_mcp` 后下一轮是否出现 MCP 工具
|
||||
- 设置 `run_in_background=true` 的 bash 调用是否返回 background placeholder
|
||||
- 到点是不是自动提醒开会
|
||||
- 队友是否提交 plan,并在 approval 前暂停
|
||||
- idle 队友是否只原子认领一个就绪 task
|
||||
- 队友所有文件工具是否都切换到已认领 task 的 `cwd`
|
||||
- 完成任务后是否在本轮剩余工具调用中保持 task `cwd`,并在 IDLE 时释放
|
||||
|
||||
---
|
||||
|
||||
## 接下来
|
||||
|
||||
[s16 Workflow Runtime](../s16_workflow_runtime/) 会在这个 host 中加入 `Workflow` 工具。Workflow 把固定的编排路径写在代码中,并记录运行进度,使同一次运行可以继续执行。
|
||||
|
||||
<!-- translation-sync: zh@v13, en@v13, ja@v13 -->
|
||||
3061
s15_integrated_harness/code.py
Normal file
3061
s15_integrated_harness/code.py
Normal file
File diff suppressed because it is too large
Load Diff
85
s15_integrated_harness/images/system-architecture.en.svg
Normal file
85
s15_integrated_harness/images/system-architecture.en.svg
Normal file
@@ -0,0 +1,85 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 920 620" font-family="'Noto Sans CJK SC', 'Droid Sans Fallback', system-ui, -apple-system, sans-serif">
|
||||
<defs>
|
||||
<linearGradient id="header" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0%" stop-color="#1e3a5f"/>
|
||||
<stop offset="100%" stop-color="#0f766e"/>
|
||||
</linearGradient>
|
||||
<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="#475569"/>
|
||||
</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="#059669"/>
|
||||
</marker>
|
||||
<marker id="arrow-purple" 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="#7c3aed"/>
|
||||
</marker>
|
||||
<marker id="arrow-orange" 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="#ea580c"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<rect width="920" height="620" rx="8" fill="#fafbfc"/>
|
||||
<rect x="0" y="0" width="920" height="46" rx="8" fill="url(#header)"/>
|
||||
<rect x="0" y="38" width="920" height="8" fill="url(#header)"/>
|
||||
<text x="460" y="29" text-anchor="middle" fill="#fff" font-size="17" font-weight="700">s15 Integrated Harness — Many Mechanisms, One Loop</text>
|
||||
<rect x="40" y="76" width="840" height="212" rx="8" fill="#eef2ff" stroke="#2563eb" stroke-width="1.8"/>
|
||||
<text x="460" y="101" text-anchor="middle" fill="#1e3a8a" font-size="13" font-weight="700">Core Agent Loop</text>
|
||||
<rect x="70" y="128" width="110" height="48" rx="7" fill="#fff" stroke="#2563eb" stroke-width="1.4"/>
|
||||
<text x="125" y="157" text-anchor="middle" fill="#1e3a5f" font-size="11" font-weight="700">messages[]</text>
|
||||
<line x1="180" y1="152" x2="222" y2="152" stroke="#475569" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
<rect x="225" y="118" width="150" height="68" rx="7" fill="#ecfdf5" stroke="#059669" stroke-width="1.6"/>
|
||||
<text x="300" y="140" text-anchor="middle" fill="#065f46" font-size="11" font-weight="700">Before LLM</text>
|
||||
<text x="240" y="158" fill="#047857" font-size="8.5">cron/background injection</text>
|
||||
<text x="240" y="171" fill="#047857" font-size="8.5">compact + memory + prompt</text>
|
||||
<line x1="375" y1="152" x2="417" y2="152" stroke="#475569" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
<rect x="420" y="118" width="110" height="68" rx="7" fill="#fff" stroke="#2563eb" stroke-width="1.4"/>
|
||||
<text x="475" y="146" text-anchor="middle" fill="#1e3a5f" font-size="12" font-weight="700">LLM</text>
|
||||
<text x="475" y="164" text-anchor="middle" fill="#64748b" font-size="8.5">stop_reason=tool_use?</text>
|
||||
<line x1="530" y1="152" x2="572" y2="152" stroke="#475569" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
<rect x="575" y="118" width="150" height="68" rx="7" fill="#fff7ed" stroke="#ea580c" stroke-width="1.6"/>
|
||||
<text x="650" y="140" text-anchor="middle" fill="#9a3412" font-size="11" font-weight="700">Before Tools</text>
|
||||
<text x="590" y="158" fill="#c2410c" font-size="8.5">PreToolUse hooks</text>
|
||||
<text x="590" y="171" fill="#c2410c" font-size="8.5">permission pipeline</text>
|
||||
<line x1="725" y1="152" x2="767" y2="152" stroke="#475569" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
<rect x="770" y="118" width="80" height="68" rx="7" fill="#fff" stroke="#2563eb" stroke-width="1.4"/>
|
||||
<text x="810" y="147" text-anchor="middle" fill="#1e3a5f" font-size="10" font-weight="700">handlers</text>
|
||||
<text x="810" y="164" text-anchor="middle" fill="#64748b" font-size="8">builtin + MCP</text>
|
||||
<path d="M 810 186 L 810 244 L 125 244 L 125 176" fill="none" stroke="#475569" stroke-width="1.4" stroke-dasharray="6,4" marker-end="url(#arrow)"/>
|
||||
<text x="460" y="263" text-anchor="middle" fill="#64748b" font-size="9">tool_result / task_notification → messages[] → next turn</text>
|
||||
<rect x="40" y="318" width="190" height="104" rx="8" fill="#ecfdf5" stroke="#059669" stroke-width="1.4"/>
|
||||
<text x="135" y="341" text-anchor="middle" fill="#065f46" font-size="11" font-weight="700">Context & Knowledge</text>
|
||||
<text x="58" y="362" fill="#047857" font-size="9">s07 skills + load_skill</text>
|
||||
<text x="58" y="377" fill="#047857" font-size="9">s09 memory selection</text>
|
||||
<text x="58" y="392" fill="#047857" font-size="9">assembled system prompt</text>
|
||||
<text x="58" y="407" fill="#047857" font-size="9">s08 compact pipeline</text>
|
||||
<path d="M 230 350 L 282 186" fill="none" stroke="#059669" stroke-width="1.3" marker-end="url(#arrow-green)" stroke-dasharray="5,3"/>
|
||||
<rect x="260" y="318" width="190" height="104" rx="8" fill="#fff7ed" stroke="#ea580c" stroke-width="1.4"/>
|
||||
<text x="355" y="341" text-anchor="middle" fill="#9a3412" font-size="11" font-weight="700">Governance</text>
|
||||
<text x="278" y="362" fill="#c2410c" font-size="9">s03 permission</text>
|
||||
<text x="278" y="377" fill="#c2410c" font-size="9">s04 hooks</text>
|
||||
<text x="278" y="392" fill="#c2410c" font-size="9">model retry / fallback</text>
|
||||
<text x="278" y="407" fill="#c2410c" font-size="9">Stop hooks</text>
|
||||
<path d="M 450 350 L 592 186" fill="none" stroke="#ea580c" stroke-width="1.3" marker-end="url(#arrow-orange)" stroke-dasharray="5,3"/>
|
||||
<rect x="480" y="318" width="190" height="104" rx="8" fill="#f5f3ff" stroke="#7c3aed" stroke-width="1.4"/>
|
||||
<text x="575" y="341" text-anchor="middle" fill="#5b21b6" font-size="11" font-weight="700">Durable Work</text>
|
||||
<text x="498" y="362" fill="#6d28d9" font-size="9">s05 todo_write</text>
|
||||
<text x="498" y="377" fill="#6d28d9" font-size="9">s10 task graph</text>
|
||||
<text x="498" y="392" fill="#6d28d9" font-size="9">s11 background</text>
|
||||
<text x="498" y="407" fill="#6d28d9" font-size="9">s12 cron scheduler</text>
|
||||
<path d="M 575 318 L 575 188" fill="none" stroke="#7c3aed" stroke-width="1.3" marker-end="url(#arrow-purple)" stroke-dasharray="5,3"/>
|
||||
<rect x="700" y="318" width="180" height="104" rx="8" fill="#f0fdfa" stroke="#0d9488" stroke-width="1.4"/>
|
||||
<text x="790" y="341" text-anchor="middle" fill="#0f766e" font-size="11" font-weight="700">Teams & Plugins</text>
|
||||
<text x="718" y="362" fill="#0f766e" font-size="9">s06 subagent</text>
|
||||
<text x="718" y="377" fill="#0f766e" font-size="9">s13 teams + task protocols</text>
|
||||
<text x="718" y="392" fill="#0f766e" font-size="9">s13 task-bound worktrees</text>
|
||||
<text x="718" y="407" fill="#0f766e" font-size="9">s14 MCP tools</text>
|
||||
<path d="M 790 318 L 790 188" fill="none" stroke="#0d9488" stroke-width="1.3" marker-end="url(#arrow-green)" stroke-dasharray="5,3"/>
|
||||
<rect x="70" y="462" width="780" height="112" rx="8" fill="#f8fafc" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<text x="460" y="487" text-anchor="middle" fill="#1e293b" font-size="12" font-weight="700">TOOL POOL: 25 builtins + dynamic mcp__server__tool</text>
|
||||
<text x="95" y="512" fill="#334155" font-size="9">file/shell: bash · read · write · edit · glob</text>
|
||||
<text x="95" y="530" fill="#334155" font-size="9">single-agent: todo_write · task · load_skill · compact</text>
|
||||
<text x="95" y="548" fill="#334155" font-size="9">durable work: task tools · cron tools</text>
|
||||
<text x="510" y="512" fill="#334155" font-size="9">team: spawn_teammate · send_message · typed protocols</text>
|
||||
<text x="510" y="530" fill="#334155" font-size="9">protocol: request_shutdown · request_plan · review_plan</text>
|
||||
<text x="510" y="548" fill="#334155" font-size="9">workdir/plugin: create_worktree · connect_mcp</text>
|
||||
<path d="M 850 518 L 890 518 L 890 152 L 850 152" fill="none" stroke="#475569" stroke-width="1.2" marker-end="url(#arrow)" stroke-dasharray="4,4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.6 KiB |
85
s15_integrated_harness/images/system-architecture.ja.svg
Normal file
85
s15_integrated_harness/images/system-architecture.ja.svg
Normal file
@@ -0,0 +1,85 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 920 620" font-family="'Noto Sans CJK JP', 'Noto Sans CJK SC', 'Droid Sans Fallback', system-ui, -apple-system, sans-serif">
|
||||
<defs>
|
||||
<linearGradient id="header" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0%" stop-color="#1e3a5f"/>
|
||||
<stop offset="100%" stop-color="#0f766e"/>
|
||||
</linearGradient>
|
||||
<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="#475569"/>
|
||||
</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="#059669"/>
|
||||
</marker>
|
||||
<marker id="arrow-purple" 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="#7c3aed"/>
|
||||
</marker>
|
||||
<marker id="arrow-orange" 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="#ea580c"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<rect width="920" height="620" rx="8" fill="#fafbfc"/>
|
||||
<rect x="0" y="0" width="920" height="46" rx="8" fill="url(#header)"/>
|
||||
<rect x="0" y="38" width="920" height="8" fill="url(#header)"/>
|
||||
<text x="460" y="29" text-anchor="middle" fill="#fff" font-size="17" font-weight="700">s15 Integrated Harness — 多くの仕組みを 1 つのループへ</text>
|
||||
<rect x="40" y="76" width="840" height="212" rx="8" fill="#eef2ff" stroke="#2563eb" stroke-width="1.8"/>
|
||||
<text x="460" y="101" text-anchor="middle" fill="#1e3a8a" font-size="13" font-weight="700">Core Agent Loop</text>
|
||||
<rect x="70" y="128" width="110" height="48" rx="7" fill="#fff" stroke="#2563eb" stroke-width="1.4"/>
|
||||
<text x="125" y="157" text-anchor="middle" fill="#1e3a5f" font-size="11" font-weight="700">messages[]</text>
|
||||
<line x1="180" y1="152" x2="222" y2="152" stroke="#475569" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
<rect x="225" y="118" width="150" height="68" rx="7" fill="#ecfdf5" stroke="#059669" stroke-width="1.6"/>
|
||||
<text x="300" y="140" text-anchor="middle" fill="#065f46" font-size="11" font-weight="700">LLM 前</text>
|
||||
<text x="240" y="158" fill="#047857" font-size="8.5">cron/background 注入</text>
|
||||
<text x="240" y="171" fill="#047857" font-size="8.5">compact + memory + prompt</text>
|
||||
<line x1="375" y1="152" x2="417" y2="152" stroke="#475569" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
<rect x="420" y="118" width="110" height="68" rx="7" fill="#fff" stroke="#2563eb" stroke-width="1.4"/>
|
||||
<text x="475" y="146" text-anchor="middle" fill="#1e3a5f" font-size="12" font-weight="700">LLM</text>
|
||||
<text x="475" y="164" text-anchor="middle" fill="#64748b" font-size="8.5">stop_reason=tool_use?</text>
|
||||
<line x1="530" y1="152" x2="572" y2="152" stroke="#475569" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
<rect x="575" y="118" width="150" height="68" rx="7" fill="#fff7ed" stroke="#ea580c" stroke-width="1.6"/>
|
||||
<text x="650" y="140" text-anchor="middle" fill="#9a3412" font-size="11" font-weight="700">Tool 前</text>
|
||||
<text x="590" y="158" fill="#c2410c" font-size="8.5">PreToolUse hooks</text>
|
||||
<text x="590" y="171" fill="#c2410c" font-size="8.5">permission pipeline</text>
|
||||
<line x1="725" y1="152" x2="767" y2="152" stroke="#475569" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
<rect x="770" y="118" width="80" height="68" rx="7" fill="#fff" stroke="#2563eb" stroke-width="1.4"/>
|
||||
<text x="810" y="147" text-anchor="middle" fill="#1e3a5f" font-size="10" font-weight="700">handlers</text>
|
||||
<text x="810" y="164" text-anchor="middle" fill="#64748b" font-size="8">builtin + MCP</text>
|
||||
<path d="M 810 186 L 810 244 L 125 244 L 125 176" fill="none" stroke="#475569" stroke-width="1.4" stroke-dasharray="6,4" marker-end="url(#arrow)"/>
|
||||
<text x="460" y="263" text-anchor="middle" fill="#64748b" font-size="9">tool_result / task_notification → messages[] → 次のターン</text>
|
||||
<rect x="40" y="318" width="190" height="104" rx="8" fill="#ecfdf5" stroke="#059669" stroke-width="1.4"/>
|
||||
<text x="135" y="341" text-anchor="middle" fill="#065f46" font-size="11" font-weight="700">Context / Knowledge</text>
|
||||
<text x="58" y="362" fill="#047857" font-size="9">s07 skills + load_skill</text>
|
||||
<text x="58" y="377" fill="#047857" font-size="9">s09 memory selection</text>
|
||||
<text x="58" y="392" fill="#047857" font-size="9">assembled system prompt</text>
|
||||
<text x="58" y="407" fill="#047857" font-size="9">s08 compact pipeline</text>
|
||||
<path d="M 230 350 L 282 186" fill="none" stroke="#059669" stroke-width="1.3" marker-end="url(#arrow-green)" stroke-dasharray="5,3"/>
|
||||
<rect x="260" y="318" width="190" height="104" rx="8" fill="#fff7ed" stroke="#ea580c" stroke-width="1.4"/>
|
||||
<text x="355" y="341" text-anchor="middle" fill="#9a3412" font-size="11" font-weight="700">Governance</text>
|
||||
<text x="278" y="362" fill="#c2410c" font-size="9">s03 permission</text>
|
||||
<text x="278" y="377" fill="#c2410c" font-size="9">s04 hooks</text>
|
||||
<text x="278" y="392" fill="#c2410c" font-size="9">model retry / fallback</text>
|
||||
<text x="278" y="407" fill="#c2410c" font-size="9">Stop hooks</text>
|
||||
<path d="M 450 350 L 592 186" fill="none" stroke="#ea580c" stroke-width="1.3" marker-end="url(#arrow-orange)" stroke-dasharray="5,3"/>
|
||||
<rect x="480" y="318" width="190" height="104" rx="8" fill="#f5f3ff" stroke="#7c3aed" stroke-width="1.4"/>
|
||||
<text x="575" y="341" text-anchor="middle" fill="#5b21b6" font-size="11" font-weight="700">Durable Work</text>
|
||||
<text x="498" y="362" fill="#6d28d9" font-size="9">s05 todo_write</text>
|
||||
<text x="498" y="377" fill="#6d28d9" font-size="9">s10 task graph</text>
|
||||
<text x="498" y="392" fill="#6d28d9" font-size="9">s11 background</text>
|
||||
<text x="498" y="407" fill="#6d28d9" font-size="9">s12 cron scheduler</text>
|
||||
<path d="M 575 318 L 575 188" fill="none" stroke="#7c3aed" stroke-width="1.3" marker-end="url(#arrow-purple)" stroke-dasharray="5,3"/>
|
||||
<rect x="700" y="318" width="180" height="104" rx="8" fill="#f0fdfa" stroke="#0d9488" stroke-width="1.4"/>
|
||||
<text x="790" y="341" text-anchor="middle" fill="#0f766e" font-size="11" font-weight="700">Teams / Plugins</text>
|
||||
<text x="718" y="362" fill="#0f766e" font-size="9">s06 subagent</text>
|
||||
<text x="718" y="377" fill="#0f766e" font-size="9">s13 teams + task protocols</text>
|
||||
<text x="718" y="392" fill="#0f766e" font-size="9">s13 task-bound worktrees</text>
|
||||
<text x="718" y="407" fill="#0f766e" font-size="9">s14 MCP tools</text>
|
||||
<path d="M 790 318 L 790 188" fill="none" stroke="#0d9488" stroke-width="1.3" marker-end="url(#arrow-green)" stroke-dasharray="5,3"/>
|
||||
<rect x="70" y="462" width="780" height="112" rx="8" fill="#f8fafc" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<text x="460" y="487" text-anchor="middle" fill="#1e293b" font-size="12" font-weight="700">TOOL POOL: 25 builtins + dynamic mcp__server__tool</text>
|
||||
<text x="95" y="512" fill="#334155" font-size="9">file/shell: bash · read · write · edit · glob</text>
|
||||
<text x="95" y="530" fill="#334155" font-size="9">single-agent: todo_write · task · load_skill · compact</text>
|
||||
<text x="95" y="548" fill="#334155" font-size="9">durable work: task tools · cron tools</text>
|
||||
<text x="510" y="512" fill="#334155" font-size="9">team: spawn_teammate · send_message · typed protocols</text>
|
||||
<text x="510" y="530" fill="#334155" font-size="9">protocol: request_shutdown · request_plan · review_plan</text>
|
||||
<text x="510" y="548" fill="#334155" font-size="9">workdir/plugin: create_worktree · connect_mcp</text>
|
||||
<path d="M 850 518 L 890 518 L 890 152 L 850 152" fill="none" stroke="#475569" stroke-width="1.2" marker-end="url(#arrow)" stroke-dasharray="4,4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.7 KiB |
105
s15_integrated_harness/images/system-architecture.svg
Normal file
105
s15_integrated_harness/images/system-architecture.svg
Normal file
@@ -0,0 +1,105 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 920 620" font-family="'Noto Sans CJK SC', 'Droid Sans Fallback', system-ui, -apple-system, sans-serif">
|
||||
<defs>
|
||||
<linearGradient id="header" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0%" stop-color="#1e3a5f"/>
|
||||
<stop offset="100%" stop-color="#0f766e"/>
|
||||
</linearGradient>
|
||||
<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="#475569"/>
|
||||
</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="#059669"/>
|
||||
</marker>
|
||||
<marker id="arrow-purple" 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="#7c3aed"/>
|
||||
</marker>
|
||||
<marker id="arrow-orange" 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="#ea580c"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<rect width="920" height="620" rx="8" fill="#fafbfc"/>
|
||||
<rect x="0" y="0" width="920" height="46" rx="8" fill="url(#header)"/>
|
||||
<rect x="0" y="38" width="920" height="8" fill="url(#header)"/>
|
||||
<text x="460" y="29" text-anchor="middle" fill="#fff" font-size="17" font-weight="700">s15 Agent Harness 集成 — 多种机制,一个循环</text>
|
||||
|
||||
<!-- Main loop band -->
|
||||
<rect x="40" y="76" width="840" height="212" rx="8" fill="#eef2ff" stroke="#2563eb" stroke-width="1.8"/>
|
||||
<text x="460" y="101" text-anchor="middle" fill="#1e3a8a" font-size="13" font-weight="700">核心 Agent Loop</text>
|
||||
|
||||
<rect x="70" y="128" width="110" height="48" rx="7" fill="#fff" stroke="#2563eb" stroke-width="1.4"/>
|
||||
<text x="125" y="157" text-anchor="middle" fill="#1e3a5f" font-size="11" font-weight="700">messages[]</text>
|
||||
|
||||
<line x1="180" y1="152" x2="222" y2="152" stroke="#475569" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
|
||||
<rect x="225" y="118" width="150" height="68" rx="7" fill="#ecfdf5" stroke="#059669" stroke-width="1.6"/>
|
||||
<text x="300" y="140" text-anchor="middle" fill="#065f46" font-size="11" font-weight="700">LLM 前处理</text>
|
||||
<text x="240" y="158" fill="#047857" font-size="8.5">cron / background 注入</text>
|
||||
<text x="240" y="171" fill="#047857" font-size="8.5">compact + memory + prompt</text>
|
||||
|
||||
<line x1="375" y1="152" x2="417" y2="152" stroke="#475569" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
|
||||
<rect x="420" y="118" width="110" height="68" rx="7" fill="#fff" stroke="#2563eb" stroke-width="1.4"/>
|
||||
<text x="475" y="146" text-anchor="middle" fill="#1e3a5f" font-size="12" font-weight="700">LLM</text>
|
||||
<text x="475" y="164" text-anchor="middle" fill="#64748b" font-size="8.5">stop_reason=tool_use?</text>
|
||||
|
||||
<line x1="530" y1="152" x2="572" y2="152" stroke="#475569" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
|
||||
<rect x="575" y="118" width="150" height="68" rx="7" fill="#fff7ed" stroke="#ea580c" stroke-width="1.6"/>
|
||||
<text x="650" y="140" text-anchor="middle" fill="#9a3412" font-size="11" font-weight="700">工具前闸门</text>
|
||||
<text x="590" y="158" fill="#c2410c" font-size="8.5">PreToolUse hooks</text>
|
||||
<text x="590" y="171" fill="#c2410c" font-size="8.5">permission pipeline</text>
|
||||
|
||||
<line x1="725" y1="152" x2="767" y2="152" stroke="#475569" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
|
||||
<rect x="770" y="118" width="80" height="68" rx="7" fill="#fff" stroke="#2563eb" stroke-width="1.4"/>
|
||||
<text x="810" y="147" text-anchor="middle" fill="#1e3a5f" font-size="10" font-weight="700">handlers</text>
|
||||
<text x="810" y="164" text-anchor="middle" fill="#64748b" font-size="8">builtin + MCP</text>
|
||||
|
||||
<path d="M 810 186 L 810 244 L 125 244 L 125 176" fill="none" stroke="#475569" stroke-width="1.4" stroke-dasharray="6,4" marker-end="url(#arrow)"/>
|
||||
<text x="460" y="263" text-anchor="middle" fill="#64748b" font-size="9">tool_result / task_notification → messages[] → 下一轮</text>
|
||||
|
||||
<!-- Top sidecars -->
|
||||
<rect x="40" y="318" width="190" height="104" rx="8" fill="#ecfdf5" stroke="#059669" stroke-width="1.4"/>
|
||||
<text x="135" y="341" text-anchor="middle" fill="#065f46" font-size="11" font-weight="700">上下文与知识</text>
|
||||
<text x="58" y="362" fill="#047857" font-size="9">s07 skills catalog + load_skill</text>
|
||||
<text x="58" y="377" fill="#047857" font-size="9">s09 memory selection</text>
|
||||
<text x="58" y="392" fill="#047857" font-size="9">组合后的 system prompt</text>
|
||||
<text x="58" y="407" fill="#047857" font-size="9">s08 compact pipeline</text>
|
||||
<path d="M 230 350 L 282 186" fill="none" stroke="#059669" stroke-width="1.3" marker-end="url(#arrow-green)" stroke-dasharray="5,3"/>
|
||||
|
||||
<rect x="260" y="318" width="190" height="104" rx="8" fill="#fff7ed" stroke="#ea580c" stroke-width="1.4"/>
|
||||
<text x="355" y="341" text-anchor="middle" fill="#9a3412" font-size="11" font-weight="700">治理与扩展点</text>
|
||||
<text x="278" y="362" fill="#c2410c" font-size="9">s03 permission</text>
|
||||
<text x="278" y="377" fill="#c2410c" font-size="9">s04 hooks</text>
|
||||
<text x="278" y="392" fill="#c2410c" font-size="9">model retry / fallback</text>
|
||||
<text x="278" y="407" fill="#c2410c" font-size="9">Stop hooks</text>
|
||||
<path d="M 450 350 L 592 186" fill="none" stroke="#ea580c" stroke-width="1.3" marker-end="url(#arrow-orange)" stroke-dasharray="5,3"/>
|
||||
|
||||
<rect x="480" y="318" width="190" height="104" rx="8" fill="#f5f3ff" stroke="#7c3aed" stroke-width="1.4"/>
|
||||
<text x="575" y="341" text-anchor="middle" fill="#5b21b6" font-size="11" font-weight="700">持久工作</text>
|
||||
<text x="498" y="362" fill="#6d28d9" font-size="9">s05 todo_write</text>
|
||||
<text x="498" y="377" fill="#6d28d9" font-size="9">s10 task graph</text>
|
||||
<text x="498" y="392" fill="#6d28d9" font-size="9">s11 background</text>
|
||||
<text x="498" y="407" fill="#6d28d9" font-size="9">s12 cron scheduler</text>
|
||||
<path d="M 575 318 L 575 188" fill="none" stroke="#7c3aed" stroke-width="1.3" marker-end="url(#arrow-purple)" stroke-dasharray="5,3"/>
|
||||
|
||||
<rect x="700" y="318" width="180" height="104" rx="8" fill="#f0fdfa" stroke="#0d9488" stroke-width="1.4"/>
|
||||
<text x="790" y="341" text-anchor="middle" fill="#0f766e" font-size="11" font-weight="700">团队与插件</text>
|
||||
<text x="718" y="362" fill="#0f766e" font-size="9">s06 subagent</text>
|
||||
<text x="718" y="377" fill="#0f766e" font-size="9">s13 teams + task protocols</text>
|
||||
<text x="718" y="392" fill="#0f766e" font-size="9">s13 task-bound worktrees</text>
|
||||
<text x="718" y="407" fill="#0f766e" font-size="9">s14 MCP tools</text>
|
||||
<path d="M 790 318 L 790 188" fill="none" stroke="#0d9488" stroke-width="1.3" marker-end="url(#arrow-green)" stroke-dasharray="5,3"/>
|
||||
|
||||
<!-- Tool pool -->
|
||||
<rect x="70" y="462" width="780" height="112" rx="8" fill="#f8fafc" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<text x="460" y="487" text-anchor="middle" fill="#1e293b" font-size="12" font-weight="700">TOOL POOL: 25 builtins + dynamic mcp__server__tool</text>
|
||||
<text x="95" y="512" fill="#334155" font-size="9">file/shell: bash · read · write · edit · glob</text>
|
||||
<text x="95" y="530" fill="#334155" font-size="9">single-agent: todo_write · task · load_skill · compact</text>
|
||||
<text x="95" y="548" fill="#334155" font-size="9">durable work: create/list/get/claim/complete_task · schedule/list/cancel_cron</text>
|
||||
<text x="510" y="512" fill="#334155" font-size="9">team: spawn_teammate · send_message · typed protocols</text>
|
||||
<text x="510" y="530" fill="#334155" font-size="9">protocol: request_shutdown · request_plan · review_plan</text>
|
||||
<text x="510" y="548" fill="#334155" font-size="9">workdir/plugin: create_worktree · connect_mcp</text>
|
||||
<path d="M 850 518 L 890 518 L 890 152 L 850 152" fill="none" stroke="#475569" stroke-width="1.2" marker-end="url(#arrow)" stroke-dasharray="4,4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.8 KiB |
Reference in New Issue
Block a user