mirror of
https://github.com/shareAI-lab/analysis_claude_code.git
synced 2026-09-20 12:13:38 +08:00
feat: refresh course through workflow and goal loops
This commit is contained in:
@@ -1,254 +0,0 @@
|
||||
# s15: Agent Teams — One Agent Isn't Enough, Form a Team
|
||||
|
||||
[中文](README.md) · [English](README.en.md) · [日本語](README.ja.md)
|
||||
|
||||
s01 → ... → s13 → s14 → `s15` → [s16](../s16_team_protocols/) → s17 → s18 → s19 → s20
|
||||
> *"One agent isn't enough, form a team"* — File-based inboxes + teammate threads.
|
||||
>
|
||||
> **Harness Layer**: Teams — Multi-agent collaboration, message bus.
|
||||
|
||||
---
|
||||
|
||||
## The Problem
|
||||
|
||||
"Refactor the entire backend" touches auth, database layer, API routes, and tests. One agent working on API routes no longer has auth module details in context. The context window is limited, a single agent can't cover every module.
|
||||
|
||||
s06's sub-agents are temps, called in for one job, then gone. Some tasks need teammates that can communicate and collaborate.
|
||||
|
||||
---
|
||||
|
||||
## The Solution
|
||||
|
||||

|
||||
|
||||
Teaching code carries forward S14's capabilities (prompt assembly, task system, background execution, cron scheduling). To stay focused on the team mechanism, it omits full error recovery, memory, and skill systems. Added: **MessageBus** (file-based inboxes), **spawn_teammate_thread** (launch teammate threads), **inbox injection** (Lead receives teammate messages and injects into history).
|
||||
|
||||
Sub-agent vs Teammate:
|
||||
|
||||
| | s06 Sub-agent | s15 Teammate |
|
||||
|---|---|---|
|
||||
| Lifetime | One-shot, destroyed after use | Multi-turn (teaching: 10 rounds; real CC: idle loop) |
|
||||
| Communication | Only returns conclusion | Async inbox, communicate anytime |
|
||||
| Context | Fully isolated | Shared via messages |
|
||||
| Count | One lead + occasional sub-agent | One Lead + multiple teammates |
|
||||
|
||||
---
|
||||
|
||||
## How It Works
|
||||
|
||||

|
||||
|
||||
### MessageBus: File-Based Inboxes
|
||||
|
||||
Each agent (including Lead and teammates) has a `.jsonl` inbox. Send = append a JSON line to the target's file. Read = read file + delete (consumption):
|
||||
|
||||
```python
|
||||
class MessageBus:
|
||||
def send(self, from_agent: str, to_agent: str,
|
||||
content: str, msg_type: str = "message"):
|
||||
msg = {"from": from_agent, "to": to_agent,
|
||||
"content": content, "type": msg_type,
|
||||
"ts": time.time()}
|
||||
inbox = MAILBOX_DIR / f"{to_agent}.jsonl"
|
||||
with open(inbox, "a") as f:
|
||||
f.write(json.dumps(msg) + "\n")
|
||||
|
||||
def read_inbox(self, agent: str) -> list[dict]:
|
||||
inbox = MAILBOX_DIR / f"{agent}.jsonl"
|
||||
if not inbox.exists():
|
||||
return []
|
||||
msgs = [json.loads(line) for line in inbox.read_text().splitlines()]
|
||||
inbox.unlink() # consume: read + delete
|
||||
return msgs
|
||||
```
|
||||
|
||||
Why files instead of in-memory queues? Teaching code uses files because they're intuitive and observable across threads. Real CC also uses file inboxes (`~/.claude/teams/{team}/inboxes/`) but adds `proper-lockfile` for concurrent write safety. The teaching version's `read_inbox` has a read + unlink race, concurrent reads could lose messages, acceptable for teaching purposes.
|
||||
|
||||
### spawn_teammate_thread: Launching a Teammate
|
||||
|
||||
Lead calls the `spawn_teammate` tool to start a teammate. The teammate runs in its own daemon thread with its own system prompt, messages, and simplified tool set:
|
||||
|
||||
```python
|
||||
def spawn_teammate_thread(name: str, role: str, prompt: str) -> str:
|
||||
system = f"You are '{name}', a {role}. Use tools to complete tasks."
|
||||
|
||||
def run():
|
||||
messages = [{"role": "user", "content": prompt}]
|
||||
sub_tools = [bash, read_file, write_file, send_message]
|
||||
for _ in range(10): # max 10 rounds
|
||||
inbox = BUS.read_inbox(name)
|
||||
if inbox:
|
||||
messages.append({"role": "user",
|
||||
"content": f"<inbox>{json.dumps(inbox)}</inbox>"})
|
||||
response = client.messages.create(
|
||||
model=MODEL, system=system, messages=messages[-20:],
|
||||
tools=sub_tools, max_tokens=8000)
|
||||
# ... execute tools, process results
|
||||
# Send final summary to Lead
|
||||
BUS.send(name, "lead", summary, "result")
|
||||
|
||||
threading.Thread(target=run, daemon=True).start()
|
||||
```
|
||||
|
||||
Key design:
|
||||
- **Simplified tool set**: bash, read, write, send_message. Teaching code omits tasks and cron to focus on communication. Real CC teammates also have TaskCreate, TaskUpdate, etc., the task system is shared across the team
|
||||
- **Teaching: 10 rounds max**: prevents infinite loops. Real CC uses idle loop: after each round, send `idle_notification`, wait for inbox messages, resume on arrival, exit only on `shutdown_request`
|
||||
- **Auto-report on completion**: `BUS.send(name, "lead", summary)` sends the final result to Lead's inbox
|
||||
|
||||
### Lead's Inbox Injection
|
||||
|
||||
Lead checks inbox after each main loop iteration. Teammate messages are injected into history so the LLM can see and react to them:
|
||||
|
||||
```python
|
||||
# After main loop iteration
|
||||
inbox = BUS.read_inbox("lead")
|
||||
if inbox:
|
||||
inbox_text = "\n".join(
|
||||
f"From {m['from']}: {m['content'][:200]}" for m in inbox)
|
||||
history.append({"role": "user",
|
||||
"content": f"[Inbox]\n{inbox_text}"})
|
||||
```
|
||||
|
||||
Teaching code injects in the user input loop. Real CC is more refined, Lead's `useInboxPoller` checks every 1 second, submitting messages as new turns without waiting for user input.
|
||||
|
||||
### Permission Bubbling
|
||||
|
||||
Teaching code omits permission bubbling. Real CC's flow (`permissionSync.ts`, `useSwarmPermissionPoller.ts`):
|
||||
|
||||
1. Teammate encounters an operation needing approval → sends `permission_request` to Lead's inbox
|
||||
2. Lead's `useInboxPoller` detects the request → routes to approval queue
|
||||
3. User approves → Lead sends `permission_response` back to teammate
|
||||
4. Teammate's `useSwarmPermissionPoller` (polls every 500ms) receives reply → continue or reject
|
||||
|
||||
### Putting It Together
|
||||
|
||||
```
|
||||
1. Lead: "Build the backend: one agent isn't enough, form a team"
|
||||
2. Lead → spawn_teammate("alice", "backend dev", "Create database schema")
|
||||
3. Lead → spawn_teammate("bob", "frontend dev", "Write API client")
|
||||
4. Alice thread starts → her own LLM call → bash "python manage.py migrate"
|
||||
5. Bob thread starts → his own LLM call → write_file("client.ts", ...)
|
||||
6. Alice done → BUS.send("alice", "lead", "Schema done: users, orders tables")
|
||||
7. Bob done → BUS.send("bob", "lead", "Client written with types")
|
||||
8. Lead next iteration → inbox injected into history → LLM sees both results
|
||||
```
|
||||
|
||||
Two teammates work in parallel.
|
||||
|
||||
---
|
||||
|
||||
## Changes from s14
|
||||
|
||||
| Component | Before (s14) | After (s15) |
|
||||
|-----------|-------------|-------------|
|
||||
| Agent count | 1 | 1 Lead + N teammate threads |
|
||||
| Communication | None | MessageBus + .mailboxes/*.jsonl |
|
||||
| New classes | — | MessageBus, active_teammates dict |
|
||||
| New functions | — | spawn_teammate_thread, run_send_message, run_check_inbox |
|
||||
| Lead tools | 11 (s14) | + spawn_teammate, send_message, check_inbox (14) |
|
||||
| Teammate tools | — | bash, read_file, write_file, send_message (4) |
|
||||
| Permissions | Local decisions | Teaching code omits (real CC has bubbling) |
|
||||
|
||||
---
|
||||
|
||||
## Try It
|
||||
|
||||
```sh
|
||||
cd learn-claude-code
|
||||
python s15_agent_teams/code.py
|
||||
```
|
||||
|
||||
Try these prompts:
|
||||
|
||||
1. `Spawn alice as a backend developer. Ask her to create a file called schema.sql with a users table.`
|
||||
2. `Check your inbox for alice's result.`
|
||||
3. `Spawn bob as a tester. Ask him to check if schema.sql exists and list its contents.`
|
||||
|
||||
What to observe: How does Lead spawn teammates? What do the `.mailboxes/` JSONL files look like? After teammates finish, is Lead's inbox injected into history?
|
||||
|
||||
---
|
||||
|
||||
## What's Next
|
||||
|
||||
Teammates can work and communicate. But if Lead wants Alice to shut down, killing the thread outright could leave half-written files. A graceful shutdown protocol is needed: Lead sends shutdown_request, teammate wraps up and exits.
|
||||
|
||||
s16 Team Protocols → Shutdown handshake and message conventions.
|
||||
|
||||
<details>
|
||||
<summary>Deep Dive into CC Source</summary>
|
||||
|
||||
> The following is a complete analysis based on CC source code `spawnMultiAgent.ts`, `useInboxPoller.ts` (969 lines), `useSwarmPermissionPoller.ts` (330 lines), `teammateMailbox.ts`, `teamHelpers.ts`.
|
||||
|
||||
### 1. No Central Message Bus, It's the Filesystem
|
||||
|
||||
Teaching code uses a `MessageBus` class to send and receive messages. Real CC is more direct, each agent writes directly to other agents' inbox files.
|
||||
|
||||
Inbox path: `~/.claude/teams/{teamName}/inboxes/{agentName}.json`
|
||||
|
||||
Writes use `proper-lockfile` for concurrent write safety (up to 10 retries). Each file is a JSON array; appending reads → appends → writes back.
|
||||
|
||||
### 2. 15 Message Types
|
||||
|
||||
CC team communication has 15 structured message types (`teammateMailbox.ts`):
|
||||
|
||||
| Type | Direction | Purpose |
|
||||
|------|-----------|---------|
|
||||
| `plain text` | Both ways | Normal inter-teammate communication |
|
||||
| `idle_notification` | Teammate→Lead | Teammate finished a turn, now idle |
|
||||
| `permission_request` | Teammate→Lead | Teammate needs operation approval |
|
||||
| `permission_response` | Lead→Teammate | Lead's approval result |
|
||||
| `plan_approval_request` | Teammate→Lead | Teammate submits plan for review |
|
||||
| `plan_approval_response` | Lead→Teammate | Lead's plan review |
|
||||
| `shutdown_request` | Lead→Teammate | Request graceful shutdown |
|
||||
| `shutdown_approved` | Teammate→Lead | Confirm shutdown |
|
||||
| `shutdown_rejected` | Teammate→Lead | Reject shutdown (with reason) |
|
||||
| `task_assignment` | Lead→Teammate | Assign a task |
|
||||
| `team_permission_update` | Lead→Teammate | Broadcast permission changes |
|
||||
| `mode_set_request` | Lead→Teammate | Change teammate's permission mode |
|
||||
| `sandbox_permission_*` | Both ways | Network permission request/reply |
|
||||
| `teammate_terminated` | System | Teammate removed notification |
|
||||
|
||||
Text messages are wrapped in `<teammate-message>` XML tags for delivery to the model.
|
||||
|
||||
### 3. Permission Bubbling: Bidirectional Polling
|
||||
|
||||
Teaching code omits permission bubbling. Real CC's flow (`permissionSync.ts`):
|
||||
|
||||
1. **Teammate** encounters operation needing approval → sends `permission_request` to Lead's inbox
|
||||
2. **Lead's** `useInboxPoller` (polls every 1s) detects request → routes to `ToolUseConfirmQueue`
|
||||
3. Lead's UI shows approval dialog with teammate name and color
|
||||
4. User approves → Lead sends `permission_response` back to teammate's inbox
|
||||
5. **Teammate's** `useSwarmPermissionPoller` (polls every 500ms) receives reply → continue or reject
|
||||
|
||||
### 4. Teammate Lifecycle
|
||||
|
||||
CC teammates are created by `spawnTeammate()` (`spawnMultiAgent.ts`):
|
||||
|
||||
1. **Spawn**: Create tmux pane (or in-process), assign color, write team config
|
||||
2. **Work**: `useInboxPoller` checks inbox every 1s → submit as new turn when messages arrive
|
||||
3. **Idle**: Stop hook fires → send `idle_notification` to Lead
|
||||
4. **Shutdown**: Lead sends `shutdown_request` → teammate replies `shutdown_approved` → Lead cleans up
|
||||
|
||||
### 5. Team Config
|
||||
|
||||
Team registry at `~/.claude/teams/{teamName}/config.json` (`teamHelpers.ts`):
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "my-team",
|
||||
"leadAgentId": "lead@my-team",
|
||||
"members": [{
|
||||
"agentId": "researcher@my-team",
|
||||
"name": "researcher",
|
||||
"agentType": "general-purpose",
|
||||
"color": "blue",
|
||||
"isActive": true
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
Teammates cannot be nested (`AgentTool.tsx:273` explicitly forbids "teammates spawning other teammates").
|
||||
|
||||
</details>
|
||||
|
||||
<!-- translation-sync: zh@v1, en@v1, ja@v1 -->
|
||||
@@ -1,12 +1,14 @@
|
||||
# s15: Agent Teams — 一人では無理、チームを組もう
|
||||
# s15: Agent Teams — ランタイム実験:永続チームメイト
|
||||
|
||||
[中文](README.md) · [English](README.en.md) · [日本語](README.ja.md)
|
||||
[English](README.md) · [中文](README.zh.md) · [日本語](README.ja.md)
|
||||
|
||||
s01 → ... → s13 → s14 → `s15` → [s16](../s16_team_protocols/) → s17 → s18 → s19 → s20
|
||||
s01 → ... → s13 → s14 → `s15` → [s16](../s16_team_protocols/) → s17 → s18 → s19 → s20 → s21 → s22
|
||||
> *"一人では無理、チームを組もう"* — ファイル受信箱 + チームメイトスレッド。
|
||||
>
|
||||
> **Harness 層**: チーム — マルチ Agent 協調、メッセージバス。
|
||||
|
||||
> **モジュール 1/2:** s15 と s16 は一つの Agent Teams モジュールに含まれる二つの集中実験。この章でランタイムを構築し、s16 はランタイムを繰り返さず型付き協調プロトコルを追加する。
|
||||
|
||||
---
|
||||
|
||||
## 課題
|
||||
@@ -172,7 +174,7 @@ python s15_agent_teams/code.py
|
||||
|
||||
チームメイトは仕事をし、通信できる。しかし、Lead が Alice にシャットダウンを頼む場合、スレッドを強制終了すると書きかけのファイルが残る。丁寧なシャットダウンプロトコルが必要:Lead が shutdown_request を送信、チームメイトは收尾後に終了。
|
||||
|
||||
s16 Team Protocols → シャットダウンハンドシェイクとメッセージの取り決め。
|
||||
s16 Agent Teams プロトコル実験 → このランタイムにシャットダウンハンドシェイク、計画承認、型付きリクエスト-返信を追加する。
|
||||
|
||||
<details>
|
||||
<summary>CC ソースコード深掘り</summary>
|
||||
|
||||
@@ -1,46 +1,48 @@
|
||||
# s15: Agent Teams — 一个搞不定,组队来
|
||||
# s15: Agent Teams — Runtime Lab: Persistent Teammates
|
||||
|
||||
[中文](README.md) · [English](README.en.md) · [日本語](README.ja.md)
|
||||
[English](README.md) · [中文](README.zh.md) · [日本語](README.ja.md)
|
||||
|
||||
s01 → ... → s13 → s14 → `s15` → [s16](../s16_team_protocols/) → s17 → s18 → s19 → s20
|
||||
> *"一个搞不定, 组队来"* — 文件收件箱 + 队友线程。
|
||||
s01 → ... → s13 → s14 → `s15` → [s16](../s16_team_protocols/) → s17 → s18 → s19 → s20 → s21 → s22
|
||||
> *"One agent isn't enough, form a team"* — File-based inboxes + teammate threads.
|
||||
>
|
||||
> **Harness 层**: 团队 — 多 Agent 协作, 消息总线。
|
||||
> **Harness Layer**: Teams — Multi-agent collaboration, message bus.
|
||||
|
||||
> **Module 1 of 2:** s15 and s16 are two focused labs in one Agent Teams module. This lab builds the runtime; s16 adds typed coordination protocols without repeating the runtime.
|
||||
|
||||
---
|
||||
|
||||
## 问题
|
||||
## The Problem
|
||||
|
||||
"重构整个后端"涉及认证模块、数据库层、API 路由、测试。一个 Agent 在修 API 路由时,认证模块的细节已经不在上下文里了。上下文窗口就那么大,单个 Agent 的注意力覆盖不了所有模块。
|
||||
"Refactor the entire backend" touches auth, database layer, API routes, and tests. One agent working on API routes no longer has auth module details in context. The context window is limited, a single agent can't cover every module.
|
||||
|
||||
s06 的子 Agent 是临时工,叫来干一件事就走了。但有些任务需要能通信、能协作的队友。
|
||||
s06's sub-agents are temps, called in for one job, then gone. Some tasks need teammates that can communicate and collaborate.
|
||||
|
||||
---
|
||||
|
||||
## 解决方案
|
||||
## The Solution
|
||||
|
||||

|
||||

|
||||
|
||||
教学代码沿用 S14 的能力(prompt 组装、任务系统、后台执行、cron 调度)。为了聚焦团队机制,省略了完整错误恢复、记忆和技能系统。新增三样:**MessageBus**(文件收件箱)、**spawn_teammate_thread**(启动队友线程)、**inbox 注入**(Lead 接收队友消息并注入 history)。
|
||||
Teaching code carries forward S14's capabilities (prompt assembly, task system, background execution, cron scheduling). To stay focused on the team mechanism, it omits full error recovery, memory, and skill systems. Added: **MessageBus** (file-based inboxes), **spawn_teammate_thread** (launch teammate threads), **inbox injection** (Lead receives teammate messages and injects into history).
|
||||
|
||||
子 Agent vs 队友:
|
||||
Sub-agent vs Teammate:
|
||||
|
||||
| | s06 子 Agent | s15 队友 |
|
||||
| | s06 Sub-agent | s15 Teammate |
|
||||
|---|---|---|
|
||||
| 生命周期 | 一次性,用完销毁 | 多轮(教学版限 10 轮,真实 CC 用 idle loop) |
|
||||
| 通信 | 只回传结论 | 异步收件箱,随时通信 |
|
||||
| 上下文 | 完全隔离 | 通过消息共享信息 |
|
||||
| 数量 | 一个主 Agent + 偶尔子 Agent | 一个 Lead + 多个队友 |
|
||||
| Lifetime | One-shot, destroyed after use | Multi-turn (teaching: 10 rounds; real CC: idle loop) |
|
||||
| Communication | Only returns conclusion | Async inbox, communicate anytime |
|
||||
| Context | Fully isolated | Shared via messages |
|
||||
| Count | One lead + occasional sub-agent | One Lead + multiple teammates |
|
||||
|
||||
---
|
||||
|
||||
## 工作原理
|
||||
## How It Works
|
||||
|
||||

|
||||

|
||||
|
||||
### MessageBus: 文件收件箱
|
||||
### MessageBus: File-Based Inboxes
|
||||
|
||||
每个 Agent(包括 Lead 和队友)有一个 `.jsonl` 邮箱。发消息 = 往对方的文件里 append 一行 JSON。读消息 = 读文件 + 删除(消费式):
|
||||
Each agent (including Lead and teammates) has a `.jsonl` inbox. Send = append a JSON line to the target's file. Read = read file + delete (consumption):
|
||||
|
||||
```python
|
||||
class MessageBus:
|
||||
@@ -58,15 +60,15 @@ class MessageBus:
|
||||
if not inbox.exists():
|
||||
return []
|
||||
msgs = [json.loads(line) for line in inbox.read_text().splitlines()]
|
||||
inbox.unlink() # 消费式:读完删除
|
||||
inbox.unlink() # consume: read + delete
|
||||
return msgs
|
||||
```
|
||||
|
||||
为什么用文件而不是内存队列?教学版选文件是因为直观、跨线程可观察。真实 CC 也用文件收件箱(`~/.claude/teams/{team}/inboxes/`),但加了 `proper-lockfile` 防并发写冲突。教学版的 `read_inbox` 有 read + unlink 竞态,多线程同时读可能丢消息,对教学场景可以接受。
|
||||
Why files instead of in-memory queues? Teaching code uses files because they're intuitive and observable across threads. Real CC also uses file inboxes (`~/.claude/teams/{team}/inboxes/`) but adds `proper-lockfile` for concurrent write safety. The teaching version's `read_inbox` has a read + unlink race, concurrent reads could lose messages, acceptable for teaching purposes.
|
||||
|
||||
### spawn_teammate_thread: 启动队友
|
||||
### spawn_teammate_thread: Launching a Teammate
|
||||
|
||||
Lead 调用 `spawn_teammate` 工具启动一个队友。队友跑在自己的 daemon 线程里,有自己的 system prompt、自己的 messages、自己的简化工具集:
|
||||
Lead calls the `spawn_teammate` tool to start a teammate. The teammate runs in its own daemon thread with its own system prompt, messages, and simplified tool set:
|
||||
|
||||
```python
|
||||
def spawn_teammate_thread(name: str, role: str, prompt: str) -> str:
|
||||
@@ -75,7 +77,7 @@ def spawn_teammate_thread(name: str, role: str, prompt: str) -> str:
|
||||
def run():
|
||||
messages = [{"role": "user", "content": prompt}]
|
||||
sub_tools = [bash, read_file, write_file, send_message]
|
||||
for _ in range(10): # 最多 10 轮
|
||||
for _ in range(10): # max 10 rounds
|
||||
inbox = BUS.read_inbox(name)
|
||||
if inbox:
|
||||
messages.append({"role": "user",
|
||||
@@ -83,24 +85,24 @@ def spawn_teammate_thread(name: str, role: str, prompt: str) -> str:
|
||||
response = client.messages.create(
|
||||
model=MODEL, system=system, messages=messages[-20:],
|
||||
tools=sub_tools, max_tokens=8000)
|
||||
# ... 执行工具、处理结果
|
||||
# 完成后发 summary 给 Lead
|
||||
# ... execute tools, process results
|
||||
# Send final summary to Lead
|
||||
BUS.send(name, "lead", summary, "result")
|
||||
|
||||
threading.Thread(target=run, daemon=True).start()
|
||||
```
|
||||
|
||||
关键设计:
|
||||
- **队友有简化工具集**:bash、read、write、send_message。教学版省略了任务和 cron,聚焦通信机制。真实 CC 的队友也有 TaskCreate、TaskUpdate 等工具,任务系统是团队共享的
|
||||
- **教学版限 10 轮**:防止队友无限循环。真实 CC 用 idle loop:跑完一轮后发 `idle_notification`,等 inbox 消息,收到后继续,直到 `shutdown_request` 才退出
|
||||
- **完成后自动汇报**:`BUS.send(name, "lead", summary)` 把最终结果发到 Lead 的收件箱
|
||||
Key design:
|
||||
- **Simplified tool set**: bash, read, write, send_message. Teaching code omits tasks and cron to focus on communication. Real CC teammates also have TaskCreate, TaskUpdate, etc., the task system is shared across the team
|
||||
- **Teaching: 10 rounds max**: prevents infinite loops. Real CC uses idle loop: after each round, send `idle_notification`, wait for inbox messages, resume on arrival, exit only on `shutdown_request`
|
||||
- **Auto-report on completion**: `BUS.send(name, "lead", summary)` sends the final result to Lead's inbox
|
||||
|
||||
### Lead 的 inbox 注入
|
||||
### Lead's Inbox Injection
|
||||
|
||||
Lead 在每轮主循环结束后检查收件箱。队友发来的消息注入到 history 里,让 LLM 能看到并做出反应:
|
||||
Lead checks inbox after each main loop iteration. Teammate messages are injected into history so the LLM can see and react to them:
|
||||
|
||||
```python
|
||||
# 主循环结束后
|
||||
# After main loop iteration
|
||||
inbox = BUS.read_inbox("lead")
|
||||
if inbox:
|
||||
inbox_text = "\n".join(
|
||||
@@ -109,129 +111,129 @@ if inbox:
|
||||
"content": f"[Inbox]\n{inbox_text}"})
|
||||
```
|
||||
|
||||
教学版在用户输入循环外注入。CC 更精细,Lead 的 `useInboxPoller` 每 1 秒检查一次,有消息就提交为新的 turn,不需要等用户输入。
|
||||
Teaching code injects in the user input loop. Real CC is more refined, Lead's `useInboxPoller` checks every 1 second, submitting messages as new turns without waiting for user input.
|
||||
|
||||
### 权限冒泡
|
||||
### Permission Bubbling
|
||||
|
||||
教学版省略了权限冒泡。真实 CC 的流程(`permissionSync.ts`、`useSwarmPermissionPoller.ts`):
|
||||
Teaching code omits permission bubbling. Real CC's flow (`permissionSync.ts`, `useSwarmPermissionPoller.ts`):
|
||||
|
||||
1. 队友遇到需要审批的操作 → 发 `permission_request` 到 Lead 收件箱
|
||||
2. Lead 的 `useInboxPoller` 检测到请求 → 路由到审批队列
|
||||
3. 用户审批后 → Lead 发 `permission_response` 回队友
|
||||
4. 队友的 `useSwarmPermissionPoller`(每 500ms 轮询)收到回复 → 继续或拒绝
|
||||
1. Teammate encounters an operation needing approval → sends `permission_request` to Lead's inbox
|
||||
2. Lead's `useInboxPoller` detects the request → routes to approval queue
|
||||
3. User approves → Lead sends `permission_response` back to teammate
|
||||
4. Teammate's `useSwarmPermissionPoller` (polls every 500ms) receives reply → continue or reject
|
||||
|
||||
### 合起来跑
|
||||
### Putting It Together
|
||||
|
||||
```
|
||||
1. Lead: "搭建后端:一个人搞不定,组队吧"
|
||||
2. Lead → spawn_teammate("alice", "backend dev", "创建数据库 schema")
|
||||
3. Lead → spawn_teammate("bob", "frontend dev", "写 API 客户端")
|
||||
4. alice 线程启动 → 自己的 LLM 调用 → bash "python manage.py migrate"
|
||||
5. bob 线程启动 → 自己的 LLM 调用 → write_file("client.ts", ...)
|
||||
6. alice 完成 → BUS.send("alice", "lead", "Schema done: users, orders tables")
|
||||
7. bob 完成 → BUS.send("bob", "lead", "Client written with types")
|
||||
8. Lead 下次循环 → inbox 注入 history → LLM 看到 alice 和 bob 的结果
|
||||
1. Lead: "Build the backend: one agent isn't enough, form a team"
|
||||
2. Lead → spawn_teammate("alice", "backend dev", "Create database schema")
|
||||
3. Lead → spawn_teammate("bob", "frontend dev", "Write API client")
|
||||
4. Alice thread starts → her own LLM call → bash "python manage.py migrate"
|
||||
5. Bob thread starts → his own LLM call → write_file("client.ts", ...)
|
||||
6. Alice done → BUS.send("alice", "lead", "Schema done: users, orders tables")
|
||||
7. Bob done → BUS.send("bob", "lead", "Client written with types")
|
||||
8. Lead next iteration → inbox injected into history → LLM sees both results
|
||||
```
|
||||
|
||||
两个队友并行工作。
|
||||
Two teammates work in parallel.
|
||||
|
||||
---
|
||||
|
||||
## 相对 s14 的变更
|
||||
## Changes from s14
|
||||
|
||||
| 组件 | 之前 (s14) | 之后 (s15) |
|
||||
|------|-----------|-----------|
|
||||
| Agent 数量 | 1 | 1 Lead + N 队友线程 |
|
||||
| 通信 | 无 | MessageBus + .mailboxes/*.jsonl |
|
||||
| 新类 | — | MessageBus, active_teammates dict |
|
||||
| 新函数 | — | spawn_teammate_thread, run_send_message, run_check_inbox |
|
||||
| Lead 工具 | 11 (s14) | + spawn_teammate, send_message, check_inbox (14) |
|
||||
| 队友工具 | — | bash, read_file, write_file, send_message (4) |
|
||||
| 权限 | 本地决策 | 教学版省略(真实 CC 有冒泡机制) |
|
||||
| Component | Before (s14) | After (s15) |
|
||||
|-----------|-------------|-------------|
|
||||
| Agent count | 1 | 1 Lead + N teammate threads |
|
||||
| Communication | None | MessageBus + .mailboxes/*.jsonl |
|
||||
| New classes | — | MessageBus, active_teammates dict |
|
||||
| New functions | — | spawn_teammate_thread, run_send_message, run_check_inbox |
|
||||
| Lead tools | 11 (s14) | + spawn_teammate, send_message, check_inbox (14) |
|
||||
| Teammate tools | — | bash, read_file, write_file, send_message (4) |
|
||||
| Permissions | Local decisions | Teaching code omits (real CC has bubbling) |
|
||||
|
||||
---
|
||||
|
||||
## 试一下
|
||||
## Try It
|
||||
|
||||
```sh
|
||||
cd learn-claude-code
|
||||
python s15_agent_teams/code.py
|
||||
```
|
||||
|
||||
试试这些 prompt:
|
||||
Try these prompts:
|
||||
|
||||
1. `Spawn alice as a backend developer. Ask her to create a file called schema.sql with a users table.`
|
||||
2. `Check your inbox for alice's result.`
|
||||
3. `Spawn bob as a tester. Ask him to check if schema.sql exists and list its contents.`
|
||||
|
||||
观察重点:Lead 如何启动队友?`.mailboxes/` 目录下的 JSONL 文件长什么样?队友完成后 Lead 的 inbox 有没有注入到 history?
|
||||
What to observe: How does Lead spawn teammates? What do the `.mailboxes/` JSONL files look like? After teammates finish, is Lead's inbox injected into history?
|
||||
|
||||
---
|
||||
|
||||
## 接下来
|
||||
## What's Next
|
||||
|
||||
队友能干活、能通信。但如果 Lead 想让 Alice 关机,直接杀线程会留下写到一半的文件。需要一个体面的关机协议:Lead 发 shutdown_request,队友收尾后退出。
|
||||
Teammates can work and communicate. But if Lead wants Alice to shut down, killing the thread outright could leave half-written files. A graceful shutdown protocol is needed: Lead sends shutdown_request, teammate wraps up and exits.
|
||||
|
||||
s16 Team Protocols → 关机握手与消息约定。
|
||||
s16 Agent Teams Protocol Lab → keep this runtime and add shutdown handshakes, plan approval, and typed request-reply messages.
|
||||
|
||||
<details>
|
||||
<summary>深入 CC 源码</summary>
|
||||
<summary>Deep Dive into CC Source</summary>
|
||||
|
||||
> 以下基于 CC 源码 `spawnMultiAgent.ts`、`useInboxPoller.ts`(969 行)、`useSwarmPermissionPoller.ts`(330 行)、`teammateMailbox.ts`、`teamHelpers.ts` 的完整分析。
|
||||
> The following is a complete analysis based on CC source code `spawnMultiAgent.ts`, `useInboxPoller.ts` (969 lines), `useSwarmPermissionPoller.ts` (330 lines), `teammateMailbox.ts`, `teamHelpers.ts`.
|
||||
|
||||
### 一、没有中央消息总线,是文件系统
|
||||
### 1. No Central Message Bus, It's the Filesystem
|
||||
|
||||
教学版用 `MessageBus` 类收发消息。CC 的做法更直接,每个 Agent 直接写其他 Agent 的收件箱文件。
|
||||
Teaching code uses a `MessageBus` class to send and receive messages. Real CC is more direct, each agent writes directly to other agents' inbox files.
|
||||
|
||||
收件箱路径:`~/.claude/teams/{teamName}/inboxes/{agentName}.json`
|
||||
Inbox path: `~/.claude/teams/{teamName}/inboxes/{agentName}.json`
|
||||
|
||||
写入时用 `proper-lockfile` 文件锁保证并发安全(最多重试 10 次)。每个文件是一个 JSON 数组,append 新消息时读→追加→写回。
|
||||
Writes use `proper-lockfile` for concurrent write safety (up to 10 retries). Each file is a JSON array; appending reads → appends → writes back.
|
||||
|
||||
### 二、15 种消息类型
|
||||
### 2. 15 Message Types
|
||||
|
||||
CC 的团队通信有 15 种结构化消息(`teammateMailbox.ts`):
|
||||
CC team communication has 15 structured message types (`teammateMailbox.ts`):
|
||||
|
||||
| 类型 | 方向 | 用途 |
|
||||
|------|------|------|
|
||||
| `plain text` | 双向 | 普通队友间通信 |
|
||||
| `idle_notification` | 队友→Lead | 队友完成一轮工作,进入空闲 |
|
||||
| `permission_request` | 队友→Lead | 队友需要操作审批 |
|
||||
| `permission_response` | Lead→队友 | Lead 审批结果 |
|
||||
| `plan_approval_request` | 队友→Lead | 队友提交计划待审 |
|
||||
| `plan_approval_response` | Lead→队友 | Lead 审批计划 |
|
||||
| `shutdown_request` | Lead→队友 | 请求体面关机 |
|
||||
| `shutdown_approved` | 队友→Lead | 确认关机 |
|
||||
| `shutdown_rejected` | 队友→Lead | 拒绝关机(附原因) |
|
||||
| `task_assignment` | Lead→队友 | 分配任务 |
|
||||
| `team_permission_update` | Lead→队友 | 广播权限变更 |
|
||||
| `mode_set_request` | Lead→队友 | 修改队友的权限模式 |
|
||||
| `sandbox_permission_*` | 双向 | 网络权限请求/回复 |
|
||||
| `teammate_terminated` | 系统 | 队友被移除通知 |
|
||||
| Type | Direction | Purpose |
|
||||
|------|-----------|---------|
|
||||
| `plain text` | Both ways | Normal inter-teammate communication |
|
||||
| `idle_notification` | Teammate→Lead | Teammate finished a turn, now idle |
|
||||
| `permission_request` | Teammate→Lead | Teammate needs operation approval |
|
||||
| `permission_response` | Lead→Teammate | Lead's approval result |
|
||||
| `plan_approval_request` | Teammate→Lead | Teammate submits plan for review |
|
||||
| `plan_approval_response` | Lead→Teammate | Lead's plan review |
|
||||
| `shutdown_request` | Lead→Teammate | Request graceful shutdown |
|
||||
| `shutdown_approved` | Teammate→Lead | Confirm shutdown |
|
||||
| `shutdown_rejected` | Teammate→Lead | Reject shutdown (with reason) |
|
||||
| `task_assignment` | Lead→Teammate | Assign a task |
|
||||
| `team_permission_update` | Lead→Teammate | Broadcast permission changes |
|
||||
| `mode_set_request` | Lead→Teammate | Change teammate's permission mode |
|
||||
| `sandbox_permission_*` | Both ways | Network permission request/reply |
|
||||
| `teammate_terminated` | System | Teammate removed notification |
|
||||
|
||||
文本消息被包装在 `<teammate-message>` XML 标签中交付给模型。
|
||||
Text messages are wrapped in `<teammate-message>` XML tags for delivery to the model.
|
||||
|
||||
### 三、权限冒泡:双向轮询
|
||||
### 3. Permission Bubbling: Bidirectional Polling
|
||||
|
||||
教学版省略了权限冒泡。CC 的实际流程(`permissionSync.ts`):
|
||||
Teaching code omits permission bubbling. Real CC's flow (`permissionSync.ts`):
|
||||
|
||||
1. **队友**遇到需要审批的操作 → 发 `permission_request` 到 Lead 的收件箱
|
||||
2. **Lead** 的 `useInboxPoller`(每 1 秒轮询)检测到请求 → 路由到 `ToolUseConfirmQueue`
|
||||
3. Lead 的 UI 显示审批对话框,带队友名字和颜色
|
||||
4. 用户审批后 → Lead 发 `permission_response` 回队友的收件箱
|
||||
5. **队友**的 `useSwarmPermissionPoller`(每 500ms 轮询)收到回复 → 继续或拒绝执行
|
||||
1. **Teammate** encounters operation needing approval → sends `permission_request` to Lead's inbox
|
||||
2. **Lead's** `useInboxPoller` (polls every 1s) detects request → routes to `ToolUseConfirmQueue`
|
||||
3. Lead's UI shows approval dialog with teammate name and color
|
||||
4. User approves → Lead sends `permission_response` back to teammate's inbox
|
||||
5. **Teammate's** `useSwarmPermissionPoller` (polls every 500ms) receives reply → continue or reject
|
||||
|
||||
### 四、队友生命周期
|
||||
### 4. Teammate Lifecycle
|
||||
|
||||
CC 的队友由 `spawnTeammate()`(`spawnMultiAgent.ts`)创建:
|
||||
CC teammates are created by `spawnTeammate()` (`spawnMultiAgent.ts`):
|
||||
|
||||
1. **Spawn**:创建 tmux 窗格(或进程内),分配颜色,写入 team config
|
||||
2. **Work**:`useInboxPoller` 每 1 秒检查收件箱 → 有消息就提交为新的 turn
|
||||
3. **Idle**:Stop hook 触发 → 发 `idle_notification` 给 Lead
|
||||
4. **Shutdown**:Lead 发 `shutdown_request` → 队友回复 `shutdown_approved` → Lead 清理
|
||||
1. **Spawn**: Create tmux pane (or in-process), assign color, write team config
|
||||
2. **Work**: `useInboxPoller` checks inbox every 1s → submit as new turn when messages arrive
|
||||
3. **Idle**: Stop hook fires → send `idle_notification` to Lead
|
||||
4. **Shutdown**: Lead sends `shutdown_request` → teammate replies `shutdown_approved` → Lead cleans up
|
||||
|
||||
### 五、Team Config
|
||||
### 5. Team Config
|
||||
|
||||
团队注册表在 `~/.claude/teams/{teamName}/config.json`(`teamHelpers.ts`):
|
||||
Team registry at `~/.claude/teams/{teamName}/config.json` (`teamHelpers.ts`):
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -247,7 +249,7 @@ CC 的队友由 `spawnTeammate()`(`spawnMultiAgent.ts`)创建:
|
||||
}
|
||||
```
|
||||
|
||||
队友之间不能嵌套(`AgentTool.tsx:273` 明确禁止 "teammates spawning other teammates")。
|
||||
Teammates cannot be nested (`AgentTool.tsx:273` explicitly forbids "teammates spawning other teammates").
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
256
s15_agent_teams/README.zh.md
Normal file
256
s15_agent_teams/README.zh.md
Normal file
@@ -0,0 +1,256 @@
|
||||
# s15: Agent Teams — 运行时实验:持久队友
|
||||
|
||||
[English](README.md) · [中文](README.zh.md) · [日本語](README.ja.md)
|
||||
|
||||
s01 → ... → s13 → s14 → `s15` → [s16](../s16_team_protocols/) → s17 → s18 → s19 → s20 → s21 → s22
|
||||
> *"一个搞不定, 组队来"* — 文件收件箱 + 队友线程。
|
||||
>
|
||||
> **Harness 层**: 团队 — 多 Agent 协作, 消息总线。
|
||||
|
||||
> **模块 1/2:** s15 与 s16 是同一个 Agent Teams 模块中的两次聚焦实验。本章搭建运行时;s16 在不重复运行时的前提下增加带类型的协作协议。
|
||||
|
||||
---
|
||||
|
||||
## 问题
|
||||
|
||||
"重构整个后端"涉及认证模块、数据库层、API 路由、测试。一个 Agent 在修 API 路由时,认证模块的细节已经不在上下文里了。上下文窗口就那么大,单个 Agent 的注意力覆盖不了所有模块。
|
||||
|
||||
s06 的子 Agent 是临时工,叫来干一件事就走了。但有些任务需要能通信、能协作的队友。
|
||||
|
||||
---
|
||||
|
||||
## 解决方案
|
||||
|
||||

|
||||
|
||||
教学代码沿用 S14 的能力(prompt 组装、任务系统、后台执行、cron 调度)。为了聚焦团队机制,省略了完整错误恢复、记忆和技能系统。新增三样:**MessageBus**(文件收件箱)、**spawn_teammate_thread**(启动队友线程)、**inbox 注入**(Lead 接收队友消息并注入 history)。
|
||||
|
||||
子 Agent vs 队友:
|
||||
|
||||
| | s06 子 Agent | s15 队友 |
|
||||
|---|---|---|
|
||||
| 生命周期 | 一次性,用完销毁 | 多轮(教学版限 10 轮,真实 CC 用 idle loop) |
|
||||
| 通信 | 只回传结论 | 异步收件箱,随时通信 |
|
||||
| 上下文 | 完全隔离 | 通过消息共享信息 |
|
||||
| 数量 | 一个主 Agent + 偶尔子 Agent | 一个 Lead + 多个队友 |
|
||||
|
||||
---
|
||||
|
||||
## 工作原理
|
||||
|
||||

|
||||
|
||||
### MessageBus: 文件收件箱
|
||||
|
||||
每个 Agent(包括 Lead 和队友)有一个 `.jsonl` 邮箱。发消息 = 往对方的文件里 append 一行 JSON。读消息 = 读文件 + 删除(消费式):
|
||||
|
||||
```python
|
||||
class MessageBus:
|
||||
def send(self, from_agent: str, to_agent: str,
|
||||
content: str, msg_type: str = "message"):
|
||||
msg = {"from": from_agent, "to": to_agent,
|
||||
"content": content, "type": msg_type,
|
||||
"ts": time.time()}
|
||||
inbox = MAILBOX_DIR / f"{to_agent}.jsonl"
|
||||
with open(inbox, "a") as f:
|
||||
f.write(json.dumps(msg) + "\n")
|
||||
|
||||
def read_inbox(self, agent: str) -> list[dict]:
|
||||
inbox = MAILBOX_DIR / f"{agent}.jsonl"
|
||||
if not inbox.exists():
|
||||
return []
|
||||
msgs = [json.loads(line) for line in inbox.read_text().splitlines()]
|
||||
inbox.unlink() # 消费式:读完删除
|
||||
return msgs
|
||||
```
|
||||
|
||||
为什么用文件而不是内存队列?教学版选文件是因为直观、跨线程可观察。真实 CC 也用文件收件箱(`~/.claude/teams/{team}/inboxes/`),但加了 `proper-lockfile` 防并发写冲突。教学版的 `read_inbox` 有 read + unlink 竞态,多线程同时读可能丢消息,对教学场景可以接受。
|
||||
|
||||
### spawn_teammate_thread: 启动队友
|
||||
|
||||
Lead 调用 `spawn_teammate` 工具启动一个队友。队友跑在自己的 daemon 线程里,有自己的 system prompt、自己的 messages、自己的简化工具集:
|
||||
|
||||
```python
|
||||
def spawn_teammate_thread(name: str, role: str, prompt: str) -> str:
|
||||
system = f"You are '{name}', a {role}. Use tools to complete tasks."
|
||||
|
||||
def run():
|
||||
messages = [{"role": "user", "content": prompt}]
|
||||
sub_tools = [bash, read_file, write_file, send_message]
|
||||
for _ in range(10): # 最多 10 轮
|
||||
inbox = BUS.read_inbox(name)
|
||||
if inbox:
|
||||
messages.append({"role": "user",
|
||||
"content": f"<inbox>{json.dumps(inbox)}</inbox>"})
|
||||
response = client.messages.create(
|
||||
model=MODEL, system=system, messages=messages[-20:],
|
||||
tools=sub_tools, max_tokens=8000)
|
||||
# ... 执行工具、处理结果
|
||||
# 完成后发 summary 给 Lead
|
||||
BUS.send(name, "lead", summary, "result")
|
||||
|
||||
threading.Thread(target=run, daemon=True).start()
|
||||
```
|
||||
|
||||
关键设计:
|
||||
- **队友有简化工具集**:bash、read、write、send_message。教学版省略了任务和 cron,聚焦通信机制。真实 CC 的队友也有 TaskCreate、TaskUpdate 等工具,任务系统是团队共享的
|
||||
- **教学版限 10 轮**:防止队友无限循环。真实 CC 用 idle loop:跑完一轮后发 `idle_notification`,等 inbox 消息,收到后继续,直到 `shutdown_request` 才退出
|
||||
- **完成后自动汇报**:`BUS.send(name, "lead", summary)` 把最终结果发到 Lead 的收件箱
|
||||
|
||||
### Lead 的 inbox 注入
|
||||
|
||||
Lead 在每轮主循环结束后检查收件箱。队友发来的消息注入到 history 里,让 LLM 能看到并做出反应:
|
||||
|
||||
```python
|
||||
# 主循环结束后
|
||||
inbox = BUS.read_inbox("lead")
|
||||
if inbox:
|
||||
inbox_text = "\n".join(
|
||||
f"From {m['from']}: {m['content'][:200]}" for m in inbox)
|
||||
history.append({"role": "user",
|
||||
"content": f"[Inbox]\n{inbox_text}"})
|
||||
```
|
||||
|
||||
教学版在用户输入循环外注入。CC 更精细,Lead 的 `useInboxPoller` 每 1 秒检查一次,有消息就提交为新的 turn,不需要等用户输入。
|
||||
|
||||
### 权限冒泡
|
||||
|
||||
教学版省略了权限冒泡。真实 CC 的流程(`permissionSync.ts`、`useSwarmPermissionPoller.ts`):
|
||||
|
||||
1. 队友遇到需要审批的操作 → 发 `permission_request` 到 Lead 收件箱
|
||||
2. Lead 的 `useInboxPoller` 检测到请求 → 路由到审批队列
|
||||
3. 用户审批后 → Lead 发 `permission_response` 回队友
|
||||
4. 队友的 `useSwarmPermissionPoller`(每 500ms 轮询)收到回复 → 继续或拒绝
|
||||
|
||||
### 合起来跑
|
||||
|
||||
```
|
||||
1. Lead: "搭建后端:一个人搞不定,组队吧"
|
||||
2. Lead → spawn_teammate("alice", "backend dev", "创建数据库 schema")
|
||||
3. Lead → spawn_teammate("bob", "frontend dev", "写 API 客户端")
|
||||
4. alice 线程启动 → 自己的 LLM 调用 → bash "python manage.py migrate"
|
||||
5. bob 线程启动 → 自己的 LLM 调用 → write_file("client.ts", ...)
|
||||
6. alice 完成 → BUS.send("alice", "lead", "Schema done: users, orders tables")
|
||||
7. bob 完成 → BUS.send("bob", "lead", "Client written with types")
|
||||
8. Lead 下次循环 → inbox 注入 history → LLM 看到 alice 和 bob 的结果
|
||||
```
|
||||
|
||||
两个队友并行工作。
|
||||
|
||||
---
|
||||
|
||||
## 相对 s14 的变更
|
||||
|
||||
| 组件 | 之前 (s14) | 之后 (s15) |
|
||||
|------|-----------|-----------|
|
||||
| Agent 数量 | 1 | 1 Lead + N 队友线程 |
|
||||
| 通信 | 无 | MessageBus + .mailboxes/*.jsonl |
|
||||
| 新类 | — | MessageBus, active_teammates dict |
|
||||
| 新函数 | — | spawn_teammate_thread, run_send_message, run_check_inbox |
|
||||
| Lead 工具 | 11 (s14) | + spawn_teammate, send_message, check_inbox (14) |
|
||||
| 队友工具 | — | bash, read_file, write_file, send_message (4) |
|
||||
| 权限 | 本地决策 | 教学版省略(真实 CC 有冒泡机制) |
|
||||
|
||||
---
|
||||
|
||||
## 试一下
|
||||
|
||||
```sh
|
||||
cd learn-claude-code
|
||||
python s15_agent_teams/code.py
|
||||
```
|
||||
|
||||
试试这些 prompt:
|
||||
|
||||
1. `Spawn alice as a backend developer. Ask her to create a file called schema.sql with a users table.`
|
||||
2. `Check your inbox for alice's result.`
|
||||
3. `Spawn bob as a tester. Ask him to check if schema.sql exists and list its contents.`
|
||||
|
||||
观察重点:Lead 如何启动队友?`.mailboxes/` 目录下的 JSONL 文件长什么样?队友完成后 Lead 的 inbox 有没有注入到 history?
|
||||
|
||||
---
|
||||
|
||||
## 接下来
|
||||
|
||||
队友能干活、能通信。但如果 Lead 想让 Alice 关机,直接杀线程会留下写到一半的文件。需要一个体面的关机协议:Lead 发 shutdown_request,队友收尾后退出。
|
||||
|
||||
s16 Agent Teams 协议实验 → 沿用本章运行时,加入关机握手、计划审批与带类型的请求-回复消息。
|
||||
|
||||
<details>
|
||||
<summary>深入 CC 源码</summary>
|
||||
|
||||
> 以下基于 CC 源码 `spawnMultiAgent.ts`、`useInboxPoller.ts`(969 行)、`useSwarmPermissionPoller.ts`(330 行)、`teammateMailbox.ts`、`teamHelpers.ts` 的完整分析。
|
||||
|
||||
### 一、没有中央消息总线,是文件系统
|
||||
|
||||
教学版用 `MessageBus` 类收发消息。CC 的做法更直接,每个 Agent 直接写其他 Agent 的收件箱文件。
|
||||
|
||||
收件箱路径:`~/.claude/teams/{teamName}/inboxes/{agentName}.json`
|
||||
|
||||
写入时用 `proper-lockfile` 文件锁保证并发安全(最多重试 10 次)。每个文件是一个 JSON 数组,append 新消息时读→追加→写回。
|
||||
|
||||
### 二、15 种消息类型
|
||||
|
||||
CC 的团队通信有 15 种结构化消息(`teammateMailbox.ts`):
|
||||
|
||||
| 类型 | 方向 | 用途 |
|
||||
|------|------|------|
|
||||
| `plain text` | 双向 | 普通队友间通信 |
|
||||
| `idle_notification` | 队友→Lead | 队友完成一轮工作,进入空闲 |
|
||||
| `permission_request` | 队友→Lead | 队友需要操作审批 |
|
||||
| `permission_response` | Lead→队友 | Lead 审批结果 |
|
||||
| `plan_approval_request` | 队友→Lead | 队友提交计划待审 |
|
||||
| `plan_approval_response` | Lead→队友 | Lead 审批计划 |
|
||||
| `shutdown_request` | Lead→队友 | 请求体面关机 |
|
||||
| `shutdown_approved` | 队友→Lead | 确认关机 |
|
||||
| `shutdown_rejected` | 队友→Lead | 拒绝关机(附原因) |
|
||||
| `task_assignment` | Lead→队友 | 分配任务 |
|
||||
| `team_permission_update` | Lead→队友 | 广播权限变更 |
|
||||
| `mode_set_request` | Lead→队友 | 修改队友的权限模式 |
|
||||
| `sandbox_permission_*` | 双向 | 网络权限请求/回复 |
|
||||
| `teammate_terminated` | 系统 | 队友被移除通知 |
|
||||
|
||||
文本消息被包装在 `<teammate-message>` XML 标签中交付给模型。
|
||||
|
||||
### 三、权限冒泡:双向轮询
|
||||
|
||||
教学版省略了权限冒泡。CC 的实际流程(`permissionSync.ts`):
|
||||
|
||||
1. **队友**遇到需要审批的操作 → 发 `permission_request` 到 Lead 的收件箱
|
||||
2. **Lead** 的 `useInboxPoller`(每 1 秒轮询)检测到请求 → 路由到 `ToolUseConfirmQueue`
|
||||
3. Lead 的 UI 显示审批对话框,带队友名字和颜色
|
||||
4. 用户审批后 → Lead 发 `permission_response` 回队友的收件箱
|
||||
5. **队友**的 `useSwarmPermissionPoller`(每 500ms 轮询)收到回复 → 继续或拒绝执行
|
||||
|
||||
### 四、队友生命周期
|
||||
|
||||
CC 的队友由 `spawnTeammate()`(`spawnMultiAgent.ts`)创建:
|
||||
|
||||
1. **Spawn**:创建 tmux 窗格(或进程内),分配颜色,写入 team config
|
||||
2. **Work**:`useInboxPoller` 每 1 秒检查收件箱 → 有消息就提交为新的 turn
|
||||
3. **Idle**:Stop hook 触发 → 发 `idle_notification` 给 Lead
|
||||
4. **Shutdown**:Lead 发 `shutdown_request` → 队友回复 `shutdown_approved` → Lead 清理
|
||||
|
||||
### 五、Team Config
|
||||
|
||||
团队注册表在 `~/.claude/teams/{teamName}/config.json`(`teamHelpers.ts`):
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "my-team",
|
||||
"leadAgentId": "lead@my-team",
|
||||
"members": [{
|
||||
"agentId": "researcher@my-team",
|
||||
"name": "researcher",
|
||||
"agentType": "general-purpose",
|
||||
"color": "blue",
|
||||
"isActive": true
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
队友之间不能嵌套(`AgentTool.tsx:273` 明确禁止 "teammates spawning other teammates")。
|
||||
|
||||
</details>
|
||||
|
||||
<!-- translation-sync: zh@v1, en@v1, ja@v1 -->
|
||||
Reference in New Issue
Block a user