跳转到内容

01 — Rust Workspace 架构:15 Crate 的职责划分

Rust Workspace 架构:15 Crate 的职责划分

学习目标

理解 CodeWhale 的 Cargo workspace 设计:15 个 crate 的职责划分、依赖方向、默认成员选择与渐进式拆分策略。

项目实践

Workspace 结构

各 Crate 职责

Crate职责代码量
cli参数解析(clap)、配置加载、入口路由入口点
tui完整运行时:引擎、工具、子 Agent、TUI 渲染、HTTP/SSE~77K 行
coreAgent 循环、会话管理、轮次编排、容量控制(从 tui 拆分中)正在拆分
agent模型/provider 注册表,将模型 ID 解析为 provider 端点注册表
config配置加载、profile、环境变量优先级、CLI 运行时覆盖配置
tools共享工具调用原语,包括工具结果/错误/能力类型原语
mcpMCP 客户端 + stdio 服务器,外部工具服务器集成MCP
stateSQLite 持久化层,线程/会话/任务存储SQLite
execpolicy审批/沙箱策略引擎,工具执行决策策略
hooks生命周期钩子(stdout、jsonl、webhook)pre/post 工具事件钩子
protocol请求/响应框架和协议类型类型
secretsOS 密钥环集成,API 密钥存储密钥
tui-core事件驱动 TUI 状态机脚手架脚手架
app-serverHTTP/SSE + JSON-RPC 应用服务器传输传输
release发布工具,二进制自更新发布

依赖方向

cli → tui → core → tools, config, agent, execpolicy, state, protocol
→ hooks, mcp, secrets
app-server → core, protocol

关键约束:

  • core 不依赖 tui——引擎独立于 UI
  • tools 是底层原语——被 coretui 共用
  • protocol 是最底层——只包含类型定义,无业务逻辑

默认成员策略

default-members = ["crates/cli", "crates/app-server", "crates/tui"]

默认构建三个二进制 crate。其余 crate 作为库 crate被依赖,不直接产出二进制。

工作空间依赖统一

[workspace.dependencies]
tokio = { version = "1.49.0", features = ["full"] }
reqwest = { version = "0.13.1", default-features = false, features = ["json", "rustls", "socks"] }
rusqlite = { version = "0.32.1", features = ["bundled"] }
axum = { version = "0.8.5", features = ["json"] }

所有 crate 共享版本,避免依赖不一致。

Rust 版本约束

rust-version = "1.88"
edition = "2024"

代码库大量使用 let_chainsif/while 条件中的 let 链式绑定),Rust 1.88 将其稳定化。旧版工具链会报 “feature edition2024 is required” 而非晦涩的 E0658 错误。

问题与规避

陷阱:crate 间循环依赖

当拆分出 core crate 时,需要仔细处理与 tui crate 的依赖方向。如果 core 依赖 tui 中的类型,会形成循环依赖。

规避

  • 共享类型下沉到 protocol crate
  • 共享原语下沉到 tools crate
  • core 只依赖 protocoltoolsconfig 等底层 crate

陷阱:依赖版本冲突

15 个 crate 使用不同版本的同一依赖会导致构建失败。

规避workspace.dependencies 统一管理,所有 crate 通过 workspace = true 引用。

陷阱:构建时间过长

全量构建 15 个 crate 可能很慢,尤其是包含 rusqlite(bundled SQLite)编译时。

规避

  • 默认只构建 3 个 binary crate
  • cargo check 增量编译
  • CI 中利用缓存

设计取舍

为什么选择 Cargo workspace 而非单 crate

  • 编译隔离:修改 mcp crate 不需要重编译 core crate
  • 职责清晰:每个 crate 有明确的边界
  • 渐进拆分:从单 crate 开始,逐步拆分出库 crate,不破坏现有功能
  • 发布灵活性:可以独立发布库 crate 到 crates.io

为什么 tui crate 仍然最大(~77K 行)

README 注释明确说明:

crates/tui 仍然是面向用户的运行时……其他 workspace crate 正在渐进式拆分,但它们尚未成为运行时的唯一真相来源。”

这是一种务实的渐进式重构策略:

  1. 先拆分出独立的底层 crate(protocol、tools、config)
  2. 然后拆分中间层(core、agent、mcp)
  3. 最后 tui 只作为薄层,编排这些 crate

当前处于第 2 阶段进行中。

rusqlite bundled vs 系统 SQLite

选择 bundled feature 的原因:

  • 用户不需要预装 SQLite 开发库
  • 跨平台一致性
  • 代价:编译时间更长(需要编译 SQLite C 代码)

参考来源

  • Cargo.toml — 工作空间定义与依赖版本
  • docs/ARCHITECTURE.md — 架构概览与 crate 职责描述
  • docs/INSTALL.md — 平台特定的安装说明(含 Rust 版本要求)