前置:先读完09-claude-code/README.md。本 lab 与 README 同基线(官方文档访问于 2026-08-30)。 Key 说明:Lab 1–2 完全不需要任何 API key / 订阅;Lab 3–8 需要能运行 Claude Code 的账号(Claude 订阅或ANTHROPIC_API_KEY)。每一步都用 ⌨(无 key)或 🔑(需 key)标注。 Scratch 目录约定:所有改动都在仓库外的 scratch 目录做(~/lab09-scratch),不要修改本仓库的examples/文件——它们是教学标本。本 lab 不写任何密钥进文件。
🎯 学习重点:通过故障循环证明每次工具 observation 会改变下一步,而最大步数是最后兜底。
目标:亲眼看到 gather context → take action → verify results 的循环如何被 harness 驱动;体会「verify 永远失败 → 循环失控」这一 agent 系统的核心故障模式。
🎯 学习重点:识别 gather、action、verify 轨迹和 verify 持续失败后的停止条件。
mkdir -p ~/lab09-scratch && cd ~/lab09-scratch
cp <仓库路径>/09-claude-code/examples/agent_loop_demo.py .
python3 agent_loop_demo.py
预期结果:你会看到 step 1 grep(gather context)、step 2 edit(take action)、step 3 read、随后 bash pytest -q(verify results)反复执行,最终打到 [max_steps] 上限,测试始终 1 failed。
已知问题(编写本 lab 时实测):FakeRepo中两个文件内容的字符串组带了尾逗号(如'exp': 1}\n",),Python 把它们变成了单元素 tuple 而不是拼接字符串。于是Edit的old string not found永远成立 → verify 永远失败 → 循环只能靠max_steps兜底。这恰好演示了真实 agent 的两类风险:工具结果与预期不符时的死循环,以及步数上限/人工升级这类停止条件的价值。来源: 官方 how-claude-code-works(https://code.claude.com/docs/en/how-claude-code-works,访问于 2026-08-30)
🎯 学习重点:把源码中的策略、上下文追加与权限模拟逐一映射到 harness 责任。
打开 agent_loop_demo.py,回答(写进你的记录):
scripted_policy 里哪几行对应 gather context / take action / verify results?run_agent 中哪一行是「把工具结果喂回上下文」?(提示:context.append(...))🎯 学习重点:比较修复前后唯一变化,理解可靠工具反馈如何让循环收敛。
在 scratch 副本里删掉那两个尾逗号(tokens.py 与 tests/test_auth.py 内容的最后一个字符串字面量后),重新运行:
预期结果:工具调用轨迹收敛为 Grep → Edit → Read → Bash 四步,结尾出现 [stop] 测试通过,任务完成(verify 通过 -> 交付)。
思考:修复前后唯一的区别是一次成功的 Edit。用一句话总结:agent loop 的「下一步」由什么决定?(答:模型/策略读到的上下文,即每次工具调用的返回。)
🎯 学习重点:把 JSON、frontmatter、密钥与最小权限检查转化为可自动执行的配置门禁。
目标:复用 examples/validate_configs.py,练习「配置即代码」的审查思维:JSON 合法性、frontmatter 必备字段、密钥不入库、最小权限。
🎯 学习重点:先识别脚本的目录假设,再区分真实配置缺陷与扫描器自引用误报。
cd ~/lab09-scratch
cp <仓库路径>/09-claude-code/examples/validate_configs.py .
cp <仓库路径>/09-claude-code/examples/config-examples/{settings-example.json,mcp-example.json,rules-example.md,subagent-example.md,skill-example-SKILL.md} .
python3 validate_configs.py; echo "exit=$?"
已知问题 1(实测):脚本用HERE = Path(__file__).parent在自身所在目录找配置文件;而仓库里配置在examples/config-examples/子目录。所以在 examples/ 下直接跑会FileNotFoundError。上面先 co-locate(复制到同一目录)就是在不改脚本的前提下满足它的假设——这也是读脚本再运行的意义。
预期结果:co-locate 后仍有 3 项失败(exit=1):
| 失败项 | 次数 | 根因 |
|---|---|---|
mcp: token 通过 ${ENV_VAR} 引用 | 1 | mcp-example.json 的 Authorization 值是残缺占位符 "***}",不含美元符+花括号形式 |
validate_configs.py: 疑似真实密钥 (xoxb-…) | 2 | 校验器扫描整个目录时扫到了自己的源码——SECRET_PATTERNS 里的 xoxb- 字面量被自己命中(自引用误报) |
已知问题 2(实测):这两类失败意味着该脚本在当前仓库布局下不可能「全绿」。它们是前任 subagent 交付时的环境性缺陷(占位符疑似被写入管道破坏),已上报协调者;本 lab 把它们变成练习。
🎯 学习重点:修复占位符与误报时保持密钥不入库,并以 exit=0 作为机器验收标准。
mcp-example.json 中 internal-lookup 的 Authorization 值改为环境变量引用形式——美元符 + 花括号包住变量名(INTERNAL_API_TOKEN),可带 Bearer 前缀。原则:token 永远走环境变量/密管,绝不入库(README 9.11)。validate_configs.py 里 re.compile(r"xoxb-") 的模式改成字符类写法 xox[b]-——语义不变(仍匹配 xoxb-),但源码字面量不再命中自己。体会 secret 扫描器的 false positive 从哪来。exit=0、全部 PASS、结尾「全部通过 ✔」(实测 73 项 PASS)。🎯 学习重点:主动制造宽权限、空 deny、缺字段和假密钥,验证门禁确实会失败。
在 scratch 副本里依次制造故障,观察校验器行为:
settings-example.json 的 deny 改成空数组 → 应 FAIL(默认拒绝思维)。Bash(*) 全放行 → 应 FAIL(对照 README 9.15 的 Bad 案例)。subagent-example.md frontmatter 的 tools: 行 → 应 FAIL(subagent 的工具约束是它的核心价值)。sk-ant- 开头随机串 → 应 FAIL(密钥扫描)。思考:为什么「allow 白名单 + 非空 deny + 占位符检查」值得做成 CI 步骤?(答:把权限/安全约定从「口头规范」变成「机器门禁」——这正是 hooks 与配置审查在架构里的位置。)
🎯 学习重点:完成安装、项目初始化、记忆和权限检查的最小可重复启动流程。
🎯 学习重点:核对安装渠道和版本,为后续实验留下可复现的运行基线。
curl -fsSL https://claude.ai/install.sh | bash # macOS/Linux/WSL;其他渠道见 README 9.1
claude --version
🎯 学习重点:用/init、/memory、/permissions建立项目上下文与权限基线。
cd ~/lab09-scratch/my-project && git init # 建一个玩具项目
claude # 首次启动按提示登录
进入会话后依次执行(README 9.8 的「首次进仓三板斧」):
/init —— 生成初始 CLAUDE.md,观察它写了什么;/memory —— 打开记忆文件编辑;/permissions —— 查看当前权限规则。🎯 学习重点:区分会话批准、本地持久规则与团队共享配置的落盘位置。
让它跑一条会改动文件的指令(如「创建 hello.txt 写入一句话」)。预期:Manual/default 模式下弹出批准提示;选择保存规则后,用 /permissions 或查看 .claude/settings.local.json 确认规则落盘。
思考:批准提示保存的规则进了哪个文件、会不会被提交进 git?(答:settings.local.json,默认个人本地,不入库;团队共享应放 .claude/settings.json。)
🎯 学习重点:用实际触边验证 deny 优先、plan 只读、受保护路径与隔离环境边界。
.claude/settings.json(可从 examples/config-examples/settings-example.json 改造)加入 deny 规则(如拒绝 rm -rf 类命令与 secrets/ 目录读取),再让 Claude 尝试触边。预期:即便你手动想放行,deny 也在所有模式下生效(README 9.6)。/plan),让它「规划一个重构」。预期:只读探索 + 输出计划,不改文件;探索由只读 Plan subagent 承担(README 9.5)。bypassPermissions 只在不可逃逸的隔离环境里试,体会 README 9.16 的 tradeoff。记录:每种模式「免询问范围」一行 → 对照 README 9.6 的表格修正你的记录。
🎯 学习重点:把常驻事实、路径规则和多步流程放入各自最省上下文的载体。
/init 产物压到 ≤200 行、只留事实(构建命令、架构红线);把一段多步流程(如部署清单)抽出去。examples/config-examples/rules-example.md,给 src/api/** 建带 paths: 的规则文件;让 Claude 读一个匹配路径的文件,观察规则自动加载(对照 README 9.4)。.claude/skills/deploy/SKILL.md(参考 examples/config-examples/skill-example-SKILL.md),用 /deploy 调用。/context 查看——预期:只有 skill 的一行描述常驻,正文在你调用时才载入(README 9.8/9.13 的「长参考资料几乎零成本」)。思考:同一份「API 参考文档」放 CLAUDE.md、rules、skill 三处,各付多少上下文成本?(答:CLAUDE.md 每会话全付;rules 仅在路径匹配时付;skill 只有描述常驻、正文用时才付。)
🎯 学习重点:证明 PreToolUse 能在模型决定之外强制阻断危险工具参数。
🎯 学习重点:用退出码和 decision 输出验证同一 guard 对危险与安全命令的分流。
cd ~/lab09-scratch
cp <仓库路径>/09-claude-code/examples/config-examples/pretooluse_guard.py .
echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /tmp/x"}}' | python3 pretooluse_guard.py; echo "exit=$?"
echo '{"tool_name":"Bash","tool_input":{"command":"ls -la"}}' | python3 pretooluse_guard.py; echo "exit=$?"
预期:第一条输出 decision: block 且 exit=2(阻止工具调用);第二条 exit=0 放行。来源: hooks reference(https://code.claude.com/docs/en/hooks,访问于 2026-08-30)
🎯 学习重点:把独立脚本挂入生命周期,确认真实会话中的阻断点位于工具执行前。
把 guard 挂进项目 .claude/settings.json 的 hooks.PreToolUse(matcher 限 Bash;路径按你项目布局写)。在会话里诱导一条 rm -rf 命令:预期无论模型多「想」执行,hook 层直接拒绝——这就是「LLM 指令 ≠ 应用层强制」的最小实证(README 9.9;模块 11 伏笔)。
🎯 学习重点:根据动作风险选择故障时放行或拒绝,并设计可用性恢复路径。
pretooluse_guard.py 在 JSON 解析失败时 return 0(fail-open)。在 scratch 副本里改成 fail-closed(解析失败即 block),讨论:生产里安全 hook 应该选哪种?什么场景允许 fail-open?(提示:可用性 vs 安全的权衡,对照 README 9.16。)
🎯 学习重点:用/context对照证明 subagent 从源头隔离大输出,而/compact是有损事后压缩。
examples/config-examples/subagent-example.md 放进玩具项目的 .claude/agents/code-explorer.md。/clear 后再问一次、但要求委派给 code-explorer。两次结束后各跑一次 /context。预期:主会话直问 → 上下文被文件内容占满;走 subagent → 主上下文只保留摘要(README 9.10/9.13)。
/compact,用 /context 对比前后;注意「skill 描述列表不会被重新注入,只有实际用过的 skill 保留」(README 9.13)。思考:/compact 与 subagent 都治「上下文满」,副作用有何不同?(答:compact 有损压缩历史细节;subagent 从一开始就不让细节进主上下文,但结果只剩摘要。)
🎯 学习重点:验证 tool search 的按需 schema,并审查共享配置、认证材料和外部内容注入风险。
claude mcp add --transport http notion https://mcp.notion.com/mcp
claude mcp list
/context 确认上下文里只有工具名列表、schema 按需加载(README 9.11 的 deferred/tool search)。.mcp.json(可基于 examples/config-examples/mcp-example.json,token 用环境变量占位),讨论:为什么官方警告「会抓取外部内容的服务器可能带来 prompt injection 风险,连接前必须信任该服务器」?(对照模块 11。)🎯 学习重点:以可演示证据而非「做过」为完成标准,逐项核对八个核心能力。
| # | 完成标准 | Lab |
|---|---|---|
| 1 | 能复述 agent loop 三阶段并指出 demo 中各自对应的工具调用 | 1 |
| 2 | validate_configs 在 scratch 修复后 exit=0,且能解释 3 处失败根因 | 2 |
| 3 | 能说出批准规则落在哪个文件、团队共享应放哪 | 3–4 |
| 4 | 能演示 deny 规则在所有模式下生效 | 4 |
| 5 | CLAUDE.md ≤200 行 + 至少 1 个带 paths 的 rule + 1 个 skill,并用 /context 验证按需加载 | 5 |
| 6 | PreToolUse guard 手动测试 exit=2,并接入会话生效 | 6 |
| 7 | /context 前后对比证明 subagent 省主上下文 | 7 |
| 8 | 至少接入 1 个 MCP server 并说出注入风险 | 8 |
🎯 学习重点:记录预期与实际差异,形成可复查的架构结论而不是只保留命令历史。
Lab N | 日期 | key: 无/有
做了什么:
预期 vs 实际:
意外发现:
一句话结论(架构师视角):
🎯 学习重点:实验行为变化时回到现行官方文档核验,避免沿用旧教程术语。