mirror of
https://github.com/shareAI-lab/analysis_claude_code.git
synced 2026-09-20 12:13:38 +08:00
Refine course progression and runtime safety
This commit is contained in:
@@ -1,22 +1,18 @@
|
||||
# s06: Subagent — 大任务拆小,每个拿到的都是干净上下文
|
||||
# s06: Subagent — 给子任务一段独立上下文
|
||||
|
||||
[English](README.md) · [中文](README.zh.md) · [日本語](README.ja.md)
|
||||
|
||||
s01 → s02 → s03 → s04 → s05 → `s06` → [s07](../s07_skill_loading/) → s08 → ... → s18 → s19
|
||||
|
||||
> *"大任务拆小, 每个小任务干净的上下文"* — Subagent 用独立 messages[], 不污染主对话。
|
||||
> Subagent 从全新的 `messages[]` 开始。最终文本返回父循环,中间对话不会进入父上下文。
|
||||
>
|
||||
> **Harness 层**: 子 Agent — 上下文隔离, 注意力不漂移。
|
||||
> **Harness 层**: 委派 — 在另一段对话上下文中处理一个明确的子任务。
|
||||
|
||||
---
|
||||
|
||||
## 问题
|
||||
|
||||
Agent 在修一个 bug。它读了 30 个文件来追踪调用链,中间聊了 60 轮。messages 列表涨到 120 条,其中大部分是"追踪调用链"的中间过程,和"修 bug"这个最终目标无关。
|
||||
|
||||
这些中间过程占着上下文位置,让 Agent 越来越"健忘",它记不住最初的问题是什么了。
|
||||
|
||||
换个角度:你修 bug 的时候,会"开一个新终端"来追踪调用链。追踪完了,终端关掉,结果写进笔记,回到原来的终端继续修 bug。Agent 也需要这个能力:开一个独立的子进程,给它一个独立的消息列表,让它专心做一件事。
|
||||
Agent 在修一个 bug。为了追踪调用链,它读取了许多文件;每次工具调用和结果都会留在父循环的 `messages[]` 中。调用链已经弄清以后,多数中间细节不再需要,却仍然占用上下文。
|
||||
|
||||
---
|
||||
|
||||
@@ -24,91 +20,69 @@ Agent 在修一个 bug。它读了 30 个文件来追踪调用链,中间聊了
|
||||
|
||||

|
||||
|
||||
保留上一章的最小 hook 结构和 `todo_write` 工具,本章重点转向新增的 `task` 工具。调用它时,spawn 一个子 Agent,拥有全新的 `messages[]`,跑自己的循环,结束后只把摘要文本回传给主 Agent。对话上下文被丢弃,但文件系统的副作用(写文件、改文件、跑命令)保留在工作目录中。
|
||||
调用 `task` 时,会同步运行一个使用全新 `messages[]` 的嵌套 Agent Loop。循环结束后,它的最终文本会成为父对话中的工具结果。
|
||||
|
||||
子 Agent 的工具受限:有 bash/read/write/edit/glob,但没有 task,不能递归 spawn 新的子 Agent。子 Agent 的工具调用仍经过权限 hook,安全策略不因上下文隔离而跳过。
|
||||
这里隔离的是消息,不是进程或文件系统。父 Agent 与子 Agent 共享 `WORKDIR`,写文件和命令仍会影响同一个工作区。子 Agent 拥有五个基础工具,但没有 `task`;它的工具调用与父 Agent 使用同一组权限和生命周期 Hooks。
|
||||
|
||||
---
|
||||
|
||||
## 工作原理
|
||||
|
||||
**spawn_subagent**,给子 Agent 一个全新的 messages 列表,跑自己的循环,只回传结论:
|
||||
**run_subagent** 创建新的消息列表,运行嵌套循环,并返回最终文本:
|
||||
|
||||
```python
|
||||
def spawn_subagent(description: str) -> str:
|
||||
# 子 Agent 的工具:基础工具,但没有 task(禁止递归)
|
||||
sub_tools = [
|
||||
{"name": "bash", ...}, {"name": "read_file", ...},
|
||||
{"name": "write_file", ...}, {"name": "edit_file", ...},
|
||||
{"name": "glob", ...},
|
||||
]
|
||||
messages = [{"role": "user", "content": description}] # 全新 messages[]
|
||||
SUB_TOOLS = list(BASE_TOOLS) # no task tool
|
||||
|
||||
for _ in range(30): # safety limit
|
||||
def run_subagent(prompt: str) -> str:
|
||||
messages = [{"role": "user", "content": prompt}]
|
||||
|
||||
for _ in range(30):
|
||||
response = client.messages.create(
|
||||
model=MODEL, system=SUB_SYSTEM,
|
||||
messages=messages, tools=sub_tools, max_tokens=8000,
|
||||
messages=messages, tools=SUB_TOOLS, max_tokens=8000,
|
||||
)
|
||||
messages.append({"role": "assistant", "content": response.content})
|
||||
if response.stop_reason != "tool_use":
|
||||
break
|
||||
return extract_text(response.content) or "(no summary)"
|
||||
|
||||
results = []
|
||||
for block in response.content:
|
||||
if block.type == "tool_use":
|
||||
blocked = trigger_hooks("PreToolUse", block)
|
||||
if blocked:
|
||||
results.append({... "content": str(blocked)})
|
||||
continue
|
||||
handler = SUB_HANDLERS.get(block.name)
|
||||
output = handler(**block.input) if handler else f"Unknown"
|
||||
trigger_hooks("PostToolUse", block, output)
|
||||
output = execute_tool(block, SUB_HANDLERS)
|
||||
results.append({... "content": output})
|
||||
messages.append({"role": "user", "content": results})
|
||||
|
||||
# 只返回最后的文本结论,中间过程全部丢弃
|
||||
return extract_text(messages[-1]["content"])
|
||||
return "Subagent stopped after 30 turns without a final answer."
|
||||
```
|
||||
|
||||
主 Agent 调用时,跟调其他工具一样:
|
||||
|
||||
```python
|
||||
TOOLS = [
|
||||
{"name": "bash", ...},
|
||||
{"name": "read_file", ...},
|
||||
{"name": "write_file", ...},
|
||||
{"name": "edit_file", ...},
|
||||
{"name": "glob", ...},
|
||||
{"name": "todo_write", ...},
|
||||
# s06: 新增 task 工具
|
||||
{"name": "task",
|
||||
"description": "Launch a subagent to handle a complex subtask. Returns only the final conclusion.",
|
||||
"input_schema": {"type": "object", "properties": {"description": {"type": "string"}}, "required": ["description"]}},
|
||||
]
|
||||
TASK_TOOL = {
|
||||
"name": "task",
|
||||
"description": "Run a subagent with fresh conversation context and return its final text.",
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {"prompt": {"type": "string"}},
|
||||
"required": ["prompt"],
|
||||
},
|
||||
}
|
||||
|
||||
TOOL_HANDLERS["task"] = spawn_subagent
|
||||
TOOLS = [*BASE_TOOLS, TASK_TOOL]
|
||||
TOOL_HANDLERS = {**BASE_HANDLERS, "task": run_subagent}
|
||||
```
|
||||
|
||||
三个关键设计决策:
|
||||
实际边界如下:
|
||||
|
||||
| 决策 | 选择 | 原因 |
|
||||
|------|------|------|
|
||||
| 上下文隔离 | 全新 `messages[]` | 子 Agent 的中间过程不污染主 Agent 的上下文 |
|
||||
| 只回传结论 | `extract_text(last_message)` | 不是回传整个 messages 列表 |
|
||||
| 禁止递归 | 子 Agent 无 task 工具 | 防止子 Agent 再 spawn 新的子 Agent |
|
||||
| 安全策略不跳过 | 子 Agent 工具调用也走 PreToolUse hook | 上下文隔离不代表权限隔离 |
|
||||
| 对话 | 全新的 `messages[]` | 不把父对话复制给子 Agent |
|
||||
| 执行 | 同一进程和 `WORKDIR` | 两个循环都能看到文件系统修改 |
|
||||
| 返回值 | 只返回最终文本 | 子 Agent 的工具调用和结果不进入父消息列表 |
|
||||
| 委派深度 | `SUB_TOOLS` 中没有 `task` | 本章只允许一层委派 |
|
||||
| 工具策略 | 共享 Hooks | 父子循环使用相同的权限检查 |
|
||||
|
||||
dispatch 机制不变,task 工具通过 `TOOL_HANDLERS[block.name]` 分发。子 Agent 有独立的 `SUB_SYSTEM` 提示,明确要求"直接完成任务,不要再委派"。
|
||||
|
||||
---
|
||||
|
||||
## 相对 s05 的变更
|
||||
|
||||
| 组件 | 之前 (s05) | 之后 (s06) |
|
||||
|------|-----------|-----------|
|
||||
| 工具数量 | 6 (bash, read, write, edit, glob, todo_write) | 7 (+task) |
|
||||
| 新函数 | — | spawn_subagent(独立 messages[] + 30 轮安全限制) |
|
||||
| 上下文隔离 | 全部在主对话中 | 子 Agent 用全新的 messages[] |
|
||||
| 循环 | 不变 | dispatch 不变,子 Agent 有独立 SUB_SYSTEM 和 hook 保护的循环 |
|
||||
父 Agent 与其他工具一样,通过 handler map 分发 `task`。子 Agent 使用 `SUB_SYSTEM`、`SUB_TOOLS` 和自己的局部 `messages` 列表。
|
||||
|
||||
---
|
||||
|
||||
@@ -125,7 +99,7 @@ python s06_subagent/code.py
|
||||
2. `Delegate: read all .py files in agents/ and summarize what each one does`
|
||||
3. `Use a task to create s06_subagent/example/string_tools.py with a slugify(text: str) function, then verify it from the parent agent`
|
||||
|
||||
观察重点:是否出现 `[Subagent spawned]` / `[Subagent done]`?子 Agent 的工具调用是否以 `[sub] ...` 输出?主 Agent 最后是否只继续处理子 Agent 返回的摘要?
|
||||
观察重点:是否出现 `[Subagent started]` / `[Subagent done]`?子 Agent 的工具调用是否以 `[sub] ...` 输出?父 Agent 是否只接收到 `task` 返回的最终文本?
|
||||
|
||||
---
|
||||
|
||||
@@ -136,4 +110,4 @@ Agent 现在能拆任务了。但每个任务需要的知识不一样:改前
|
||||
s07 Skill Loading → 技能按需注入,不在 system prompt 里堆文档。用到的时候才加载,和读文件一样自然。
|
||||
|
||||
|
||||
<!-- translation-sync: zh@v1, en@v0, ja@v0 -->
|
||||
<!-- translation-sync: zh@v2, en@v2, ja@v2 -->
|
||||
|
||||
Reference in New Issue
Block a user