docs: simplify Chinese course prose

This commit is contained in:
Haoran 2026-07-31 03:54:12 +08:00
parent 4a14529774
commit 2affb3f345
15 changed files with 44 additions and 44 deletions

View file

@ -107,7 +107,7 @@ def agent_loop(messages):
messages.append({"role": "user", "content": results})
```
不到 30 行,这就是最小可运行的 agent harness 内核。它不是智能本身,而是让模型能持续行动的最小运行框架,模型负责决策要不要调工具、调哪个harness 负责执行(调了就跑、结果喂回去)。后面 18 个章节都在这个循环上叠加机制,循环本身始终不变。
不到 30 行,这就是最小可运行的 agent harness 内核。它为模型提供持续行动的最小运行框架:模型负责决策要不要调工具、调哪个harness 负责执行(调了就跑、结果喂回去)。后面 20 个章节都在这个循环上叠加机制,循环本身始终不变。
---

View file

@ -13,7 +13,7 @@ s01 → s02 → `s03` → [s04](../s04_hooks/) → s05 → ... → s20 → s21
s02 的 Agent 有 5 个工具。file tools 受 `safe_path` 保护,但 bash 不受限制。让它"清理一下项目",可能执行 `rm -rf /`
安全不能靠信任模型,要靠代码——在工具执行之前做判断
安全边界由代码负责,判断发生在工具执行之前
---
@ -21,7 +21,7 @@ s02 的 Agent 有 5 个工具。file tools 受 `safe_path` 保护,但 bash 不
![Permission Overview](images/permission-overview.svg)
s02 的循环完全保留。唯一的变动在工具执行前插入 `check_permission()`——每个工具调用经过三道闸门,顺序固定:硬拒绝优先,软询问次之,都没命中就放行。
s02 的循环完全保留。唯一的变动在工具执行前插入 `check_permission()`。每个工具调用依次经过三道闸门:硬拒绝优先,软询问次之,都没命中就放行。
三道闸门对应三种决策:
@ -54,7 +54,7 @@ def check_deny_list(command: str) -> str | None:
return None
```
**闸门 2**:规则匹配——描述"什么时候需要问用户"。每条规则指定工具和检查条件。
**闸门 2**负责规则匹配,用来描述"什么时候需要问用户"。每条规则指定工具和检查条件。
```python
PERMISSION_RULES = [
@ -149,7 +149,7 @@ python s03_permission/code.py
## 接下来
权限检查做了——但每次都在循环里硬编码 `check_permission()`。如果我想在每次工具执行前后加日志?如果想在某些操作后自动触发 git commit这些扩展逻辑散落在 loop 里,循环很快就会膨胀。
当前权限检查每次都在循环里硬编码 `check_permission()`。如果我想在每次工具执行前后加日志?如果想在某些操作后自动触发 git commit这些扩展逻辑散落在 loop 里,循环很快就会膨胀。
s04 Hooks → 给循环加钩子,扩展逻辑挂在钩子上,循环保持干净。

View file

@ -22,7 +22,7 @@ SYSTEM = (
)
```
6500 行 system prompt。Agent 每次调用 LLM 都带着这些文档——不管是在改 CSS 颜色还是修 SQL 查询。99% 的内容和当前任务无关,白白消耗 token。
6500 行 system prompt。Agent 每次调用 LLM 都带着这些文档,无论是在改 CSS 颜色还是修 SQL 查询。99% 的内容和当前任务无关,白白消耗 token。
---

View file

@ -60,7 +60,7 @@ def snip_compact(messages, max_messages=50):
return messages[:head_end] + [placeholder] + messages[tail_start:]
```
裁掉的是消息本身,只是在切口处多做一步保护;剩下的消息里 `tool_result` 内容仍在累积——第 34 条消息里可能躺着 30KB 的旧文件内容。→ L2。
裁掉的是消息本身,只是在切口处多做一步保护;剩下的消息里 `tool_result` 内容仍在累积第 34 条消息里可能躺着 30KB 的旧文件内容。→ L2。
### L2: micro_compact — 旧工具结果占位
@ -83,7 +83,7 @@ def micro_compact(messages):
return messages
```
旧结果清掉了,但单条新结果可能就有 500KB——一个 `cat` 大文件的输出就能打满上下文。→ L3。
旧结果清掉了,但单条新结果可能就有 500KB。一次 `cat` 大文件的输出就能打满上下文。→ L3。
### L3: tool_result_budget — 大结果落盘

View file

@ -17,7 +17,7 @@ Agent 跑着跑着报错了:
Error: 529 overloaded
```
Agent 崩溃了。它没有重试,没有换模型,没有减少上下文——直接崩溃
Agent 崩溃了。它没有重试、切换模型或减少上下文,调用直接终止
LLM API 调用可能失败。本章处理三种情况输出截断、上下文超限和临时故障429/529
@ -45,7 +45,7 @@ s10 的循环、prompt 组装全部保留。唯一的变动LLM 调用包裹
模型话说一半,`max_tokens` 用完了。默认 8000 token 不够它输出完整回答。
第一次发生时,直接把 `max_tokens` 从 8K 升级到 64K8 倍空间),重试同一请求——此时不追加截断输出到 messages保持原始请求不变。如果 64K 还是不够,才保存截断输出并注入续写提示让模型接着刚才的话继续说,最多 3 次:
第一次发生时,直接把 `max_tokens` 从 8K 升级到 64K8 倍空间),然后重试同一请求。这个阶段不追加截断输出到 messages保持原始请求不变。如果 64K 还是不够,才保存截断输出并注入续写提示让模型接着刚才的话继续说,最多 3 次:
```python
if response.stop_reason == "max_tokens":
@ -67,7 +67,7 @@ if response.stop_reason == "max_tokens":
messages.append({"role": "assistant", "content": response.content})
```
升级只有一次机会,续写最多 3 次。超过就退出——继续续写也不会有实质产出。
升级只有一次机会,续写最多 3 次。超过这个上限就退出,因为继续续写也不会有实质产出。
### 路径 2: 上下文超限
@ -86,7 +86,7 @@ except PromptTooLongError:
### 路径 3: 临时故障
网络抖动、429 限流、529 过载——这些不是 bug是分布式系统的常态
网络抖动、429 限流和 529 过载是分布式系统的常态,并不表示代码存在 bug
429 和 529 统一走指数退避 + 抖动:第一次等 0.5 秒,第二次等 1 秒,第三次等 2 秒,最多 10 次。加随机抖动让并发请求不在同一时刻重试。连续 3 次 529 过载 → 切换到备用模型(若配置了 `FALLBACK_MODEL_ID` 环境变量):
@ -190,9 +190,9 @@ python s11_error_recovery/code.py
## 接下来
Agent 现在能在错误中自动恢复了。但它处理的任务仍然是"一次性"的——你给它一个任务,它做完,结束。
Agent 现在能在错误中自动恢复了,但仍然一次只处理一个任务:接收任务、完成任务,然后结束。
能不能让 Agent 管理一个**任务列表**——有依赖关系、持久化到磁盘、跨会话能恢复TODO 列表不是任务系统
下一步要让 Agent 管理一个具备依赖关系、磁盘持久化和跨会话恢复能力的**任务列表**。TODO 列表无法承担任务系统的职责
s12 Task System → 任务是有依赖、有状态、持久化的图。这是多 Agent 协作的基础。

View file

@ -220,7 +220,7 @@ python s12_task_system/code.py
## 接下来
任务图有了。但有些任务要跑很久——比如全量测试、部署到服务器。Agent 调 LLM 按量计费,不能干等一个慢操作。
任务图有了,但全量测试、部署到服务器等任务需要很长时间。Agent 调 LLM 按量计费,不能干等一个慢操作。
s13 Background Tasks → 慢操作放后台。Agent 继续处理其他任务,后台跑完了通知它。

View file

@ -12,7 +12,7 @@ s01 → ... → s11 → s12 → `s13` → [s14](../s14_cron_scheduler/) → s15
## 问题
你用过洗衣机吗?把衣服扔进去,按下启动,然后去干别的——做饭、回消息、看论文。30 分钟后洗衣机"滴滴滴"提醒你:好了。你不会站在洗衣机前面干等 30 分钟。
你用过洗衣机吗?把衣服扔进去,按下启动,然后去做饭、回消息或看论文。30 分钟后洗衣机"滴滴滴"提醒你:好了。你不会站在洗衣机前面干等 30 分钟。
Agent 的 bash 工具也一样。`pip install torch` 要 10 分钟,`npm run build` 要 3 分钟。这些命令一跑Agent 就在等 bash 工具返回,没法利用这段时间处理别的任务。

View file

@ -21,7 +21,7 @@ s01 → ... → s13 → s14 → `s15` → [s16](../s16_autonomous_agents/) → s
保持现有接口兼容,并确保测试通过。
```
因此Harness 需要解决的不只是“再启动几个 Agent”而是四个连续问题:
因此Harness 需要连续解决四个问题:
1. 谁判断任务是否值得并行,以及如何征得用户确认?
2. 队友如何保留自己的身份和上下文,持续接收工作?

View file

@ -142,7 +142,7 @@ while True:
→ 再次扫描
```
自治不是再造一个 Agent Loop而是给既有循环增加一个由共享状态触发的入口。
自治是在既有 Agent Loop 上增加一个由共享状态触发的入口。
---

View file

@ -14,7 +14,7 @@ s01 → ... → s15 → s16 → `s17` → [s18](../s18_mcp_plugin/) → s19 →
s16 中Alice 和 Bob 都在同一个目录下工作。Alice 的任务是"重构认证模块"Bob 的任务是"重构 UI 登录页"。
Alice `write_file("config.py", ...)`。Bob 也 `write_file("config.py", ...)`。两个人改同一个文件,互相覆盖。而且无法干净地回滚——分不清哪些改动是谁的
Alice `write_file("config.py", ...)`。Bob 也 `write_file("config.py", ...)`。两个人改同一个文件,互相覆盖,而且无法干净地回滚,因为已经分不清每处改动来自谁
s15-s16 解决了"谁干什么"(任务系统)和"怎么通信"(消息总线),但没解决"在哪干"。
@ -24,7 +24,7 @@ s15-s16 解决了"谁干什么"(任务系统)和"怎么通信"(消息总
![Worktree Overview](images/worktree-overview.svg)
Git worktree 让你在同一仓库中创建多个独立的工作目录每个有自己的分支。Alice 在 `.worktrees/auth-refactor/` 下工作Bob 在 `.worktrees/ui-login/` 下工作——互不干扰。
Git worktree 让你在同一仓库中创建多个独立的工作目录,每个目录都有自己的分支。Alice 在 `.worktrees/auth-refactor/` 下工作Bob 在 `.worktrees/ui-login/` 下工作,两者互不干扰。
沿用 s16 的 MessageBus、协议和自治认领机制。本章新增
@ -59,7 +59,7 @@ def bind_task_to_worktree(task_id: str, worktree_name: str):
save_task(task) # 状态保持 pending等队友 claim
```
绑定规则:一个任务绑定一个 worktree。绑定不改任务状态——任务仍是 `pending`,队友自动认领时才推进到 `in_progress`。这样 Lead 可以提前创建任务和 worktree队友 idle 时自然认领带 worktree 的任务。
绑定规则:一个任务绑定一个 worktree。绑定不会改变任务状态。任务仍是 `pending`,队友自动认领时才推进到 `in_progress`。这样 Lead 可以提前创建任务和 worktree队友 idle 时自然认领带 worktree 的任务。
### 队友工具的 cwd 切换
@ -103,7 +103,7 @@ def keep_worktree(name: str) -> str:
return f"Worktree '{name}' kept for review (branch: wt/{name})"
```
Keep = 留着分支,等人工 review 后合并到主分支。Remove = 有改动时默认拒绝,需要 `discard_changes=true` 确认。不自动 complete task——任务完成由队友的 `complete_task` 显式触发。
Keep = 留着分支,等人工 review 后合并到主分支。Remove = 有改动时默认拒绝,需要 `discard_changes=true` 确认。系统不会自动 complete task任务完成由队友的 `complete_task` 显式触发。
### 事件流:可审计
@ -162,7 +162,7 @@ python s17_worktree_isolation/code.py
## 接下来
Agent 团队能在隔离的工作空间中自组织了。但 Agent 的能力受限于我们给它写的工具——bash、read、write、task...
Agent 团队能在隔离的工作空间中自组织了,但 Agent 的能力仅限于我们为它编写的 bash、read、write、task 等工具。
如果用户已经有了自己的工具怎么办?比如一个公司内部的 Jira API、一个自建的部署系统

View file

@ -12,11 +12,11 @@ s01 → ... → s16 → s17 → `s18` → [s19](../s19_comprehensive/) → s20
## 问题
s01 到 s17Agent 的所有工具都是手写的——bash、read、write、task、worktree。每个工具的输入验证、执行逻辑、错误处理都是你一行行写的。
s01 到 s17Agent 的所有工具都是手写的,包括 bash、read、write、task 和 worktree。每个工具的输入验证、执行逻辑、错误处理都是你一行行写的。
现在你有 3 个外部服务想接入:公司的 Jira API查 issue、建 ticket、自建的部署系统触发 deploy、看日志、团队的 Notion 知识库(搜文档、建页面)。你不想为每个服务重写一套工具代码。
你需要一个标准协议——外部服务只要实现它Agent 就能直接调用,不管服务用什么语言写的。
你需要一个标准协议外部服务只要实现它Agent 就能直接调用,不管服务用什么语言写的。
---
@ -146,7 +146,7 @@ def agent_loop(messages, context):
| 新类型 | — | MCPClient 类(模拟 tools/list + tools/call |
| 命名空间 | — | mcp\_\_server\_\_tool 避免冲突 |
| 工具描述 | 无标注 | (readOnly)/(destructive) 标注 |
| prompt 缓存 | 有s10 起) | 去掉——工具池动态变化后缓存失效 |
| prompt 缓存 | 有s10 起) | 去掉,因为工具池动态变化后缓存失效 |
| Lead 工具 | worktree 与团队工具 | + connect_mcp 和动态发现的 MCP 工具 |
| Teammate 工具 | 任务、文件、消息与计划工具 | 不变 |
| 扩展方式 | 写代码加工具 | 标准协议,任意语言实现 server |

View file

@ -26,7 +26,7 @@ s01 → ... → s17 → s18 → `s19` → [s20](../s20_workflow_runtime/) → s2
- worktree 隔离
- MCP 外部工具接入
难点不是把功能堆起来,而是看清楚它们都挂在循环的哪个位置。S19 是集成检查点:先把此前组件归位,再由 s20-s21 在外层加入编排与目标闭环。
本章的难点在于看清楚每项功能挂在循环的哪个位置。S19 是集成检查点:先把此前组件归位,再由 s20-s21 在外层加入编排与目标闭环。
---

View file

@ -20,7 +20,7 @@ s01 → ... → s18 → s19 → `s20` → [s21](../s21_goal_loop/)
- **确定**,同样的输入跑出来同样的结果结构;
- **可恢复**,跑到一半断了,已经做完的部分别从头再来。
让模型在主循环里一步一步驱动这套流程,又慢、结果又不确定,断了还得从头跑。这时候你要的不是"再聊一轮",而是把这套编排直接写成代码。
让模型在主循环里一步一步驱动这套流程,会拖慢执行速度、增加结果的不确定性,中断后还得从头运行。更合适的做法是把整套编排直接写成代码。
## 计划写在代码里,不是靠聊天一轮轮凑
@ -62,7 +62,7 @@ class WorkflowTool:
每个 workflow 都要注册一个元数据对象,包含 `name``description` 和可选的 `phases`。运行时会在执行任何 workflow 代码之前校验它:`name``description` 用来标识任务,`phases` 给进度条分组命名。
不对的输入直接抛 `WorkflowInputError`,注册的时候就拦住——这和 s14 校验 cron 表达式是一个思路:坏脚本别让它跑到执行的时候才炸。
运行时在注册阶段直接拒绝错误输入并抛出 `WorkflowInputError`这和 s14 校验 cron 表达式是一个思路:坏脚本别让它跑到执行的时候才炸。
运行时会把 `meta.name` 用在本地产物文件名中,因此还要求它是 1-64 个字符的安全 slug只能包含字母、数字、`.``_``-`

View file

@ -40,7 +40,7 @@ if not has_tool_use(response):
## 设目标:证据从命令之后开始算
`set_goal` 会存一个活跃目标:目标文本、最大轮数预算、计数器,还有 `start_index`——也就是证据窗口的起点。它取当前对话记录的长度,所以 `/goal` 这行命令本身在窗口外面。这是第一道防线:命令自己不能证明自己完成了。
`set_goal` 会存一个活跃目标:目标文本、最大轮数预算、计数器`start_index`。其中,`start_index` 表示证据窗口的起点。它取当前对话记录的长度,所以 `/goal` 这行命令本身在窗口外面。这是第一道防线:命令自己不能证明自己完成了。
```python
def set_goal(self, objective, max_turns=20):

File diff suppressed because one or more lines are too long