IronClaw 学习笔记 07:Plan + Subagent

显式 Plan vs 隐式 ReAct、expected_outcome 自检、不达预期时的降级策略、Session 与 Sandbox 两种子 Agent 模式

March 16, 2026·16 min read·Yimin
#Rust#AI#Agent#IronClaw#学习笔记#Plan#Subagent#Orchestration

上一篇我们让 Agent 能被浏览器、脚本、手机远程访问。但一个能触达的 Agent 如果只会一步步地等 LLM 告诉自己下一步做什么,面对真正复杂的任务就像一个没有计划的施工队——有人有工具,但不知道先挖地基还是先砌墙。

🎯 这一篇要解决什么问题?

Phase 06 之后,tiny-claw 已经具备完整的推理、工具调用、记忆、技能、多渠道访问能力。但它的工作方式始终是逐步反应——用户说一句话,LLM 想一步做一步。

当前 tiny-claw 的工作方式:

  用户:"帮我清理三个月前的订单数据"
    → LLM 想了想,调了 bash_exec 看表结构
    → LLM 又想了想,直接开始 DELETE
    → 用户:"等等!你先备份了吗??"

  ❌ 不可逆操作没有提前展示计划
  ❌ 多步骤任务没有全局视角
  ❌ 复杂调研任务无法拆给多个 agent 并行做
  ❌ 执行用户代码没有沙箱隔离

这些"不行"揭示了两个缺失的能力:

  1. Plan——执行前展示完整计划,让用户和 LLM 都有预期
  2. Subagent——把子任务委派给独立的 agent 实例,支持并行和隔离

🧠 两种编排流派

在动手之前,先理解业界两种根本不同的多步骤编排策略。

显式 Plan:先想后做

┌─────────────────────────────────────────────────────────────┐
│                显式 Plan + 顺序执行                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  用户:"分析 sales.csv 并生成可视化报告"                       │
│                                                             │
│  Phase 1: Planning(一次 LLM 调用)                          │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ {                                                   │   │
│  │   "goal": "分析 sales.csv 并生成可视化报告",         │   │
│  │   "actions": [                                      │   │
│  │     { "tool": "bash_exec",                          │   │
│  │       "params": { "command": "head sales.csv" },    │   │
│  │       "expected_outcome": "获取 CSV 列名和行数" },   │   │
│  │     { "tool": "bash_exec",                          │   │
│  │       "params": { "command": "python analyze.py" }, │   │
│  │       "expected_outcome": "输出统计结果" },          │   │
│  │     { "tool": "write_file",                         │   │
│  │       "params": { "path": "report.md" },            │   │
│  │       "expected_outcome": "生成报告文件" }           │   │
│  │   ],                                                │   │
│  │   "confidence": 0.85                                │   │
│  │ }                                                   │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  Phase 2: Execution(按步骤机械执行)                        │
│    for action in plan.actions:                              │
│      execute_tool(action.tool, action.params)               │
│      sleep(100ms)                                           │
│                                                             │
│  代表项目: IronClaw (Rust)                                   │
│  优势: 可预览、可确认、省 LLM 调用                           │
│  劣势: 执行过程僵硬、不能根据结果调整方向                     │
│                                                             │
└─────────────────────────────────────────────────────────────┘

IronClaw 在 reasoning.rs 中完整实现了 ActionPlan——一次 LLM 调用生成 JSON 计划,然后 execute_plan() 逐步执行。每个 PlannedAction 都带有 expected_outcome 字段。

隐式 ReAct:边想边做

┌─────────────────────────────────────────────────────────────┐
│                隐式 ReAct Loop                                 │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  用户:"分析 sales.csv 并生成可视化报告"                       │
│                                                             │
│  Round 1: LLM 决定先看文件结构                               │
│    → bash_exec("head sales.csv")                            │
│    → 结果:4 列,10000 行                                    │
│                                                             │
│  Round 2: LLM 看到列名,决定用 pandas 分析                   │
│    → bash_exec("python analyze.py")                         │
│    → 结果:报错 "pandas not found"                            │
│                                                             │
│  Round 3: LLM 调整方向,先安装依赖                           │
│    → bash_exec("pip install pandas")                        │
│    → 结果:安装成功                                          │
│                                                             │
│  Round 4: 重新运行分析                                       │
│    → bash_exec("python analyze.py")                         │
│    → 结果:统计数据输出                                      │
│                                                             │
│  Round 5: 写报告                                             │
│    → write_file("report.md")                                │
│                                                             │
│  代表项目: Cursor, OpenClaw, 当前 tiny-claw                  │
│  优势: 灵活、能自我纠错、每步都能根据实际结果调整             │
│  劣势: LLM 调用次数多、用户无法提前看到全貌                   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

注意 Round 2 → Round 3 的转折:ReAct 在 pandas not found 时自动调整了方向。如果是显式 Plan,这里要么中断计划,要么需要复杂的重规划逻辑。

一个关键发现

IronClaw 完整实现了 Plan + Execute,但 use_planning 在整个代码库中全部设为 false

这不是 bug,而是工程判断——在大多数场景下,ReAct loop 已经足够好。Plan 是特定场景的优化,不是通用需求。


📋 什么时候真的需要 Plan?

Plan 的使用场景比直觉预期的少得多。

场景一:不可逆操作需要人工预览

任务:"清理生产数据库三个月前的订单"

× ReAct:直接开始执行,用户不知道会删什么
  → LLM 也许会先查再删——但你能保证它每次都这样做吗?

✓ Plan:先展示完整步骤
  Goal: 清理 orders 表 90 天前的数据
  ┌─────────────────────────────────────────────────────┐
  │ Step 1: 查询 orders 表记录数                        │
  │         预期:获得当前数据量                        │
  │ Step 2: 备份到 orders_backup_20260316               │
  │         预期:备份完成,行数与源一致                │
  │ Step 3: DELETE WHERE created_at < ...               │
  │         预期:删除约 N 万条旧订单                   │
  └─────────────────────────────────────────────────────┘
  ⚠️  警告:DELETE 操作不可逆

Plan 的价值:让用户在"不可逆"之前看到全貌。

场景二:步骤极多,上下文会撑爆

任务:"重构项目中所有 50 个文件的 import 语句"

× ReAct:每步都把前面所有文件的工具结果带进 context
  → 第 20 步时 context 可能超 128k token

✓ Plan:一次性规划好 50 个步骤
  → 执行时 context 只需 [Plan] + [当前步骤结果]
  → 上下文始终可控

场景三:执行环境无法调用 LLM

场景:离线批处理、网络受限的 Docker sandbox、低延迟要求的自动化

× ReAct:每步都要 LLM call,受限环境里跑不了
✓ Plan:在有 LLM 的阶段一次性规划好,执行阶段纯机械执行

其他场景呢?

都不需要 Plan。

  • "任务有 5 步" → 不需要 Plan,ReAct loop 本来就是多步骤的
  • "任务很复杂" → 不需要 Plan,复杂度不等于需要预先规划
  • "想让结果更可靠" → 不需要 Plan,LLM 拿到实际结果比拿到计划预期更可靠

🔧 plan 工具设计:让 Plan 成为 ReAct 的一部分

既然 Plan 只在特定场景有用,那不要把 Plan 做成一个独立的执行引擎。而是把它做成 ReAct loop 里的一个普通工具——plan 展示计划,但执行仍然由 ReAct loop 驱动。

核心理念:Plan 是展示,不是执行

┌─────────────────────────────────────────────────────────────┐
│           Plan 工具 vs Plan 执行引擎                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  IronClaw 的做法(Plan 执行引擎):                            │
│                                                             │
│    plan() → ActionPlan JSON                                 │
│      → execute_plan():                                      │
│          for action in plan.actions:                         │
│            execute_tool(action.tool, action.params)          │
│      → 问 LLM "完成了吗?"                                  │
│      → 不够 → 继续 run_agentic_loop()                       │
│                                                             │
│    问题:                                                    │
│    • 需要独立的 plan 执行引擎代码(200+ 行)                  │
│    • 执行过程僵硬,中途不调整                                │
│    • 所以 IronClaw 自己没开启这个功能                         │
│                                                             │
│  ─────────────────────────────────────────────────────────   │
│                                                             │
│  tiny-claw 的做法(Plan 工具 + Step Tracker):                 │
│                                                             │
│    plan() → 展示计划 → AgentLoop 解析 steps → ActivePlan     │
│      → 每轮 LLM 调用前,system prompt 末尾追加 plan progress │
│      → LLM 每轮都看到 Expected vs Actual 的对比              │
│      → ReAct loop 继续执行,Step Tracker 持续追踪            │
│                                                             │
│    优势:                                                    │
│    • plan 是一个普通工具,execute() 返回结构化 JSON           │
│    • 不需要 plan 执行引擎,ReAct loop 负责执行               │
│    • Step Tracker 每轮刷新,LLM 不会"忘记"检查预期          │
│    • 代码量极小(< 150 行)                                  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

两种触发方式

Plan 有两种触发场景,但共享同一套展示和写入逻辑:

/plan 命令plan 工具
谁触发用户(主动要求看计划)LLM(判断任务需要多步骤编排)
使用时机用户知道任务复杂或危险LLM 识别到不可逆操作或高复杂度
底层机制slash 命令 → 注入 plan 提示 → LLM 调用 plan 工具LLM 自主决定调用 plan 工具
确认流程无需确认,直接进入 ReAct loop无需确认,直接进入 ReAct loop

为什么不需要用户确认(y/n)?因为 Plan 是信息展示,不是权限审批。用户已经发了请求(/plan 清理数据),Plan 是让他们看到全貌。如果需要权限控制,那是 Safety Layer 的责任(Phase 04),不是 Plan 的。


✅ expected_outcome + Step Tracker:持续验证机制

这是整个 Plan 设计中最精巧的部分。

问题:一次性 reminder 会被遗忘

如果只把 plan 作为 tool_result 写入 context,LLM 执行了 5 轮工具调用之后,plan 的内容已经被推到 context 很远的位置。LLM 可能忘记检查 expected_outcome,也可能跳步。

解法:Active Plan — 每轮动态追踪

AgentLoop 在检测到 plan 工具被调用后,进入 Active Plan 模式。核心机制是每轮 LLM 调用前,在 system prompt 末尾动态追加 plan progress

┌─────────────────────────────────────────────────────────────┐
│  system prompt 末尾(每轮动态更新):                           │
│                                                             │
│  ## Active Plan                                             │
│  Goal: 清理 orders 表 90 天前的数据                          │
│                                                             │
│  ✅ Step 1: 查询 orders 表记录数                             │
│     Expected: 获得当前数据量                                │
│     Actual: 42857 rows                                     │
│                                                             │
│  →  Step 2: 备份到 orders_backup_20260316                    │
│     Expected: 备份完成,行数与源一致                         │
│                                                             │
│     Step 3: DELETE WHERE created_at < ...                   │
│     Expected: 旧记录被删除                                  │
│                                                             │
│  Before proceeding to the next step, verify that            │
│  the actual result matches the expected outcome.            │
│  If it deviates significantly, adjust your approach          │
│  or inform the user.                                        │
└─────────────────────────────────────────────────────────────┘

关键区别:Plan progress 不是一次性写入 context 然后被遗忘的,而是每轮 LLM 调用前动态更新在 system prompt 中。LLM 每轮都能看到当前在第几步、前面步骤的实际结果、以及下一步的预期。

验证是上下文做的,不是代码做的

expected_outcome 对两种角色都有价值:

  • 对用户:执行前知道每步预期产出什么,形成共同预期
  • 对 LLM:每轮都看到 Expected vs Actual 的并排对比,自然做验证

传统验证需要硬编码检查逻辑(result > 0 ? continue : abort)。Active Plan 把验证变成了持续的上下文——只要 LLM 每轮都能看到预期和实际的对比,验证就自动发生。零验证代码,但比一次性 reminder 可靠得多。


↩️ 不达预期时:Step Tracker 让偏差无处可藏

当实际结果不符合预期时会发生什么?来看一个完整的执行流程:

plan({
  goal: "清理服务器日志",
  steps: [
    { action: "查看各目录日志大小",
      expected_outcome: "获得占用空间列表" },
    { action: "备份最近7天日志到 /backup/",
      expected_outcome: "备份文件已创建,大小与源一致" },
    { action: "删除30天以前的日志",
      expected_outcome: "旧日志清除,磁盘释放 > 1GB" }
  ]
})

Step 1 顺利完成后,Step 2 执行失败:

LLM 在第 3 轮调用前看到的 system prompt 末尾:

┌──────────────────────────────────────────────────────┐
│ ## Active Plan                                       │
│ Goal: 清理服务器日志                                  │
│                                                      │
│ ✅ Step 1: 查看各目录日志大小                         │
│    Expected: 获得占用空间列表                         │
│    Actual: /var/log 占 8.2GB, /tmp/log 占 1.1GB      │
│                                                      │
│ →  Step 2: 备份最近7天日志到 /backup/                 │
│    Expected: 备份文件已创建,大小与源一致              │
│    Actual: No space left on device                   │
│                                                      │
│    Step 3: 删除30天以前的日志                         │
│    Expected: 旧日志清除,磁盘释放 > 1GB               │
│                                                      │
│ Before proceeding to the next step, verify that      │
│ the actual result matches the expected outcome...    │
└──────────────────────────────────────────────────────┘

LLM 判断: Step 2 Expected ≠ Actual → 需要调整

可能的决策:
  ├─ 清理 /backup 中的旧文件,腾出空间后重试
  ├─ 换备份路径(/tmp/backup/)
  ├─ 压缩率更高的方式备份
  └─ 告知用户:备份空间不足,是否继续?

Step Tracker 比一次性 reminder 可靠的原因:LLM 不是在 context 的某个遥远位置找到 expected_outcome,而是在每轮 system prompt 的末尾直接看到 Expected vs Actual 的对比。这个偏差无法被忽略。

不需要硬编码决策树。 LLM 拿到清晰的对比上下文(预期 + 实际 + 偏差),它比任何 if/else 都更擅长判断该怎么办。ReAct loop 本身支持多轮工具调用,LLM 有足够的 turns 来自我纠错。

这正是 tiny-claw 的设计优势:Plan 提供预期,Step Tracker 提供持续对比,ReAct 负责执行和纠错。三者组合得到的灵活度和可靠性远超纯 Plan 执行引擎。


🤖 Subagent:什么时候才真的需要?

在讲实现之前,先纠正一个常见误区:不是所有多步骤任务都需要 Subagent。

ReAct 已经能做的事

用户:"帮我分析 sales.csv,生成报告"

→ 同一个 agent,ReAct loop 连续调用工具:
  bash_exec("head sales.csv")    ← tool call #1
  bash_exec("python analyze.py") ← tool call #2
  write_file("report.md")        ← tool call #3

→ 这不需要 subagent——这只是连续工具调用。

ReAct loop 本身就处理:

  • 顺序多步骤(A → B → C)
  • 条件分支(工具失败 → LLM 换方式)
  • 同轮并行(LLM 返回多个 tool_calls → JoinSet 并行执行)

真正需要 Subagent 的四个场景

场景一:需要不同的角色 / 系统提示

父 agent(系统提示:项目经理)
  收到:"把这个功能实现一下"

  → spawn 子 agent(系统提示:后端工程师,只有代码工具)
  → spawn 子 agent(系统提示:测试工程师,只有测试工具)

  父 agent 做协调,子 agent 做执行。
  各自有不同 system prompt 和工具集——不会互相污染。

场景二:需要跨轮次并行

区分两种并行——同轮工具并行 vs 跨轮次 agent 并行:

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  同轮工具并行(JoinSet,已有):                               │
│  LLM 返回: [搜品牌A, 搜品牌B, 搜品牌C]                      │
│  → 三个工具同时执行,等全部完成                              │
│  → 结果合并回 context,继续下一轮                            │
│  → 每个并行项只是一次工具调用                                │
│                                                             │
│  ─────────────────────────────────────────────────────────   │
│                                                             │
│  跨轮次 agent 并行(需要 Subagent):                         │
│  任务:"同时让三个 agent 深度调研三个竞品"                    │
│  → 每个竞品调研需要 多轮 agent loop(搜→分析→写报告)        │
│  → 不能用 JoinSet 并行一个 "多轮 loop"                      │
│  → 需要 spawn 三个独立子 agent,各跑自己的 ReAct loop        │
│                                                             │
│  subagent_1: 搜 A → 分析 A → 写 A 报告(多轮)              │
│  subagent_2: 搜 B → 分析 B → 写 B 报告(多轮)              │
│  subagent_3: 搜 C → 分析 C → 写 C 报告(多轮)              │
│  父 agent 收集三份结果 → 汇总对比                            │
│                                                             │
└─────────────────────────────────────────────────────────────┘

关键区别:并行的粒度是"一次工具调用"还是"一个完整的 agent loop"。

场景三:需要上下文隔离

子任务产生大量输出,不应污染主对话上下文。或者子任务的失败不应影响父 agent 的状态。

场景四:需要安全沙箱

执行用户提供的代码或脚本。主进程不能直接 exec——用户代码可能删文件、耗尽资源、访问敏感路径。需要 Docker 容器级别的隔离。

判断公式

需要 subagent 吗?

问题 1:任务能在同一个 context / 角色下完成吗?
  → 是 → 不需要,继续 ReAct loop

问题 2:需要多个 agent 各自跑多轮 loop 并行?
  → 是 → 需要 subagent(并行 spawn)

问题 3:需要独立的 system prompt / 工具集?
  → 是 → 需要 subagent(角色分工)

问题 4:需要执行不可信代码?
  → 是 → 需要 subagent(Docker 沙箱)

🔀 Subagent 两种模式

Mode: Session(同进程,轻量)

┌─────────────────────────────────────────────────────────────┐
│              Session 模式                                     │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  父 AgentLoop.chat("同时调研三个竞品")                       │
│    │                                                        │
│    └─→ LLM 同时调用三个 subagent(JoinSet 并行):           │
│          subagent({ task: "调研A", mode: "session" })        │
│          subagent({ task: "调研B", mode: "session" })        │
│          subagent({ task: "调研C", mode: "session" })        │
│            │                                                │
│            每个内部:                                         │
│              new AgentLoop → .chat(task) → 返回结果         │
│              多轮: web_search → 分析 → write_file → 总结    │
│            │                                                │
│    └─→ 父 LLM 收到三份结果 → 汇总对比                       │
│                                                             │
│  特点:                                                       │
│    ✅ 毫秒级 spawn                                          │
│    ✅ 通信零开销(同进程)                                   │
│    ✅ 共享 provider 和 registry(Arc 克隆)                   │
│    ❌ 隔离弱(子 agent panic 可能影响主进程)                 │
│    ❌ 无法安全执行不可信代码                                 │
│                                                             │
│  适合: 角色分工、并行调研、上下文隔离                        │
│                                                             │
└─────────────────────────────────────────────────────────────┘

实现极其简洁:SubagentToolexecute() 方法只需要 new 一个 AgentLoop、set system prompt、调 .chat(task)、返回结果。全部代码不到 80 行。

Mode: Sandbox(Docker 容器,强隔离)

┌─────────────────────────────────────────────────────────────┐
│              Sandbox 模式                                     │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  父 AgentLoop.chat("运行这段 Python 脚本")                  │
│    │                                                        │
│    └─→ LLM 调用 subagent({ mode: "sandbox", task: "..." })  │
│          │                                                  │
│          ├─ docker run                                      │
│          │    --rm                                          │
│          │    --network none          # 默认断网             │
│          │    --memory 512m                                  │
│          │    --cpus 1.0                                     │
│          │    --pids-limit 100        # 防 fork bomb         │
│          │    -e OPENAI_API_KEY=$KEY  # 直接注入             │
│          │    -v $dir:/workspace                             │
│          │    tiny-claw:latest                               │
│          │    --message "$task" --no-color                    │
│          │                                                  │
│          ├─ 等待完成(timeout 10 min)                       │
│          │                                                  │
│          └─→ 返回容器 stdout 给父 agent                     │
│                                                             │
│  特点:                                                       │
│    ✅ 强隔离:文件系统、网络、进程完全隔离                   │
│    ✅ 安全:用户代码不影响主进程                             │
│    ✅ 资源限制:内存、CPU、进程数受控                        │
│    ❌ 冷启动慢(秒级)                                      │
│    ❌ 资源消耗大                                             │
│                                                             │
│  适合: 执行用户代码、不可信输入、需要文件系统隔离            │
│                                                             │
└─────────────────────────────────────────────────────────────┘

为什么 tiny-claw 不需要 ProxyLlmProvider

这是 tiny-claw 比 IronClaw 简单得多的地方。

IronClaw 的 Docker 容器不能直接调用 LLM——API Key 不在容器内。所以 IronClaw 实现了一套完整的代理层:

IronClaw:
  容器 → ProxyLlmProvider → HTTP → 宿主 Orchestrator API → 真正的 LLM API
  容器 → WorkerHttpClient → HTTP → 宿主 Orchestrator API → 凭据存储

  需要:orchestrator HTTP API(6+ endpoints)、ProxyLlmProvider、TokenStore、
        CredentialGrant 权限管理、worker token 认证

tiny-claw 的做法简单得多:

tiny-claw:
  docker run -e OPENAI_API_KEY=$KEY ... tiny-claw --message "$task"

  容器内的 tiny-claw 直接用注入的 API Key 调 LLM。
  不需要 proxy、不需要 orchestrator API、不需要 token 认证。

安全权衡:tiny-claw 是个人工具,容器虽然拿到了 API Key,但容器本身是 --network none(默认断网)或受限网络。对于个人使用场景,这个权衡完全合理。


🛡️ 深度保护:防止无限递归

Subagent 能 spawn 子 agent,子 agent 理论上也能 spawn 孙 agent。如果不加限制,一个 LLM 幻觉就能触发无限递归。

max_depth = 3(默认)

depth 0: 主 agent(用户对话)
depth 1: 子 agent                → 可以 spawn
depth 2: 孙 agent               → 可以 spawn
depth 3: 超过限制               → 返回错误,不再 spawn

实现方式:每个 SubagentTool 在构造时记录 current_depth。spawn 子 agent 时,子 agent 的 SubagentToolcurrent_depth = parent_depth + 1。超过 max_depth 时,execute() 直接返回错误信息——LLM 看到错误后会调整策略。

有趣的是,IronClaw 没有实现深度保护。它的 Job 系统不支持 Job spawn 子 Job,所以递归不是问题。但 OpenClaw 有 callerDepth >= maxSpawnDepth 的检查——因为它的 Session 模式支持嵌套。


📊 完整对比

维度IronClawOpenClawtiny-claw (Phase 07)
Plan 触发Worker 内部 use_planning无显式 Planplan 工具 + /plan 命令
Plan 执行独立引擎 execute_plan()ReAct loop + ActivePlan step tracker
Plan 状态已实现但未启用启用
预期验证expected_outcome(未使用)Step Tracker 每轮注入 Expected vs Actual
Subagent 载体Docker 容器同进程 Session两种都有
LLM 访问ProxyLlmProvider 代理Gateway RPC直接注入 API Key
深度保护maxSpawnDepthmax_depth=3
通信方式SSE + inject channelGateway 事件同进程直调 / Docker stdout
代码量~1000 行~500 行~200 行

🧪 动手试试

Phase 07 之后 tiny-claw 能展示计划和委派子任务。试试这些场景:

场景操作观察点
用户主动要求计划/plan 帮我清理旧日志Agent 先展示计划再执行
LLM 自动展示计划"清理三个月前的数据库记录"LLM 判断不可逆,主动调用 plan 工具
计划不达预期步骤执行报错LLM 对比 expected_outcome 与实际结果,自动调整
并行子 agent"同时调研 A、B、C 三个竞品"三个子 agent 并行跑,父 agent 汇总
角色分工"让一个分析师和一个工程师协作"子 agent 有不同 system prompt
深度保护让子 agent 尝试 spawn 子子子 agent超过 max_depth 时返回错误

📝 本篇总结

理解项描述
两种流派显式 Plan(先想后做) vs 隐式 ReAct(边想边做)
Plan 真正需要时不可逆操作预览、步骤极多上下文爆炸、离线执行
Plan 作为工具展示计划 → AgentLoop 解析 steps → ActivePlan → ReAct loop 继续
Step Tracker每轮 system prompt 末尾追加 plan progress(Expected vs Actual)
expected_outcomeLLM 每轮看到预期+实际的并排对比,持续验证
不达预期降级偏差在 step tracker 中无处可藏——LLM 自然处理
两种触发/plan 用户主动、plan 工具 LLM 主动,底层逻辑统一
Subagent 判断角色分工、跨轮次并行、上下文隔离、安全沙箱——其他场景不需要
Session 模式同进程 new AgentLoop,轻量、快速、共享资源
Sandbox 模式Docker 容器,强隔离、直接注入 API Key、无需 proxy
深度保护max_depth=3,每层 spawn 时 depth+1,超限返回错误

核心洞察

1. Plan 是信息展示,不是执行引擎

IronClaw 写了 200+ 行的 execute_plan() 来按步骤执行计划——然后没启用它。tiny-claw 的做法是:Plan 只负责"展示",执行仍然交给 ReAct loop。结果是代码更少、更灵活、而且真正启用了

2. Step Tracker 让 expected_outcome 真正起作用

一次性把 plan 写入 context 的问题是 LLM 会"忘记"。Active Plan 通过每轮在 system prompt 末尾动态追加 Expected vs Actual 对比,把验证变成了持续的、不可忽略的上下文。零验证代码,但比硬编码 if/else 更可靠——因为 LLM 理解语义,而 if/else 只能比较字符串。

3. 不需要 Subagent 的场景远比需要的多

多步骤 ≠ 需要 Subagent。连续工具调用、同轮并行、条件分支——这些 ReAct loop 全都能做。只有角色分工、跨轮次并行、安全沙箱这几个场景才真的需要 spawn 独立 agent。过度使用 Subagent 只会增加复杂度。


下一篇IronClaw 学习笔记 08:WASM 沙箱 —— WASM 安全模型、Capability-based 权限、Credential Injection、三层 Tool 体系。