第五篇:Subagent —— 进程隔离就是上下文隔离
Subagent —— 进程隔离就是上下文隔离
模型和人一样,在一个拥挤的房间里没法思考。有时候你需要一个安静的小房间。
上下文是 Agent 的 RAM
先说个常识。
你写代码的时候,如果浏览器开着 50 个标签页、微信在闪、Slack 在响、IDE 里有 20 个打开的文件……你能写好代码吗?
不能。你的"上下文"被塞爆了。
Agent 也一样。模型的上下文窗口就是它的"工作记忆"。你把之前的文件内容、工具结果、对话历史全都塞在一起:
messages = [
"帮我看看这个项目" (5K tokens)
→ 读了 10 个文件 (30K tokens)
→ 跑了 3 个测试 (10K tokens)
→ 修改了 2 个文件 (8K tokens)
→ "现在帮我写一个新功能"
→ 又读了 15 个文件 (50K tokens)
→ ...
]
100K tokens 往后,模型的推理能力明显下降。它会"忘记"对话早期做的决定,开始前后矛盾,甚至重复做已经做完的事。
s01-s03 的问题:所有工作在同一个上下文里完成,上下文越来越脏。
s04 的解法:Subagent = 给模型一个干净的房间
s04 做的事情其实很简单——模型可以 spawn 一个子代理,给它一个独立的任务,然后等它返回结果。 子代理运行在自己的上下文里,父代理的上下文不受影响。
def run_subagent(prompt: str) -> str:
sub_messages = [{"role": "user", "content": prompt}] # ← 干净的上下文!
for _ in range(30): # safety limit
response = client.messages.create(
model=MODEL, system=SUBAGENT_SYSTEM, messages=sub_messages,
tools=CHILD_TOOLS, max_tokens=8000,
)
sub_messages.append({"role": "assistant", "content": response.content})
if response.stop_reason != "tool_use":
break
# 执行工具...
# 只有最终文本摘要返回给父代理
return "".join(b.text for b in response.content if hasattr(b, "text")) or "(no summary)"
注意那句注释:# fresh context。子代理的 sub_messages 从零开始。它不知道父代理之前聊了什么,不知道之前的文件内容,不知道之前的决策。它只知道你告诉它的任务。
执行过程是一个独立的 agent loop,和父代理的循环完全一样:
子代理循环:
sub_messages = [{user: prompt}] ← 只有任务描述
while True:
response = LLM(sub_messages) ← 独立调用
sub_messages.append(response)
if stop:
break
execute tools
sub_messages.append(results)
return summary_text ← 只返回摘要
父代理拿到摘要后,子代理的上下文直接被丢弃——那些对话、工具结果、中间状态,全部消失了。
这就像是:你让一个实习生去查个资料,他查完后回来告诉你结论。你不会把他查资料时看的每一页书都塞到自己的脑子里。你只需要结论。
为什么 Subagent 比单上下文好?
做个对比:
单上下文(s01-s03 模式):
main context: [读代码A(5K)] [读代码B(3K)] [改文件A(2K)] [跑测试(8K)]
[读代码C(4K)] [读代码D(6K)] [改文件B(3K)] [跑测试(10K)]
[读代码E(12K)] [思考新功能(2K)] [读数据库schema(8K)]
→ 63K tokens, 模型开始犯傻
子代理模式(s04):
main context: [读代码A(5K)] [读代码B(3K)] [改文件A(2K)]
[subagent结果: "数据库schema已分析完毕"]
[读代码E(12K)] [思考新功能(2K)]
→ 干净的24K tokens, 模型清醒得很
subagent context: [读代码C(4K)] [读代码D(6K)] [读数据库schema(8K)]
→ 用完即焚,18K tokens不污染主上下文
这是"分工"和"一锅烩"的区别。
作者在 s04 的文件头里写了一句关键注释:
“Process isolation gives context isolation for free.”
(进程隔离自动带来了上下文隔离。)
对,这不是什么新发明。这就是操作系统的进程隔离思想——每个进程有自己的地址空间。Subagent 就是进程,messages 列表就是它的地址空间。
安全措施:子代理的权限限制
注意一个细节——子代理的工具集比父代理少:
# 父代理有 task 工具(可以 spawn 子代理)
PARENT_TOOLS = CHILD_TOOLS + [
{"name": "task", ...}
]
# 子代理没有 task 工具 → 不能递归 spawn
CHILD_TOOLS = [
{"name": "bash", ...},
{"name": "read_file", ...},
{"name": "write_file", ...},
{"name": "edit_file", ...},
]
子代理不能 spawn 子代理。没有递归创建。
这是有意的安全设计。否则你可能会看到:
Agent spawns Subagent A
→ Subagent A spawns Subagent B
→ Subagent B spawns Subagent C
→ Subagent C spawns Subagent D
→ ...
无限递归炸掉你的 API 账单和线程池。s04 用一个简单的"子代理没有 task 工具"就杜绝了这个问题。
s09 的进化:从一次性 Subagent 到持久化 Teammate
Subagent 有一个明显的局限:它是一次性的。
spawn → work → return summary → destroyed
它没有身份。没有跨调用的记忆。没有名字,没有角色,没有"我就是上次那个干过这事的 Agent"。
s09 解决了这个问题——引入了 Teammate(队友) 系统。
Teammate lifecycle:
spawn → WORKING → IDLE → WORKING → ... → SHUTDOWN
每个 Teammate 是持久的,有自己的名字和角色:
def spawn(self, name: str, role: str, prompt: str) -> str:
member = {"name": name, "role": role, "status": "working"}
self.config["members"].append(member)
thread = threading.Thread(target=self._teammate_loop, args=(name, role, prompt))
thread.start()
return f"Spawned '{name}' (role: {role})"
每个 Teammate 运行在自己的线程里,有自己的 agent loop:
def _teammate_loop(self, name: str, role: str, prompt: str):
sys_prompt = f"You are '{name}', role: {role}, at {WORKDIR}."
messages = [{"role": "user", "content": prompt}]
for _ in range(50):
inbox = BUS.read_inbox(name) # 检查邮箱
for msg in inbox:
messages.append({"role": "user", "content": json.dumps(msg)})
response = client.messages.create(
model=MODEL, system=sys_prompt, messages=messages, ...
)
# 执行工具、追加结果...
self._set_status(name, "idle")
关键的区别在哪?
Subagent(s04):父代理显式调用 task 工具,等结果 → 类似"函数调用"
Teammate(s09):team lead spawn 一个队友,队友自己运行,通过邮箱通信 → 类似"微服务"
通信方式也变了——从"返回值"变成了JSONL 邮箱:
class MessageBus:
def send(self, sender: str, to: str, content: str, msg_type: str = "message"):
msg = {"type": msg_type, "from": sender, "content": content, "timestamp": time.time()}
inbox_path = self.dir / f"{to}.jsonl"
with open(inbox_path, "a") as f:
f.write(json.dumps(msg) + "\n")
return f"Sent {msg_type} to {to}"
def read_inbox(self, name: str) -> list:
inbox_path = self.dir / f"{name}.jsonl"
messages = [json.loads(line) for line in ...]
inbox_path.write_text("") # drain
return messages
想想这个设计的精妙之处:
- 每个队友有独立邮箱 ——
alice.jsonl,bob.jsonl - 邮箱是文件 —— 没有任何中间件,不需要 Redis/RabbitMQ
- 读取即清空(drain)—— 每条消息只被处理一次
- append-only —— 没有锁竞争,并发安全
- JSONL 格式 —— 每行一个 JSON,可以 grep,可以审计
5 种消息类型,定义了队友间可能发生的所有交互:
message → 普通文本消息
broadcast → 群发
shutdown_request → 请求关闭
shutdown_response → 同意/拒绝关闭
plan_approval_response → 批准/拒绝计划
这些消息类型在 s10 变成了完整的状态机协议(Shutdown Protocol + Plan Approval Protocol)。
从 Subagent 到 Teammate:一条清晰的抽象路径
回头看,从 s04 到 s09 的进化路径非常清晰:
s04 Subagent: spawn → work → return → die
类似一个独立的函数调用
s09 Teammate: spawn → work → idle → work → ... → shutdown
类似一个微服务进程
s10 Protocols: 队友之间可以走协议:shutdown、plan_approval
请求 + request_id + 响应 = 异步 RPC
s11 Autonomous: 队友空闲时自动扫描任务板,认领任务
"我不需要被告诉做什么,我自己找活干"
每一步都在增加 Agent 的自主性和持久性,但同时 Harness 的复杂度几乎没变——还是那个 while 循环,还是那个 tool dispatch map,只是多加了几行工具注册。
这个进化说明了什么?
这个 evolution 说明了一个深刻的工程道理:
AI Agent 的扩展不是通过修改 Agent 本身,而是通过给 Agent 更好的组织架构。
你没法让模型一下子变聪明 10 倍(你又不能改权重)。但你可以给模型配合伙人(Subagent),可以给模型建团队(Teammate),可以给团队设计协作协议(Protocol),可以让团队自发地找活干(Autonomous)。
每加一层组织能力,Agent 系统能处理的任务复杂度就上一个台阶。
这就是 Harness Engineering 的终极形态:你不是在写一个 Agent,你是在设计一个组织。
下集预告
s04-s09 给了 Agent 队友和团队。但还有一个基础问题没解决:上下文总会满的。
即使你有 Subagent 来分担压力,主上下文的 messages 还是会随着时间增长。s06 的三层压缩策略解决了这个问题——让 Agent 可以永远工作下去。
但在此之前,s05 先插了一课:技能加载。一个关于"不要什么都往 system prompt 里塞"的教训。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)