该系列文章基于
github.com/shareAI-lab/learn-claude-code写就,该仓库以大道至简的风格剖析了Claude Code的核心原理,值得大家学习。由于该仓库是基于Python语言,为方便.NET开发者学习,我已经将代码基于.NET 10的dotnet file重写,源码已上传至github,源码地址见文末。
本文是 Learn Claude Code (C# 版) 系列的第九篇,对应代码文件
v8_agent_teams.cs。
v7 让智能体能在后台运行命令,但智能体本身仍然是一个:
一个智能体 → 一个上下文窗口 → 一个注意力焦点
任务A: 修改前端 CSS
任务B: 同时调试后端 API
任务C: 同时审查测试覆盖率
→ 上下文互相污染
→ 一个任务的细节冲刷掉另一个任务的关键信息
→ 切换注意力的代价极高
真正的软件团队不会让一个人干所有事。我们需要多个独立的智能体,各有自己的上下文窗口和工具集。
持久化的队友通过 JSONL 收件箱提供了一种教学协议,将孤立的智能体转变为可通信的团队。
Lead Agent
(你直接交互的)
/ | \
spawn send read broadcast
/ | \ \
+--------+ +--------+ +--------+
| FE Dev | | BE Dev | | Tester |
| (loop) | | (loop) | | (loop) |
+--------+ +--------+ +--------+
↕ ↕ ↕
inbox_fe inbox_be inbox_test
(.jsonl) (.jsonl) (.jsonl)
每个队友有一个 JSONL 文件作为收件箱,每行一条 JSON 消息:
class MessageBus
{
string inboxDir;
public MessageBus()
{
inboxDir = Path.Combine(".team", "inbox");
Directory.CreateDirectory(inboxDir);
}
public void Send(string from, string to, string content, string type = "message")
{
var msg = new
{
from, to, content, type,
timestamp = DateTime.UtcNow.ToString("o")
};
var path = Path.Combine(inboxDir, $"{to}.jsonl");
File.AppendAllText(path, JsonSerializer.Serialize(msg) + "\n");
}
public List<Dictionary<string, object>> Read(string who)
{
var path = Path.Combine(inboxDir, $"{who}.jsonl");
if (!File.Exists(path)) return [];
var lines = File.ReadAllLines(path).Where(l => l.Trim().Length > 0);
var msgs = lines.Select(l => JsonSerializer.Deserialize<...>(l)).ToList();
// Drain-on-read: 清空已读消息
File.WriteAllText(path, "");
return msgs;
}
public void Broadcast(string from, string content, IEnumerable<string> teammates)
{
foreach (var t in teammates)
Send(from, t, content, "broadcast");
}
}Drain-on-read 模式:读取后清空收件箱,确保消息不会重复处理。
class TeammateManager
{
Dictionary<string, TeammateConfig> teammates = new();
public string Spawn(string name, string role)
{
var config = new TeammateConfig(name, role);
teammates[name] = config;
SaveConfig();
// 在独立线程中启动队友的 agent loop
Task.Run(async () => await RunTeammateLoopAsync(name, role));
return $"Spawned teammate: {name} ({role})";
}
}每个队友运行自己的 agent loop,有自己的消息数组,有自己的工具集:
async Task RunTeammateLoopAsync(string name, string role)
{
var messages = new List<MessageParam>();
var systemPrompt = $"You are {name}, a teammate with role: {role}. " +
$"Check your inbox regularly with read_inbox.";
while (true)
{
// 1. 检查收件箱
var inbox = messageBus.Read(name);
if (inbox.Count > 0)
messages.Add(new() { Role = Role.User,
Content = FormatInboxMessages(inbox) });
// 2. 调用 LLM
var response = await client.Messages.Create(new MessageCreateParams
{
Model = "claude-sonnet-4-20250514",
System = systemPrompt,
Messages = messages,
Tools = teammateTools, // 精简的工具集
MaxTokens = 4096
});
// 3. 执行工具调用
// ... 标准 agent loop ...
}
}| Lead Agent 工具 | Teammate 工具 |
|---|---|
| bash, read_file, write_file, edit_file | bash, read_file, write_file, edit_file |
| spawn_teammate | ✗ |
| list_teammates | ✗ |
| send_message | send_message |
| read_inbox | read_inbox |
| broadcast | ✗ |
Lead 有管理权限(spawn、broadcast),Teammate 只能收发消息和执行代码。
// 支持的消息类型
"message" // 普通消息
"broadcast" // 广播
"shutdown_request" // 关机请求 (v9 扩展)
"shutdown_response" // 关机响应 (v9 扩展)
"plan_approval_response" // 计划审批 (v9 扩展)| 组件 | 之前 (v7) | 之后 (v8) |
|---|---|---|
| 智能体数量 | 1 | N (Lead + Teammates) |
| 通信 | 无 | JSONL 收件箱 |
| 工具数 | 6 | Lead 9 / Teammate 6 |
| 上下文隔离 | 单一共享 | 每个智能体独立 |
| 状态存储 | .tasks/ |
.team/inbox/ + config.json |
| 后台线程 | 命令级 | 智能体级 |
JSONL 收件箱是消息传递的最小实现:
- 文件即队列:追加写入 = 入队,全文读取 + 清空 = 出队
- 无需中间件:不需要 Redis、RabbitMQ 或任何消息队列
- 可观察:用
cat或记事本就能查看消息 - 持久化:重启后消息不丢失
每个队友运行自己的 agent loop,不共享上下文。隔离是特性:前端开发者的上下文不会被后端 API 的细节填满。
dotnet run v8_agent_teams.cs可以尝试的提示:
Spawn a teammate called 'fe-dev' with role 'frontend developer' and a teammate called 'be-dev' with role 'backend developer'Send a message to fe-dev: "Please create a simple HTML page"Broadcast to all teammates: "Share your progress"List all teammates