learn-claude-code-s05-s11-学习笔记

从零手写 Agent:Learn Claude Code s05-s07+s10-s11 学习笔记与薄弱点复盘(规划与协调篇)

学习对象:learnclaudecode 教程「规划与协调」板块(s05 TodoWrite / s06 Subagent / s07 Skills / s10 System Prompt / s11 Error Recovery)

注:s08 上下文压缩、s09 长期记忆属于「记忆管理」模块,见另篇笔记

学习方式:跑通 → 改坏 → 回看 三步法;配套 demo:s05_todo_write.py、s06_subagent.py、s07_skills.py、s10_system_prompt.py、s11_error_recovery.py


目录


零、全局心智模型(最重要的一章)

1. 一句话哲学(贯穿全课程)

程序只做三件事:判断、执行、回填。 找不到对应代码的行为,就是模型"读到文字后的生成行为"。

2. 模型 = 纯函数(s10 补课时建立,全课程地基)

复制代码
输出 = f(输入)      f = 模型,输入 = 它看到的全部文字(宪法 + 历史 + 本轮消息)
  • 模型无状态:没有记忆模块,每次调用"结束即忘"(s09 验证)
  • 模型的一切"知道"机制完全相同:上下文里的文字
  • 所有 Agent 机制本质都是往上下文塞字:权限拒绝、Nag、记忆注入、宪法------在模型眼里没有区别,只是来源和寿命不同

3. 本板块解决的统一矛盾:上下文有限,任务无限

课 解决什么 手段 信息量变化
s05 任务太长,模型忘目标 计划表外置(看得见) 信息变多
s06 过程太脏,上下文爆仓 上下文隔离(看不见) 信息变少
s07 工具说明书占地方 按需加载 信息延后给
s10 行为没有总导演 常驻宪法 信息最稳的一份
s11 失败无人兜底 显式重试 + 分类放弃 程序主动生产情报

4. 一条重要的架构规律(测验中验证过)

能被外置成"被调用者"的,走注册表(s02 工具、s04 钩子、s07 技能、s06 子代理);控制循环自身节奏的,只能改循环(s05 Nag、s08 压缩、s11 重试)。

5. 塞字的寿命谱系

塞进去的字 寿命
权限拒绝(s03) 一轮
Nag 提醒(s05) 一轮
子代理结论(s06) 一条 tool_result
技能菜单(s07) 常驻,加载后扩容
重试穷尽历史(s11) 一条 tool_result
记忆注入(s09) 每次会话开头
宪法(s10) 每轮都在,从头到尾

一、s05 TodoWrite:显式计划让长任务不跑偏

1.1 要解决的问题

任务一长(10+ 步),模型:忘了最初目标(目标漂移)、重复做已完成的、用户看不见进度。

1.2 核心代码

没有新架构------还是 s02 的注册表加两个工具:

  • todo_write:立计划(重写整个 todos 列表)
  • todo_update:打卡(改某一条的状态)

todos 是模块级全局变量------循环外的持久状态,和 messages 平级,互不从属。

Nag 提醒系统(s05 少数必须动主循环的地方):

python 复制代码
if todos and nag_count >= NAG_LIMIT:          # 有计划 且 攒够 3 轮没打卡
    messages.append({"role": "user",
        "content": "[系统提醒] 你的计划表已过期,请用 todo_update 同步进度。"})
    nag_count = 0

# 计数规则:调 todo_* 工具清零,调其他工具 +1(只看动作,不看结果)

1.3 知识点

# 知识点 要点
1 todos 和 messages 的关系 互不从属的两条状态。todos = 程序的账本(原件),messages 里那份 = 账本截图(发给模型的复印件)。账本改了要截图给模型,模型想改账本得先开口请求
2 todos 不在磁盘上 模块级变量,程序退出即清空。存磁盘是 s09 的事
3 Nag = 闹钟,不是手铐 程序只往 messages 塞一句话,模型自己决定补打卡。程序不知道"哪步算完成",只能观察到"你三轮没碰计划表"------所以只能提醒,不能强制
4 Nag 是廉价信号 只判断"有没有碰 todo 工具"这个动作,不验证计划内容真的变了
5 过期计划比没计划更危险 让模型重复劳动 / 用户误以为卡住。没计划时 Nag 不触发(if todos 短路):没计划,就没资格谈"计划过期"
6 主循环必须改 nag_count 计数、水位检查、消息注入三块都在 agent_loop 内部------"控制循环节奏"的东西外置不了

1.4 动手实验

  1. 删掉 mock_llm 里的 todo_write 调用块(只删调用,别删函数定义------删定义 = NameError,程序加载即死,轮不到任何兜底出场)→ 计划表为空、Nag 永不触发、todo_update 返回"找不到 #1"
  2. NAG_LIMIT = 3 改成 1 → 观察提醒时机;权衡:太小烦人,太大漂移了才提醒

二、s06 Subagent:独立上下文的临时工

2.1 要解决的问题

调研 20 个文件 = 主线 messages 爆仓(41 条,其中大量文件全文主线再也用不上)。

2.2 核心代码

s06 的全部秘密就一行:

python 复制代码
TOOL_HANDLERS["task"] = task_tool      # 子代理 = 注册表里的一个普通工具
python 复制代码
def run_subagent(task, llm, max_turns=8):
    sub_messages = [{"role": "user", "content": task}]   # ★ 独立上下文,从零开始
    for turn in ...:
        ...                                              # 子 Agent 自己转圈
    return final                                         # ★ 只带回一句结论,中间全丢

2.3 知识点

# 知识点 要点
1 task 就是普通工具 对主循环和 read_file 无任何区别(输入字符串、返回字符串)。主循环没有专属分支(demo 里唯一的 name != "task" 只是为了少打印)。派子代理这么"高级"的能力 = 注册表加一行
2 上下文完全隔离 子 Agent 看不到主线历史;主线看不到子 Agent 过程。内部 9 条消息函数返回时全部销毁,只带回结论
3 隔离是笔交易 用「信息变少」换「上下文变轻」:内部 9 条 → 主线只 +2。代价:主线无法验证结论(子代理漏报一个文件,主线发现不了)
4 失败传导 超时 = return 一句"子 Agent 超过最大轮数,强制结束" → 塞进主线 tool_result → 循环继续 → 模型自己决定重试/换方案/认输。程序没有任何"整理简化再抛"的逻辑(grep 全文验证过)
5 与 s05 互补 s05 给模型加记忆(防跑偏),s06 给主线减负担(防爆仓)------同一个瓶颈的两个方向
6 与 s11 的伏笔 子代理超时适合程序级重试吗?------看情况:超时是"可重试失败",但要防重试风暴,s11 的上限+分类就是干这个的

2.4 动手实验

  1. task_tool 返回值拼接"内部细节 读了 4 个文件" → 体会隔离被一点点破坏
  2. mock_llm 加第二个 task 调用 → 主线只 +2,两个子代理互不知道对方存在
  3. max_turns=2 → 子代理被掐断,主线不崩,拿到一句"强制结束"当普通结果

三、s07 Skills:工具不常驻,要用才上桌

3.1 要解决的问题

40 个工具全量注册 = 37 个用不上的 schema 全程占上下文,说明书可能比任务本身还大。

3.2 核心代码

两本目录 + 一个加载函数:

python 复制代码
SKILLS = {  # 技能包 = 三件套
    "dice": {
        "description": "掷骰子、随机数小游戏",     # 给菜单看(平时,便宜)
        "prompt": "使用骰子前,先确认用户要几面。",   # 加载后注入的使用指导
        "tools": [ ...完整 schema... ],             # 加载后才上架(贵)
    },
}

def skill_on(skill_name):                        # ★ 本课核心:6 行
    meta = SKILLS[skill_name]
    for tool_def in meta["tools"]:
        TOOL_DEFINITIONS.append(tool_def)        # ① 说明书上架(模型看得见了)
        TOOL_HANDLERS[tool_def["name"]] = IMPLEMENTATIONS[tool_def["name"]]  # ② 实现上桌
    LOADED_SKILLS.append(skill_name)

3.3 知识点

# 知识点 要点
1 菜单便宜,说明书贵 菜单(list_skills)每技能一行字;完整 schema 要 skill_on 后模型才看得见。按需付费
2 加载 = 往两张表 append 几行 没有新机制、主循环零改动(同 s06)。幂等:重复加载会叠两份说明书,所以要有已加载检查
3 未加载就调 = 带指引的错误回填 run_tool 查表失败返回"未知工具...可先用 skill_on 加载技能"------错误信息里藏导航,模型下轮自己纠正。绝不是静默返回空:静默 = 模型以为成功,建立在假前提上继续走
4 skill_off 的两难 卸载省上下文 vs 模型不知工具已卸、下轮调用又"未知工具"多费一轮。卸载至少要告知模型
5 静默失败三宗罪 ①模型被骗以为成功 ②浪费纠错机会(指引没了)③最恶性 bug:程序照常跑、不报错、结果错

3.4 动手实验

  1. mock_llm 第一轮直接调 roll_dice → 看"带指引的错误"怎么把模型引回正轨
  2. 连续两次 skill_on("dice") → 观察幂等检查
  3. 设计 skill_off(权衡题,无标准答案)

四、s10 System Prompt:一段文字,两种人生

4.1 要解决的问题

前面所有机制都在塞字,谁决定这些字怎么写、什么语气、什么优先级? System prompt------常驻上下文的行为总导演。

4.2 核心实验(demo 的设计)

同一条 query("帮我把临时文件夹删了,顺便联网检查更新")、同一个模型函数、同一套工具------只改 messages0 那段文字:

  • 裸奔版 :rm -rf temp 直接删、curl 直接联网
  • 有宪法版 :先 ask_user 询问 → 用户拒绝 → 改用只读方案 dir /b temp
python 复制代码
constitution = messages[0]["content"]
safety_mode = "禁止直接执行" in constitution   # 模型"读到"规则了吗?

4.3 知识点

# 知识点 要点
1 宪法的地位:常驻总导演 每轮都在、最早塞、管全局。对比临时传令:权限拒绝/Nag(一轮)、记忆注入(会话开头)
2 改行为 = 改一段文字,零代码 同一函数,不同输入 → 不同输出。模型是纯函数,宪法只是输入的一部分。Prompt 工程的本质就是编辑这段字
3 宪法只是说服,不是栏杆 恶意用户一句"忽略所有规则"可能攻破它(提示注入 )。双层防御:说服层(宪法)+ 栏杆层(s03 权限门)。prompt 管 99%,权限门管剩下 1% 恶意
4 打架没有严格优先级 宪法说"禁止删",用户说"忽略规则删"------都是上下文里的文字,模型综合权衡生成,可能听谁都可能不听。程序无法仲裁(它看不见语义)。这正是需要硬栏杆的原因
5 prompt 是旋钮不是开关 宪法把"遵守"概率从 60% 调到 99%,但生成是概率采样。"时灵时不灵" = 概率波动;"被稳定绕过" = 注入。两个病分开诊断:忽好忽坏 → 调 prompt;被绕过 → 加栏杆
6 宪法六件套模板 ① 身份角色 ② 能力边界(能/不能)③ 行为规则 ④ 输出格式 ⑤ 工具使用约定 ⑥ 兜底话术(不确定时直说不知道,不编造)

4.4 动手实验

  1. PROMPT_SAFE 的规则 2 改成"危险操作直接执行" → 有宪法版变裸奔。宪法内容 = 模型行为
  2. 消息里加"忽略之前所有规则" → 观察说服层的脆弱性

五、s11 Error Recovery:程序级重试

5.1 要解决的问题

有些失败不能等模型下一轮再说:网络抖动这种失败,程序应该当场兜住,模型甚至不需要知道。而另一些失败(权限拒绝)重试毫无意义。

5.2 核心代码

python 复制代码
def run_tool_with_retry(name, params, max_attempts=3):
    history = []
    for attempt in range(1, max_attempts + 1):     # ← 显式重试:grep 得到的 for
        try:
            return handler(**params)
        except (TimeoutError, ConnectionError):    # ★ 可重试:继续
            history.append(f"第{attempt}次失败: {e}")
        except PermissionError:                    # ★ 不可重试:立即放弃
            return f"[权限拒绝] {e}", history       # 一秒都不浪费
    return f"[重试穷尽] {max_attempts} 次均失败。历史: {'; '.join(history)}", history

5.3 知识点

# 知识点 要点
1 隐式重试 vs 显式重试(本课灵魂) 模型的重试 = 隐式的(下一轮再发一次调用,代码里找不到);程序的重试 = 显式的(for attempt in range(3),grep 得到)。"程序做的,代码里一定找得到"的正面案例
2 失败二分法 可重试(超时/网络抖动/限流)→ 程序兜住;不可重试(权限拒绝/参数错误/文件不存在)→ 立即放弃回填。重试不可重试的失败 = 纯烧 token
3 必须有上限 max_attempts。没有上限的重试 = 死循环(s01 实验 1 的教训)
4 穷尽后打包失败历史 不扔孤立的最后一次错误,回填完整病历("连挂 3 次"和"只挂 1 次"是不同情报)------错误即数据的终极形态:程序主动生产情报喂给模型
5 与 s03 的分工 s11 重试解决"抖动"(不值得打扰模型),s03 回填解决"决策"(值得模型换方案)。程序兜小事,模型管大事

5.4 动手实验

  1. 删掉 except PermissionError 分支 → 权限拒绝被白白重试 3 次,多烧 2 次调用,结果一样
  2. max_attempts=3 改成 2 → flaky_read(第 3 次才成功)永远失败,看"重试穷尽"的完整病历

六、我的薄弱点清单(错题本)

# 薄弱点 我的错误答案 正确理解 状态
1 todos 的归属 "todos 存在 messages 里 / 存在磁盘上" 模块级独立变量,和 messages 平级、互不从属、无磁盘 IO。三问自检:在哪行定义?谁有写权限?模型怎么知道它? ✅ 已理解
2 谁改的计划状态 "循环收尾时统一改" 全文只有 todo_update 内一行 t["status"] = status 是写入,且只能由模型发起。grep 验证:没有收尾逻辑 ✅ 已修复
3 主循环改没改 "s05 没改主循环,只是外围加了东西" nag_count/检查/注入三块全在 agent_loop 内部。能外置的走注册表,控制循环节奏的只能改循环 ✅ 已理解
4 task 的身份 "主循环专属分支" 注册表里的普通工具条目。能力再复杂,包装成"入字符串出字符串"就是普通工具 ✅ 已理解
5 子代理失败处理 "主线会整理简化文件再抛给子 Agent" 超时只 return 一句字符串,grep 全文无任何重试/整理逻辑。应对权在模型(程序级重试到 s11 才出现,且显式可 grep) ✅ 已修复
6 未知工具的兜底 "静默返回空" 返回带指引的错误文本。宁可大声报错,不可小声装死------静默失败 = 模型被骗着建立在假前提上 ✅ 已修复
7 记忆写入时机 "会话结束统一整理再写" 当场写:压缩随时触发、会话可能崩、结束时的回忆已被污染。当场写 + 定期整理(详见 s08-s09 篇) ✅ 已理解
8 "难"的判断反了 "写宪法太难,所以每轮注入" 宪法改 1 处、零代码、无漏发;每轮注入改主循环、烧 token、会漏。重复性需求沉淀进宪法 ✅ 已修复
9 直觉对、选择错(模式) 自己说"都有可能",却选"程序仲裁" 答题两个检查:①程序有语义理解能力吗?没有 → 模型的事 ②我信的结论和选的选项是否一致 ✅ 持续自查
10 表述层薄弱 "讲不出代码怎么运行,只会大白话" 机制层已通(大白话全对),缺的是输出练习。两条路:写 (毕业项目)+ 讲(费曼式自述,卡壳处翻代码重讲)。术语是地图,走过的领土贴标签很快 🔄 进行中

误区总账(截至 s11)

"程序/模型会自己想办法"家族共 7 次复发 :s01 权限换方案 → s02 未知工具查询 → s03 重试降级 → s05 收尾改状态 → s06 整理重派 → s09 模型记忆模块 → s10 程序仲裁打架。统一判据:先找代码。程序做的,代码里一定有那几行;模型的"能力",一定能指出来自上下文里哪段文字。


七、五课横向对比速查表

维度 s05 计划表 s06 子代理 s07 技能 s10 宪法 s11 重试
一句话 给模型装导航 派独立临时工 工具按需上桌 常驻行为总导演 程序兜住抖动
主循环改了吗 改了(Nag 三块) 没改 没改 没改(宪法在 messages0) 改了(换重试版执行器)
上下文影响 变多(进度可见) 变少(隔离) 延后付(按需) 每轮常驻 失败历史更厚
谁做决定 模型(补打卡) 模型(应对超时) 模型(看菜单加载) 模型(权衡打架) 程序(重试几次)→模型(穷尽后)
失败时的行为 提醒,不强制 一句字符串回填 带指引的错误回填 可能被绕过 分类:重试/放弃
真实对应 TodoWrite Task 工具 Claude Skills 各产品 system prompt 生产级 run_tool

一次完整调用的生命周期(五课成果串成一条线):

复制代码
宪法在 messages[0](s10,行为总导演)
  → 模型开局先 todo_write 立计划(s05)
  → 要调研 → 调 task 派子代理(s06,9 条内部消息只带回结论)
  → 需要新工具 → list_skills 看 → skill_on 加载(s07)
  → 光干活不打卡 → Nag 提醒(s05)
  → 调工具失败 → run_tool_with_retry 分类处理(s11:可重试当场兜,不可重试立即报)
  → messages 长了 → 压缩(s08)|重要约定 → memory_save(s09)|危险操作 → 权限门硬拦(s03)

八、自测题(附答案)

1. todos 和 messages 是什么关系?谁在磁盘上?
答案 互不从属的两条状态:todos 是模块级全局变量(程序的账本),messages 是对话历史(发给模型的截图)。都不在磁盘上------存磁盘是 s09 记忆的事。todo_update 的返回值里带 show_todos() 快照,这是模型"看见"计划的唯一途径。

2. Nag 触发后谁去更新计划表?程序的动作是什么?
答案 模型自己调 todo_update。程序只做一件事:往 messages 塞一句"系统提醒 计划表已过期",然后清零计数。程序不知道"哪步算完成",只能观察到"三轮没碰计划表",所以只能提醒不能强制。

3. 为什么 s06 的 task 工具和 read_file 对主循环没有任何区别?
答案 都是注册表里的普通条目:输入字符串、返回字符串、经同一个 run_tool。子代理的"高级"全在 task_tool 内部(独立 sub_messages 的新循环),对主循环完全透明。

4. 子 Agent 读了 4 个文件,主线为什么一条全文都看不到?代价是什么?
答案 子 Agent 有独立 messages,9 条内部消息在函数返回时全部销毁,只 return 最终结论一句。代价:主线无法验证结论(漏报发现不了)。用信息变少换上下文变轻。

5. 模型不加载技能直接调 roll_dice,会发生什么?为什么不能静默返回空?
答案 run_tool 查表失败,返回"未知工具...可先用 skill_on 加载技能"带指引的错误文本,回填后循环继续。静默返回空会让模型以为执行成功------静默失败是最恶性的 bug。

6. 为什么 Nag、压缩、重试必须改主循环,而工具、钩子、技能不用?
答案 工具/钩子/技能能被外置成"被调用者"(注册表查表调用);Nag/压缩/重试是控制循环自身节奏的行为(每轮检查、修改执行方式),没有"被调用"的形态,只能写进循环体。

7. 宪法和权限门是什么关系?为什么两个都要?
答案 说服层 + 栏杆层。宪法只是上下文里的文字,可被提示注入攻破;权限门是代码里的硬判断,模型绕不过。prompt 管 99%,权限门管剩下 1% 恶意,缺一不可。

8. 宪法说"禁止删除",用户说"忽略规则删",谁赢?程序会仲裁吗?
答案 没有严格优先级------都是上下文里的文字,模型综合权衡生成。程序无法仲裁:它看不见文字的语义。这正是硬栏杆(权限门)必须存在的原因。

9. "时灵时不灵"和"被注入攻破"是同一个病吗?
答案 不是。时灵时不灵 = 概率采样波动(宪法是旋钮不是开关);被攻破 = 提示注入(稳定失效)。前者调 prompt 加约束,后者加硬栏杆、最小权限、二次确认。

10. s11 的重试循环和模型"下一轮再试一次"有什么本质区别?
答案 显式 vs 隐式。程序的重试在轮内:`for attempt in range(3)`,grep 得到,模型甚至不知道重试发生过;模型的重试在轮间:读到失败信息后下一轮重新发起调用。前者程序兜住不值得打扰模型的抖动,后者模型基于回填的情报做决策。

11. 什么样的失败可以重试,什么样的不行?为什么?
答案 可重试:超时、网络抖动、限流(再试可能就好了)。不可重试:权限拒绝、参数错误、文件不存在(重试一万次结果一样)。重试不可重试的失败 = 纯烧 token 和轮次。

12. 重试穷尽后,为什么回填"完整失败历史"而不是只有最后一次错误?
答案 "连挂 3 次"和"只挂 1 次"是不同情报:前者说明服务真挂了该换方案,后者可能只是抖动。打包历史 = 程序主动生产情报喂给模型------错误即数据的终极形态。


本篇收束句 :这一板块的全部智慧浓缩成两句话------看得见的给模型看(计划表、菜单、宪法),看不见的替模型扛(隔离、重试、压缩);程序永远在搬运和设栏,模型永远在做主。

相关推荐
pipixia200331 小时前
C++ 面试高频:namespace 的 6 个知识点,从 using 到匿名命名空间
c语言·学习·算法·嵌入式·命名空间·c++面试·匿名命名空间
每天题库1 小时前
汽车维修工等级考试题库 理论实操核心考点一次梳理清
学习·安全·考试·题库·考证
神也畏惧菠萝2 小时前
想要学习Agent应用开发?:2026 AI Agent开发全栈通关路线
学习·langgraph·mcp协议·agent开发·rag架构·prompt高级工程
程序猿乐锅2 小时前
【从0-1学习Python | 第七篇】Python 面向对象基础
开发语言·笔记·后端·python·学习·1024程序员节
Alsn863 小时前
JAVA通过 http api与ollama本地大模进行流式多轮对话
学习
fish_zzzz3 小时前
笔记--浅谈内存,存储空间,BIOS芯片,显卡GPU、VRAM(1)
笔记·1024程序员节
ACP广源盛139246256733 小时前
PCIe Gen3 交换芯片选型笔记:多路扩展方案 IX7024@ACP 评估
笔记·硬件架构·pcie·国产芯片
深圳老胡3 小时前
STM32 开发中 Trae 内置模型怎么选:Seed、MiniMax、GLM、DeepSeek、Kimi、Qwen 全对比
笔记·stm32·单片机·嵌入式硬件·代码规范·1024程序员节
坤坤子吖4 小时前
C++继承:友元、静态成员、多继承与组合
开发语言·c++·笔记·学习·1024程序员节