第 2 章:80 个 Crate 如何组成一个 Agent
面对大型 Rust workspace,最危险的阅读方式是从目录第一项开始逐个读。你会记住很多名字,却看不到边界。本章用“稳定依赖层 + 运行时主链 + 外围适配”重建仓库地图。
2.1 先看组合根
真正产出 grok 可执行文件的是 xai-grok-pager-bin。它的 Cargo manifest 明确承担 composition root:把 pager、shell、workspace、sandbox、telemetry、update 等组装起来,避免核心 crate 之间形成依赖环。
这带来一个实用判断:
- 想知道“程序有哪些运行模式”,看 binary crate;
- 想知道“Agent 怎么工作”,不要停在 binary,继续进 shell/session;
- 想知道“某种能力的稳定抽象”,去 common 或专用 leaf crate。
2.2 五层架构
层与层不是严格单向的教科书结构,但这个模型足以帮助定位。
第一层:入口与产品
| Crate | 职责 |
|---|---|
xai-grok-pager-bin | CLI 解析、运行模式分派、运行时与全局初始化 |
xai-grok-pager | TUI、输入、渲染、交互状态 |
xai-grok-pager-render | 渲染相关逻辑 |
xai-grok-shell | Agent 应用服务、ACP、会话、扩展和 Leader |
“pager” 的体积很大,但它仍是 Client/UI;核心 Agent loop 在 shell 的 session 模块。
第二层:编排
编排层没有独立统一的 crate,它主要位于 xai-grok-shell:
agent/mvp_agent:ACP Agent 与 session registry;session/acp_session.rs:单 session actor;session/acp_session_impl/*:回合、采样、工具、压缩、目标等行为;leader/*:多客户端连接与路由;agent/subagent/*:子代理协调。
第三层:领域服务
| Crate | 核心对象 |
|---|---|
xai-chat-state | ChatStateHandle、规范化 conversation |
xai-grok-sampler | SamplerHandle、SamplingEvent |
xai-grok-tools | 工具实现、注册表、ToolBridge |
xai-grok-workspace | 文件、VCS、权限、session storage、worktree |
xai-grok-compaction | 压缩请求与历史重组 |
xai-grok-memory | Markdown 记忆、索引、检索、Dream |
xai-grok-mcp | MCP transport、OAuth、server lifecycle |
xai-workflow | Rhai 工作流引擎与 journal |
xai-grok-subagent-resolution | 子代理角色、模型与恢复参数解析 |
第四层:协议与稳定类型
这层刻意减少 I/O,以便不同运行时共享。
| Crate | 为什么单独存在 |
|---|---|
xai-grok-sampling-types | 模型请求、响应、usage、tool call 的纯数据模型 |
xai-tool-types | 工具 schema 与任务数据的规范类型 |
xai-tool-protocol | 工具服务器 JSON-RPC wire contract |
xai-tool-runtime | 类型化 Tool、type erasure、流式结果 |
xai-grok-workspace-types | 本地/代理 workspace 共同使用的 operation 类型 |
xai-grok-workspace-client | 不依赖完整二进制的 RPC 客户端 |
xai-acp-lib | ACP message、gateway、stdio transport |
xai-hooks-plugins-types | Hook/Plugin 跨层共享类型 |
把“纯类型”与“执行网络请求”拆开,可以避免一个常见问题:为了引用一个请求 struct,被迫把整个 HTTP/Tokio 栈拉进来。
第五层:基础设施
基础设施散布在专用 crate 和上游依赖中,例如:
- 非阻塞终端与 PTY;
- HTTP/SSE/WebSocket;
- SQLite、FTS5 和可选向量扩展;
- git/worktree;
- nono/seccomp 沙箱;
- Sentry、tracing、heap profile。
2.3 三种关键依赖倒置
Tool:类型化实现向动态调用倒置
具体工具实现 Tool,运行时通过 ToolDyn 统一处理模型给出的 JSON。业务代码依赖抽象,不依赖每个参数类型。
Concrete Tool<Args, Output>
│
▼
ToolDyn
│
▼
ToolBridge / FinalizedToolset
│
▼
SessionActor2
3
4
5
6
7
8
9
10
Workspace:本地与代理共享 operation
调用方表达 WorkspaceOp,但执行可以落到本地 WorkspaceHandle,也可以由 WorkspaceClient 通过 RPC 发送给 workspace server。
这使 Agent 编排逻辑不需要散布“如果远程则……”分支。
Sampling:业务请求与供应商协议隔离
ConversationRequest 是规范模型;client 层再转换成 Chat Completions、Responses 或 Anthropic Messages。SessionActor 不直接拼某个供应商的 HTTP payload。
2.4 为什么 shell 是“大中枢”
xai-grok-shell 不是 Unix shell 工具集合,而是应用服务层。它包含:
- 认证和模型发现;
- Agent、session、subagent;
- Leader、relay、remote;
- MCP、插件、Hook、LSP/代码导航;
- 持久化、分享、上传;
- goal、workflow、memory 的会话集成。
它很大,是因为它负责协调诸多相对独立的领域 crate。判断代码是否应继续放在这里,可使用一个简单问题:
这段逻辑是在定义某个领域能力,还是在决定一次会话何时调用该能力?
- 前者更适合专用 crate;
- 后者通常属于 shell/session。
2.5 主干与外围
不是所有 crate 都要在第一遍阅读。
主干
xai-grok-pager-bin
└─ xai-grok-shell
├─ xai-chat-state
├─ xai-grok-sampler
│ └─ xai-grok-sampling-types
├─ xai-grok-tools
│ └─ xai-tool-runtime / types / protocol
└─ xai-grok-workspace2
3
4
5
6
7
8
第二圈
xai-grok-compactionxai-grok-memoryxai-grok-mcpxai-workflowxai-grok-subagent-resolution
外围适配与产品能力
- telemetry、update、auth/login;
- markdown、render、voice、browser;
- plugin marketplace、share、upload;
- fast-worktree、code index、LSP;
- managed configuration 与企业策略。
外围不表示不重要,而是它们不会帮助你第一次理解主循环。
2.6 模块边界中的“数据所有权”
大型异步系统最重要的不是函数在哪,而是谁能改状态:
| 状态 | 所有者 | 外部访问方式 |
|---|---|---|
| session 列表 | MvpAgent | ACP 请求、handle |
| 单 session 运行状态 | SessionActor | actor mailbox |
| conversation/token 账本 | chat-state actor | ChatStateHandle |
| 活跃模型请求 | sampler actor | SamplerHandle + event receiver |
| permission grants | permission manager | request/decision channel |
| workspace 文件状态 | workspace/server + file tracker | typed operation |
| durable session history | persistence actor | PersistenceMsg |
| TUI state | AppView | Action → Effect → TaskResult |
当你看到一个 channel,先问“它在保护哪个状态所有权”,比问“为什么不用直接函数调用”更有效。
2.7 Cargo 结构透露的工程策略
根 Cargo.toml 是自动生成的,并提示应修改各 crate manifest,而非直接维护根依赖。workspace 统一 Rust edition 2024,并通过 workspace dependency 管理大量共享版本。
这种布局常见于单体仓库同步出来的构建快照:
- 源 crate 保持独立 manifest;
- 根清单负责组合、统一版本和 feature;
SOURCE_REV记录上游源码修订;- binary crate 是最终 feature 汇合点,例如默认启用 allocator 与 sandbox enforcement。
阅读时要警惕 feature gate:某段代码存在,不代表默认构建一定启用。Memory、sandbox、enterprise/managed 能力尤其要结合 manifest 和配置门控判断。
2.8 本章结论
80 个 crate 并不是 80 个同等重要的入口。主线可以收敛为:
binary 负责组合,shell 负责会话编排,领域 crate 负责模型/工具/工作区/记忆,common types 负责稳定协议,pager 负责把更新变成体验。
下一章从 main 开始,追踪不同运行模式如何最终汇聚到同一个 MvpAgent,并解释为什么还需要 Leader。