从 Team Protocol 到 MCP:Claude Code Harness 后半程架构演进分析

前面十几个章节里,我们已经完成了一个 Agent 的基本能力:

  • Tool Calling
  • Hook
  • Todo
  • Memory
  • Task System
  • Background Task
  • Scheduler
  • Team

看到这里会发现一个变化。

最开始的 Agent 其实只有一个循环:

1
2
3
while True:
response = llm(...)
execute_tool(...)

但到了 Team 阶段之后,系统已经不再是一个 Agent。

而变成了:

1
2
3
4
Lead
├── Alice
├── Bob
└── Charlie

这时候很多以前不存在的问题开始出现:

  • Agent 之间如何协作?
  • Agent 如何自己找任务?
  • Agent 之间如何避免修改同一个文件?
  • 如何接入外部系统?

s16~s19 实际上都在解决这些问题。


一、s16:为什么 Team 需要协议

s15 里面已经有 Team 了。

Lead 可以给 Alice 发消息:

1
2
3
4
5
BUS.send(
"alice",
"lead",
"please shutdown"
)

Alice 收到消息后执行。

看起来没问题。

但如果 Alice 正在执行:

1
write_file(...)

或者:

1
bash("npm install")

这时候直接退出线程会怎样?

可能出现:

1
2
3
文件写了一半
任务状态没更新
结果没有汇报

整个系统会进入不可预期状态。

所以问题不是:

1
能不能发消息

而是:

1
消息如何确认

request_id 的作用

s16 最核心的设计其实不是 Inbox。

而是:

1
request_id

例如:

1
2
3
4
{
"type":"shutdown_request",
"request_id":"req_001"
}

Alice 收到后:

1
2
3
4
{
"type":"shutdown_response",
"request_id":"req_001"
}

Lead 再根据:

1
pending_requests

找到对应请求。

这里本质上是在做:

1
2
3
Request

Response

模式。

其实和 HTTP 非常像:

1
2
3
4
5
6
7
Client

Request

Server

Response

只是这里变成了:

1
2
3
4
5
6
7
Lead

Request

Teammate

Response

为什么不能只发消息

很多初学者会觉得:

1
send_message()

不就够了吗?

其实不够。

因为:

1
2
3
发送成功

处理成功

例如:

1
BUS.send(...)

返回成功。

只能证明:

1
消息进入邮箱

不能证明:

1
Alice 真执行了

所以才需要:

1
match_response()

以及:

1
ProtocolState

来维护状态。


二、s17:Lead 不应该负责分配所有任务

有了协议之后。

系统运行起来了。

但新的问题出现了。

看 Task Board:

1
2
3
create_task(...)
create_task(...)
create_task(...)

Lead 创建完任务后还要:

1
2
3
Alice做这个
Bob做那个
Charlie做第三个

如果有:

1
100个任务

怎么办?

Lead 会变成瓶颈。


从派单到抢单

所以 s17 最重要的函数其实是:

1
scan_unclaimed_tasks()

以及:

1
claim_task()

Agent 空闲时:

1
available = scan_unclaimed_tasks()

发现任务:

1
claim_task(task.id)

直接认领。

这里意味着系统从:

1
Lead 派任务

变成:

1
Agent 自己找任务

为什么要增加 IDLE 状态

观察 Teammate 的循环。

前面章节里:

1
run()

基本就是:

1
2
3
4
5
收到任务

执行

结束

但 s17 之后变成:

1
2
3
4
5
6
7
WORK

IDLE

WORK

IDLE

循环。

对应代码实际上就是:

1
2
3
4
5
while not shutdown:
poll_inbox()

if idle:
scan_unclaimed_tasks()

这样 Agent 才能持续工作。

否则:

1
2
3
任务完成

线程退出

下一次还得重新创建 Agent。


为什么说 s17 才是真正的 Agent Team

因为从这一章开始:

Lead 不再负责具体调度。

Lead 只负责:

1
创建任务

Agent 自己负责:

1
2
3
4
发现任务
认领任务
执行任务
完成任务

这时候 Team 才真正具备自治能力。


三、s18:Task 和 Workspace 必须绑定

到了 s17。

Agent 已经会自己找活干了。

但很快又出现新的问题。

例如:

1
2
3
4
Task A:
重构认证模块
Task B:
重构登录页面

Alice 和 Bob 同时工作。

结果:

1
write_file("config.py")

两个人都改了。

最后谁覆盖谁?

不知道。


create_worktree 干了什么

核心代码:

1
def create_worktree(name, task_id=""):

里面真正关键的是:

1
2
3
4
5
run_git([
"worktree",
"add",
...
])

这一句。

它会创建:

1
2
3
.worktrees/
├── auth-refactor
└── login-page

两个独立目录。


为什么还要 bind_task_to_worktree

创建目录之后:

1
2
3
4
bind_task_to_worktree(
task_id,
name
)

很多人第一次看这里会误解。

以为:

1
2
3
绑定成功
=
开始任务

实际上不是。

看代码:

1
2
task.worktree = worktree_name
save_task(task)

这里只修改:

1
task.worktree

没有修改:

1
task.status

因此:

1
pending

仍然保持不变。

后面:

1
claim_task()

时才进入:

1
in_progress

为什么这么设计

因为:

1
Task System

和:

1
Worktree System

其实是两个系统。

Task 负责:

1
谁干活

Worktree 负责:

1
在哪干活

s18 只是通过:

1
task.worktree

把它们关联起来。

这样设计耦合度更低。


自动切换目录

认领任务之后:

1
_run_claim_task()

里面有:

1
wt_ctx["path"] = ...

后面:

1
2
3
4
run_bash(
command,
cwd=wt_ctx["path"]
)

于是:

1
Alice

所有工具:

1
2
3
bash
read_file
write_file

都运行在:

1
auth-refactor

目录。

Bob 同理。

这就是隔离的实现方式。


四、s19:为什么需要 MCP

到这里已经有很多工具:

1
2
3
4
5
bash
read_file
write_file
task
worktree

但问题来了。

如果公司有:

1
2
3
Jira
Notion
Deploy Platform

怎么办?

难道每个都写:

1
2
3
query_issue()
create_ticket()
deploy()

吗?

显然不现实。


MCPClient 干了什么

s19 引入:

1
class MCPClient

里面最重要两个接口:

1
register()

和:

1
call_tool()

对应 MCP 协议:

1
2
tools/list
tools/call

本质上:

1
2
发现工具
调用工具

整个 s19 最核心的函数

我认为是:

1
assemble_tool_pool()

因为 Agent 根本不知道什么叫 MCP。

Agent 只认识:

1
TOOLS

所以:

1
MCP Tool

必须转换成:

1
普通 Tool

看代码:

1
2
3
prefixed = (
f"mcp__{server}__{tool}"
)

然后:

1
tools.append(...)

加入工具池。

再:

1
handlers[prefixed] = ...

注册调用逻辑。

于是:

1
jira.create_ticket

变成:

1
mcp__jira__create_ticket

为什么要重建 Tool Pool

前面章节一直有:

1
prompt cache

但 s19 删掉了。

原因就在这里。

例如:

1
connect_mcp("docs")

执行前:

1
2
3
4
5
TOOLS
=
bash
read_file
write_file

执行后:

1
2
3
4
5
6
TOOLS
=
bash
read_file
write_file
mcp__docs__search

工具池已经变了。

缓存自然失效。

所以:

1
assemble_tool_pool()

必须重新执行。


normalize_mcp_name 为什么存在

看起来只是:

1
re.sub(...)

实际上很重要。

否则:

1
docs-server

和:

1
docs/server

可能生成非法 Tool 名称。

统一转换后:

1
mcp__docs_server__search

整个 Tool Namespace 才能保持稳定。


总结

s16 到 s19 其实对应 Agent Team 发展过程中的四个阶段。

第一步:

1
如何协作

于是有了:

1
Protocol

第二步:

1
如何自动工作

于是有了:

1
Autonomous Agent

第三步:

1
如何避免互相影响

于是有了:

1
Worktree Isolation

第四步:

1
如何扩展能力

于是有了:

1
MCP Plugin

如果说前面的章节是在解决:

1
Agent 如何完成任务

那么 s16~s19 解决的其实是:

1
多个 Agent 如何长期协作完成任务

到了这里,Claude Code Harness 已经不再是一个简单的 ReAct Agent,而逐渐演化成了一个具备协议、自治、隔离和插件能力的工程系统。