mirror of
https://github.com/shareAI-lab/learn-claude-code.git
synced 2026-08-17 20:33:48 +00:00
docs: simplify Chinese course prose
This commit is contained in:
parent
4a14529774
commit
2affb3f345
15 changed files with 44 additions and 44 deletions
|
|
@ -107,7 +107,7 @@ def agent_loop(messages):
|
|||
messages.append({"role": "user", "content": results})
|
||||
```
|
||||
|
||||
不到 30 行,这就是最小可运行的 agent harness 内核。它不是智能本身,而是让模型能持续行动的最小运行框架,模型负责决策(要不要调工具、调哪个),harness 负责执行(调了就跑、结果喂回去)。后面 18 个章节都在这个循环上叠加机制,循环本身始终不变。
|
||||
不到 30 行,这就是最小可运行的 agent harness 内核。它为模型提供持续行动的最小运行框架:模型负责决策(要不要调工具、调哪个),harness 负责执行(调了就跑、结果喂回去)。后面 20 个章节都在这个循环上叠加机制,循环本身始终不变。
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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 不
|
|||
|
||||

|
||||
|
||||
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 → 给循环加钩子,扩展逻辑挂在钩子上,循环保持干净。
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ SYSTEM = (
|
|||
)
|
||||
```
|
||||
|
||||
6500 行 system prompt。Agent 每次调用 LLM 都带着这些文档——不管是在改 CSS 颜色还是修 SQL 查询。99% 的内容和当前任务无关,白白消耗 token。
|
||||
6500 行 system prompt。Agent 每次调用 LLM 都带着这些文档,无论是在改 CSS 颜色还是修 SQL 查询。99% 的内容和当前任务无关,白白消耗 token。
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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 — 大结果落盘
|
||||
|
||||
|
|
|
|||
|
|
@ -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 升级到 64K(8 倍空间),重试同一请求——此时不追加截断输出到 messages,保持原始请求不变。如果 64K 还是不够,才保存截断输出并注入续写提示让模型接着刚才的话继续说,最多 3 次:
|
||||
第一次发生时,直接把 `max_tokens` 从 8K 升级到 64K(8 倍空间),然后重试同一请求。这个阶段不追加截断输出到 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 协作的基础。
|
||||
|
||||
|
|
|
|||
|
|
@ -220,7 +220,7 @@ python s12_task_system/code.py
|
|||
|
||||
## 接下来
|
||||
|
||||
任务图有了。但有些任务要跑很久——比如全量测试、部署到服务器。Agent 调 LLM 按量计费,不能干等一个慢操作。
|
||||
任务图有了,但全量测试、部署到服务器等任务需要很长时间。Agent 调 LLM 按量计费,不能干等一个慢操作。
|
||||
|
||||
s13 Background Tasks → 慢操作放后台。Agent 继续处理其他任务,后台跑完了通知它。
|
||||
|
||||
|
|
|
|||
|
|
@ -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 工具返回,没法利用这段时间处理别的任务。
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ s01 → ... → s13 → s14 → `s15` → [s16](../s16_autonomous_agents/) → s
|
|||
保持现有接口兼容,并确保测试通过。
|
||||
```
|
||||
|
||||
因此,Harness 需要解决的不只是“再启动几个 Agent”,而是四个连续问题:
|
||||
因此,Harness 需要连续解决四个问题:
|
||||
|
||||
1. 谁判断任务是否值得并行,以及如何征得用户确认?
|
||||
2. 队友如何保留自己的身份和上下文,持续接收工作?
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ while True:
|
|||
→ 再次扫描
|
||||
```
|
||||
|
||||
自治不是再造一个 Agent Loop,而是给既有循环增加一个由共享状态触发的入口。
|
||||
自治是在既有 Agent Loop 上增加一个由共享状态触发的入口。
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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 解决了"谁干什么"(任务系统)和"怎么通信"(消息总
|
|||
|
||||

|
||||
|
||||
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、一个自建的部署系统?
|
||||
|
||||
|
|
|
|||
|
|
@ -12,11 +12,11 @@ s01 → ... → s16 → s17 → `s18` → [s19](../s19_comprehensive/) → s20
|
|||
|
||||
## 问题
|
||||
|
||||
s01 到 s17,Agent 的所有工具都是手写的——bash、read、write、task、worktree。每个工具的输入验证、执行逻辑、错误处理,都是你一行行写的。
|
||||
s01 到 s17,Agent 的所有工具都是手写的,包括 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 |
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ s01 → ... → s17 → s18 → `s19` → [s20](../s20_workflow_runtime/) → s2
|
|||
- worktree 隔离
|
||||
- MCP 外部工具接入
|
||||
|
||||
难点不是把功能堆起来,而是看清楚它们都挂在循环的哪个位置。S19 是集成检查点:先把此前组件归位,再由 s20-s21 在外层加入编排与目标闭环。
|
||||
本章的难点在于看清楚每项功能挂在循环的哪个位置。S19 是集成检查点:先把此前组件归位,再由 s20-s21 在外层加入编排与目标闭环。
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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,只能包含字母、数字、`.`、`_`、`-`。
|
||||
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue