1
00:00:00,100 --> 00:00:06,432
本内容改编自小山学堂《学 AI 产品，从入门到精通》，为二次演绎配音版。

2
00:00:06,382 --> 00:00:07,536
第五集。

3
00:00:07,536 --> 00:00:14,891
上一集讲了子 Agent 的配置怎么合并，这一集往前走两步，看隔离和组织，最后看沙箱。

4
00:00:14,891 --> 00:00:17,680
先抛一个本集最重要的观点。

5
00:00:17,680 --> 00:00:21,490
隔离不是一个等级，而是一组正交的维度。

6
00:00:21,490 --> 00:00:22,908
什么意思。

7
00:00:22,908 --> 00:00:30,492
很多人谈子 Agent 隔离，脑子里想的是一根轴，从弱到强，隔离等级一、二、三。

8
00:00:30,492 --> 00:00:38,906
但 Grok Build 的源码告诉你，至少有四根轴，上下文来源、身份连续性、工作目录、文件改动空间。

9
00:00:38,906 --> 00:00:41,923
它们各自独立，可以任意组合。

10
00:00:41,923 --> 00:00:49,326
一个恢复了上下文的子 Agent，可以复用独立的 worktree，也可以继承普通的当前工作目录。

11
00:00:49,326 --> 00:00:54,963
上下文连续和文件空间隔离，是两件事，必须分开判断。

12
00:00:54,963 --> 00:01:02,127
把这个搞混了，你就会得出错误的结论，比如以为新会话就等于独立文件空间，其实不是。

13
00:01:02,127 --> 00:01:05,204
除了隔离，这一集还要看两件事。

14
00:01:05,204 --> 00:01:10,612
多 Agent 系统是怎么组织的，以及五种沙箱 Profile 的真实边界。

15
00:01:10,612 --> 00:01:12,223
我们开始。

16
00:01:12,364 --> 00:01:14,345
先看上下文来源。

17
00:01:14,295 --> 00:01:18,850
公开枚举叫 ContextSource，只有两个变体，New 和 Resumed。

18
00:01:18,850 --> 00:01:21,482
New 是新会话，不继承历史。

19
00:01:21,482 --> 00:01:27,492
spawn 流程会建立新的 system prompt 和 prompt context，再接收当前任务输入。

20
00:01:27,492 --> 00:01:30,917
重点是，它不代表独立文件空间。

21
00:01:30,917 --> 00:01:34,836
文件空间仍然取决于 isolation 和 cwd。

22
00:01:34,836 --> 00:01:37,384
这是第一个容易搞混的地方。

23
00:01:37,384 --> 00:01:40,857
Resumed 是从已完成的 peer subagent 继续。

24
00:01:40,857 --> 00:01:50,989
源码会复制原始 transcript 和 tool state，模型沿用 source model，而 system prompt 和 prompt context 会根据当前的 AgentDefinition 重新渲染。

25
00:01:50,989 --> 00:01:53,718
这里有个实现补充要说清楚。

26
00:01:53,718 --> 00:01:56,987
公开枚举只有 New 和 Resumed 两个。

27
00:01:56,987 --> 00:02:04,271
shell 内部还有一个叫 InitialContextSource 的 Forked 变体，用于从父会话镜像或摘要上下文。

28
00:02:04,271 --> 00:02:09,187
它属于另一条 bootstrap 分支，不应该被当成公开枚举的成员。

29
00:02:09,187 --> 00:02:12,179
别在复述里把它塞进 ContextSource。

30
00:02:12,179 --> 00:02:16,975
恢复需要定位信息，结构体叫 ResumeSourceData，四类。

31
00:02:16,975 --> 00:02:22,456
身份类，subagent_id、type、persona、model_id。

32
00:02:22,456 --> 00:02:27,083
目录类，child_cwd 和 worktree_path。

33
00:02:27,083 --> 00:02:33,501
会话数据类，child_session_id，用来定位 transcript 和 tool state。

34
00:02:33,501 --> 00:02:39,054
快照类，snapshot_ref，可以在 worktree 被删除之后重建它。

35
00:02:39,054 --> 00:02:41,735
然后是身份校验，三条。

36
00:02:41,735 --> 00:02:45,112
subagent_type 必须与 source 一致。

37
00:02:45,112 --> 00:02:50,593
显式给出的 Persona 必须与 source 一致，未显式给出则沿用 source。

38
00:02:50,593 --> 00:02:53,730
第三条最关键，model 不参与身份 gate。

39
00:02:53,730 --> 00:02:58,285
请求中的 model override 会被软忽略，并且 pin 到 source model。

40
00:02:58,285 --> 00:03:00,196
这句话什么意思。

41
00:03:00,196 --> 00:03:04,812
你恢复一个子 Agent 的时候，不能顺便把它换成别的模型。

42
00:03:04,812 --> 00:03:07,780
想换模型，那是新会话的事。

43
00:03:07,780 --> 00:03:10,449
最后是恢复限制，三条。

44
00:03:10,449 --> 00:03:15,569
复制 transcript 或读取失败时，显式 resume 会失败关闭。

45
00:03:15,569 --> 00:03:21,446
source transcript 超过目标模型上下文窗口百分之八十时，拒绝恢复。

46
00:03:21,446 --> 00:03:26,999
恢复只复制 tool state，不复制 plan state、plan mode state 和 signals。

47
00:03:27,148 --> 00:03:29,561
第二个维度，文件空间。

48
00:03:29,511 --> 00:03:34,884
枚举叫 SubagentIsolationMode，只有两个变体，None 和 Worktree。

49
00:03:34,884 --> 00:03:41,194
None 是默认值，子 Agent 使用解析后的 cwd，通常与父工作区相同。

50
00:03:41,194 --> 00:03:45,906
但要特别注意，上下文窗口依然是独立的子会话。

51
00:03:45,906 --> 00:03:50,665
None 描述的是文件工作空间隔离，不等于共享对话历史。

52
00:03:50,665 --> 00:03:53,225
这是第二个容易搞混的地方。

53
00:03:53,225 --> 00:03:56,987
Worktree 是子 Agent 使用独立的 worktree 路径。

54
00:03:56,987 --> 00:03:59,752
恢复时优先复用 source worktree。

55
00:03:59,752 --> 00:04:06,759
如果路径已经被移除，但存在 snapshot_ref，就可以从持久的 git ref 重新水化。

56
00:04:06,759 --> 00:04:10,160
还要注意，这个枚举没有 sandbox 成员。

57
00:04:10,160 --> 00:04:15,136
沙箱是另一套东西，就是本集最后要讲的五种 Profile。

58
00:04:15,136 --> 00:04:19,872
别把隔离模式和沙箱混为一谈，它们是两层机制。

59
00:04:20,020 --> 00:04:23,022
第三个话题，多 Agent 怎么组织。

60
00:04:22,972 --> 00:04:30,785
多 Agent 系统要同时解决角色定义、spawn、状态查询、取消、完成通知和资源继承。

61
00:04:30,785 --> 00:04:35,520
Grok Build 把这些职责落在两层，定义层和协调事件层。

62
00:04:35,520 --> 00:04:38,441
定义层是 AgentDefinition 加 Persona。

63
00:04:38,441 --> 00:04:45,328
AgentDefinition 给出 prompt、工具、权限、模型、MCP 继承和可 spawn 类型等合同。

64
00:04:45,328 --> 00:04:50,196
Persona 追加行为指令、I/O 契约和部分运行时默认值。

65
00:04:50,196 --> 00:04:55,280
二者合起来，让每个子 Agent 有可观察的身份和能力边界。

66
00:04:55,280 --> 00:05:01,206
解析顺序是，先 type，再 definition，最后 role 和 persona 的运行时配置。

67
00:05:01,206 --> 00:05:04,114
协调层的核心是 SubagentEvent。

68
00:05:04,114 --> 00:05:15,352
start_subagent_coordinator 只启动一次 drain task，每个 Spawn 事件再启动本地异步任务，调用 handle_subagent_request。

69
00:05:15,352 --> 00:05:16,915
事件有六类。

70
00:05:16,915 --> 00:05:21,987
Spawn、Query、Cancel、ListActive、Completions、Outstanding。

71
00:05:21,987 --> 00:05:23,922
并行是怎么来的。

72
00:05:23,922 --> 00:05:30,893
Spawn 事件独立进入 spawn_local，协调器登记 pending、active 和 completed 状态。

73
00:05:30,893 --> 00:05:34,932
并行能力来自异步任务，不受 Persona 数量限制。

74
00:05:34,932 --> 00:05:39,811
这句话很重要，并行度不是由你定义了多少 Persona 决定的。

75
00:05:39,811 --> 00:05:41,434
结果与等待。

76
00:05:41,434 --> 00:05:46,855
Query 可以立即返回快照，也可以注册 block wait slot 并轮询状态。

77
00:05:46,855 --> 00:05:52,468
Completions 会 drain 待通知的完成项，并按 suppress_ids 过滤。

78
00:05:52,468 --> 00:05:54,138
取消与清理。

79
00:05:54,138 --> 00:05:57,576
Cancel 支持按 subagent ID 或 parent prompt ID。

80
00:05:57,576 --> 00:06:02,444
协调器还会淘汰过期的 completed 记录，并记录显式 kill。

81
00:06:02,596 --> 00:06:05,142
然后是组织策略怎么选。

82
00:06:05,092 --> 00:06:09,118
这里做个跨产品对照，但要严格遵守边界。

83
00:06:09,118 --> 00:06:13,601
Grok Build 这边是源码事实，task 加 coordinator 事件。

84
00:06:13,601 --> 00:06:26,810
Claude Code 那边只用公开行为，公开支持自定义 subagents，每个 subagent 可拥有独立上下文、system prompt、工具权限与模型，主会话可自动委派或由用户显式调用。

85
00:06:26,810 --> 00:06:32,856
公开的 Agent Teams 功能描述包含共享任务、成员间消息与独立上下文。

86
00:06:32,856 --> 00:06:40,164
本课不把 Coordinator 或者 Swarm 当成 Claude Code 源码的内部类型，也不推断它的调度器实现。

87
00:06:40,164 --> 00:06:41,931
这条线不能越。

88
00:06:41,931 --> 00:06:43,421
三种策略。

89
00:06:43,421 --> 00:06:48,806
单主会话委派，适合一个 owner 统一拆解、串联依赖并汇总。

90
00:06:48,806 --> 00:06:51,029
两边的机制都能支持。

91
00:06:51,029 --> 00:06:56,354
多成员协作，适合成员需要彼此通信、认领共享任务的工作。

92
00:06:56,354 --> 00:07:00,536
评估时应以公开行为和当前版本限制为准。

93
00:07:00,536 --> 00:07:05,621
角色复用，适合长期重复的 reviewer、explorer、planner。

94
00:07:05,621 --> 00:07:12,532
选择的判据有四个，任务依赖、上下文需求、文件冲突和结果汇总成本。

95
00:07:12,532 --> 00:07:15,873
先解决任务图，再选产品机制。

96
00:07:15,873 --> 00:07:19,671
别反过来，先选了机制再硬套任务。

97
00:07:19,828 --> 00:07:21,376
最后是沙箱。

98
00:07:21,326 --> 00:07:27,071
五种内置 Profile，workspace、devbox、read-only、strict、off。

99
00:07:27,071 --> 00:07:31,458
它们定义文件系统和子进程网络的不同能力集合。

100
00:07:31,458 --> 00:07:36,410
名称只提供方向，真实边界要看解析后的 capability set。

101
00:07:36,410 --> 00:07:38,694
这是本段的核心提醒。

102
00:07:38,694 --> 00:07:39,992
workspace。

103
00:07:39,992 --> 00:07:47,528
全文件系统默认可读，workspace、GROK_HOME、临时目录可写，不限制子进程网络。

104
00:07:47,528 --> 00:07:52,167
default_read 是 true，restrict_network 是 false。

105
00:07:52,167 --> 00:07:53,333
devbox。

106
00:07:53,333 --> 00:08:02,023
全文件系统默认可读，枚举根目录，除 data 目录和虚拟文件系统之外广泛授予写权限，网络不限制。

107
00:08:02,023 --> 00:08:06,999
注意 data 目录保持可读，在 Linux 上通过 bwrap 做写保护。

108
00:08:06,999 --> 00:08:08,165
read-only。

109
00:08:08,165 --> 00:08:15,929
全文件系统默认可读，workspace 不可写，但 GROK_HOME、临时目录和必要设备仍然可写。

110
00:08:15,929 --> 00:08:18,682
restrict_network 是 true。

111
00:08:18,682 --> 00:08:19,800
strict。

112
00:08:19,800 --> 00:08:27,937
关闭全局默认读，只开放系统运行目录和 workspace，workspace、GROK_HOME、临时目录可写。

113
00:08:27,937 --> 00:08:32,528
default_read 是 false，restrict_network 是 true。

114
00:08:32,528 --> 00:08:33,502
off。

115
00:08:33,502 --> 00:08:38,117
跳过 capability set 应用，记录一条沙箱已禁用的日志。

116
00:08:38,117 --> 00:08:39,980
它也接受别名 none。

117
00:08:39,980 --> 00:08:42,877
off 不能作为 custom extends 的基类。

118
00:08:42,877 --> 00:08:45,953
两个最容易误读的点，必须记住。

119
00:08:45,953 --> 00:08:49,728
workspace 仍然允许读取工作区之外的文件。

120
00:08:49,728 --> 00:08:52,408
strict 仍然允许写 workspace。

121
00:08:52,408 --> 00:08:56,062
read-only 也保留了运行所需的最小写目录。

122
00:08:56,062 --> 00:09:01,879
所以课程名称必须服从源码 capability，不能按字面去补全规则。

123
00:09:01,879 --> 00:09:05,990
你看到 read-only 就以为哪儿都写不了，这是错的。

124
00:09:06,148 --> 00:09:09,162
再看 custom profile 和配置边界。

125
00:09:09,112 --> 00:09:11,035
extends 规则四条。

126
00:09:11,035 --> 00:09:13,740
custom 默认从 workspace 开始。

127
00:09:13,740 --> 00:09:17,706
可以 extends workspace、devbox、read-only、strict。

128
00:09:17,706 --> 00:09:20,074
不能 extends off 或者 none。

129
00:09:20,074 --> 00:09:22,898
不能 extends 另一个 custom profile。

130
00:09:22,898 --> 00:09:28,656
read_only、read_write、deny 这三类是追加到基类上的。

131
00:09:28,656 --> 00:09:35,603
需要限制子进程网络时，应该在 custom 中显式设置 restrict_network 为 true。

132
00:09:35,603 --> 00:09:38,560
全局优先保护，这条很妙。

133
00:09:38,560 --> 00:09:45,783
系统先读用户目录下的 grok 配置里的 sandbox.toml，再读项目目录下的同名文件。

134
00:09:45,783 --> 00:09:48,932
项目配置只能新增 profile 名称。

135
00:09:48,932 --> 00:09:57,790
如果项目声明了一个全局已经存在的同名 profile，合并用的是 or_insert 语义，全局定义保持生效。

136
00:09:57,790 --> 00:10:03,079
换句话说，项目不能悄悄削弱全局已经定义好的同名策略。

137
00:10:03,079 --> 00:10:05,338
这是个防降级的设计。

138
00:10:05,338 --> 00:10:12,778
最后是平台机制与降级条件，这段必须讲准确，因为它很容易被写成绝对化的承诺。

139
00:10:12,778 --> 00:10:21,324
文件系统约束方面，启用 enforce 且运行在 Unix 时，nono 会把 capability set 应用到 Landlock 或者 Seatbelt。

140
00:10:21,324 --> 00:10:27,310
macOS 的 deny 使用 Seatbelt 规则，Linux 的子路径 read-deny 还需要 bwrap bind-over。

141
00:10:27,310 --> 00:10:32,370
网络约束方面，主进程网络保持开放以访问模型 API。

142
00:10:32,370 --> 00:10:41,480
restrict_network 当前通过子进程过滤表达，源码中的 seccomp 实现在 Linux 生效，非 Linux 函数为空操作。

143
00:10:41,480 --> 00:10:42,935
降级条件。

144
00:10:42,935 --> 00:10:51,072
如果平台不支持、构建未启用 enforce、或者内核层的 apply 失败，源码会记录警告并继续运行。

145
00:10:51,072 --> 00:10:59,870
但要注意，能力集与 profile 解析这一步是用问号上抛的，失败时 apply 直接返回 Err，不是静默降级。

146
00:10:59,870 --> 00:11:04,761
所以只有 is_active 才能反映是否真的应用上了。

147
00:11:04,761 --> 00:11:08,800
结论是，不能承诺所有环境都无法绕过。

148
00:11:08,800 --> 00:11:12,622
这句话听起来不性感，但它是诚实的。

149
00:11:12,748 --> 00:11:15,294
收尾，列能带走的。

150
00:11:15,244 --> 00:11:18,849
第一，隔离是正交维度，不是一个等级。

151
00:11:18,849 --> 00:11:25,748
上下文来源、身份连续性、工作目录、文件改动空间，四根轴分开判断。

152
00:11:25,748 --> 00:11:31,061
新会话不等于独立文件空间，None 也不等于共享对话历史。

153
00:11:31,061 --> 00:11:33,729
第二，恢复有身份门控。

154
00:11:33,729 --> 00:11:39,282
type 和 Persona 必须一致，model 不参与，会被软忽略并 pin 到 source model。

155
00:11:39,282 --> 00:11:46,169
transcript 超过上下文窗口百分之八十拒绝恢复，复制 tool state 但不复制 plan state。

156
00:11:46,169 --> 00:11:50,797
第三，并行能力来自异步任务，不受 Persona 数量限制。

157
00:11:50,797 --> 00:11:54,258
别以为多定义几个 Persona 就能多并行。

158
00:11:54,258 --> 00:11:58,273
第四，先解决任务图，再选产品机制。

159
00:11:58,273 --> 00:12:03,729
判据是任务依赖、上下文需求、文件冲突、汇总成本。

160
00:12:03,729 --> 00:12:09,510
第五，沙箱 Profile 的名字只提供方向，真实边界看 capability set。

161
00:12:09,510 --> 00:12:15,604
workspace 能读工作区外，strict 能写 workspace，read-only 保留最小写目录。

162
00:12:15,604 --> 00:12:20,424
第六，custom 不能 extends off，也不能 extends 另一个 custom。

163
00:12:20,424 --> 00:12:24,895
项目无法替换全局同名定义，这是防降级设计。

164
00:12:24,895 --> 00:12:29,114
第七，沙箱不能承诺所有环境都无法绕过。

165
00:12:29,114 --> 00:12:36,566
平台不支持、未启用 enforce、apply 失败都会退化，只有 is_active 能反映真实状态。

166
00:12:36,566 --> 00:12:38,357
最后留一个问题。

167
00:12:38,357 --> 00:12:45,075
你的系统里，有没有把多个正交维度压缩成一个枚举或者一个等级字段的地方。

168
00:12:45,075 --> 00:12:50,520
如果有，那大概率是你最难解释清楚、也最难扩展的地方。

169
00:12:50,520 --> 00:12:55,917
把它们拆开，往往会发现之前想不明白的问题突然就清楚了。

170
00:12:55,917 --> 00:12:59,306
本集到此，我们下一集继续往下拆。

