claw-code 源码分析:cargo 视角的 definitive runtime——会话、压缩、MCP、提示构造如何落到系统语言?
Rust workspace 中的 runtime crate 作为系统核心运行时,通过清晰的模块划分和接口设计实现了以下功能: 模块化架构:通过 Cargo.toml 管理依赖,暴露统一的 API 接口面(lib.rs),内部实现可替换 会话管理:采用强类型会话模型(Session/Message/ContentBlock),支持工具调用和结果追踪 持久化机制:内置 JSON 序列化/反序列化,
分析对象:Rust workspace 中的
rust/crates/runtime(系统语言侧的 runtime 核心),以及它通过lib.rs暴露给上层(CLI、commands、tools、plugins)的接口面。
1. cargo 视角:definitive runtime = crate 边界 + 可复用 re-export
从 Cargo.toml 看,runtime crate 把 LSP、plugins、tokio 等作为依赖,定位就是“跑起来的内核”:
# 1:18:rust/crates/runtime/Cargo.toml
[package]
name = "runtime"
...
[dependencies]
lsp = { path = "../lsp" }
plugins = { path = "../plugins" }
tokio = { version = "1", features = ["io-util", "macros", "process", "rt", "rt-multi-thread", "time"] }
...
src/lib.rs 列出模块并大量 pub use,等同于在系统语言里把“运行时公共 API 面”钉死:
// 1:18:rust/crates/runtime/src/lib.rs
mod compact;
mod conversation;
mod mcp;
mod mcp_client;
mod mcp_stdio;
mod permissions;
mod prompt;
mod session;
mod usage;
...
pub use compact::{ compact_session, should_compact, CompactionConfig, CompactionResult, ... };
pub use conversation::{ ConversationRuntime, AssistantEvent, ToolExecutor, ... };
pub use mcp_client::{ McpClientBootstrap, McpClientTransport, ... };
pub use prompt::{ load_system_prompt, SystemPromptBuilder, ProjectContext, ... };
pub use session::{ Session, ConversationMessage, ContentBlock, MessageRole, ... };
工程含义:上层(例如 claw-cli)可以只依赖 runtime crate 的 re-export,不必直接触碰每个子模块文件;这就是“definitive runtime”常见形态:一个 crate 提供稳定的核心接口,内部实现可替换。
2. 会话(Session):把对话与工具调用变成强类型、可持久化的事件序列
2.1 会话数据模型:role + blocks(含 tool use / tool result)
session.rs 把会话表达为 Session { version, messages },每条消息是 ConversationMessage { role, blocks, usage }。blocks 是 tagged enum,直接内建 tool 调用与结果的结构:
// 11:50:rust/crates/runtime/src/session.rs
pub enum MessageRole { System, User, Assistant, Tool }
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ContentBlock {
Text { text: String },
ToolUse { id: String, name: String, input: String },
ToolResult { tool_use_id: String, tool_name: String, output: String, is_error: bool },
}
pub struct ConversationMessage {
pub role: MessageRole,
pub blocks: Vec<ContentBlock>,
pub usage: Option<TokenUsage>,
}
pub struct Session {
pub version: u32,
pub messages: Vec<ConversationMessage>,
}
学习点:这比 Python 移植层的「字符串列表」更接近产品级运行史(见 result/06.md):
- 工具调用拥有
id与name,结果可与之关联。 usage作为可选字段内嵌在 message 上,为对账/复盘提供结构位。version提供 schema 演进钩子(PythonStoredSession当前无版本)。
2.2 会话持久化:JSON 序列化/反序列化在 runtime 内闭环
Session::save_to_path / load_from_path 直接读写 JSON:
// 92:100:rust/crates/runtime/src/session.rs
pub fn save_to_path(&self, path: impl AsRef<Path>) -> Result<(), SessionError> {
fs::write(path, self.to_json().render())?;
Ok(())
}
pub fn load_from_path(path: impl AsRef<Path>) -> Result<Self, SessionError> {
let contents = fs::read_to_string(path)?;
Self::from_json(&JsonValue::parse(&contents)?)
}
学习点:把 session schema 的解析权放在 runtime crate,而不是散落在 CLI;这样上层可统一复用,并且更容易做版本迁移。
3. 压缩(Compaction):以可配置策略把“旧消息”折叠为 system continuation
Rust 侧 compaction 不是简单截断,它生成明确的 CompactionConfig 与 CompactionResult,并把摘要注入 MessageRole::System 的 continuation 消息:
// 8:29:rust/crates/runtime/src/compact.rs
pub struct CompactionConfig {
pub preserve_recent_messages: usize,
pub max_estimated_tokens: usize,
}
pub struct CompactionResult {
pub summary: String,
pub formatted_summary: String,
pub compacted_session: Session,
pub removed_message_count: usize,
}
压缩的关键产物是“system summary + 保留最近消息”,对应产品常见的“上下文续写”语义:
// 89:129:rust/crates/runtime/src/compact.rs
pub fn compact_session(session: &Session, config: CompactionConfig) -> CompactionResult {
if !should_compact(session, config) { ... }
...
let continuation = get_compact_continuation_message(&summary, true, !preserved.is_empty());
let mut compacted_messages = vec![ConversationMessage {
role: MessageRole::System,
blocks: vec![ContentBlock::Text { text: continuation }],
usage: None,
}];
compacted_messages.extend(preserved);
...
}
对比 Python:Python compact_after_turns 目前是“尾部截断”;Rust 在接口层已经具备:
- 触发阈值(按估算 token)
- 保留窗口
- 可审计结果对象(removed_count、summary 文本、compacted_session)
这就是“硬化”的典型体现:策略从魔法数变成可配置、可测试的 API 面(详见 result/07.md)。
4. MCP:从配置 → transport → JSON-RPC 协议对象的完整落地
Rust runtime 把 MCP 作为一等子系统:mcp(命名/签名/hash)、mcp_client(transport bootstrap)、mcp_stdio(JSON-RPC 交互与工具/资源枚举)。
4.1 从配置到 transport:McpClientBootstrap
McpClientBootstrap::from_scoped_config 把 server_name 与 ScopedMcpServerConfig 映射成:
normalized_nametool_prefix(用于工具命名空间)signaturetransport(stdio/http/ws/sdk/managed proxy)
// 49:67:rust/crates/runtime/src/mcp_client.rs
pub struct McpClientBootstrap {
pub server_name: String,
pub normalized_name: String,
pub tool_prefix: String,
pub signature: Option<String>,
pub transport: McpClientTransport,
}
pub fn from_scoped_config(server_name: &str, config: &ScopedMcpServerConfig) -> Self {
Self {
server_name: server_name.to_string(),
normalized_name: normalize_name_for_mcp(server_name),
tool_prefix: mcp_tool_prefix(server_name),
signature: mcp_server_signature(&config.config),
transport: McpClientTransport::from_config(&config.config),
}
}
学习点:MCP 的“可用工具面”不是靠字符串过滤(Python 的 include_mcp 启发式),而是由配置与 transport 类型驱动,天然更可维护、也更可审计。
4.2 JSON-RPC 协议对象:mcp_stdio.rs
mcp_stdio.rs 定义了 JSON-RPC request/response、initialize、list tools/resources、tool call 等结构体,作为运行时与 MCP server 的“线协议”模型:
// 23:60:rust/crates/runtime/src/mcp_stdio.rs
pub struct JsonRpcRequest<T = JsonValue> { ... }
pub struct JsonRpcResponse<T = JsonValue> { ... }
pub struct McpInitializeParams { ... }
pub struct McpListToolsResult { pub tools: Vec<McpTool>, ... }
pub struct McpTool { pub name: String, pub description: Option<String>, ... }
学习点:把协议对象化(typed protocol model)是系统语言硬化的重要原因之一:
上层不需要手写 JSON map,也不必在字符串里拼字段名,协议演进更可控。
5. 提示构造(Prompt construction):SystemPromptBuilder + ProjectContext
Rust runtime 把 prompt 构造作为独立模块 prompt.rs,并且以“builder”形态提供稳定接口:
5.1 项目上下文发现:instruction files + git status/diff
ProjectContext::discover_with_git 会收集:
- 工作目录、日期
- instruction files(
CLAW.md等) git status与git diff
// 49:82:rust/crates/runtime/src/prompt.rs
pub struct ProjectContext { pub cwd: PathBuf, pub current_date: String, pub git_status: Option<String>, pub git_diff: Option<String>, pub instruction_files: Vec<ContextFile> }
pub fn discover_with_git(...) -> std::io::Result<Self> {
let mut context = Self::discover(cwd, current_date)?;
context.git_status = read_git_status(&context.cwd);
context.git_diff = read_git_diff(&context.cwd);
Ok(context)
}
学习点:把“工作区上下文”作为 prompt 的一等输入,且与 git 状态绑定,正是 coding agent CLI 的典型产品需求。
5.2 System prompt 的分段构造 + 动态边界
SystemPromptBuilder.build() 生成 Vec<String> sections,并插入 SYSTEM_PROMPT_DYNAMIC_BOUNDARY,为后续动态注入(例如工具列表、会话摘要等)预留稳定锚点:
// 143:166:rust/crates/runtime/src/prompt.rs
pub fn build(&self) -> Vec<String> {
let mut sections = Vec::new();
sections.push(get_simple_intro_section(...));
...
sections.push(SYSTEM_PROMPT_DYNAMIC_BOUNDARY.to_string());
sections.push(self.environment_section());
if let Some(project_context) = &self.project_context { ... }
if let Some(config) = &self.config { sections.push(render_config_section(config)); }
sections.extend(self.append_sections.iter().cloned());
sections
}
学习点:Prompt 构造被硬化为“可组合的段”,而不是散落在 runtime loop 的字符串拼接里;这对测试、对配置化、对多平台差异都更友好。
6. 会话 + 工具 + 权限 + hooks:ConversationRuntime 把它们缝起来
conversation.rs 是“definitive runtime”的另一个核心:把 session、API client、tool executor、permission policy、hooks、usage tracker 串成可运行的 turn loop。
6.1 Turn loop:从 assistant message 抽取 ToolUse blocks
// 153:199:rust/crates/runtime/src/conversation.rs
pub fn run_turn(&mut self, user_input: impl Into<String>, mut prompter: Option<&mut dyn PermissionPrompter>) -> Result<TurnSummary, RuntimeError> {
self.session.messages.push(ConversationMessage::user_text(user_input.into()));
...
let request = ApiRequest { system_prompt: self.system_prompt.clone(), messages: self.session.messages.clone() };
let events = self.api_client.stream(request)?;
let (assistant_message, usage) = build_assistant_message(events)?;
...
let pending_tool_uses = assistant_message.blocks.iter().filter_map(|block| match block {
ContentBlock::ToolUse { id, name, input } => Some((id.clone(), name.clone(), input.clone())),
_ => None,
}).collect::<Vec<_>>();
self.session.messages.push(assistant_message.clone());
...
}
6.2 权限与 hook:PreToolUse 可拒绝并写入 tool_result(error=true)
// 201:220:rust/crates/runtime/src/conversation.rs
for (tool_use_id, tool_name, input) in pending_tool_uses {
let permission_outcome = self.permission_policy.authorize(&tool_name, &input, ...);
let result_message = match permission_outcome {
PermissionOutcome::Allow => {
let pre_hook_result = self.hook_runner.run_pre_tool_use(&tool_name, &input);
if pre_hook_result.is_denied() {
ConversationMessage::tool_result(tool_use_id, tool_name, ..., true)
} else {
...
}
}
...
};
}
学习点:系统语言侧把 “PermissionDenial 不是补丁”落到了执行链:
permission/hook 的 deny 变成 结构化的 tool_result,进入 session messages,可持久化、可回放、可对账(对照 Python 文档 result/05.md 的“应变成执行闸门”结论)。
7. 小结:系统语言如何承载“definitive runtime”
从 cargo/crate 视角看,Rust runtime 把四个难点落地为强类型与可复用 API 面:
- 会话:
Session/ConversationMessage/ContentBlock(含 tool use/result + usage + version + JSON 持久化)。 - 压缩:
CompactionConfig/CompactionResult+ continuation system 消息(可测试、可审计)。 - MCP:配置→transport→JSON-RPC 协议对象→工具命名空间(从启发式过滤升级为协议级模型)。
- 提示构造:
ProjectContext+SystemPromptBuilder分段构造与动态边界(适合产品化演进)。
再由 ConversationRuntime 把 权限、hooks、工具执行、usage 记录 缝成可运行循环。
这就是“definitive runtime”的系统语言表达:把接口与语义固化为类型与模块边界,让上层 CLI 只做装配与呈现。
更多推荐



所有评论(0)