IronClaw 深度剖析(三):双引擎架构——Engine v2 如何重新定义 Agent 执行模型
系列导读:本文是 IronClaw 深度剖析系列的第三篇。在前两篇中,我们分别探讨了 IronClaw 的整体架构设计和权限系统。本篇将聚焦于 IronClaw 最核心的创新——Engine v2 统一执行引擎,解读它如何通过五个原语重新定义 Agent 的执行模型。
一、双引擎架构:新旧共存的智慧
IronClaw 的代码库中藏着一个罕见的架构决策:两个完整的 Agent 引擎并行运行。这不是技术债务的堆积,而是一次深思熟虑的渐进式架构升级。
Engine v1:传统 Agent 系统
位于 src/agent/ 的 v1 引擎是一个典型的传统 Agent 实现,包含约 15 个模块:
Agent结构体作为核心入口Session管理交互式对话Job处理后台任务队列Routine处理定时和事件触发任务run_agentic_loop()驱动主循环LoopDelegate实现行为委托模式
// src/agent/mod.rs - Engine v1 模块结构
mod agent_loop; // Agent 结构体 + AgentDeps
mod agentic_loop; // 原始的 run_agentic_loop
mod router; // 消息意图路由 (Chat/Tool/Research/...)
mod session; // Session + Turn 管理
mod scheduler; // Job 调度
mod routine; // 定时/响应式任务
mod heartbeat; // 主动后台执行
mod self_repair; // 卡死任务恢复
mod compaction; // 上下文压缩
mod cost_guard; // Token 预算管理Engine v2:统一执行引擎
位于 crates/ironclaw_engine/ 的 v2 引擎是一次彻底的重构,它将 v1 中约 10 个独立抽象(Session、Job、Routine、Channel、Tool、Skill、Hook、Observer、Extension、LoopDelegate)统一为 5 个核心原语:Thread、Step、Capability、MemoryDoc 和 Project。
桥接适配器:让两者共存
两个引擎通过一个桥接层(src/bridge/)实现共存:
┌─────────────────────────────────────────┐
│ Host Application │
│ ┌──────────────┐ ┌───────────────┐ │
│ │ Engine v1 │◄──►│ Bridge │ │
│ │ src/agent/ │ │ src/bridge/ │ │
│ └──────────────┘ └──────┬────────┘ │
│ │ │
│ ┌────────▼────────┐ │
│ │ Engine v2 │ │
│ │crates/ironclaw_ │ │
│ │ engine/ │ │
│ └─────────────────┘ │
└─────────────────────────────────────────┘v2 引擎通过 ENGINE_V2=true 环境变量启用。同时,IRONCLAW_DISABLE_CODEACT 可以在 v2 中禁用 CodeAct 功能(来自 PR #3665),这种精细的开关控制体现了架构的灵活性。
二、五大核心原语:从十个抽象到一个统一模型
Engine v2 最核心的架构创新是将原本分散在 v1 中的众多概念统一为五个正交的原语。这种"做减法"的设计哲学,使得整个系统的认知复杂度大幅降低。
抽象映射全景图
graph TB
subgraph "Engine v1 (Legacy)"
V1_S[Session]
V1_J[Job]
V1_R[Routine]
V1_SA[Sub-agent]
V1_T[Tool]
V1_SK[Skill]
V1_H[Hook]
V1_E[Extension]
V1_W[Workspace]
V1_M[Memory Blob]
end
subgraph "Engine v2 (Unified)"
V2_Th[Thread
+ ThreadType]
V2_St[Step]
V2_C[Capability
+ ActionDef + Policy]
V2_P[Project]
V2_Md[MemoryDoc
+ DocType]
end
V1_S --> V2_Th
V1_J --> V2_Th
V1_R --> V2_Th
V1_SA --> V2_Th
V1_T --> V2_C
V1_SK --> V2_C
V1_H --> V2_C
V1_E --> V2_C
V1_W --> V2_P
V1_M --> V2_Md
style V2_Th fill:#4a90d9,color:#fff
style V2_St fill:#4a90d9,color:#fff
style V2_C fill:#4a90d9,color:#fff
style V2_P fill:#4a90d9,color:#fff
style V2_Md fill:#4a90d9,color:#fffThread:统一的工作单元
Thread 是 v2 中最核心的抽象,它将 v1 中分散的 Session(交互对话)、Job(后台任务)、Routine(定时任务)和 Sub-agent(子代理)统一为一个类型:
// crates/ironclaw_engine/src/types/thread.rs
/// Thread 统一了 v1 中 Session + Job + Routine + Sub-agent
pub struct Thread {
pub id: ThreadId,
pub project_id: ProjectId,
pub thread_type: ThreadType, // 区分不同用途
pub state: ThreadState, // 显式状态机
pub parent_id: Option<ThreadId>, // 支持父子树
pub capability_leases: Vec<LeaseId>,
pub config: ThreadConfig,
}
pub enum ThreadType {
Chat, // 交互式对话(替代 Session)
Background, // 后台工作(替代 Job)
Routine, // 定时执行(替代 Routine)
Sub, // 委托推理(替代 Sub-agent)
}
pub enum ThreadState {
Created, // 已创建但未启动
Running, // 正在执行 Steps
Waiting, // 等待外部输入
Suspended, // 被系统暂停
Completed, // 执行完成
Done, // 完全结束(终态)
Failed, // 终态失败
}状态转换规则:
Created → Running → Waiting → Running (resume)
→ Suspended → Running (resume)
→ Completed → Done
→ FailedStep:显式的执行单元
v1 中的 Agent loop 迭代是隐式的——每次循环产生一组 tool call,但没有显式的数据结构来代表"一次 LLM 调用 + 其动作执行"。v2 引入了 Step 来显式建模这个过程:
// crates/ironclaw_engine/src/types/step.rs
pub struct Step {
pub id: StepId,
pub thread_id: ThreadId,
pub sequence: usize, // Thread 内的序号(1-indexed)
pub status: StepStatus, // Pending | LlmCalling | Executing | Completed | Failed
pub execution_tier: ExecutionTier,
pub llm_response: Option<LlmResponse>,
pub action_calls: Vec<ActionCall>,
pub action_results: Vec<ActionResult>,
pub token_usage: TokenUsage,
pub started_at: Option<DateTime<Utc>>,
pub completed_at: Option<DateTime<Utc>>,
}Capability:统一的效果模型
Capability 将 Tool、Skill、Hook 和 Extension 统一为一个"能力"概念,每个能力包含动作定义(ActionDef)和策略(Policy):
// crates/ironclaw_engine/src/types/capability.rs
pub struct Capability {
pub id: CapabilityId,
pub name: String,
pub actions: Vec<ActionDef>, // 该能力提供的动作
pub policies: Vec<Policy>, // 执行策略约束
}
pub struct ActionDef {
pub name: String,
pub description: String,
pub parameters: JsonSchema,
pub returns: JsonSchema,
}MemoryDoc 与 Project:结构化知识管理
MemoryDoc 替代了 v1 中临时的内存 blob,提供了类型化的知识管理;Project 替代了扁平的 workspace,提供了作用域隔离。
| v1 抽象 | v2 原语 | 核心改进 |
|---|---|---|
| Session + Job + Routine + Sub-agent | Thread + ThreadType | 单一生命周期管理,支持父子树 |
| Tool + Skill + Hook + Extension | Capability + ActionDef | 统一的效果模型,策略驱动 |
| 隐式 loop 迭代 | Step + 显式状态机 | 可观测、可恢复的执行 |
| 扁平 workspace | Project + 作用域 | 多租户内存隔离 |
| 临时 memory blob | MemoryDoc + DocType | 结构化知识管理 |
三、Agent Loop 消息处理流程
Engine v2 的核心是 ExecutionLoop(位于 executor/loop_engine.rs,约 2143 行),它替代了 v1 的 run_agentic_loop()。这是一个自包含的循环,清晰地定义了从 Thread 创建到完成的完整生命周期。
执行流程全景
flowchart TD
A[ThreadManager::spawn] --> B[ExecutionLoop::run]
B --> C[Build Context
messages + actions + memory]
C --> D[LLM call via
LlmBackend::complete]
D --> E{Response type?}
E -->|Text| F[Stream to user]
E -->|ActionCalls| G[For each ActionCall]
G --> H[GatePipeline.evaluate]
H --> I{Decision?}
I -->|Allow| J[EffectExecutor.execute]
J --> K[Update Step
emit ThreadEvent]
I -->|Pause| L[Store RunState
wait for resume]
I -->|Deny| M[Record failure
continue]
K --> N{More actions?}
N -->|Yes| G
N -->|No| O{Thread complete?}
M --> N
F --> O
O -->|No| C
O -->|Yes| P[Emit ThreadCompleted
return ThreadOutcome]
style A fill:#5b8c5a,color:#fff
style P fill:#5b8c5a,color:#fff
style L fill:#c9a227,color:#000
style M fill:#c44,color:#fffExecutionLoop 的核心逻辑
// crates/ironclaw_engine/src/executor/loop_engine.rs (简化示意)
impl ExecutionLoop {
pub async fn run(&mut self, thread_id: ThreadId) -> Result<ThreadOutcome, EngineError> {
loop {
// 1. 构建上下文:消息 + 能力动作 + 内存文档
let context = self.build_context(thread_id).await?;
// 2. 调用 LLM
let llm_response = self.llm.complete(
&context.messages,
&context.available_actions,
&context.config,
).await?;
// 3. 创建新 Step
let mut step = self.create_step(thread_id).await?;
match llm_response {
LlmResponse::Text(text) => {
// 纯文本响应,直接流式输出
self.stream_to_user(text).await?;
step.complete_text().await?;
}
LlmResponse::ActionCalls(calls) => {
// 需要执行动作
step.start_executing().await?;
for call in calls {
// 4. 门控评估
let gate_ctx = GateContext::new(&call, &step);
match self.gate_pipeline.evaluate(&gate_ctx).await {
// 5a. 允许执行
GateDecision::Allow => {
let result = self.effector.execute(&call, &context).await?;
step.record_action_result(result).await?;
}
// 5b. 暂停等待
GateDecision::Pause { reason, resume_kind } => {
self.run_state.store_pause(thread_id, reason, resume_kind);
self.emit_event(EventKind::GatePaused).await?;
return Ok(ThreadOutcome::Paused);
}
// 5c. 拒绝执行
GateDecision::Deny { reason } => {
step.record_action_failure(&call, reason).await?;
self.emit_event(EventKind::GateDenied).await?;
continue;
}
}
}
step.complete().await?;
}
}
// 6. 检查 Thread 是否完成
if self.should_complete(thread_id).await? {
self.emit_event(EventKind::ThreadCompleted).await?;
return Ok(ThreadOutcome::Completed);
}
}
}
}这个循环相比 v1 的 run_agentic_loop() 有几个关键改进:
- 显式的 Step 管理:每次 LLM 调用及其动作执行都被封装为一个 Step,有明确的状态和生命周期
- 统一的 GatePipeline:所有动作执行前必须通过门控评估,不存在"绕过"路径
- 事件驱动:每个状态变更都产生事件,可被外部系统观测
- 可暂停恢复:Pause 决策将运行状态持久化,支持后续恢复
四、Gate Pipeline:执行门控系统
IronClaw 的安全模型建立在"fail-closed"原则之上——任何未明确允许的操作都被拒绝。GatePipeline 是这一原则的实现核心。
状态机设计
stateDiagram-v2
[*] --> Evaluating : ActionCall received
Evaluating --> Executing : GateDecision::Allow
Executing --> Completed : ActionResult OK
Executing --> Failed : ActionResult Err
Evaluating --> Paused : GateDecision::Pause
Paused --> Evaluating : ResumeKind::Approval
Paused --> Evaluating : ResumeKind::Authentication
Paused --> Evaluating : ResumeKind::External
Evaluating --> Denied : GateDecision::Deny
Denied --> NextAction : Record failure, continue
Completed --> [*]
Failed --> [*]
NextAction --> Evaluating : More actions
NextAction --> [*] : No more actions
note right of Paused
RunState persisted
ThreadEvent::GatePaused emitted
Thread enters Waiting state
end note核心枚举定义
// crates/ironclaw_engine/src/gate/mod.rs
/// 门控决策——注意:没有 None 变体,fail-closed 是设计 invariant
pub enum GateDecision {
Allow,
Pause {
reason: String,
resume_kind: ResumeKind
},
Deny {
reason: String
},
}
/// 恢复类型——封闭枚举,所有暂停路径走同一套机制
pub enum ResumeKind {
Approval {
allow_always: bool // 用户批准
},
Authentication {
credential_name: String, // OAuth/API Key
instructions: String,
auth_url: String,
},
External {
callback_id: Uuid // Webhook 确认
},
}三个关键设计不变量:
- 无 None 变体:
GateDecision只有Allow、Pause、Deny三种可能,不存在"未决定"状态。这是 fail-closed 的根本保障。 - 封闭 ResumeKind:所有暂停都必须携带恢复类型,强制所有暂停路径走同一套恢复机制。
- 零拷贝热路径:
GateContext对所有数据使用借用而非克隆,保证高频调用场景下的性能。
Pipeline 评估逻辑
// crates/ironclaw_engine/src/gate/pipeline.rs
pub struct GatePipeline {
gates: Vec<Box<dyn ExecutionGate>>,
}
impl GatePipeline {
/// 顺序评估所有 gate,第一个非-Allow 的决定获胜
pub async fn evaluate(&self, ctx: &GateContext<'_>) -> GateDecision {
for gate in &self.gates {
match gate.evaluate(ctx).await {
GateDecision::Allow => continue,
decision => return decision, // Pause 或 Deny 立即返回
}
}
GateDecision::Allow // 所有 gate 都通过
}
}工具分层(Tool Tier)
GatePipeline 与工具分层系统配合,对不同风险级别的操作施加不同的门控策略:
// crates/ironclaw_engine/src/gate/tool_tier.rs
pub enum ToolTier {
Tier0, // 结构化工具调用(JSON action)— 无需审批
Tier1, // CodeAct / 脚本执行 — 需要审批
Tier2, // 高风险操作(文件删除、网络请求等)— 严格审批
}| 层级 | 类型 | 示例 | 门控策略 |
|---|---|---|---|
| Tier 0 | 结构化调用 | 读取文件、查询数据库 | 自动允许(Capability lease 已授权) |
| Tier 1 | CodeAct 脚本 | Python 代码执行 | 首次需用户批准,allow_always 可记住 |
| Tier 2 | 高风险操作 | 删除文件、发起网络请求 | 每次都需要明确审批 |
五、CodeAct 模式:当 Agent 开始写代码
CodeAct 是 Engine v2 中的一项重要能力,它允许 LLM 生成 Python 代码来执行复杂任务,而非仅仅调用预定义的工具。
ExecutionTier 分层
// crates/ironclaw_engine/src/types/step.rs
pub enum ExecutionTier {
Structured, // Tier 0: 结构化工具调用(JSON action calls)
Scripting, // Tier 1: 内嵌 Python via Monty(CodeAct/RLM 模式)
}执行流程
CodeAct 的执行涉及多个组件的协作:
- LLM 生成 Python 代码:包含在
<code>标签中 - 语法校验:
scripting::validate_python_syntax()检查代码合法性 - Monty 沙箱执行:Pydantic 的 Python 沙箱环境隔离运行
- 结果捕获:执行结果封装为
ActionResult - 事件发射:
ThreadEvent::ActionCompleted被发出
Monty 是 v2 中唯一的 CodeAct/RLM 执行器:
# crates/ironclaw_engine/Cargo.toml
[dependencies]
monty = { git = "https://github.com/pydantic/monty.git", tag = "v0.0.16" }安全措施
CodeAct 涉及执行 LLM 生成的任意代码,安全至关重要。IronClaw 采取了多层防护:
- 沙箱隔离:Monty 提供独立的 Python 执行环境
- fail-closed 门控:所有脚本执行必须通过
ToolTier::Tier1的审批 - ReDoS 防护:在
executor/orchestrator.rs中有这样一段注释:
// Security: `__regex_match__` 接受来自 Python orchestrator 的任意模式。
// 使用 `regex = "1"`(线性时间)而非 `fancy-regex`(ReDoS 风险)。
// 不要在没有 wall-clock budget 重新设计的情况下添加 `fancy-regex`。这段代码使用线性时间的正则引擎,从根本上杜绝了正则表达式拒绝服务攻击的可能性。选择 "1" 版本而非功能更丰富的 fancy-regex,是安全优先于功能的典型架构决策。
禁用开关
// 来自 PR #3665 (commit cab708e)
// IRONCLAW_DISABLE_CODEACT=true 完全禁用 v2 的 CodeAct 功能这个环境变量提供了一种紧急刹车机制,当发现 CodeAct 存在安全问题时可以立即全局禁用。
六、任务调度模型
v2 的任务调度采用三层架构,分别对应不同时间尺度的任务管理。
MissionManager:长期目标
Mission 代表需要持续执行的长期目标,按 cadence(节奏)生成 Thread:
// crates/ironclaw_engine/src/types/mission.rs
pub struct Mission {
pub id: MissionId,
pub project_id: ProjectId,
pub description: String,
pub cadence: MissionCadence, // OneShot | Interval | Cron
pub status: MissionStatus, // Active | Paused | Completed | Failed
pub next_run: Option<DateTime<Utc>>,
}当 Mission 因为门控暂停时,MissionManager 会在门控解决后自动恢复(来自 PR #3166)。
ThreadManager:线程生命周期
// crates/ironclaw_engine/src/runtime/manager.rs
pub struct ThreadManager {
threads: HashMap<ThreadId, Thread>,
tree: ThreadTree, // 父子关系追踪
lease_manager: LeaseManager, // 能力租约管理
}
impl ThreadManager {
pub async fn spawn(&mut self, config: ThreadConfig) -> ThreadId;
pub async fn stop(&mut self, id: ThreadId) -> Result<ThreadOutcome>;
pub async fn inject_message(&mut self, id: ThreadId, msg: ThreadMessage);
pub async fn join(&mut self, id: ThreadId) -> Result<ThreadOutcome>;
}Dispatcher:运行时分发
ironclaw_dispatcher crate 提供了组合式的运行时分发:
// crates/ironclaw_dispatcher/src/lib.rs
pub struct RuntimeAdapterRequest<'a, F, G> {
pub package: &'a ExtensionPackage,
pub descriptor: &'a CapabilityDescriptor,
pub filesystem: &'a F,
pub governor: &'a G,
pub capability_id: &'a CapabilityId,
pub runtime_kind: RuntimeKind,
pub resource_reservation: Option<ResourceReservation>,
}Dispatcher 的设计非常克制——它不解析扩展清单、不实现沙箱策略、不预留预算、不执行产品工作流。它只做一件事:将经过验证的扩展描述符连接到运行时 lane。
七、事件驱动架构:一切皆事件
Engine v2 是一个完全事件驱动的系统。ironclaw_events crate 提供了持久化的事件/审计基板。
事件类型
// crates/ironclaw_events/src/lib.rs
pub struct RuntimeEvent {
pub id: Uuid,
pub timestamp: chrono::DateTime<chrono::Utc>,
pub kind: EventKind,
pub payload: serde_json::Value,
}
pub trait EventSink: Send + Sync {
async fn emit(&self, event: RuntimeEvent) -> Result<(), EventError>;
}
// crates/ironclaw_engine/src/types/event.rs
pub enum EventKind {
// Thread 生命周期
ThreadCreated,
ThreadStarted,
ThreadCompleted,
ThreadFailed,
// Step 生命周期
StepStarted,
StepCompleted,
StepFailed,
// LLM 交互
LlmCalled,
LlmResponded,
// 动作执行
ActionCalled,
ActionCompleted,
ActionFailed,
// 门控事件
GatePaused,
GateResumed,
GateDenied,
// 知识与能力
MemoryDocCreated,
MemoryDocUpdated,
CapabilityLeased,
// ... 共 18 种变体
}事件流架构
sequenceDiagram
participant EL as ExecutionLoop
participant TE as ThreadEvent
participant ES as EventSink
participant DS as Durable Storage
participant SSE as SSE Stream
participant UI as Debug Panel
EL->>TE: Step 开始
TE->>ES: emit(EventKind::StepStarted)
ES->>DS: 持久化存储
ES->>SSE: 实时推送
SSE->>UI: 更新 Activity 面板
EL->>EL: LLM 调用完成
EL->>TE: 动作需要执行
TE->>ES: emit(EventKind::ActionCalled)
ES->>DS: 持久化存储
EL->>EL: GatePipeline 评估
alt Allow
EL->>TE: 执行动作
TE->>ES: emit(EventKind::ActionCompleted)
else Pause
TE->>ES: emit(EventKind::GatePaused)
ES->>DS: 保存 RunState
Note over EL,UI: 等待用户审批...
UI->>EL: Resume signal
TE->>ES: emit(EventKind::GateResumed)
else Deny
TE->>ES: emit(EventKind::GateDenied)
end
ES->>DS: 持久化存储
ES->>SSE: 实时推送
SSE->>UI: 更新状态显示事件流的消费者
事件流同时服务于多个消费者:
- 持久化存储:所有事件被持久化,提供完整的审计追踪
- SSE 流:通过 Server-Sent Events 实时推送给前端
- 调试面板:Activity tab 实时显示执行状态
- Run State:从事件流中派生当前运行状态,回答"这个调用现在在等什么?"
// crates/ironclaw_run_state/src/lib.rs
pub enum RunStatus {
Running,
BlockedApproval, // 等待用户审批
BlockedAuth, // 等待认证
Completed,
Failed,
}值得注意的是,RunState 不是事件的简单累加,而是对"当前阻塞点"的建模。它与 append-only 的事件历史是互补关系:事件历史告诉你发生了什么,RunState 告诉你现在在等什么。
八、总结:架构演进的方法论启示
Engine v2 的设计给我们提供了几个有价值的架构启示:
1. 统一比拆分更有力量
v1 将 Session、Job、Routine、Sub-agent 作为独立概念,每个都有自己的生命周期和管理逻辑。v2 用一个 Thread + ThreadType 就统一了全部。这不是简单的代码合并,而是找到了"工作单元"这个更本质的抽象。
2. 显式优于隐式
v1 的 loop 迭代是隐式的——你无法在运行时询问"当前执行到第几次迭代"。v2 的 Step 使每次执行显式化,带来了可观测性、可恢复性和可调试性的全面提升。
3. 安全是设计出来的,不是补丁
GatePipeline 的 fail-closed 设计(无 None 变体)、ToolTier 的分层策略、ReDoS 的线性时间正则——这些安全特性都是架构层面的设计决策,而非事后修补。
4. 渐进式迁移的策略智慧
通过 Bridge 模式让 v1 和 v2 共存,用环境变量控制启用/禁用,IronClaw 展示了如何在生产环境中进行重大架构升级而不中断服务。这比"大爆炸式重写"务实得多。
Engine v2 的五个原语——Thread、Step、Capability、MemoryDoc、Project——构成了 IronClaw Agent 执行的坚实基础。在这个基础上,GatePipeline 提供了安全保证,Event System 提供了可观测性,CodeAct 提供了灵活性。这套架构不仅解决了当前的问题,更为未来的扩展预留了清晰的演化路径。
关于本文:本文基于 IronClaw 开源代码库(https://github.com/nearai/ironclaw)的深度分析,分析日期为 2026-05-17。如有不准确之处,欢迎指正。