learn-claude-code s01-s10:Quiz 标准答案复习笔记
0. 总览
s01-s10 的主线
这 10 节课是在一步步搭建一个越来越完整的 Agent 系统:
- s01:基础 Agent Loop,模型决定是否发起工具调用,Python 真正执行工具。
- s02:多工具系统,增加文件读写、编辑、glob,并用 handler 字典分发。
- s03:权限系统,在 Python 层阻止危险工具调用。
- s04:Hooks,把日志、检查、自动处理等扩展逻辑从主循环拆出去。
- s05:TodoWrite,让模型管理复杂任务的计划和状态。
- s06:Subagent,把复杂任务交给子 Agent,主 Agent 只接收总结结果。
- s07:Skill Loading,用轻量目录 + 按需加载的方式给模型扩展能力。
- s08:Context Compact,压缩当前
messages,防止上下文过长。 - s09:Memory,把长期有价值的信息保存到磁盘,跨会话复用。
- s10:System Prompt,把 system prompt 拆成 section,并根据 context 动态组装。
最重要的一句话
模型负责“理解、推理、决定要不要请求工具”;
Python 负责“维护 messages、发送 API 请求、执行工具、检查权限、触发 hook、保存状态、再把结果返回给模型”。
一条主线记忆
s01 基础循环
-> s02 多工具
-> s03 权限检查
-> s04 hook 解耦
-> s05 todo 计划
-> s06 subagent 分工
-> s07 skill 能力加载
-> s08 compact 当前上下文
-> s09 memory 长期记忆
-> s10 system prompt 动态组装
1. s01 Agent Loop 的完整流程是什么?
用户输入先被放入 messages。Python 调用 client.messages.create,把 messages、system、tools schema 一起发给模型。
模型返回普通文本,或者返回 tool_use。如果返回 tool_use,Python 根据工具名找到真正的函数,例如 run_bash,然后执行工具。
工具执行完成后,Python 把结果包装成 tool_result,再放回 messages,然后再次调用模型,让模型基于工具结果继续回答。
核心结论:模型负责决定是否请求工具;Python 负责真正执行工具。
2. 为什么说模型拿到的是 tools schema,不是 Python 函数本身?
tools schema 是给模型看的工具说明书。它告诉模型:
- 工具叫什么
- 工具能做什么
- 工具需要哪些参数
- 参数是什么类型
但它不包含 Python 函数代码。模型看不到 def run_bash(...) 的实现,所以模型不能自己执行函数,只能返回一个工具调用请求。
3. TOOLS schema 和 TOOL_HANDLERS 的区别是什么?
TOOLS schema 是给模型看的工具菜单。
TOOL_HANDLERS 是给 Python 用的工具路由表。
模型返回类似“我要调用 read_file,参数是 {"path": "a.txt"}”的结构化请求。Python 再用工具名去 TOOL_HANDLERS 里找到真正的函数,并执行:
handler = TOOL_HANDLERS.get(block.name)
output = handler(**block.input)
4. 为什么 s03 不能只靠 system prompt 做权限控制?
提示词只是劝模型遵守规则,Python 权限检查才是真正的门禁。
模型可能会请求危险命令,例如:
rm -rf /
如果没有 Python 层的 DENY_LIST 或 check_permission,程序可能真的执行危险操作。
所以安全边界必须放在程序里。模型可以提出工具调用请求,但 Python 必须在执行前判断是否允许。
5. hook 在 s04 的 agent loop 里解决了什么问题?
hook 把“循环过程中额外要做的事”从 agent_loop 主流程里拆出去。
如果没有 hook,每次想增加功能,比如工具调用前记录日志、工具调用后自动处理、停止时保存结果,都要修改 agent_loop。
有了 hook 后,主循环只需要在固定时机触发:
trigger_hooks("PreToolUse", ...)
trigger_hooks("PostToolUse", ...)
trigger_hooks("Stop", ...)
具体执行什么逻辑,由注册进去的 hook 函数决定。
一句话:hook 是主循环关键节点预留给外部功能插入的位置。
6. hook 和 tool 的最大区别是什么?
tool 是模型能看见并请求调用的能力,例如 bash、read_file、todo_write。
hook 是 Python 内部自动触发的扩展点,模型通常看不见,也不会主动调用。
一句话:
tool 给模型用,hook 给程序内部生命周期用。
7. s05 为什么要做 todo_write?
todo_write 用来让模型管理复杂任务的计划和状态。
它的作用不是读文件、写文件、执行命令,而是:
- 把复杂任务拆成待办事项
- 记录每一步的状态
- 让用户和程序看到当前进度
- 防止模型做着做着忘记任务
一句话:
普通工具负责做事,todo_write 负责管理做事的计划。
8. 子 Agent 的详细工具调用会进入主 Agent 的 messages 吗?
不会。
如果主 Agent 调用 task 工具,子 Agent 内部读了 10 个文件、执行了 5 次 bash,这些详细过程只存在子 Agent 自己的临时循环里。
主 Agent 的 messages 里只保存 task 工具返回的最终总结字符串。
这样可以让主 Agent 的上下文更干净,避免被子 Agent 的大量中间过程撑爆。
9. SKILL.md 是怎么变成模型能看到的技能目录的?
完整链路是:
SKILL.md 文件
-> _parse_frontmatter() 提取 name / description
-> _scan_skills() 扫描所有技能并登记
-> 存进 SKILL_REGISTRY
-> list_skills() 把字典转成普通文本目录
-> build_system() 把目录放进 system prompt
-> client.messages.create(system=SYSTEM) 发给模型
模型一开始看到的是轻量技能目录,不是所有完整 SKILL.md。
如果模型需要某个技能,再调用 load_skill(name),Python 才把完整技能内容返回给模型。
10. 为什么不直接把所有 SKILL.md 都塞进 system prompt?
可以直接塞,但成本高,效果也可能变差。
主要问题是:
- 上下文太大
- 浪费 token
- 当前任务可能只需要一个技能
- 无关技能太多会干扰模型判断
- system prompt 变得难维护
所以 s07 使用渐进式披露:
先给模型轻量技能目录
模型判断需要哪个技能
再用 load_skill 读取完整 SKILL.md
11. s08 为什么要做 context compact?
s08 是为了防止 messages 越积越多,工具结果越来越长,最后超过模型上下文限制,导致 API 报 prompt_too_long。
它主要处理:
- 大工具结果
- 老的
tool_result - 中间历史消息
- 过长的对话上下文
目标是在尽量保留有用信息的前提下,把当前上下文压到模型能接受的范围内。
12. 为什么 s08 要先处理大的 tool_result,再做历史总结?
原则是:能便宜处理的先便宜处理,只有不够时才做昂贵且可能丢信息的总结压缩。
s08 的压缩层次大致是:
1. persist_large_output
把特别大的工具结果写到文件,只保留路径和预览
2. micro_compact
把旧的长 tool_result 替换成占位信息
3. snip_compact
裁掉中间一部分历史,只保留开头和最近几轮
4. compact_history
让模型总结旧历史,用摘要替换原始消息
不一开始就总结,是因为总结会丢细节,也要额外调用模型,还可能总结错。
13. s08 compact 和 s09 memory 的区别是什么?
s08 compact 处理当前对话上下文太长的问题。
s09 memory 处理跨会话长期记忆的问题。
更具体地说:
compact 处理 messages
memory 处理 .memory/*.md 文件
compact 的目标:让当前上下文变短
memory 的目标:把用户偏好、项目事实、长期约束保存下来
14. s09 为什么要保存 pre_compress?
因为压缩后的 messages 可能已经丢掉细节。
如果直接从压缩后的内容里提取 memory,可能漏掉重要信息,例如用户偏好、项目事实、薄弱点、长期约束。
所以 s09 会在压缩前临时保存一份更完整的历史:
pre_compress = messages.copy()
然后用这份更完整的 pre_compress 去提取长期记忆。
注意:它不是永久把整段超长对话塞回主上下文,而是在当前 Python 流程里临时使用。
15. s10 相比前面版本最大的变化是什么?
s10 不再只用一个写死的 SYSTEM 字符串,而是根据当前 context 动态组装 system prompt。
它把 prompt 分成多个 section,例如:
identitytoolsworkspacememory
然后根据当前上下文决定哪些 section 要加入最终 system prompt。
一句话:
system prompt = 根据 context 动态拼出来的提示词。
16. 如果 .memory/MEMORY.md 不存在,memory section 会进入 system prompt 吗?
不会。
因为 s10 的逻辑大致是:
memories = context.get("memories")
if memories:
sections.append(memory_section)
在 Python 里,空字符串 ""、None 都会被当成 False。
如果 .memory/MEMORY.md 不存在,update_context() 读不到 memory,context["memories"] 就会是空值,所以 if memories 不成立,memory section 不会加入最终 system prompt。
17. s01 到 s10 是怎么一步步演进的?
整体演进路线是:
s01:基础 Agent Loop 和 bash 工具
s02:多工具系统和 TOOL_HANDLERS 分发
s03:权限检查
s04:hook 解耦扩展逻辑
s05:todo_write 计划管理
s06:task / subagent 分工
s07:skill loading,轻量目录 + 按需加载完整技能
s08:context compact,压缩当前 messages
s09:memory,保存跨会话长期记忆
s10:动态 system prompt,根据 context 拼装提示词
主线是:
从“模型请求一个工具,Python 执行”
逐步扩展成
“安全、可扩展、有计划、可分工、能加载能力、能压缩上下文、能长期记忆、能动态组装提示词”的 Agent 系统。
18. s07 skill loading 和 s09 memory 的区别是什么?
一句话:
skill = 我会什么
memory = 我记得什么
skill loading 加载的是能力说明,告诉模型某种任务应该怎么做、有哪些规则和步骤。
memory 加载的是过去记住的信息,告诉模型用户偏好、项目事实、长期约束等。
19. 每次工具调用结束后自动记录日志,应该用 tool、hook、memory 还是 system prompt?
应该用 hook,具体适合挂在 PostToolUse hook。
因为这个功能不是让模型主动调用的能力,也不是长期记忆,也不是提示词规则,而是每次工具调用结束后 Python 自动执行的一件事。
流程是:
工具执行结束
-> trigger_hooks("PostToolUse", tool_name, result)
-> 日志 hook 自动记录工具名和结果长度
这正是 hook 的典型用途:在 agent loop 的固定节点插入额外程序逻辑,而不修改主循环。