分析对象: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):

  • 工具调用拥有 idname,结果可与之关联。
  • usage 作为可选字段内嵌在 message 上,为对账/复盘提供结构位。
  • version 提供 schema 演进钩子(Python StoredSession 当前无版本)。

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 不是简单截断,它生成明确的 CompactionConfigCompactionResult,并把摘要注入 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_configserver_nameScopedMcpServerConfig 映射成:

  • normalized_name
  • tool_prefix(用于工具命名空间)
  • signature
  • transport(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 statusgit 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 只做装配与呈现


Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐