Harness实战:Plan模式——让Agent先想清楚再动手
Agent收到任务就立刻开干,简单任务没问题,复杂任务容易走弯路。一个技巧:第一次API调用不传tools,强制模型只输出计划文本;用户确认后再带tools执行。用一次额外调用换取执行方向的确定性。
写在前面
前几篇我们的 Agent 收到任务就立刻开干——模型边想边调工具,一步一步摸着石头过河。
对简单任务没问题。但当任务复杂时,比如"重构这个模块的错误处理",直接上手容易走弯路:改到一半发现方向不对再回头,浪费大量 token。
怎么让模型先想清楚再动手?这就是 Plan → Execute 模式。
为什么需要先计划?
对比两种执行方式。
不做计划(当前模式):
用户: 给这个项目添加单元测试
模型: [调用 bash: ls] → [调用 read_file: main.py] → [调用 write_file: test_main.py]
→ 发现遗漏了 utils.py → [调用 read_file: utils.py] → [调用 edit_file: test_main.py]
→ 运行测试失败 → [调用 edit_file: test_main.py] → ...模型走一步看一步,中途才发现遗漏,回头修改,来来回回好几轮。
先做计划:
用户: 给这个项目添加单元测试
模型(计划阶段,无工具):
1. 先读取项目结构了解有哪些模块
2. 读取 main.py 和 utils.py 的代码
3. 为每个模块设计测试用例
4. 创建 test_main.py 和 test_utils.py
5. 运行测试确保通过
用户: 好的,执行吧
模型(执行阶段,有工具):
按计划有条理地完成...
好处很明显:
- 减少无效调用 — 先看全貌再动手,避免"改了 A 才发现还需要 B"
- 用户可以介入 — 计划出来后用户可以审核、调整、拒绝
- 可追溯 — 执行过程有计划对照,出问题知道偏离了哪一步
- 省 token — 避免走弯路的反复调用
两种实现方式
方式一:两阶段调用(推荐)
最干净的做法——第一次调用不传 tools,强制模型只输出文本计划;第二次调用传 tools,让模型执行计划。
def plan_then_execute(messages: list):
# ── 阶段 1:计划(不给工具,模型只能输出文本) ──
plan_messages = messages + [{
"role": "user",
"content": "Before acting, outline your plan step by step. Do NOT use any tools yet."
}]
plan_response = client.messages.create(
model=MODEL,
system=SYSTEM,
messages=plan_messages,
# 关键:不传 tools 参数,模型无法调用工具
max_tokens=2000,
)
plan_text = plan_response.content[0].text
print(f"\n📋 Plan:\n{plan_text}\n")
# 用户确认
confirm = input("Execute this plan? (y/n) ")
if confirm.lower() != "y":
print("Plan cancelled.")
return
# ── 阶段 2:执行(正常 agent loop,带工具) ──
messages.append({"role": "assistant", "content": plan_response.content})
messages.append({"role": "user", "content": "Good plan. Now execute it step by step."})
agent_loop(messages) # 复用之前写的 agent_loop为什么不传 tools 就能强制输出计划?因为模型没有工具可调用时,stop_reason 一定是 end_turn,只能返回文本。这比在 system prompt 里写"请先计划再执行"要可靠得多——prompt 是"请求",不传 tools 是"约束"。
方式二:System Prompt 引导
更轻量的做法——通过 system prompt 告诉模型先输出计划文本,再调用工具:
SYSTEM_WITH_PLAN = f"""You are a coding agent at {WORKDIR}.
When given a task:
1. FIRST output your plan as a numbered list (text only, no tool calls)
2. THEN execute the plan using tools
Always plan before acting."""这种方式更简单,但有个问题:模型可能跳过计划直接调工具(尤其是简单任务时)。而且用户无法在计划和执行之间介入审核。
对比
| 两阶段调用 | System Prompt | |
|---|---|---|
| 计划可靠性 | 100%(没有工具可调) | 依赖模型遵从 prompt |
| 用户审核 | 天然支持(两阶段之间) | 需要额外逻辑 |
| 实现复杂度 | 多一次 API 调用 | 改一行 prompt |
| 适合场景 | 高风险操作、复杂任务 | 日常轻量任务 |

完整实现
在 agentic-demo.py 基础上增加 plan 模式,用户输入 /plan 前缀触发。
工具定义和 Agent Loop 和之前完全一样,只新增一个 plan_then_execute 函数:
def plan_then_execute(messages: list, task: str):
# 阶段 1:生成计划(不传 tools)
plan_messages = messages + [
{"role": "user", "content": task},
{"role": "user", "content": "Analyze the task and outline a step-by-step plan. Do NOT execute anything yet."},
]
print("\n⏳ Generating plan...\n")
plan_response = client.messages.create(
model=MODEL,
system=SYSTEM,
messages=plan_messages,
max_tokens=2000,
# 注意:没有 tools 参数
)
plan_text = plan_response.content[0].text
print(f"📋 Plan:\n{plan_text}\n")
# 用户确认
confirm = input("Execute? (y/n/or type feedback) ")
if confirm.strip().lower() == "n":
print("Cancelled.")
return
feedback = ""
if confirm.strip().lower() not in ("y", "yes"):
feedback = f" User feedback: {confirm}"
# 阶段 2:执行(把计划作为上下文,启动正常 agent loop)
messages.append({"role": "user", "content": task})
messages.append({"role": "assistant", "content": plan_response.content})
messages.append({"role": "user", "content": f"Good plan.{feedback} Now execute it step by step."})
print("\n⚡ Executing...\n")
agent_loop(messages)主程序加一个 /plan 前缀的判断:
if __name__ == "__main__":
history = []
while True:
query = input("❯ ")
if query.strip().lower() in ("q", "exit", ""):
break
if query.strip().startswith("/plan "):
task = query.strip()[6:]
plan_then_execute(history, task)
else:
history.append({"role": "user", "content": query})
agent_loop(history)就这么多。核心只是一个函数,复用已有的 agent_loop。
运行效果
普通模式(直接执行)
❯ 当前目录有什么文件
> bash: agentic-demo.py harness-agent-loop.md ...和之前一样,收到就干。
计划模式(/plan 前缀)
❯ /plan 给当前目录的所有 py 文件添加文件头注释
⏳ Generating plan...
📋 Plan:
1. 用 bash 列出当前目录的所有 .py 文件
2. 逐个读取每个 .py 文件的前几行,检查是否已有文件头注释
3. 对没有注释的文件,用 edit_file 在文件开头插入统一格式的注释
4. 最后用 bash 验证所有文件的头部
Execute? (y/n/or type feedback) y
⚡ Executing...
> bash: agentic-demo.py harness.py llm-claude.py ...
> read_file: import json ...
> edit_file: Edited agentic-demo.py
...注意 Execute? 那一步,用户可以输入:
y— 直接执行n— 取消- 任意文字 — 作为反馈传给模型,比如"跳过 harness.py,那个是空文件"
这就是 Plan 模式的核心价值:在执行前插入一个人类审核点。
核心机制解析
整个 plan 模式只改了一个地方——plan_then_execute 函数。它和 agent_loop 的关系:
plan_then_execute
├── 第 1 次 API 调用(无 tools)→ 输出计划文本
├── 用户确认
└── agent_loop(有 tools)→ 正常执行
关键技巧是第一次调用不传 tools:
# 计划阶段 — 没有 tools,模型被迫只输出文本
plan_response = client.messages.create(
model=MODEL,
system=SYSTEM,
messages=plan_messages,
max_tokens=2000,
# 没有 tools=TOOLS
)
# 执行阶段 — 正常带 tools 的 agent loop
agent_loop(messages)这比在 prompt 里写"请先计划"要可靠得多。Prompt 是"建议",模型可能忽略;不传 tools 是"约束",模型物理上无法调用工具。
Claude Code 的 Plan Mode
Claude Code 的 Plan Mode(Shift+Tab 切换)就是这个模式的生产级实现:
- 进入 plan mode 后,模型只输出分析和计划,不执行任何工具
- 用户确认后切回执行模式,模型按计划操作
- 还可以在计划中和模型对话,反复修正策略
底层原理完全一样:plan mode 时不给工具,execute mode 时给工具。
小结
- 两阶段调用 — 不传 tools 强制输出计划,传 tools 正常执行
- 用户审核点 — 计划和执行之间插入确认,用户可以修正方向
- 实现极简 — 核心只是一个
plan_then_execute函数,复用已有的agent_loop
这个模式的本质是:用一次额外的 API 调用换取执行方向的确定性。对简单任务可能显得多余,但对复杂任务——重构、迁移、批量修改——能显著减少无效操作和 token 浪费。