feat: consolidate course into 21 lessons

This commit is contained in:
Haoran
2026-07-31 03:15:58 +08:00
parent 4bc33ec858
commit 2d69019342
200 changed files with 7338 additions and 10829 deletions

View File

@@ -0,0 +1,204 @@
# s16: Autonomous Agents — ボードを見て、自分で Claim する
[English](README.md) · [中文](README.zh.md) · [日本語](README.ja.md)
s01 → ... → s14 → s15 → `s16` → [s17](../s17_worktree_isolation/) → s18 → s19 → s20 → s21
> *「IDLE はメッセージを待つだけでなく、開始可能な仕事を探す時間でもある。」* — 共有タスクボード、自動発見、原子的な Claim。
>
> **Harness レイヤー**:自律 — Lead は目標を管理し、チームメイトは共有状態から次の仕事を見つける。
---
## 問題
s15 のチームメイトは作業後に IDLE へ入り、Lead から次の依頼を待つ。タスクボードに 10 件の pending task があっても、Lead はチームメイトを選び、メッセージを送り、結果を待つ操作を 10 回繰り返す必要がある。
仕事がすでに分解され、依存関係もタスクボードに記録されているなら、次の ready task の割り当てに毎回モデル判断は要らない。IDLE のチームメイト自身が共有状態を読み、前提条件を満たした仕事を Claim できる。
---
## 解決策
![Autonomous Agents Overview](images/autonomous-agents-overview.ja.svg)
s16 は s15 のチームライフサイクルを変えず、IDLE の動作だけを拡張する:
```text
s15: WORK → result → IDLE → メッセージを待つ
s16: WORK → result → IDLE → メッセージを待つ
└→ ボード走査 → Claim → WORK
```
追加する関数は 2 つ:
- `scan_unclaimed_tasks()`:現在開始できるタスクを探す。
- `claim_next_task(name)`:候補の 1 件を原子的に Claim する。
チームメイトのツールにも `list_tasks``claim_task``complete_task` を加え、同じループ内で作業を完了できるようにする。
---
## 仕組み
### 1. 発見と所有権を分離する
走査は状態を変更せず、読み取りだけを行う:
```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)
)
]
```
候補は `pending` で、owner がなく、すべての `blockedBy` が完了していなければならない。
ただし候補一覧は一時点のスナップショットにすぎない。直後に別のチームメイトが同じタスクを Claim する可能性があるため、「発見した」と「所有した」を同じ意味にしてはいけない。
### 2. Claim はロック内で読み取り、確認、書き込みを行う
`claim_task()` は状態遷移全体を `task_lock` で保護する:
```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:
return "Task is no longer available"
if not can_start(task_id):
return "Task is blocked"
task.owner = owner
task.status = "in_progress"
save_task(task)
return f"Claimed {task.id}"
```
`claim_next_task()` は成功する候補が見つかるまで順に試す:
```python
def claim_next_task(name: str) -> Task | None:
for task in scan_unclaimed_tasks():
result = claim_task(task.id, owner=name)
if result.startswith("Claimed "):
return load_task(task.id)
return None
```
複数のチームメイトが同時にボードを観察しても、最終的な owner は Claim 関数によって 1 人に決まる。
### 3. メッセージを優先し、その後にタスクを探す
IDLE に入ったチームメイトは、まず短時間だけ受信イベントを待つ:
```python
while True:
inbox = BUS.wait_for_messages(name, IDLE_SCAN_INTERVAL)
if inbox:
handle_messages(inbox)
break
task = claim_next_task(name)
if task:
messages.append({
"role": "user",
"content": (
f"[Auto-claimed task {task.id}] "
f"{task.subject}\n{task.description}"
),
})
break
```
この順序にする理由は明確だ:
- shutdown、計画承認、Lead からの直接メッセージにはすぐ応答する。
- メッセージがない IDLE 時間だけを、共有タスクの探索に使う。
メッセージも ready task もなければ IDLE を続ける。候補が空なのは、依存タスクがまだ完了していないだけかもしれない。
### 4. Claim 後は同じ WORK ループを再利用する
Claim に成功すると、ランタイムはタスク ID、件名、説明をチームメイトの messages へ追加する。ファイルツール、Shell、計画ゲート、結果通知、終了プロトコルはすべて s15 の仕組みをそのまま使う。
```text
ready task が現れる
→ IDLE のチームメイトが発見
→ claim_task が owner と in_progress を記録
→ タスクが messages に入る
→ WORK
→ complete_task
→ result + idle_notification
→ 再び走査
```
自律のために別の Agent Loop を作る必要はない。既存ループへ共有状態から入る入口を追加すればよい。
---
## この設計を選ぶ理由
**Lead が毎回割り当てないのはなぜか。**
`status``owner``blockedBy` が実行可能性をすでに表している。同じ状態を Lead に毎回解釈させても、調整ターンが増えるだけである。
**走査時に owner を設定しないのはなぜか。**
走査は並行実行され得る。所有権変更を 1 つのロック付き関数に集めれば、すべての呼び出し元が同じ規則に従う。
**ready task がない時に終了しないのはなぜか。**
依存タスクが完了すれば、後続タスクが ready になる。IDLE を維持すれば、その瞬間に次の仕事を引き継げる。
---
## s15 からの変更
| コンポーネント | s15 | s16 |
|---|---|---|
| IDLE | チームメッセージを待つ | メッセージ待機後にボードを走査 |
| 割り当て | Lead が明示的に送る | チームメイトが自動 Claim 可能 |
| 所有権 | 呼び出し元が Claim | `task_lock` で Claim を原子的にする |
| チームメイトツール | ファイル、Shell、メッセージ、計画 | list / claim / complete task を追加 |
| 結果と終了 | `result``idle_notification`、shutdown protocol | 変更なし |
---
## 試してみる
```sh
cd learn-claude-code
python s16_autonomous_agents/code.py
```
通常の要求を入力する:
```text
バックエンド改修を共有タスクボードへ分解し、依存関係が許す範囲で
設定、認証、テストを並行実行してください。既存インターフェースを
維持し、最後に結果をまとめてください。
```
Lead がチーム案を示したら、次のように返す:
```text
始めてください
```
`.tasks/` のタスクが `pending``in_progress``completed` と変化する様子を確認する。2 人の IDLE チームメイトは別々のタスクを Claim し、`blockedBy` のあるタスクは前提完了後にだけ候補になるはずだ。
---
## 次へ
チームメイトは仕事を自分で見つけられるようになったが、まだ同じディレクトリでファイルを変更する。次のセッションではタスク所有権を分離された作業ディレクトリへ結び付ける。
次へ:[s17 Worktree Isolation](../s17_worktree_isolation/)。
<!-- translation-sync: zh@v3, en@v3, ja@v3 -->

View File

@@ -0,0 +1,204 @@
# s16: Autonomous Agents — Check the Board, Claim the Work
[English](README.md) · [中文](README.zh.md) · [日本語](README.ja.md)
s01 → ... → s14 → s15 → `s16` → [s17](../s17_worktree_isolation/) → s18 → s19 → s20 → s21
> *"Idle does not only mean waiting for a message; it can also mean looking for ready work."* — Shared task board, automatic discovery, and atomic claims.
>
> **Harness layer**: Autonomy — Lead owns the goal while teammates discover the next step from shared state.
---
## The Problem
In s15, a teammate enters IDLE after finishing an assignment and waits for Lead to send more work. If the task board already contains ten pending tasks, Lead still has to choose a teammate, send a message, and wait for a result ten times.
Once work has been decomposed and dependencies are recorded on the task board, assigning the next ready task does not always need another model decision. An idle teammate can read shared state and claim work whose prerequisites are complete.
---
## The Solution
![Autonomous Agents Overview](images/autonomous-agents-overview.en.svg)
s16 keeps the s15 team lifecycle and extends only the IDLE state:
```text
s15: WORK → result → IDLE → wait for a message
s16: WORK → result → IDLE → wait for a message
└→ scan board → claim → WORK
```
It adds two functions:
- `scan_unclaimed_tasks()` finds tasks that can start now.
- `claim_next_task(name)` attempts to claim one candidate atomically.
Teammates also receive `list_tasks`, `claim_task`, and `complete_task`, allowing the claimed work to close inside the same loop.
---
## How It Works
### 1. Discovery and ownership are separate steps
Scanning reads state without changing it:
```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)
)
]
```
A candidate must be `pending`, have no owner, and have every `blockedBy` dependency completed.
The resulting list is only a snapshot. Another teammate may claim the same task immediately afterward, so "discovered" must never mean "owned."
### 2. Claim performs read, validation, and write under one lock
`claim_task()` protects the full state transition with `task_lock`:
```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:
return "Task is no longer available"
if not can_start(task_id):
return "Task is blocked"
task.owner = owner
task.status = "in_progress"
save_task(task)
return f"Claimed {task.id}"
```
`claim_next_task()` tries candidates until one claim succeeds:
```python
def claim_next_task(name: str) -> Task | None:
for task in scan_unclaimed_tasks():
result = claim_task(task.id, owner=name)
if result.startswith("Claimed "):
return load_task(task.id)
return None
```
Many teammates may observe the board at once, but the claim function gives each task one final owner.
### 3. Messages take priority over board scans
In IDLE, a teammate first waits briefly for mailbox events:
```python
while True:
inbox = BUS.wait_for_messages(name, IDLE_SCAN_INTERVAL)
if inbox:
handle_messages(inbox)
break
task = claim_next_task(name)
if task:
messages.append({
"role": "user",
"content": (
f"[Auto-claimed task {task.id}] "
f"{task.subject}\n{task.description}"
),
})
break
```
This ordering matters:
- Shutdown, plan approval, and direct Lead messages should be handled promptly.
- Only otherwise idle time is used to look for shared work.
If there is neither a message nor a ready task, the teammate stays idle. An empty scan is not a reason to exit because a blocked task may become ready later.
### 4. A claimed task reuses the same WORK loop
After a successful claim, the runtime injects the task ID, subject, and description into the teammate's messages. The existing file tools, Shell, plan gate, result reporting, and shutdown protocol all remain unchanged.
```text
ready task appears
→ idle teammate discovers it
→ claim_task writes owner and in_progress
→ task enters teammate messages
→ WORK
→ complete_task
→ result + idle_notification
→ scan again
```
Autonomy does not require another agent loop. It adds a shared-state entry point to the loop that already exists.
---
## Why This Design
**Why not ask Lead to assign every task?**
The task's `status`, `owner`, and `blockedBy` already encode whether it can run. Reinterpreting that same state through Lead adds coordination turns without adding judgment.
**Why not set the owner during scanning?**
Scans may overlap. Keeping ownership changes in one locked function gives every caller the same rule.
**Why keep teammates alive when no task is ready?**
An empty candidate list may only mean that prerequisites are still running. IDLE teammates can pick up downstream work as soon as it becomes ready.
---
## What Changed from s15
| Component | s15 | s16 |
|---|---|---|
| IDLE behavior | Wait for team messages | Wait for messages, then scan the board |
| Assignment | Lead sends work explicitly | Teammates may auto-claim |
| Ownership | Caller initiates claim | `task_lock` makes claim atomic |
| Teammate tools | Files, Shell, messages, plans | Adds list / claim / complete task |
| Result and shutdown | `result`, `idle_notification`, shutdown protocol | Unchanged |
---
## Try It
```sh
cd learn-claude-code
python s16_autonomous_agents/code.py
```
Enter an ordinary request:
```text
Put the backend refactor on a shared task board. Complete configuration,
authentication, and tests in parallel where dependencies allow, preserve
existing interfaces, and summarize the result.
```
After Lead proposes a team, reply:
```text
Go ahead.
```
Watch tasks move from `pending` to `in_progress` and `completed` under `.tasks/`. Two idle teammates should claim different tasks, and a task with `blockedBy` should become a candidate only after its prerequisites finish.
---
## Next
Teammates can now discover work, but they still edit files in the same directory. The next lesson binds task ownership to isolated working directories.
Next: [s17 Worktree Isolation](../s17_worktree_isolation/).
<!-- translation-sync: zh@v3, en@v3, ja@v3 -->

View File

@@ -0,0 +1,207 @@
# s16: Autonomous Agents — 自己看板,自己认领
[English](README.md) · [中文](README.zh.md) · [日本語](README.ja.md)
s01 → ... → s14 → s15 → `s16` → [s17](../s17_worktree_isolation/) → s18 → s19 → s20 → s21
> *"空闲时不只等消息,也主动寻找可以开始的工作。"* — 共享任务板、自动发现与原子认领。
>
> **Harness 层**:自治 — Lead 管目标,队友从任务状态中发现下一步。
---
## 问题
s15 的队友会在完成一项工作后进入 IDLE等待 Lead 继续派发。如果任务板上已经有十个待办任务Lead 仍然要逐个选择队友、发送消息,再等待结果。
当任务已经被拆分,并且依赖关系也写进了任务板,谁来执行下一项工作不一定需要 Lead 再做一次模型决策。空闲队友可以直接读取共享状态,找到已经满足条件的任务并认领它。
---
## 解决方案
![Autonomous Agents Overview](images/autonomous-agents-overview.svg)
s16 不改变 s15 的团队生命周期,只扩展 IDLE 状态:
```text
s15: WORK → result → IDLE → 等待消息
s16: WORK → result → IDLE → 等待消息
└→ 扫描任务板 → 认领 → WORK
```
新增两个函数:
- `scan_unclaimed_tasks()`:找出当前可以开始的任务。
- `claim_next_task(name)`:尝试原子认领其中一个任务。
队友工具集同时增加 `list_tasks``claim_task``complete_task`,让认领后的工作能在同一个循环中闭合。
---
## 工作原理
### 1. 发现任务和认领任务是两步
扫描只读取状态,不修改任务:
```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)
)
]
```
一个任务必须同时满足三个条件:
- 状态是 `pending`
- 还没有 `owner`
- `blockedBy` 中的任务都已经完成。
扫描得到的只是候选列表。另一个队友可能在下一瞬间认领同一任务,因此不能把“扫描到”当成“已经拥有”。
### 2. claim 在锁内完成读、检查和写入
`claim_task()` 使用同一把 `task_lock` 包住完整的读改写过程:
```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:
return "Task is no longer available"
if not can_start(task_id):
return "Task is blocked"
task.owner = owner
task.status = "in_progress"
save_task(task)
return f"Claimed {task.id}"
```
`claim_next_task()` 依次尝试候选任务。某次认领失败时,它会继续尝试下一个,而不是把失败误当成成功:
```python
def claim_next_task(name: str) -> Task | None:
for task in scan_unclaimed_tasks():
result = claim_task(task.id, owner=name)
if result.startswith("Claimed "):
return load_task(task.id)
return None
```
扫描负责发现claim 负责所有权。把两者分开后,多个队友可以同时观察任务板,但每个任务只能有一个最终 owner。
### 3. 消息优先,任务扫描其次
队友进入 IDLE 后,先等待一小段时间的收件箱事件:
```python
while True:
inbox = BUS.wait_for_messages(name, IDLE_SCAN_INTERVAL)
if inbox:
handle_messages(inbox)
break
task = claim_next_task(name)
if task:
messages.append({
"role": "user",
"content": (
f"[Auto-claimed task {task.id}] "
f"{task.subject}\n{task.description}"
),
})
break
```
这样安排有两个原因:
- 关机、计划审批和 Lead 的直接消息应该尽快响应;
- 没有消息时,空闲时间才用于寻找共享任务。
如果既没有消息也没有可认领任务,队友继续保持 IDLE不会因为一次扫描为空就退出。
### 4. 自动认领后复用同一个 WORK 循环
认领成功后,运行时把任务 ID、标题和描述写入队友 messages。对模型来说它只是收到了一项新工作文件、Shell、计划闸门、结果上报都继续使用 s15 的机制。
```text
任务板出现 ready task
→ 空闲队友扫描到候选
→ claim_task 写入 owner 和 in_progress
→ 任务进入队友 messages
→ WORK
→ complete_task
→ result + idle_notification
→ 再次扫描
```
自治不是再造一个 Agent Loop而是给既有循环增加一个由共享状态触发的入口。
---
## 为什么这样设计
**为什么不是 Lead 每次分配?**
任务依赖已经编码在 `status``owner``blockedBy` 中。让 Lead 反复解释同一状态,只会增加协调轮次。
**为什么不是扫描时直接改 owner**
扫描可能并发发生。把认领集中到带锁的函数中,所有调用方共享同一个所有权规则。
**为什么不在没有任务时关闭队友?**
暂时没有 ready task 可能只是因为依赖尚未完成。保持 IDLE 后,前置任务完成时队友可以自动接上后续工作。
---
## 相对 s15 的变化
| 组件 | s15 | s16 |
|---|---|---|
| IDLE 行为 | 等待团队消息 | 先等消息,再扫描任务板 |
| 任务分配 | Lead 明确派发 | 队友可自动认领 |
| 任务所有权 | 调用方发起 claim | `task_lock` 保证认领原子性 |
| 队友工具 | 文件、Shell、消息、计划 | 增加 list / claim / complete task |
| 结果与关机 | `result``idle_notification`、shutdown 协议 | 保持不变 |
---
## 试一下
```sh
cd learn-claude-code
python s16_autonomous_agents/code.py
```
输入一个自然需求:
```text
请把后端改造拆到共享任务板,按依赖关系并行完成配置、认证和测试,
保持现有接口兼容,并在最后汇总结果。
```
Lead 提出团队方案后回复:
```text
开始吧
```
观察 `.tasks/` 中任务如何从 `pending` 进入 `in_progress``completed`,以及两个空闲队友是否会认领不同任务。带 `blockedBy` 的任务应该只在前置任务完成后出现为候选。
---
## 接下来
队友已经能自己找到任务,但仍然在同一个工作目录里修改文件。下一章把任务所有权和工作目录绑定起来,让并行工作彼此隔离。
下一章:[s17 Worktree Isolation](../s17_worktree_isolation/)。
<!-- translation-sync: zh@v3, en@v3, ja@v3 -->

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,109 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760 470" font-family="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="#059669"/>
</linearGradient>
<marker id="arrow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#555"/>
</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-red" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#dc2626"/>
</marker>
</defs>
<rect width="760" height="470" fill="#fafbfc" rx="8"/>
<!-- Title -->
<rect x="0" y="0" width="760" height="44" fill="url(#header)" rx="8"/>
<rect x="0" y="36" width="760" height="8" fill="url(#header)"/>
<text x="380" y="28" fill="#fff" font-size="15" font-weight="700" text-anchor="middle">Autonomous Agents — Idle Task Discovery + Atomic Claim</text>
<!-- Legend -->
<rect x="40" y="56" width="12" height="10" rx="2" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
<text x="58" y="66" fill="#2563eb" font-size="10" font-weight="600">s15 Preserved</text>
<rect x="160" y="56" width="12" height="10" rx="2" fill="#ecfdf5" stroke="#059669" stroke-width="1"/>
<text x="178" y="66" fill="#059669" font-size="10" font-weight="600">s16 New</text>
<!-- ===== Row 1: Lead Loop (s15 preserved) ===== -->
<rect x="20" y="90" width="70" height="40" rx="8" fill="#eef2ff" stroke="#4f46e5" stroke-width="1.5"/>
<text x="55" y="114" fill="#4f46e5" font-size="8" font-weight="600" text-anchor="middle">turn</text>
<line x1="90" y1="110" x2="104" y2="110" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
<rect x="107" y="90" width="70" height="40" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
<text x="142" y="114" fill="#1e3a5f" font-size="10" font-weight="600" text-anchor="middle">messages</text>
<line x1="177" y1="110" x2="191" y2="110" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
<rect x="194" y="86" width="80" height="48" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
<text x="234" y="114" fill="#1e3a5f" font-size="9" font-weight="600" text-anchor="middle">prompt</text>
<line x1="274" y1="110" x2="288" y2="110" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
<rect x="291" y="86" width="70" height="48" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
<text x="326" y="114" fill="#1e3a5f" font-size="9" font-weight="600" text-anchor="middle">LLM</text>
<line x1="361" y1="110" x2="375" y2="110" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
<rect x="378" y="80" width="356" height="60" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
<text x="556" y="98" fill="#1e3a5f" font-size="10" font-weight="600" text-anchor="middle">TOOL DISPATCH (all s15 preserved)</text>
<text x="394" y="114" fill="#2563eb" font-size="8">bash · read · write · task tools · send · protocols</text>
<text x="394" y="128" fill="#7c3aed" font-size="8" font-weight="700">★ request_shutdown · request_plan · review_plan</text>
<!-- Loop back -->
<path d="M 734 110 L 748 110 L 748 150 L 55 150 L 55 130" fill="none" stroke="#94a3b8" stroke-width="1" marker-end="url(#arrow)" stroke-dasharray="5,4"/>
<!-- Connector: s16 extends the existing idle state with task discovery -->
<path d="M 326 134 L 326 160 L 170 160 L 170 210" fill="none" stroke="#059669" stroke-width="1.5" marker-end="url(#arrow-green)" stroke-dasharray="5,3"/>
<text x="248" y="156" fill="#059669" font-size="8" font-weight="600" text-anchor="middle">idle teammates look for ready work</text>
<!-- ===== Row 2: Task discovery added to the s15 lifecycle ===== -->
<rect x="30" y="172" width="700" height="195" rx="8" fill="#ecfdf5" stroke="#059669" stroke-width="2"/>
<text x="380" y="194" fill="#065f46" font-size="11" font-weight="700" text-anchor="middle">s15 Lifecycle + s16 Task-Board Entry</text>
<!-- WORK box -->
<rect x="55" y="210" width="230" height="100" rx="6" fill="#fff" stroke="#059669" stroke-width="1.5"/>
<text x="170" y="230" fill="#059669" font-size="10" font-weight="700" text-anchor="middle">WORK Phase</text>
<text x="70" y="248" fill="#374151" font-size="8">runtime-delivered messages → LLM → tool calls</text>
<text x="70" y="262" fill="#374151" font-size="8">stop_reason == tool_use → loop</text>
<text x="70" y="276" fill="#374151" font-size="8">stop_reason != tool_use → IDLE</text>
<text x="70" y="298" fill="#6b7280" font-size="7">send result, then enter IDLE</text>
<!-- Arrow: WORK → IDLE -->
<line x1="285" y1="260" x2="415" y2="260" stroke="#059669" stroke-width="1.5" marker-end="url(#arrow-green)"/>
<text x="350" y="253" fill="#059669" font-size="8" font-weight="600" text-anchor="middle">task done</text>
<!-- Arrow: IDLE → WORK (curved, above) -->
<path d="M 415 232 C 375 200, 320 200, 285 232" fill="none" stroke="#059669" stroke-width="1.5" marker-end="url(#arrow-green)" stroke-dasharray="5,3"/>
<text x="350" y="208" fill="#059669" font-size="8" font-weight="600" text-anchor="middle">work found</text>
<!-- IDLE box -->
<rect x="418" y="210" width="295" height="100" rx="6" fill="#fff" stroke="#059669" stroke-width="1.5"/>
<text x="565" y="230" fill="#059669" font-size="10" font-weight="700" text-anchor="middle">IDLE Phase</text>
<text x="433" y="248" fill="#374151" font-size="8">├ Wait for runtime delivery → back to WORK</text>
<text x="433" y="264" fill="#374151" font-size="8">├ scan_unclaimed_tasks → claim → back to WORK</text>
<text x="433" y="280" fill="#374151" font-size="8">└ No ready task → remain IDLE</text>
<text x="433" y="298" fill="#6b7280" font-size="7">wait_for_messages() + claim_next_task()</text>
<!-- SHUTDOWN box -->
<rect x="515" y="335" width="130" height="24" rx="6" fill="#fef2f2" stroke="#dc2626" stroke-width="1.5"/>
<text x="580" y="351" fill="#991b1b" font-size="9" font-weight="700" text-anchor="middle">SHUTDOWN</text>
<!-- Arrow: IDLE → SHUTDOWN -->
<line x1="580" y1="310" x2="580" y2="335" stroke="#dc2626" stroke-width="1.5" marker-end="url(#arrow-red)"/>
<text x="598" y="326" fill="#dc2626" font-size="7">shutdown_request</text>
<!-- ===== Row 3: Bottom notes ===== -->
<rect x="30" y="388" width="700" height="42" rx="6" fill="#f8fafc" stroke="#e2e8f0" stroke-width="1"/>
<rect x="50" y="400" width="12" height="10" rx="2" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
<text x="70" y="410" fill="#475569" font-size="10">s15: MessageBus + protocols + request_shutdown + plan approval</text>
<rect x="50" y="414" width="12" height="10" rx="2" fill="#ecfdf5" stroke="#059669" stroke-width="1"/>
<text x="70" y="424" fill="#475569" font-size="10">s16: scan_unclaimed_tasks + claim_next_task + task_lock</text>
<!-- ===== Row 4: Autonomous note ===== -->
<rect x="30" y="442" width="700" height="22" rx="4" fill="#ecfdf5" stroke="#059669" stroke-width="1"/>
<text x="380" y="457" fill="#065f46" font-size="9" text-anchor="middle">Lead creates the task graph · teammates gain 3 task tools and atomically claim ready work</text>
</svg>

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@@ -0,0 +1,109 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760 470" font-family="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="#059669"/>
</linearGradient>
<marker id="arrow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#555"/>
</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-red" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#dc2626"/>
</marker>
</defs>
<rect width="760" height="470" fill="#fafbfc" rx="8"/>
<!-- Title -->
<rect x="0" y="0" width="760" height="44" fill="url(#header)" rx="8"/>
<rect x="0" y="36" width="760" height="8" fill="url(#header)"/>
<text x="380" y="28" fill="#fff" font-size="14" font-weight="700" text-anchor="middle">Autonomous Agents — アイドル時のタスク発見 + 原子的な認領</text>
<!-- Legend -->
<rect x="40" y="56" width="12" height="10" rx="2" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
<text x="58" y="66" fill="#2563eb" font-size="10" font-weight="600">s15 保持</text>
<rect x="130" y="56" width="12" height="10" rx="2" fill="#ecfdf5" stroke="#059669" stroke-width="1"/>
<text x="148" y="66" fill="#059669" font-size="10" font-weight="600">s16 新規</text>
<!-- ===== Row 1: Lead Loop ===== -->
<rect x="20" y="90" width="70" height="40" rx="8" fill="#eef2ff" stroke="#4f46e5" stroke-width="1.5"/>
<text x="55" y="114" fill="#4f46e5" font-size="8" font-weight="600" text-anchor="middle">turn</text>
<line x1="90" y1="110" x2="104" y2="110" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
<rect x="107" y="90" width="70" height="40" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
<text x="142" y="114" fill="#1e3a5f" font-size="10" font-weight="600" text-anchor="middle">messages</text>
<line x1="177" y1="110" x2="191" y2="110" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
<rect x="194" y="86" width="80" height="48" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
<text x="234" y="114" fill="#1e3a5f" font-size="9" font-weight="600" text-anchor="middle">prompt</text>
<line x1="274" y1="110" x2="288" y2="110" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
<rect x="291" y="86" width="70" height="48" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
<text x="326" y="114" fill="#1e3a5f" font-size="9" font-weight="600" text-anchor="middle">LLM</text>
<line x1="361" y1="110" x2="375" y2="110" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
<rect x="378" y="80" width="356" height="60" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
<text x="556" y="98" fill="#1e3a5f" font-size="10" font-weight="600" text-anchor="middle">TOOL DISPATCHs15 全保持)</text>
<text x="394" y="114" fill="#2563eb" font-size="8">bash · read · write · task tools · send · protocols</text>
<text x="394" y="128" fill="#7c3aed" font-size="8" font-weight="700">★ request_shutdown · request_plan · review_plan</text>
<!-- Loop back -->
<path d="M 734 110 L 748 110 L 748 150 L 55 150 L 55 130" fill="none" stroke="#94a3b8" stroke-width="1" marker-end="url(#arrow)" stroke-dasharray="5,4"/>
<!-- Connector: s16 extends the existing idle state with task discovery -->
<path d="M 326 134 L 326 160 L 170 160 L 170 210" fill="none" stroke="#059669" stroke-width="1.5" marker-end="url(#arrow-green)" stroke-dasharray="5,3"/>
<text x="248" y="156" fill="#059669" font-size="8" font-weight="600" text-anchor="middle">待機中に実行可能な仕事を探す</text>
<!-- ===== Row 2: Task discovery added to the s15 lifecycle ===== -->
<rect x="30" y="172" width="700" height="195" rx="8" fill="#ecfdf5" stroke="#059669" stroke-width="2"/>
<text x="380" y="194" fill="#065f46" font-size="11" font-weight="700" text-anchor="middle">s15 ライフサイクル + s16 タスクボード入口</text>
<!-- WORK box -->
<rect x="55" y="210" width="230" height="100" rx="6" fill="#fff" stroke="#059669" stroke-width="1.5"/>
<text x="170" y="230" fill="#059669" font-size="10" font-weight="700" text-anchor="middle">WORK フェーズ</text>
<text x="70" y="248" fill="#374151" font-size="8">ランタイム配信メッセージ → LLM → ツール呼び出し</text>
<text x="70" y="262" fill="#374151" font-size="8">stop_reason == tool_use → ループ</text>
<text x="70" y="276" fill="#374151" font-size="8">stop_reason != tool_use → IDLE</text>
<text x="70" y="298" fill="#6b7280" font-size="7">result を送り、IDLE に入る</text>
<!-- Arrow: WORK → IDLE -->
<line x1="285" y1="260" x2="415" y2="260" stroke="#059669" stroke-width="1.5" marker-end="url(#arrow-green)"/>
<text x="350" y="253" fill="#059669" font-size="8" font-weight="600" text-anchor="middle">タスク完了</text>
<!-- Arrow: IDLE → WORK -->
<path d="M 415 232 C 375 200, 320 200, 285 232" fill="none" stroke="#059669" stroke-width="1.5" marker-end="url(#arrow-green)" stroke-dasharray="5,3"/>
<text x="350" y="208" fill="#059669" font-size="8" font-weight="600" text-anchor="middle">仕事を発見</text>
<!-- IDLE box -->
<rect x="418" y="210" width="295" height="100" rx="6" fill="#fff" stroke="#059669" stroke-width="1.5"/>
<text x="565" y="230" fill="#059669" font-size="10" font-weight="700" text-anchor="middle">IDLE フェーズ</text>
<text x="433" y="248" fill="#374151" font-size="8">├ ランタイム配信を待つ → WORK に戻る</text>
<text x="433" y="264" fill="#374151" font-size="8">├ scan_unclaimed_tasks → 認領 → WORK に戻る</text>
<text x="433" y="280" fill="#374151" font-size="8">└ 実行可能なタスクなし → IDLE を維持</text>
<text x="433" y="298" fill="#6b7280" font-size="7">wait_for_messages() + claim_next_task()</text>
<!-- SHUTDOWN box -->
<rect x="515" y="335" width="130" height="24" rx="6" fill="#fef2f2" stroke="#dc2626" stroke-width="1.5"/>
<text x="580" y="351" fill="#991b1b" font-size="9" font-weight="700" text-anchor="middle">SHUTDOWN</text>
<!-- Arrow: IDLE → SHUTDOWN -->
<line x1="580" y1="310" x2="580" y2="335" stroke="#dc2626" stroke-width="1.5" marker-end="url(#arrow-red)"/>
<text x="598" y="326" fill="#dc2626" font-size="7">shutdown_request</text>
<!-- ===== Row 3: Bottom notes ===== -->
<rect x="30" y="388" width="700" height="42" rx="6" fill="#f8fafc" stroke="#e2e8f0" stroke-width="1"/>
<rect x="50" y="400" width="12" height="10" rx="2" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
<text x="70" y="410" fill="#475569" font-size="10">s15: MessageBus + protocols + request_shutdown + plan approval</text>
<rect x="50" y="414" width="12" height="10" rx="2" fill="#ecfdf5" stroke="#059669" stroke-width="1"/>
<text x="70" y="424" fill="#475569" font-size="10">s16: scan_unclaimed_tasks + claim_next_task + task_lock</text>
<!-- ===== Row 4 ===== -->
<rect x="30" y="442" width="700" height="22" rx="4" fill="#ecfdf5" stroke="#059669" stroke-width="1"/>
<text x="380" y="457" fill="#065f46" font-size="9" text-anchor="middle">Lead が依存グラフを作成 · チームメイトは 3 つのタスクツールで仕事を原子的に認領</text>
</svg>

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

@@ -0,0 +1,109 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760 470" font-family="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="#059669"/>
</linearGradient>
<marker id="arrow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#555"/>
</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-red" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#dc2626"/>
</marker>
</defs>
<rect width="760" height="470" fill="#fafbfc" rx="8"/>
<!-- Title -->
<rect x="0" y="0" width="760" height="44" fill="url(#header)" rx="8"/>
<rect x="0" y="36" width="760" height="8" fill="url(#header)"/>
<text x="380" y="28" fill="#fff" font-size="15" font-weight="700" text-anchor="middle">Autonomous Agents — 空闲任务发现 + 原子认领</text>
<!-- Legend -->
<rect x="40" y="56" width="12" height="10" rx="2" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
<text x="58" y="66" fill="#2563eb" font-size="10" font-weight="600">s15 保留</text>
<rect x="140" y="56" width="12" height="10" rx="2" fill="#ecfdf5" stroke="#059669" stroke-width="1"/>
<text x="158" y="66" fill="#059669" font-size="10" font-weight="600">s16 新增</text>
<!-- ===== Row 1: Lead Loop (s15 preserved) ===== -->
<rect x="20" y="90" width="70" height="40" rx="8" fill="#eef2ff" stroke="#4f46e5" stroke-width="1.5"/>
<text x="55" y="114" fill="#4f46e5" font-size="8" font-weight="600" text-anchor="middle">turn</text>
<line x1="90" y1="110" x2="104" y2="110" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
<rect x="107" y="90" width="70" height="40" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
<text x="142" y="114" fill="#1e3a5f" font-size="10" font-weight="600" text-anchor="middle">messages</text>
<line x1="177" y1="110" x2="191" y2="110" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
<rect x="194" y="86" width="80" height="48" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
<text x="234" y="114" fill="#1e3a5f" font-size="9" font-weight="600" text-anchor="middle">prompt</text>
<line x1="274" y1="110" x2="288" y2="110" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
<rect x="291" y="86" width="70" height="48" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
<text x="326" y="114" fill="#1e3a5f" font-size="9" font-weight="600" text-anchor="middle">LLM</text>
<line x1="361" y1="110" x2="375" y2="110" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
<rect x="378" y="80" width="356" height="60" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
<text x="556" y="98" fill="#1e3a5f" font-size="10" font-weight="600" text-anchor="middle">TOOL DISPATCH (s15 全保留)</text>
<text x="394" y="114" fill="#2563eb" font-size="8">bash · read · write · task tools · send · protocols</text>
<text x="394" y="128" fill="#7c3aed" font-size="8" font-weight="700">★ request_shutdown · request_plan · review_plan</text>
<!-- Loop back -->
<path d="M 734 110 L 748 110 L 748 150 L 55 150 L 55 130" fill="none" stroke="#94a3b8" stroke-width="1" marker-end="url(#arrow)" stroke-dasharray="5,4"/>
<!-- Connector: s16 extends the existing idle state with task discovery -->
<path d="M 326 134 L 326 160 L 170 160 L 170 210" fill="none" stroke="#059669" stroke-width="1.5" marker-end="url(#arrow-green)" stroke-dasharray="5,3"/>
<text x="248" y="156" fill="#059669" font-size="8" font-weight="600" text-anchor="middle">空闲时从任务板寻找可执行工作</text>
<!-- ===== Row 2: Task discovery added to the s15 lifecycle ===== -->
<rect x="30" y="172" width="700" height="195" rx="8" fill="#ecfdf5" stroke="#059669" stroke-width="2"/>
<text x="380" y="194" fill="#065f46" font-size="11" font-weight="700" text-anchor="middle">s15 生命周期 + s16 任务板入口</text>
<!-- WORK box -->
<rect x="55" y="210" width="230" height="100" rx="6" fill="#fff" stroke="#059669" stroke-width="1.5"/>
<text x="170" y="230" fill="#059669" font-size="10" font-weight="700" text-anchor="middle">WORK 阶段</text>
<text x="70" y="248" fill="#374151" font-size="8">消息自动进入上下文 → LLM → 工具调用</text>
<text x="70" y="262" fill="#374151" font-size="8">stop_reason == tool_use → loop</text>
<text x="70" y="276" fill="#374151" font-size="8">stop_reason != tool_use → IDLE</text>
<text x="70" y="298" fill="#6b7280" font-size="7">完成后发送 result再进入 IDLE</text>
<!-- Arrow: WORK → IDLE -->
<line x1="285" y1="260" x2="415" y2="260" stroke="#059669" stroke-width="1.5" marker-end="url(#arrow-green)"/>
<text x="350" y="253" fill="#059669" font-size="8" font-weight="600" text-anchor="middle">任务完成</text>
<!-- Arrow: IDLE → WORK (curved, above) -->
<path d="M 415 232 C 375 200, 320 200, 285 232" fill="none" stroke="#059669" stroke-width="1.5" marker-end="url(#arrow-green)" stroke-dasharray="5,3"/>
<text x="350" y="208" fill="#059669" font-size="8" font-weight="600" text-anchor="middle">发现新任务</text>
<!-- IDLE box -->
<rect x="418" y="210" width="295" height="100" rx="6" fill="#fff" stroke="#059669" stroke-width="1.5"/>
<text x="565" y="230" fill="#059669" font-size="10" font-weight="700" text-anchor="middle">IDLE 阶段</text>
<text x="433" y="248" fill="#374151" font-size="8">├ 等待运行时投递消息 → 回 WORK</text>
<text x="433" y="264" fill="#374151" font-size="8">├ scan_unclaimed_tasks → 认领 → 回 WORK</text>
<text x="433" y="280" fill="#374151" font-size="8">└ 没有就绪任务 → 保持 IDLE</text>
<text x="433" y="298" fill="#6b7280" font-size="7">wait_for_messages() + claim_next_task()</text>
<!-- SHUTDOWN box -->
<rect x="515" y="335" width="130" height="24" rx="6" fill="#fef2f2" stroke="#dc2626" stroke-width="1.5"/>
<text x="580" y="351" fill="#991b1b" font-size="9" font-weight="700" text-anchor="middle">SHUTDOWN</text>
<!-- Arrow: IDLE → SHUTDOWN -->
<line x1="580" y1="310" x2="580" y2="335" stroke="#dc2626" stroke-width="1.5" marker-end="url(#arrow-red)"/>
<text x="598" y="326" fill="#dc2626" font-size="7">shutdown_request</text>
<!-- ===== Row 3: Bottom notes ===== -->
<rect x="30" y="388" width="700" height="42" rx="6" fill="#f8fafc" stroke="#e2e8f0" stroke-width="1"/>
<rect x="50" y="400" width="12" height="10" rx="2" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
<text x="70" y="410" fill="#475569" font-size="10">s15: MessageBus + protocols + request_shutdown + plan approval</text>
<rect x="50" y="414" width="12" height="10" rx="2" fill="#ecfdf5" stroke="#059669" stroke-width="1"/>
<text x="70" y="424" fill="#475569" font-size="10">s16: scan_unclaimed_tasks + claim_next_task + task_lock</text>
<!-- ===== Row 4: Autonomous note ===== -->
<rect x="30" y="442" width="700" height="22" rx="4" fill="#ecfdf5" stroke="#059669" stroke-width="1"/>
<text x="380" y="457" fill="#065f46" font-size="9" text-anchor="middle">Lead 创建任务依赖图 · 队友获得 3 个任务工具并原子认领就绪工作</text>
</svg>

After

Width:  |  Height:  |  Size: 7.4 KiB