WEBVTT

1
00:00:00.000 --> 00:00:03.258
仪表盘把一个构建显示成 unknown，

2
00:00:03.258 --> 00:00:06.028
它的原始状态是前后带空格的 OK。

3
00:00:06.028 --> 00:00:11.079
约定的规则忽略前后空白和大小写，所以它应该显示 passed；

4
00:00:11.079 --> 00:00:14.826
而一个陌生的状态应该仍然显示 unknown。

5
00:00:15.167 --> 00:00:16.828
审查者建议改模板。

6
00:00:16.828 --> 00:00:20.334
但智能体只能依据送到它面前的内容行动。

7
00:00:20.334 --> 00:00:25.871
所以先找到这个值从哪里开始出错，以及智能体应该检查哪个文件。

8
00:00:26.916 --> 00:00:28.671
沿着请求路径追踪。

9
00:00:28.671 --> 00:00:31.401
加载器原样保留了带空格的值。

10
00:00:31.401 --> 00:00:36.860
状态映射把它转成小写，但空格还在，所以没匹配上键 ok，

11
00:00:36.860 --> 00:00:39.200
返回了 unknown。

12
00:00:39.550 --> 00:00:43.289
所以，第一次和规则不一致的地方在映射这一步。

13
00:00:43.289 --> 00:00:48.559
去检查那里的规范化：在查表之前去掉前后空白，就能修好这个用例。

14
00:00:48.900 --> 00:00:53.191
如果在模板里把所有 unknown 都改成 passed，

15
00:00:53.191 --> 00:00:55.114
一个陌生的状态也会被错标。

16
00:00:55.114 --> 00:00:56.446
两种输入都要检查。

17
00:00:57.482 --> 00:00:59.135
刚才那条路径是示意。

18
00:00:59.135 --> 00:01:02.605
课程的仪表盘里有一条真实的路径：项目列表。

19
00:01:02.605 --> 00:01:05.083
路由函数选出调用者自己的项目。

20
00:01:05.083 --> 00:01:09.214
一个辅助函数对没有会话的访问者返回 401 拒绝。

21
00:01:09.214 --> 00:01:13.841
列表为空时，模板打印 No projects yet.。

22
00:01:14.182 --> 00:01:19.319
路由函数说明了会发生什么；另外两个文件决定访问者实际看到什么。

23
00:01:19.319 --> 00:01:21.971
所以，这条路径的地图是一组说法。

24
00:01:21.971 --> 00:01:26.942
对照你那个版本，把每支箭头标成：已确认、已推翻，或无法判断。

25
00:01:27.982 --> 00:01:31.536
现在就这条路径，问模型一个有边界的问题：

26
00:01:31.536 --> 00:01:34.023
没有登录的访问者会收到什么？

27
00:01:34.023 --> 00:01:37.400
一个已登录但没有项目的用户会看到什么？

28
00:01:37.400 --> 00:01:39.176
各自由哪个文件决定？

29
00:01:39.176 --> 00:01:42.553
上下文里看不出来的，可以填 null。

30
00:01:42.899 --> 00:01:48.104
两个上下文包的字节数相同，各 639 字节，只有内容不同。

31
00:01:48.104 --> 00:01:51.693
上下文包 A 沿着路由所在的文件往下读。

32
00:01:51.693 --> 00:01:54.565
上下文包 B 跟着调用跨文件走：

33
00:01:54.565 --> 00:01:58.155
返回 401 的那一行、路由、辅助函数，

34
00:01:58.155 --> 00:02:00.488
以及模板里列表为空的分支。

35
00:02:00.832 --> 00:02:03.558
先预测每个上下文包的四个答案。

36
00:02:03.558 --> 00:02:09.009
模型、问题、全新的一次调用、各调用一次、检查器，都保持不变。

37
00:02:09.009 --> 00:02:12.825
只改上下文包，也不要一直重跑到它通过为止。

38
00:02:13.864 --> 00:02:18.661
检查器依据仪表盘本身的答案，给每个字段打一个标签：

39
00:02:18.661 --> 00:02:23.267
正确、错误、弃答（明确填了 null），或缺失。

40
00:02:23.267 --> 00:02:27.296
弃答是诚实的；一个自信却错误的文件名更糟。

41
00:02:27.630 --> 00:02:30.078
为了演示检查器怎样读两个答案，

42
00:02:30.078 --> 00:02:33.995
课程团队为每个上下文包手写了一个看似合理的答案，

43
00:02:33.995 --> 00:02:35.464
没有调用任何模型。

44
00:02:35.464 --> 00:02:38.728
上下文包 A 的答案，四项一项也没答对。

45
00:02:38.728 --> 00:02:42.808
上下文包 B 的答案对了三项：路由把列表传了下去，

46
00:02:42.808 --> 00:02:44.767
但决定那行文字的是模板。

47
00:02:45.114 --> 00:02:48.808
这次回放完全说明不了模型多常给出哪一种答案；

48
00:02:48.808 --> 00:02:51.160
你自己的那一对运行才是测量。

49
00:02:51.160 --> 00:02:55.358
打成平手，或者 A 胜过 B，都是正当的观察结果；

50
00:02:55.358 --> 00:02:59.557
一对运行只能引出下一个实验，说明不了更普遍的结论。

51
00:03:00.596 --> 00:03:04.417
在这个练习里，上下文包是我们自己放进提示词的：

52
00:03:04.417 --> 00:03:06.742
没有哪个文件名会自动起作用。

53
00:03:06.742 --> 00:03:11.062
一个存在却从没被加载的上下文文件，解释不了任何结果。

54
00:03:11.062 --> 00:03:14.052
所以要核实实际送到模型面前的是什么。

55
00:03:14.396 --> 00:03:19.512
接着，为第二个问题自己做一个上下文包，最多 400 字节，

56
00:03:19.512 --> 00:03:22.159
每段摘录上方加一行写明文件名。

57
00:03:22.159 --> 00:03:26.923
它的检查只告诉你证据在不在包里，而不是模型会怎么回答。

58
00:03:27.961 --> 00:03:33.144
摘要是你在会话进行到一半、历史已经放不下时选择的上下文。

59
00:03:33.144 --> 00:03:38.512
课程手写了一段很长的会话：一个智能体正在修复这个状态用例，

60
00:03:38.512 --> 00:03:41.659
19 条消息，刚好没超出字节预算。

61
00:03:41.659 --> 00:03:44.250
下一次检查的结果就放不下了。

62
00:03:44.595 --> 00:03:47.661
压缩脚本用四种方式构建下一次请求。

63
00:03:47.661 --> 00:03:50.005
full 保留每一条消息。

64
00:03:50.005 --> 00:03:53.432
auto 保留任务和智能体自己的消息，

65
00:03:53.432 --> 00:03:56.498
把较长的旧工具结果清理成一行占位。

66
00:03:56.498 --> 00:04:00.286
summary 用你的摘要替换较早的消息。

67
00:04:00.286 --> 00:04:05.516
fresh 只保留任务和你的文件：凭一份交接说明重新开始。

68
00:04:05.861 --> 00:04:06.379
然后，

69
00:04:06.379 --> 00:04:11.729
一个课程编写的策略（不是模型）只依据这份上下文挑选下一次修改，

70
00:04:11.729 --> 00:04:13.627
再运行同一个六项检查。

71
00:04:13.627 --> 00:04:17.941
只有当某一行点名 view.py 并说不要改它时，

72
00:04:17.941 --> 00:04:19.667
它才会避开这个文件。

73
00:04:19.667 --> 00:04:23.636
否则，它就会做出我们前面警告过的那种模板补丁。

74
00:04:24.677 --> 00:04:29.165
先预测：auto 会保留哪些事实，下一次修改会动哪个文件。

75
00:04:29.165 --> 00:04:29.939
然后运行。

76
00:04:29.939 --> 00:04:34.273
用完整历史时，下一次修改在状态映射里同时去空白、转小写：

77
00:04:34.273 --> 00:04:35.046
六项全过。

78
00:04:35.046 --> 00:04:39.535
用 auto 时，修改落在 view.py 上，检查失败：

79
00:04:39.535 --> 00:04:42.475
queued 显示成了 passed，

80
00:04:42.475 --> 00:04:44.797
而且 view.py 被改了。

81
00:04:45.143 --> 00:04:48.582
这条规则只出现过一次，夹在一条工具结果中间：

82
00:04:48.582 --> 00:04:51.239
第 2 条消息里读到的贡献者说明。

83
00:04:51.239 --> 00:04:53.740
清理旧的工具输出，把它也清掉了。

84
00:04:53.740 --> 00:04:55.303
上下文里剩下的内容，

85
00:04:55.303 --> 00:04:59.367
没有任何一处和这次对 view.py 的修改相矛盾；

86
00:04:59.367 --> 00:05:02.024
所以只看上下文，很难发现丢了东西。

87
00:05:02.360 --> 00:05:05.166
现在生成一份摘要，用同样的方式运行。

88
00:05:05.166 --> 00:05:08.750
在我们的运行里，只写着“不要改模板”的那一份，

89
00:05:08.750 --> 00:05:10.777
和 auto 一样失败了。

90
00:05:10.777 --> 00:05:15.297
写明了 view.py 以及这条规则出处的那一份，通过了，

91
00:05:15.297 --> 00:05:17.635
无论作为摘要还是作为重新开始。

92
00:05:17.635 --> 00:05:19.972
只有保住这条规则，它才能通过。

93
00:05:20.310 --> 00:05:22.952
但检查通过，并不是验证的全部。

94
00:05:22.952 --> 00:05:27.002
每种方式都保留了任务，而这个策略只读两样事实。

95
00:05:27.002 --> 00:05:28.939
一份摘要可以通过检查，

96
00:05:28.939 --> 00:05:32.990
却仍然漏掉“对状态文件的哪一次读取已经过时”。

97
00:05:32.990 --> 00:05:35.984
所以，还要对照完整记录把清单填完。

98
00:05:36.327 --> 00:05:39.243
如果最新的结果仍然重要，就压缩。

99
00:05:39.243 --> 00:05:44.346
如果大部分历史已经过时或自相矛盾，或者一个阶段已经结束，

100
00:05:44.346 --> 00:05:45.440
就重新开始。

101
00:05:45.440 --> 00:05:49.632
无论哪种，都要重新读一遍这次修改会涉及的文件。

102
00:05:49.632 --> 00:05:53.277
这个对比就是活动 5：选做练习，不计分。

103
00:05:54.326 --> 00:05:59.186
技能把一套流程打包起来：一个 SKILL.md 文件，

104
00:05:59.186 --> 00:06:01.167
包含名字、描述和正文。

105
00:06:01.167 --> 00:06:06.207
智能体只把名字和描述放在眼前，当一个请求看起来需要它时，

106
00:06:06.207 --> 00:06:07.287
才加载正文。

107
00:06:07.287 --> 00:06:10.708
所以，描述决定了这个技能会不会被用上。

108
00:06:11.043 --> 00:06:14.452
只写“什么时候用”的描述，会漏掉换了说法的请求；

109
00:06:14.452 --> 00:06:18.429
只写“做什么”的描述，可能对每个和标签有关的问题都触发。

110
00:06:18.429 --> 00:06:22.407
所以要检验触发条件：用人们的说法写三个应该加载它的请求，

111
00:06:22.407 --> 00:06:25.674
再写一个险些命中的，比如“重命名标签那一列”。

112
00:06:25.674 --> 00:06:27.947
观察之前，先写下对每一个的预测。

113
00:06:28.293 --> 00:06:32.025
险些命中的请求却触发了，说明描述太宽。

114
00:06:32.025 --> 00:06:35.562
漏掉的请求，通常和描述没有共同的词。

115
00:06:35.562 --> 00:06:38.902
上下文文件和技能都不授予任何权限。

116
00:06:38.902 --> 00:06:42.045
把你的技能在课程仪表盘上用一次，

117
00:06:42.045 --> 00:06:45.582
再以只读方式在你的候选项目上用一次。

118
00:06:45.582 --> 00:06:48.529
一个没人跑过的技能，只是草稿。

119
00:06:49.575 --> 00:06:50.965
工具也是上下文。

120
00:06:50.965 --> 00:06:55.480
无论是通过 MCP 这样的协议，还是普通的函数调用，

121
00:06:55.480 --> 00:06:59.648
智能体看到的都是每个工具的名字、描述和输入模式，

122
00:06:59.648 --> 00:07:01.559
并把结果当作数据来读。

123
00:07:01.559 --> 00:07:06.595
它只凭这些文字来挑选工具，所以要为一个会去猜的读者来设计。

124
00:07:06.942 --> 00:07:09.976
五条规则把这一点变成可以检查的东西。

125
00:07:09.976 --> 00:07:11.999
把每一个限制都写进模式。

126
00:07:11.999 --> 00:07:14.022
只返回下一步需要的内容。

127
00:07:14.022 --> 00:07:16.720
把失败报告成智能体读得懂的结果。

128
00:07:16.720 --> 00:07:19.923
说明会返回什么，以及每个输入从哪里来。

129
00:07:19.923 --> 00:07:23.126
还有，给每一个不可撤销的操作加上防护。

130
00:07:23.475 --> 00:07:28.365
讲义里的初稿，一个搜索工具、一个删除工具，看起来已经完成了。

131
00:07:28.365 --> 00:07:30.646
但读一遍不算验证，调用才算。

132
00:07:30.646 --> 00:07:35.047
在我们的运行里，初稿只返回名字，没有可以往下传的编号。

133
00:07:35.047 --> 00:07:37.981
一个不带参数的删除调用，直接就删了。

134
00:07:37.981 --> 00:07:41.077
遇到未知的编号，它直接崩溃，打出回溯。

135
00:07:41.077 --> 00:07:44.826
上限五十也被接受了：这个上限只写在说明文字里。

136
00:07:45.175 --> 00:07:49.736
修订版返回标签编号；除非关掉试运行，删除只是预览；

137
00:07:49.736 --> 00:07:53.567
返回一个“找不到”的错误，并说明重试没用；

138
00:07:53.567 --> 00:07:56.303
还会由模式拒绝超出范围的上限。

139
00:07:56.303 --> 00:07:59.222
每一行都是一个你可以重跑的检查。

140
00:07:59.222 --> 00:08:03.600
新的规则需要一次新的调用，而不是一段更长的描述。

141
00:08:04.641 --> 00:08:08.671
现在把同一个请求交给分别连着两个服务器的智能体：

142
00:08:08.671 --> 00:08:12.869
删除 red-team，而旧工单给出的编号是错的。

143
00:08:12.869 --> 00:08:17.403
回放里的智能体是一个固定的、课程编写的策略，不是模型。

144
00:08:17.403 --> 00:08:21.098
在初稿上，它碰到回溯，找不到编号，于是停下。

145
00:08:21.098 --> 00:08:25.632
在修订版上，它读到“找不到”，去搜索，先预览，再删除。

146
00:08:25.974 --> 00:08:30.483
如果那个旧编号属于另一个标签，初稿会立刻把它删掉。

147
00:08:30.483 --> 00:08:34.992
修订版只做预览，于是智能体看到名字不对，转去搜索。

148
00:08:34.992 --> 00:08:39.140
制动并没有让智能体变聪明；它把一次错误的调用，

149
00:08:39.140 --> 00:08:41.485
变成了智能体读得懂的结果。

150
00:08:41.824 --> 00:08:44.834
有三层机制防止一次错误的调用造成伤害。

151
00:08:44.834 --> 00:08:49.269
制动让一次省略了 dry_run 的调用不产生任何影响。

152
00:08:49.269 --> 00:08:52.120
调用前的钩子在效果发生之前检查事件。

153
00:08:52.120 --> 00:08:56.872
这两者都约束不了一个会修改或无视它们的进程；安全启动器可以。

154
00:08:56.872 --> 00:09:00.040
一个事后才记日志的钩子，什么也没有阻止。

155
00:09:01.074 --> 00:09:03.884
所以，把上下文当作一种干预。

156
00:09:03.884 --> 00:09:10.106
只改一样东西，其余保持不变，并且把你的预测和观察到的结果分开。

157
00:09:10.440 --> 00:09:11.701
试一个问题。

158
00:09:11.701 --> 00:09:17.375
上下文包 B 的答案，要在第二次调用补上模板文件之后，

159
00:09:17.375 --> 00:09:18.636
才四项全对。

160
00:09:18.636 --> 00:09:21.999
这个差别能归因于 B 的内容吗？

161
00:09:21.999 --> 00:09:27.043
先写下预期的行为和接下来的检查，再和同伴对答案。
