Harness · Coding Agent Book03 / Grok Build
研究总览
M03 · SOURCE-GROUNDED TUTORIAL

Grok Build
从源码学会它怎么工作

Actor 化运行时、内核沙箱和插件能力包组合先进,但沙箱失败时会降级继续。 我们不把 README 当结论,而是沿主循环、工具、上下文、权限、扩展、协作和状态一路读到实现。

Rust · Actor + Secure Worktree AgentApache-2.002d9359435d017 个结论 · 42 处引用
这门课怎么读

先建立直觉,再沿一条任务链钻进代码

参考教程的做法不是把 API 名称罗列出来,而是从一个小白能理解的问题开始,先解释“为什么需要这个机制”,再用概念对比、执行链路和固定提交的源码回答“它究竟怎么做”。本页把 Grok Build 的 17 个源码结论重新编排成十节课;每个结论都保留证据等级、文件路径、行号和可点击源码。

你会得到一张可复述的架构地图一次完整任务的链路追踪能迁移到自研 Harness 的设计判断
你不会得到把 README 功能当成已验证事实把 prompt 约束说成 OS 沙箱把一次双模型调用夸成多 Agent 平台
M00 · MAP

先看全景:这个 Agent 的控制面在哪里

下面的图不是产品宣传图,而是把固定提交里最关键的入口、循环、模型、工具、安全、状态和协作节点放在一张地图上。

架构图全屏打开 ↗
一轮执行链路全屏打开 ↗
核心机制

长期 SessionActor;prepare → parallel dispatch → post-flight

上下文

预热、两阶段压缩、任务状态重建与恢复梯子

适用建设

长任务、工作树隔离、可组合研发插件

M00.5 · TRACE

跟踪一个任务:从输入到交付

把下面九步当成你读源码时的“地图坐标”。每到一个节点,都能回到后面的章节查具体实现。

读图提醒

箭头只表示控制面之间的关系,不代表每个实现都同步、串行或拥有 OS 隔离;真正的边界要以对应章节的源码摘录和 caveat 为准。

M01 · ORIENTATION

先把 Agent 看成一台会交付的机器

如果只看 README,你知道它能做什么;钻进源码后,我们要知道它为什么能做、什么时候会停、失败后谁负责收拾。

先用一个生活比喻

把 Agent 想成一间带传送带的工作室:入口收任务,主循环决定下一步,模型负责提出动作,工具负责动手,状态账本负责让下一班人接着干。

本节阅读法先问问题读事实看代码做迁移判断
读源码时先问本课的判断方式
这一层有没有独立证据?没有就不把其他层的能力冒充成默认行为;回到固定提交继续追调用链。
谁拥有最终控制权?区分模型输出、框架规则、用户审批和 OS 隔离四种不同力量。
这一章先建立通用概念

固定提交账本没有把该维度单独拆出,但它会在其他章节的源码路径中体现。先沿执行链路阅读,再回到报告页核对证据。

小练习 1

打开本节任意一个源码摘录,先遮住白话解释,只根据函数名、状态字段和调用顺序猜它解决什么问题;再展开证据列表,检查你的猜测有没有越过源码边界。

M02 · LOOP

主循环:模型为什么会继续动

一次模型调用为什么会变成十几步?循环靠什么继续,靠什么停止?

先用一个生活比喻

像一个会看回执的快递员:模型先写行动单,工具返回回执,主循环把回执放回桌面,再让模型决定下一张行动单。

这套实现先回答了什么?

它不像一个简单 while 循环,更像一间控制室:用户输入、工具结果、文件变化、后台任务、模型切换都从不同通道进来,由同一个会话 Actor 排队处理。

本节阅读法先问问题读事实看代码做迁移判断
源码问题固定提交给出的线索白话结论
SessionActor 是事件驱动的长期存活 Actorcrates/codegen/xai-grok-shell/src/session/acp_session_impl/run_loop.rs:120它不像一个简单 while 循环,更像一间控制室:用户输入、工具结果、文件变化、后台任务、模型切换都从不同通道进来,由同一个会话 Actor 排队处理。
01
L1 · fact · grok-loop-001

SessionActor 是事件驱动的长期存活 Actor

先看源码事实

run_session 同时监听命令、chat-state 事件、内部事件、模型切换、任务完成和多个定时器;启动时还建立文件监听、MCP liveness dispatcher 与 replay buffer。

翻译成白话

它不像一个简单 while 循环,更像一间控制室:用户输入、工具结果、文件变化、后台任务、模型切换都从不同通道进来,由同一个会话 Actor 排队处理。

为什么这对自研重要

这套 Harness 面向长会话、后台工作和 IDE/ACP 集成,控制面复杂度显著高于纯 CLI Agent。

固定提交源码摘录
  120  pub(super) async fn run_session(
  121      session: Arc<SessionActor>,
  122      mut cmd_rx: mpsc::UnboundedReceiver<SessionCommand>,
  123      mut chat_state_event_rx: mpsc::UnboundedReceiver<xai_chat_state::ChatStateEvent>,
  124      mut event_rx: mpsc::UnboundedReceiver<SessionEvent>,
  125      fs_notify_config: Option<ClientFsConfig>,
  126      codebase_indexes: std::sync::Arc<parking_lot::Mutex<CodebaseIndexManager>>,
  127      index_root: std::path::PathBuf,
  128      fs_watch_caps: fs_watch::FsWatchCapabilities,
  129  ) {
  130      let (completion_tx, mut completion_rx) =
  131          mpsc::unbounded_channel::<(String, PromptTurnResult)>();
  132      tracing::debug!("fs_notify_config: {:?}", fs_notify_config);
  133      let mut replay_buffer = ReplayBuffer::new(session.buffering_settings.clone());
  134      let event_tx_for_flush_timer = session.event_tx.clone();
  135      let buffering_flush_interval = replay_buffer.max_wait_duration_ms();
  136      if let Some(buffering_flush_interval) = buffering_flush_interval {
  137          tokio::task::spawn_local(async move {
  138              let mut interval = tokio::time::interval(Duration::from_millis(std::cmp::max(
  139                  20,
  140                  buffering_flush_interval * 2,
  141              )));
  142              loop {
  143                  interval.tick().await;
  144                  let _ =
      … 28 lines omitted; exact range 120–183 …
  173              index_root.clone(),
  174          );
  175          tracing::debug!(?fs_watch_caps, "fs-notify: spawning");
  176          Some(fs_watch::spawn(fs_watch::FsWatchPlan::build(
  177              fs_watch_caps,
  178              deps,
  179          )))
  180      } else {
  181          tracing::debug!("fs-notify: skipped (no consumers)");
  182          None
  183      };
为什么相信这条结论?查看 3 处证据
小练习 2

打开本节任意一个源码摘录,先遮住白话解释,只根据函数名、状态字段和调用顺序猜它解决什么问题;再展开证据列表,检查你的猜测有没有越过源码边界。

M03 · MODEL

模型调用:流式输出如何变成可执行步骤

模型输出的文字、思考、工具调用和错误,经过哪些转换才进入 Agent 状态?

先用一个生活比喻

模型像电话另一端的同事:你听到的不是一整段录音,而是一串实时片段;Harness 要边听边拼装,还要能在电话断线时留下可恢复的记录。

本节阅读法先问问题读事实看代码做迁移判断
读源码时先问本课的判断方式
这一层有没有独立证据?没有就不把其他层的能力冒充成默认行为;回到固定提交继续追调用链。
谁拥有最终控制权?区分模型输出、框架规则、用户审批和 OS 隔离四种不同力量。
这一章先建立通用概念

固定提交账本没有把该维度单独拆出,但它会在其他章节的源码路径中体现。先沿执行链路阅读,再回到报告页核对证据。

小练习 3

打开本节任意一个源码摘录,先遮住白话解释,只根据函数名、状态字段和调用顺序猜它解决什么问题;再展开证据列表,检查你的猜测有没有越过源码边界。

M04 · TOOLS

工具系统:Agent 的手脚怎样被注册和调度

模型看见的工具说明,和真正执行工具的代码,是不是同一个东西?并发、编辑和失败结果怎么处理?

先用一个生活比喻

工具系统像机场:模型提交登机牌,注册表确认航班,权限闸机检查证件,调度器决定跑道,最后才允许真正起飞。

这套实现先回答了什么?

能并行的尽量并行,但两个工具若同时写同一个文件会排队,避免互相覆盖。

本节阅读法先问问题读事实看代码做迁移判断
源码问题固定提交给出的线索白话结论
工具执行明确拆成 prepare、并发 dispatch、post-flightcrates/codegen/xai-grok-shell/src/session/acp_session_impl/tool_calls.rs:355能并行的尽量并行,但两个工具若同时写同一个文件会排队,避免互相覆盖。
工具参数对模型瑕疵有恢复层crates/codegen/xai-grok-shell/src/session/acp_session_impl/tool_calls.rs:878模型偶尔把两个 JSON 粘在一起,Grok Build 会先抢救,不是一看到格式错就整轮失败。
一个插件可同时交付 Skills、Commands、Agents、Hooks、MCP、LSPcrates/codegen/xai-grok-agent/src/plugins/manifest.rs:103插件不是只加一个工具,而是可以连同说明书、快捷命令、子 Agent 角色、策略 hook、远程工具和语言服务一起打包。
02
L1 · fact · grok-tools-001

工具执行明确拆成 prepare、并发 dispatch、post-flight

先看源码事实

execute_tool_calls_batch 先逐个解析、运行 Plan gate、hooks 和权限;批准后用 FuturesUnordered 并发执行,写同一路径的工具共享 Mutex 串行;完成后按原 slot 做事件和结果处理。

翻译成白话

能并行的尽量并行,但两个工具若同时写同一个文件会排队,避免互相覆盖。

为什么这对自研重要

并发策略不是简单 all-at-once,而是带资源锁的调度。

固定提交源码摘录
  355      /// Prepare → dispatch → post-flight. Caller owns the outer tail flush.
  356      async fn execute_tool_calls_batch(
  357          &self,
  358          tool_calls: Vec<crate::sampling::types::ToolCallResponse>,
  359          deferred_followups: &mut Vec<ConversationItem>,
  360          final_result: &mut Option<ToolLoop>,
  361      ) -> Result<(), acp::Error> {
  362          let mut approved: Vec<PreparedToolCall> = Vec::new();
  363          for call in tool_calls.into_iter() {
  364              if final_result.is_some() {
  365                  let message = match &*final_result {
  366                      Some(ToolLoop::PermissionReject { .. }) => {
  367                          format!(
  368                              "Tool execution cancelled due to earlier permission rejection for tool `{}`",
  369                              call.function.name
  370                          )
  371                      }
  372                      Some(ToolLoop::Cancelled) => {
  373                          format!(
  374                              "Tool execution cancelled due to earlier user cancellation for tool `{}`",
  375                              call.function.name
  376                          )
  377                      }
  378                      Some(ToolLoop::FollowupMessage(_)) => {
  379                          format!(
      … 59 lines omitted; exact range 355–449 …
  439                          tool_loop,
  440                          ToolLoop::PermissionReject { .. }
  441                              | ToolLoop::Cancelled
  442                              | ToolLoop::FollowupMessage(_)
  443                      ) && final_result.is_none()
  444                      {
  445                          *final_result = Some(tool_loop);
  446                      }
  447                  }
  448              }
  449          }
为什么相信这条结论?查看 2 处证据
03
L1 · fact · grok-tools-002

工具参数对模型瑕疵有恢复层

先看源码事实

prepare_tool_call 会规范空参数、尝试解析 JSON;遇到拼接的多个 JSON 对象会逐个试配工具 schema,选出匹配对象;仍无法解析才产生 ToolParsingError。

翻译成白话

模型偶尔把两个 JSON 粘在一起,Grok Build 会先抢救,不是一看到格式错就整轮失败。

为什么这对自研重要

对模型输出做保守修复能提高成功率,但必须记录恢复行为,避免悄悄改变语义。

固定提交源码摘录
  878          let args_str = crate::session::helpers::tool_input_parsing::normalize_empty_arguments(
  879              &call.function.arguments,
  880          );
  881          let parse_result = serde_json::from_str::<serde_json::Value>(args_str);
  882          let mut concatenated_json_count: usize = 0;
  883          let raw_input = match &parse_result {
  884              Ok(value) => value.clone(),
  885              Err(e) => {
  886                  if let Some(objects) = crate::session::helpers::tool_input_parsing::try_extract_concatenated_json_objects(
  887                      &call.function.arguments,
  888                  ) {
  889                      let total_count = objects.len();
  890                      if objects.is_empty() {
  891                          json!({ "raw": call.function.arguments.clone() })
  892                      } else {
  893                          let best_match = objects[0].clone();
  894                          let mut selected_index = 0;
  895                          let mut matched_tool = false;
  896                          let bridge = self.agent.borrow().tool_bridge().clone();
  897                          for (idx, obj) in objects.iter().enumerate() {
  898                              if bridge
  899                                  .try_parse(&call.function.name, obj.clone())
  900                                  .await
  901                                  .is_ok()
  902                              {
      … 37 lines omitted; exact range 878–950 …
  940                  self.handle_tool_parse_error(
  941                      &tool_call_id,
  942                      &call.id,
  943                      &call.function.name,
  944                      err,
  945                      &call.function.arguments,
  946                      &model_id_str,
  947                  )
  948                  .await?;
  949                  return Ok(Err(ToolLoop::ToolParsingError));
  950              }
为什么相信这条结论?查看 1 处证据
04
L2 · fact · grok-plugin-001

一个插件可同时交付 Skills、Commands、Agents、Hooks、MCP、LSP

先看源码事实

PluginManifest 明确定义六类组件;所有路径必须位于 canonical plugin root。PluginRegistry 只把 enabled 且 trusted 的插件作为 active,项目插件需要显式信任,disabled 冲突优先。

翻译成白话

插件不是只加一个工具,而是可以连同说明书、快捷命令、子 Agent 角色、策略 hook、远程工具和语言服务一起打包。

为什么这对自研重要

生态能力完整,但插件供应链和信任 UI 变成核心安全面。

固定提交源码摘录
  103      match field {
  104          Some(PathOrInline::Path(p)) => {
  105              let resolved = plugin_root.join(p);
  106              if !is_path_contained(&resolved, plugin_root) {
  107                  tracing::warn!(
  108                      path = %resolved.display(),
  109                      plugin_root = %plugin_root.display(),
  110                      "{label} path escapes plugin root; skipping"
  111                  );
  112                  return None;
  113              }
  114              resolved.is_file().then_some(resolved)
  115          }
  116          Some(PathOrInline::Inline(_)) => None,
  117          None => {
  118              let default = plugin_root.join(default_file);
  119              default.is_file().then_some(default)
  120          }
  121      }
  122  }
  123  
  124  /// A value that can be either a file path (string) or an inline JSON object.
  125  #[derive(Debug, Clone, Deserialize)]
  126  #[serde(untagged)]
  127  pub enum PathOrInline {
      … 32 lines omitted; exact range 103–170 …
  160      #[serde(default)]
  161      pub commands: Option<PathOrPaths>,
  162      #[serde(default)]
  163      pub agents: Option<PathOrPaths>,
  164      #[serde(default)]
  165      pub hooks: Option<PathOrInline>,
  166      #[serde(default)]
  167      pub mcp_servers: Option<PathOrInline>,
  168      #[serde(default)]
  169      pub lsp_servers: Option<PathOrInline>,
  170  }
为什么相信这条结论?查看 4 处证据
小练习 4

打开本节任意一个源码摘录,先遮住白话解释,只根据函数名、状态字段和调用顺序猜它解决什么问题;再展开证据列表,检查你的猜测有没有越过源码边界。

M05 · CONTEXT

上下文:有限窗口怎样装下长任务

当对话、工具输出、计划和记忆越来越多,系统怎样决定留下什么、折叠什么、放到哪里?

先用一个生活比喻

上下文不是聊天记录,而是一张会整理的工作台:常用零件放桌面,旧材料装进档案盒,必要时只留下索引卡。

这套实现先回答了什么?

不是等窗口爆了才临时总结:它会提前准备摘要草稿,到红线时再完成第二遍;若摘要输入也太大,就逐级减料。

本节阅读法先问问题读事实看代码做迁移判断
源码问题固定提交给出的线索白话结论
压缩是一条带预热、两阶段和恢复梯子的子系统crates/codegen/xai-grok-shell/src/session/compaction.rs:3不是等窗口爆了才临时总结:它会提前准备摘要草稿,到红线时再完成第二遍;若摘要输入也太大,就逐级减料。
压缩后重建的是“任务状态”,不是纯聊天摘要crates/codegen/xai-grok-shell/src/session/compaction.rs:1282总结完以后还会把“哪些子任务在跑、待办是什么、插件有哪些、当前计划阶段”重新装回去,避免只剩一段模糊回忆。
05
L1 · fact · grok-context-001

压缩是一条带预热、两阶段和恢复梯子的子系统

先看源码事实

会话在阈值前若干百分点预先生成 pass-1;真正压缩支持 two-pass/full-replace,输入溢出时逐级缩小 verbatim/normal conversation,记录多类拒绝原因,并在完成后自动继续。

翻译成白话

不是等窗口爆了才临时总结:它会提前准备摘要草稿,到红线时再完成第二遍;若摘要输入也太大,就逐级减料。

为什么这对自研重要

长任务连续性被当作独立可靠性系统,而非一个 prompt helper。

固定提交源码摘录
    3  //! This module contains all compaction-related methods: manual `/compact`,
    4  //! auto-compact threshold checks, inline auto-compact with auto-continue,
    5  //! error-recovery compaction, preflight overflow detection, and checkpoint
    6  //! persistence. These methods form a second `impl SessionActor` block that
    7  //! lives alongside the primary one in `acp_session.rs`.
    8  use super::SessionActor;
    9  use super::is_project_instructions;
   10  use crate::remote::DEFAULT_CONTEXT_WINDOW;
   11  use crate::session::compaction_config::{
   12      AsyncCompactionCache, SUPPRESS_AUTH, SUPPRESS_NONE, SUPPRESS_STICKY, SUPPRESS_TURN,
   13      SUPPRESS_UNTIL_SUCCESS,
   14  };
   15  use crate::session::helpers::CompactionStateContext;
   16  use crate::session::helpers::compaction_context::CompactionInputs;
   17  use crate::session::helpers::compaction_context::to_system_reminder;
   18  use crate::session::helpers::session_compact::{
   19      CompactOutput, CompactionOutcome, build_compaction_chat_history,
   20      build_two_pass_compaction_prompt, generate_session_compact, is_context_length_error,
   21  };
   22  use crate::session::persistence::PersistenceMsg;
   23  use crate::session::two_pass::{
   24      TWO_PASS_DEFAULT_SPLIT_FRACTION, build_two_pass_pass1_history, build_two_pass_pass2_history,
   25      note_for_two_pass_pass2, split_conversation_for_two_pass,
   26  };
   27  use agent_client_protocol as acp;
   28  use std::sync::Arc;
   29  use xai_chat_state::compaction_utils::{
   30      CompactedHistoryInput, CompactionAttempt, build_compacted_history, is_degenerate_summary,
   31      prepare_conversation_for_verbatim_summarization, sanitize_compacted_history,
   32      validate_compacted_history,
   33  };
   34  use xai_grok_sampling_types::{ApiBackend, ConversationItem};
   35  /// Default percentage points below the auto-compact threshold at which prefire
为什么相信这条结论?查看 3 处证据
06
L1 · fact · grok-context-002

压缩后重建的是“任务状态”,不是纯聊天摘要

先看源码事实

压缩会把 AGENTS.md、skills、编辑路径、运行中子 Agent、MCP server、Todo、Plan mode 和 memory 状态重新注入;清理孤儿 ToolResult,验证失败则退到无 recent_messages 的最小历史,并持久化 segment/checkpoint。

翻译成白话

总结完以后还会把“哪些子任务在跑、待办是什么、插件有哪些、当前计划阶段”重新装回去,避免只剩一段模糊回忆。

为什么这对自研重要

可靠 compaction 必须有结构化状态源,不能只依赖模型自由文本。

固定提交源码摘录
 1282          let generate_session_compact = compact_output.content.clone();
 1283          let user_message_prefix = self.build_user_message_prefix().await;
 1284          let conversation = self.chat_state_handle.get_conversation().await;
 1285          let (discovered_agents_md, all_skills_for_compaction, _agent_edited_paths, state_context) =
 1286              if use_short_prompt {
 1287                  let empty_edited: std::collections::BTreeSet<String> = Default::default();
 1288                  let ctx =
 1289                      CompactionStateContext::build(&conversation, CompactionInputs::default()).await;
 1290                  (Vec::<std::path::PathBuf>::new(), vec![], empty_edited, ctx)
 1291              } else {
 1292                  let agents_md: Vec<std::path::PathBuf> = self
 1293                      .agent
 1294                      .borrow()
 1295                      .tool_bridge()
 1296                      .agents_md_reminded_paths()
 1297                      .await
 1298                      .into_iter()
 1299                      .collect();
 1300                  let bridge_for_skills = self.agent.borrow().tool_bridge().clone();
 1301                  let skills = bridge_for_skills.slash_skills().await;
 1302                  let edited_paths = self.chat_state_handle.get_agent_edited_paths().await;
 1303                  let ctx = {
 1304                      let bridge_tasks = self
 1305                          .agent
 1306                          .borrow()
      … 143 lines omitted; exact range 1282–1460 …
 1450                              session_id = %self.session_info.id.0,
 1451                              poll_resolved = poll.is_some(),
 1452                              cancel_resolved = cancel.is_some(),
 1453                              "could not resolve subagent tool names, \
 1454                               omitting subagent reminder from compacted conversation"
 1455                          );
 1456                          None
 1457                      }
 1458                  }
 1459              };
 1460          use crate::session::helpers::compaction_context::McpToolNames;
为什么相信这条结论?查看 2 处证据
小练习 5

打开本节任意一个源码摘录,先遮住白话解释,只根据函数名、状态字段和调用顺序猜它解决什么问题;再展开证据列表,检查你的猜测有没有越过源码边界。

M06 · SECURITY

权限与沙箱:能做什么,在哪里做

审批按钮、规则引擎、容器和操作系统沙箱分别解决什么问题?为什么“问过用户”不等于“隔离了风险”?

先用一个生活比喻

审批像门卫问你有没有预约,沙箱像把访客关在指定房间;前者决定是否放行,后者限制放行后能摸到什么。

这套实现先回答了什么?

“全部自动批准”也不等于“计划阶段可以乱改代码”。计划模式另有一把更早、更硬的锁。

本节阅读法先问问题读事实看代码做迁移判断
源码问题固定提交给出的线索白话结论
Plan Mode 的只读约束独立于 Always Approvecrates/codegen/xai-grok-shell/src/session/acp_session_impl/tool_calls.rs:157“全部自动批准”也不等于“计划阶段可以乱改代码”。计划模式另有一把更早、更硬的锁。
权限判断理解访问类型和会话上下文crates/codegen/xai-grok-shell/src/session/acp_session_impl/tool_calls.rs:1035它不是只看工具名,而是知道“这是读哪个路径、改哪个文件、跑什么命令、访问哪个网站”,自动模式还会参考最近几轮对话。
PreToolUse Hook 可在权限前阻断crates/codegen/xai-grok-shell/src/session/acp_session_impl/tool_calls.rs:977企业策略脚本可以比用户批准更早说“不”,避免用户误点同意覆盖组织规则。
提供真正的内核级文件系统沙箱crates/codegen/xai-grok-sandbox/src/lib.rs:8这不只是“执行前问一下”,操作系统内核会真的挡住不允许的文件访问。
07
L1 · fact · grok-plan-001

Plan Mode 的只读约束独立于 Always Approve

先看源码事实

plan_mode_edit_gate 在权限管理器之前执行,即使 YOLO/always-approve 也不能绕过;普通编辑只允许计划文件,apply_patch 因无法预知目标而保守拒绝,未知退出结果默认取消。

翻译成白话

“全部自动批准”也不等于“计划阶段可以乱改代码”。计划模式另有一把更早、更硬的锁。

为什么这对自研重要

工作流阶段约束不能依赖通用权限模式,否则高权限模式会破坏阶段不变量。

固定提交源码摘录
  157  /// Verdict for a tool call evaluated against the plan-mode edit gate.
  158  #[derive(Debug, Clone, Copy, PartialEq, Eq)]
  159  pub(super) enum PlanEditGate {
  160      /// Execute normally (plan mode inactive, not an edit, or allowed target).
  161      Allow,
  162      /// Grok-toolset edit outside the plan file (plan-file-only rule).
  163      RejectNonPlanFile,
  164  }
  165  /// Gate edit-class tool calls while plan mode is active.
  166  ///
  167  /// Plan mode is read-only **in every permission mode, including
  168  /// always-approve**: the permission manager's YOLO fast path deliberately
  169  /// knows nothing about plan mode, so this gate — not the permission system —
  170  /// is what enforces it. Two rules, matching the two toolsets' contracts:
  171  ///
  172  /// - **Compat-toolset `Write`/`StrReplace`**: any markdown
  173  ///   file is editable in plan mode (plan docs are written with these
  174  ///   same tools); everything else is rejected. Pre-existing behavior.
  175  /// - **Compat-toolset `Delete`** is **not** on the markdown carve-out: it maps to
  176  ///   `AccessKind::Edit` and is plan-file-only (same as grok edits). Deleting
  177  ///   an arbitrary `.md` in plan mode must not pass.
  178  /// - **Every other edit tool** (`AccessKind::Edit`) is restricted to the plan
  179  ///   file itself, via the same predicate that auto-approves plan-file edits
  180  ///   ([`PlanModeTracker::should_auto_approve_edit`]) so the gate and the
  181  ///   permission bypass can never disagree.
      … 13 lines omitted; exact range 157–205 …
  195      if !tracker.is_active() {
  196          return PlanEditGate::Allow;
  197      }
  198      let _ = tool_input;
  199      match access_kind {
  200          AccessKind::Edit(path) if !tracker.should_auto_approve_edit(Path::new(path)) => {
  201              PlanEditGate::RejectNonPlanFile
  202          }
  203          _ => PlanEditGate::Allow,
  204      }
  205  }
为什么相信这条结论?查看 2 处证据
08
L1 · fact · grok-permission-001

权限判断理解访问类型和会话上下文

先看源码事实

工具被分类为 Read、Edit、Bash、Grep、MCP、Web;Ask/Auto/Always Approve 三种模式分别走交互、带最近对话的分类器或快速放行,决定来源与等待时间进入 trace 和 telemetry。

翻译成白话

它不是只看工具名,而是知道“这是读哪个路径、改哪个文件、跑什么命令、访问哪个网站”,自动模式还会参考最近几轮对话。

为什么这对自研重要

权限系统既是策略引擎,也是可观测的决策服务。

固定提交源码摘录
 1035          let plan_file_auto_approve = if let AccessKind::Edit(ref path) = access_kind {
 1036              self.plan_mode
 1037                  .lock()
 1038                  .should_auto_approve_edit(std::path::Path::new(path))
 1039          } else {
 1040              false
 1041          };
 1042          if plan_file_auto_approve {
 1043              tracing::info_span!(
 1044                  "tool.decision",
 1045                  tool_name = %call.function.name,
 1046                  tool_use_id = %call.id,
 1047                  decision = "allow",
 1048                  source = "config",
 1049                  wait_ms = 0_i64,
 1050              )
 1051              .in_scope(|| {});
 1052          }
 1053          if !plan_file_auto_approve {
 1054              let (perm_title, perm_kind, perm_raw_input) = tool_call_display
 1055                  .as_ref()
 1056                  .map(|(t, k, r)| (Some(t.clone()), Some(*k), Some(r.clone())))
 1057                  .unwrap_or((None, None, None));
 1058              let tool_call_update = acp::ToolCallUpdate::new(
 1059                  tool_call_id.clone(),
      … 32 lines omitted; exact range 1035–1102 …
 1092                  Some(self.session_id_string())
 1093              } else {
 1094                  None
 1095              };
 1096              let perm_mode = if self.permissions.is_yolo_mode() {
 1097                  xai_grok_telemetry::enums::PermissionMode::AlwaysApprove
 1098              } else if self.permissions.is_auto_mode() {
 1099                  xai_grok_telemetry::enums::PermissionMode::Auto
 1100              } else {
 1101                  xai_grok_telemetry::enums::PermissionMode::Ask
 1102              };
为什么相信这条结论?查看 2 处证据
09
L1 · fact · grok-hooks-001

PreToolUse Hook 可在权限前阻断

先看源码事实

工具解析和 Plan gate 后,运行服务端/客户端 PreToolUse hooks;任一 hook 的 Deny 会产生 HookDenied,不再进入权限请求和 dispatch。

翻译成白话

企业策略脚本可以比用户批准更早说“不”,避免用户误点同意覆盖组织规则。

为什么这对自研重要

Hook 是治理层而不仅是通知回调。

固定提交源码摘录
  977          let dispatch_target_name = tool_input.dispatch_target_name();
  978          let resolved_tool_name = dispatch_target_name
  979              .clone()
  980              .unwrap_or_else(|| call.function.name.clone());
  981          if self.hook_event_active(xai_grok_hooks::event::HookEventName::PreToolUse) {
  982              let (hook_tool_input, hook_tool_input_truncated) =
  983                  xai_grok_hooks::event::truncate_payload(raw_input.clone());
  984              let envelope = self.make_hook_envelope(
  985                  xai_grok_hooks::event::HookEventName::PreToolUse,
  986                  None,
  987                  xai_grok_hooks::event::HookPayload::PreToolUse {
  988                      tool_name: resolved_tool_name.clone(),
  989                      tool_use_id: call.id.clone(),
  990                      tool_input: hook_tool_input,
  991                      tool_input_truncated: hook_tool_input_truncated,
  992                      subagent_type: self.subagent_type_label(),
  993                  },
  994              );
  995              let hook_registry_snapshot = self.hook_registry.borrow().clone();
  996              if let Some(registry) = hook_registry_snapshot {
  997                  let ctx = self.hook_run_ctx();
  998                  let pre_result =
  999                      xai_grok_hooks::dispatcher::dispatch_pre_tool_use(&registry, &envelope, &ctx)
 1000                          .await;
 1001                  self.send_hook_execution(
      … 22 lines omitted; exact range 977–1034 …
 1024                          )
 1025                          .await?));
 1026                  }
 1027              }
 1028              if let Some(denied) = self
 1029                  .run_pre_tool_use_client_hook(&call, &tool_call_id, &envelope)
 1030                  .await?
 1031              {
 1032                  return Ok(Err(denied));
 1033              }
 1034          }
为什么相信这条结论?查看 1 处证据
10
L1 · fact · grok-sandbox-001

提供真正的内核级文件系统沙箱

先看源码事实

默认 enforce feature 在 Unix 使用 nono,映射到 Landlock/Seatbelt;Profile capability set 控制全盘读、显式只读/读写、deny,应用后不可逆并覆盖进程内文件 API 与子进程。

翻译成白话

这不只是“执行前问一下”,操作系统内核会真的挡住不允许的文件访问。

为什么这对自研重要

在这 14 个项目里,Grok Build 的安全边界属于更强的系统级设计。

固定提交源码摘录
    8  //! OS-level sandboxing for Grok Build via [nono](https://crates.io/crates/nono).
    9  //!
   10  //! Applied once at process startup. Covers in-process `tokio::fs` calls
   11  //! and child processes. Network is left open at the process level (agent
   12  //! needs LLM API); child network is blocked per-subprocess via seccomp.
   13  //!
   14  //! The `enforce` feature (on by default) pulls in `nono` for
   15  //! kernel-enforced sandboxing (Landlock/Seatbelt). When disabled, the
   16  //! crate still provides lightweight helpers (`log_violation`,
   17  //! `should_restrict_child_network`, `child_net`) that compile on all
   18  //! targets including musl.
为什么相信这条结论?查看 3 处证据
11
L1 · limitation · grok-sandbox-002

沙箱不支持或应用失败时会降级继续

先看源码事实

若平台不支持、apply 失败或构建关闭 enforce feature,SandboxManager 记录警告和 apply_failed 事件后返回 Ok,进程继续运行;is_active 可用于区分实际生效与仅请求。

翻译成白话

配置了沙箱不等于一定锁住;系统不支持时默认是“报警后继续”,不是整个 Agent 拒绝启动。

为什么这对自研重要

企业部署必须把“sandbox requested”和“sandbox active”分开做启动门禁;需要 fail-closed 时应由外层策略强制。

固定提交源码摘录
  107  /// The non-`off` sandbox profile this process was **requested** with, if any.
  108  ///
  109  /// This is the configured request, not a report that enforcement succeeded —
  110  /// `is_active()` can be false while the process is still confined (e.g. some
  111  /// Linux bwrap paths), and a requested-but-unapplied profile already warns the
  112  /// user. Keying on the request is the fail-closed choice.
  113  pub fn requested_confinement_profile() -> Option<&'static str> {
  114      configured_profile_name().filter(|name| profile_confines(name))
  115  }
  116  fn profile_confines(name: &str) -> bool {
  117      name.parse::<ProfileName>()
  118          .is_ok_and(|profile| profile != ProfileName::Off)
  119  }
  120  /// Whether the sandbox was successfully applied to this process.
  121  pub fn is_active() -> bool {
  122      SANDBOX.get().is_some_and(|s| s.applied)
  123  }
  124  /// The active sandbox profile name, or `None` if sandbox is not applied.
  125  pub fn profile_name() -> Option<&'static str> {
  126      SANDBOX
  127          .get()
  128          .filter(|s| s.applied)
  129          .map(|s| s.profile.as_str())
为什么相信这条结论?查看 2 处证据
12
L1 · fact · grok-sandbox-003

子进程网络隔离与主进程网络分离

先看源码事实

主进程保留网络供 LLM API;ReadOnly/Strict profile 在已生效的 Linux 沙箱中,对已知子进程启动路径安装 seccomp 网络过滤,并额外锁定 namespace 逃逸 syscall。

翻译成白话

Agent 自己可以连模型服务,但它启动的 bash 不一定能上网,减少 curl 下载执行或数据外传风险。

为什么这对自研重要

网络隔离需要按进程角色拆分;完全断主进程网络会破坏云模型调用。

固定提交源码摘录
    8  //! OS-level sandboxing for Grok Build via [nono](https://crates.io/crates/nono).
    9  //!
   10  //! Applied once at process startup. Covers in-process `tokio::fs` calls
   11  //! and child processes. Network is left open at the process level (agent
   12  //! needs LLM API); child network is blocked per-subprocess via seccomp.
   13  //!
   14  //! The `enforce` feature (on by default) pulls in `nono` for
   15  //! kernel-enforced sandboxing (Landlock/Seatbelt). When disabled, the
   16  //! crate still provides lightweight helpers (`log_violation`,
   17  //! `should_restrict_child_network`, `child_net`) that compile on all
   18  //! targets including musl.
为什么相信这条结论?查看 3 处证据
13
L1 · fact · grok-sandbox-004

项目不能覆写同名全局安全 Profile

先看源码事实

全局 ~/.grok/sandbox.toml 先加载;项目 .grok/sandbox.toml 只能新增自定义 profile,若名称已存在则忽略项目定义。

翻译成白话

恶意仓库不能悄悄做一个同名“strict”配置,把你信任的全局规则掏空。

为什么这对自研重要

配置合并本身也是信任边界,不能简单 last-write-wins。

固定提交源码摘录
  113  /// Load sandbox config from `~/.grok/sandbox.toml` and `.grok/sandbox.toml`.
  114  ///
  115  /// Project config may **add** new profile names only. It cannot redefine a
  116  /// name already present in the global config — last-write-wins would let a
  117  /// malicious workspace hollow out a user/enterprise custom profile (e.g.
  118  /// empty `deny` / broad `read_write`) while keeping the trusted name.
  119  pub fn load_sandbox_config(workspace: &Path) -> SandboxConfig {
  120      let mut config = SandboxConfig::default();
  121  
  122      // Global config: ~/.grok/sandbox.toml
  123      let global_path = grok_home().join("sandbox.toml");
  124      if let Some(global) = load_config_file(&global_path) {
  125          config = global;
  126      }
  127  
  128      // Project config: <workspace>/.grok/sandbox.toml (additive only)
  129      let project_path = workspace.join(".grok").join("sandbox.toml");
  130      if let Some(project) = load_config_file(&project_path) {
  131          merge_project_profiles(&mut config, project);
  132      }
  133  
  134      config
  135  }
  136  
  137  pub fn sandbox_profile_conflicts(workspace: &Path) -> Vec<String> {
      … 19 lines omitted; exact range 113–167 …
  157      names.sort_unstable();
  158      names
  159  }
  160  
  161  /// Merge project profiles into `config`. Names already defined globally are
  162  /// ignored so a workspace cannot replace a global custom profile's policy.
  163  fn merge_project_profiles(config: &mut SandboxConfig, project: SandboxConfig) {
  164      for (name, profile) in project.profiles {
  165          config.profiles.entry(name).or_insert(profile);
  166      }
  167  }
为什么相信这条结论?查看 1 处证据
小练习 6

打开本节任意一个源码摘录,先遮住白话解释,只根据函数名、状态字段和调用顺序猜它解决什么问题;再展开证据列表,检查你的猜测有没有越过源码边界。

M07 · ECOSYSTEM

指令、MCP、Skills 与插件:能力如何接进来

一条系统指令、一个 Skill、一个 MCP server 和一个插件,分别在什么时候进入上下文和执行路径?

先用一个生活比喻

这像给工作室接设备:说明书不是设备,设备也不等于电源;成熟 Harness 会分别治理发现、信任、加载、调用和卸载。

这套实现先回答了什么?

它能读懂多个编辑器生态留下的项目规则,并按“越靠近当前目录越具体”叠加。

本节阅读法先问问题读事实看代码做迁移判断
源码问题固定提交给出的线索白话结论
项目指令跨 Grok、Claude、Cursor 生态发现crates/codegen/xai-grok-agent/src/prompt/agents_md.rs:1它能读懂多个编辑器生态留下的项目规则,并按“越靠近当前目录越具体”叠加。
PromptContext 是可序列化、可检查的一等契约crates/codegen/xai-grok-agent/src/prompt/context.rs:80系统提示词的输入不是散落变量,而是一张可以导出检查的配置表。
14
L1 · fact · grok-instructions-001

项目指令跨 Grok、Claude、Cursor 生态发现

先看源码事实

从 home、仓库根到 cwd 分层发现 AGENTS/Claude 指令与 .grok/.claude/.cursor rules,按兼容开关和 gitignore 过滤、canonical path 去重;更深目录排在后面。

翻译成白话

它能读懂多个编辑器生态留下的项目规则,并按“越靠近当前目录越具体”叠加。

为什么这对自研重要

兼容性高,但必须在报告中展示来源和优先级,避免用户不知道哪条规则生效。

固定提交源码摘录
    1  //! AGENTS.md / Claude.md / rules directory discovery and loading.
    2  //!
    3  //! Searches from cwd to repo root, plus `~/.grok/`. Also discovers
    4  //! `*.md` files in rules directories: vendor-prefixed `.grok/rules/`,
    5  //! `.claude/rules/`, and `.cursor/rules/` in project directories, and a
    6  //! plain `rules/` directly under the vendor-qualified home-scope roots
    7  //! (`~/.grok/rules/`, `~/.claude/rules/`, `~/.cursor/rules/`).
为什么相信这条结论?查看 2 处证据
15
L2 · fact · grok-prompt-001

PromptContext 是可序列化、可检查的一等契约

先看源码事实

PromptContext 保存版本、模式、受众、模板、指令文件、memory、角色、OS/shell/cwd/date 与交互模式;Primary/Subagent 选择不同基础模板,但子 Agent 仍拿到完整 AGENTS.md。

翻译成白话

系统提示词的输入不是散落变量,而是一张可以导出检查的配置表。

为什么这对自研重要

Prompt provenance 和可重放性明显优于只在运行时拼字符串。

固定提交源码摘录
   80  /// Agent-specific inputs for system prompt rendering.
   81  ///
   82  /// Serializable (JSON/YAML) so users can dump it and inspect fields.
   83  /// Rendering goes through `ToolBridge::render_prompt()`.
   84  #[derive(Debug, Clone, Serialize, Deserialize)]
   85  pub struct PromptContext {
   86      /// Schema version for forward-compatible persistence.
   87      pub version: u32,
   88      /// Which prompt mode produced this context.
   89      pub prompt_mode: PromptMode,
   90      /// Whether this is a primary (parent) or subagent (child) session.
   91      /// Controls base template choice and catalog section rendering.
   92      #[serde(default)]
   93      pub audience: PromptAudience,
   94      /// Custom body: appended after base template (Extend) or the entire
   95      /// prompt (Full). `None` = base template only.
   96      #[serde(skip_serializing_if = "Option::is_none")]
   97      pub prompt_body: Option<String>,
   98      /// Which base template to use for `Extend` mode.
   99      /// `TemplateOverride::None` = standard base/subagent template.
  100      /// `TemplateOverride::Codex` = apply-patch profile template (decrypted on demand).
  101      /// `TemplateOverride::Custom` = caller-provided template string.
  102      #[serde(default, skip_serializing_if = "is_template_override_none")]
  103      pub system_prompt: TemplateOverride,
  104      /// AGENTS.md files discovered during build, in precedence order
      … 37 lines omitted; exact range 80–152 …
  142      #[serde(default, skip_serializing_if = "Option::is_none")]
  143      pub current_date: Option<String>,
  144      /// Whether the agent is running in a non-interactive (headless / SDK /
  145      /// stdio / generic-ACP).
  146      #[serde(default)]
  147      pub is_non_interactive: bool,
  148      /// Identity in the primary grok-build system prompt (`You are <label>…`).
  149      /// Not the UI picker name. Defaults to [`DEFAULT_SYSTEM_PROMPT_LABEL`].
  150      #[serde(default = "default_system_prompt_label")]
  151      pub system_prompt_label: String,
  152  }
为什么相信这条结论?查看 3 处证据
小练习 7

打开本节任意一个源码摘录,先遮住白话解释,只根据函数名、状态字段和调用顺序猜它解决什么问题;再展开证据列表,检查你的猜测有没有越过源码边界。

M08 · COLLABORATION

子 Agent:把一个大任务拆成可治理的协作

什么时候是普通工具调用,什么时候才算子 Agent?子 Agent 的上下文、预算、取消和结果怎样回到父 Agent?

先用一个生活比喻

不是把同事叫来聊天就叫协作;真正的协作要有工单、权限、截止时间、交付物和回收机制。

这套实现先回答了什么?

它是真正的多 Agent 调度:子任务可以后台跑、以后接着跑、在独立分支目录里改代码,但不能无限生孩子。

本节阅读法先问问题读事实看代码做迁移判断
源码问题固定提交给出的线索白话结论
子 Agent 支持后台执行、恢复、深度限制与 worktree 隔离crates/codegen/xai-grok-shell/src/session/acp_session_impl/tool_calls.rs:811它是真正的多 Agent 调度:子任务可以后台跑、以后接着跑、在独立分支目录里改代码,但不能无限生孩子。
16
L1 · fact · grok-subagent-001

子 Agent 支持后台执行、恢复、深度限制与 worktree 隔离

先看源码事实

task/spawn_subagent 默认后台运行;启动前校验类型白名单和 toggle,能力取父子交集;可选创建独立 Git worktree,持久化子会话并从 snapshot 恢复;达到 max depth 后移除子 Agent 的 task 工具。

翻译成白话

它是真正的多 Agent 调度:子任务可以后台跑、以后接着跑、在独立分支目录里改代码,但不能无限生孩子。

为什么这对自研重要

并行协作能力强,同时引入 worktree 生命周期、结果合并、成本与递归治理的高复杂度。

固定提交源码摘录
  811          {
  812              let _span = tracing::info_span!("tool.register").entered();
  813              let early_raw_input =
  814                  serde_json::from_str::<serde_json::Value>(&call.function.arguments).ok();
  815              let subagent_background = matches!(
  816                  call.function.name.as_str(),
  817                  "task" | "Task" | "spawn_subagent"
  818              )
  819              .then(|| {
  820                  early_raw_input
  821                      .as_ref()
  822                      .and_then(|v| v.get("run_in_background").or_else(|| v.get("background")))
  823                      .and_then(serde_json::Value::as_bool)
  824                      .unwrap_or(true)
  825              });
  826              let mut meta = self.stamp_tool_meta(None, &call.function.name, None);
  827              if let Some(bg) = subagent_background {
  828                  meta.get_or_insert_with(serde_json::Map::new).insert(
  829                      "subagentBackground".to_string(),
  830                      serde_json::Value::Bool(bg),
  831                  );
  832              }
  833              self.send_update(
  834                  acp::SessionUpdate::ToolCall(
  835                      acp::ToolCall::new(tool_call_id.clone(), call.function.name.clone())
  836                          .kind(acp::ToolKind::Other)
  837                          .status(acp::ToolCallStatus::Pending)
  838                          .raw_input(early_raw_input)
  839                          .meta(meta),
为什么相信这条结论?查看 4 处证据
小练习 8

打开本节任意一个源码摘录,先遮住白话解释,只根据函数名、状态字段和调用顺序猜它解决什么问题;再展开证据列表,检查你的猜测有没有越过源码边界。

M09 · STATE

会话、持久化与观测:让一次运行变成可追溯事实

如果进程崩了、用户刷新了、任务跑了一夜,系统凭什么恢复并解释“刚才究竟发生了什么”?

先用一个生活比喻

内存像白板,数据库像目录,append-only journal 像监控录像;可靠 Harness 不只保存最后答案,还保存每次转弯。

这套实现先回答了什么?

不仅能看到“失败了”,还能回答失败在哪个关、谁批准的、等了多久、压缩试了几次、沙箱挡了什么。

本节阅读法先问问题读事实看代码做迁移判断
源码问题固定提交给出的线索白话结论
工具、权限、压缩和沙箱均产出结构化事件crates/codegen/xai-grok-shell/src/session/acp_session_impl/tool_calls.rs:392不仅能看到“失败了”,还能回答失败在哪个关、谁批准的、等了多久、压缩试了几次、沙箱挡了什么。
17
L1 · fact · grok-observe-001

工具、权限、压缩和沙箱均产出结构化事件

先看源码事实

工具开始/完成进入 ACP 与统一 SessionEvent,权限记录模式、来源、等待时间,压缩 span 记录 token/尝试/拒绝/TTFT,沙箱事件立即追加 ~/.grok/sandbox-events.jsonl。

翻译成白话

不仅能看到“失败了”,还能回答失败在哪个关、谁批准的、等了多久、压缩试了几次、沙箱挡了什么。

为什么这对自研重要

具备平台级诊断基础,但事件面很大,需要统一 trace/session/tool/subagent ID 规范。

固定提交源码摘录
  392              self.emit_event(crate::session::events::Event::ToolStarted {
  393                  tool_name: call.function.name.clone(),
  394              });
  395              self.observability_bridge
  396                  .emit(
  397                      xai_tool_protocol::session_event::SessionEvent::ToolCallStarted {
  398                          tool_call_id: call.id.clone(),
  399                          tool_name: call.function.name.clone(),
  400                          turn_number: self.current_turn_number.get(),
  401                      },
  402                  )
  403                  .await;
  404              let call_name = call.function.name.clone();
  405              match self.prepare_tool_call(call, deferred_followups).await? {
  406                  Ok(prepared) => approved.push(prepared),
  407                  Err(tool_loop) => {
  408                      self.events.tool_finished();
  409                      if let Some((server, tool)) =
  410                          crate::session::mcp_servers::parse_mcp_tool_name(&call_name)
  411                      {
  412                          let error_reason = match &tool_loop {
  413                              ToolLoop::PermissionReject { reason, .. } => reason.clone(),
  414                              ToolLoop::Cancelled => "cancelled".to_string(),
  415                              ToolLoop::FollowupMessage(_) => "followup".to_string(),
  416                              ToolLoop::HookDenied { hook_name, .. } => {
      … 9 lines omitted; exact range 392–436 …
  426                                  server,
  427                                  crate::session::mcp_servers::MCP_TOOL_NAME_DELIMITER,
  428                                  tool
  429                              ),
  430                              duration_ms: 0,
  431                              success: false,
  432                              is_timeout: false,
  433                              error: Some(error_reason),
  434                              reconnect_attempted: false,
  435                              auth_retry_attempted: false,
  436                          });
为什么相信这条结论?查看 4 处证据
小练习 9

打开本节任意一个源码摘录,先遮住白话解释,只根据函数名、状态字段和调用顺序猜它解决什么问题;再展开证据列表,检查你的猜测有没有越过源码边界。

M10 · ENGINEERING

测试、恢复与工程取舍:把漂亮机制变成可靠产品

哪些行为有测试证明?哪些只是配置或 prompt 约定?当安全、性能、可恢复性冲突时,源码选择了什么?

先用一个生活比喻

这像验收一座桥:图纸说明结构,测试证明承重,故障演练证明断电后还能不能让人安全回来。

本节阅读法先问问题读事实看代码做迁移判断
读源码时先问本课的判断方式
这一层有没有独立证据?没有就不把其他层的能力冒充成默认行为;回到固定提交继续追调用链。
谁拥有最终控制权?区分模型输出、框架规则、用户审批和 OS 隔离四种不同力量。
这一章先建立通用概念

固定提交账本没有把该维度单独拆出,但它会在其他章节的源码路径中体现。先沿执行链路阅读,再回到报告页核对证据。

小练习 10

打开本节任意一个源码摘录,先遮住白话解释,只根据函数名、状态字段和调用顺序猜它解决什么问题;再展开证据列表,检查你的猜测有没有越过源码边界。

M11 · PRACTICE

把读懂变成会判断

下面的练习不要求你先写一个完整 Agent,而是训练你检查设计边界:事实是什么、推断是什么、如果换成自研产品要补哪一层。

Q1

SessionActor 是事件驱动的长期存活 Actor

问题:如果把这段机制移植到你的 Agent,最先要确认哪个输入、状态或安全边界?

参考答案

这套 Harness 面向长会话、后台工作和 IDE/ACP 集成,控制面复杂度显著高于纯 CLI Agent。

证据:crates/codegen/xai-grok-shell/src/session/acp_session_impl/run_loop.rs:120
Q2

工具执行明确拆成 prepare、并发 dispatch、post-flight

问题:如果把这段机制移植到你的 Agent,最先要确认哪个输入、状态或安全边界?

参考答案

并发策略不是简单 all-at-once,而是带资源锁的调度。

证据:crates/codegen/xai-grok-shell/src/session/acp_session_impl/tool_calls.rs:355
Q3

工具参数对模型瑕疵有恢复层

问题:如果把这段机制移植到你的 Agent,最先要确认哪个输入、状态或安全边界?

参考答案

对模型输出做保守修复能提高成功率,但必须记录恢复行为,避免悄悄改变语义。

证据:crates/codegen/xai-grok-shell/src/session/acp_session_impl/tool_calls.rs:878
Q4

Plan Mode 的只读约束独立于 Always Approve

问题:如果把这段机制移植到你的 Agent,最先要确认哪个输入、状态或安全边界?

参考答案

工作流阶段约束不能依赖通用权限模式,否则高权限模式会破坏阶段不变量。

证据:crates/codegen/xai-grok-shell/src/session/acp_session_impl/tool_calls.rs:157
Q5

权限判断理解访问类型和会话上下文

问题:如果把这段机制移植到你的 Agent,最先要确认哪个输入、状态或安全边界?

参考答案

权限系统既是策略引擎,也是可观测的决策服务。

证据:crates/codegen/xai-grok-shell/src/session/acp_session_impl/tool_calls.rs:1035
APPENDIX · SOURCE INDEX

本课读过的实现文件

文件索引帮助你在课程外继续追踪调用链;每一条路径来自固定提交的证据账本。

  1. 01crates/codegen/xai-grok-shell/src/session/acp_session_impl/run_loop.rsL120–183, L188–246, L247–280
  2. 02crates/codegen/xai-grok-shell/src/session/acp_session_impl/tool_calls.rsL355–449, L453–565, L878–950, L157–205, L206–215, L1035–1102, L1112–1227, L977–1034, L811–839, L392–436, L1198–1227
  3. 03crates/codegen/xai-grok-sandbox/src/lib.rsL8–18, L173–219, L107–129, L190–243, L8–18, L83–95
  4. 04crates/codegen/xai-grok-sandbox/src/profiles.rsL24–41, L113–167
  5. 05crates/codegen/xai-grok-sandbox/src/child_net.rsL62–144
  6. 06crates/codegen/xai-grok-shell/src/session/compaction.rsL3–35, L212–262, L1098–1197, L1282–1460, L1577–1655, L859–895
  7. 07crates/codegen/xai-grok-agent/src/prompt/agents_md.rsL1–7, L177–307
  8. 08crates/codegen/xai-grok-agent/src/prompt/context.rsL80–152, L199–232, L253–301
  9. 09crates/codegen/xai-grok-agent/src/plugins/manifest.rsL103–170
  10. 10crates/codegen/xai-grok-agent/src/plugins/registry.rsL12–78, L114–184, L274–279
  11. 11crates/codegen/xai-grok-shell/src/agent/subagent/handle_request.rsL84–140, L149–343, L375–423
  12. 12crates/codegen/xai-grok-sandbox/src/logging.rsL3–97
下一步

从课程回到报告,做一次反向核验

教程负责让你读懂,报告负责让你查证。打开报告页,任选一个章节,尝试只靠源码摘录复述它的边界。

查看 Grok Build 报告 ↗