1. 为什么 HarnessAgent 需要计划模式
如果你用过 AgentScope Java 的 HarnessAgent 跑多步任务,大概率遇到过这种场景:你让它"帮我搭一个用户认证模块",它上来就开始写代码,写到一半发现数据库表结构没设计,回头改,改完发现接口定义又对不上,来回折腾四五轮。更糟的是,有些操作是破坏性的——文件已经覆盖了,命令已经执行了,你想喊停都来不及。
这就是 ReAct 循环的天然缺陷:思考、调工具、看结果、再思考,每一步都是临场判断,没有全局视角。任务越复杂,跑偏的概率越高。
计划模式(Plan Mode)要解决的就是这个问题。开启enablePlanMode(true)之后,HarnessAgent 会把一次复杂任务拆成两个阶段:先进入只读的 PLAN 阶段,agent 只能看文件、调研现状,然后把计划写进workspace/plans/PLAN.md;你审完这份计划说"行",它才进入 BUILD 阶段真正动手。这就像装修队先出图纸给你审,审过了再砸墙布线,而不是一进门就开始砸。
这篇面向 AgentScope Java 新手,从零讲清楚enablePlanMode开启后 HarnessAgent 怎么把复杂指令拆成可执行步骤,给出可复制的配置骨架,并用一次多步任务让你直观看到计划模式前后的行为差异。适合正在用 HarnessAgent 做多步工作流、又不想让 agent 跑偏的开发者。
2. 前置准备:模型接入与依赖
在写代码之前,先把模型接入这块理清楚。AgentScope Java 的 HarnessAgent 需要一个 ChatModel 实例,你可以用 DashScope、OpenAI 兼容接口,或者通过 TaoToken 这类聚合网关来统一管理模型调用。
TaoToken 的定位是模型 API 聚合平台,把不同厂商的模型接口统一成一套 OpenAI 兼容协议,你换模型时不用改代码,只换 modelName 就行。对于新手来说,好处是不用同时维护好几套 SDK 和 Key。
注册和拿 Key 的流程不复杂:进官网注册账号,在控制台创建一个 API Key,然后把它配到环境变量里。接入文档里有各语言的调用示例,Java 这边直接用 OpenAI 兼容的 base_url 即可。
# 把 Key 配到环境变量,避免硬编码进代码 export TAOTOKEN_API_KEY="sk-你的key"如果你用的是 DashScope 原生接口,那就配DASHSCOPE_API_KEY,两者选其一即可。下面示例里我用 TaoToken 的兼容端点,方便你换模型。
依赖方面,AgentScope Java 的核心包和 harness 包都要引进来。Maven 里大致是这样:
<dependency> <groupId>io.agentscope</groupId> <artifactId>agentscope-core</artifactId> <version>2.0.0</version> </dependency> <dependency> <groupId>io.agentscope</groupId> <artifactId>agentscope-harness</artifactId> <version>2.0.0</version> </dependency>版本号以你实际拉到的为准,2.0 之后enablePlanMode才下沉到 HarnessAgent,1.x 时代用的是 PlanNotebook,这个后面会讲迁移。
3. 可复制的 HarnessAgent 配置骨架
先看一个最小的计划模式配置。核心就一行.enablePlanMode(true),但周边几个参数决定了 agent 的行为边界。
import io.agentscope.core.agent.RuntimeContext; import io.agentscope.core.message.UserMessage; import io.agentscope.core.model.OpenAIChatModel; import io.agentscope.harness.HarnessAgent; import java.nio.file.Path; import java.util.List; public class PlanModeDemo { public static void main(String[] args) { OpenAIChatModel model = OpenAIChatModel.builder() .apiKey(System.getenv("TAOTOKEN_API_KEY")) .baseUrl("https://taotoken.net/api") .modelName("qwen-plus") .build(); HarnessAgent agent = HarnessAgent.builder() .name("project_planner") .sysPrompt("你是一个项目经理,遇到多步任务先用 plan mode 写计划。") .model(model) .workspace(Path.of("./workspace")) .enablePlanMode(true) // 关键:开启计划模式 .build(); agent.call( List.of(new UserMessage("user", """ 我下周要办一场 200 人技术大会,请帮我做一份执行计划: - 场地 - 议程 - 嘉宾 - 报名 - 现场 """)), RuntimeContext.empty()) .block(); } }几个参数值得单独说:
workspace是 agent 的工作目录,计划文件会落在workspace/plans/PLAN.md,todo 状态会持久化到workspace/state/session-*.json。这个目录建议纳入 git 管理,计划变更就有版本记录。
sysPrompt里最好明确告诉 agent"多步任务先写计划",虽然 plan mode 本身有强制门控,但系统提示词能减少它误判单步任务的情况。
enablePlanMode(true)是开关。关掉它,agent 就是普通 ReAct 循环;打开它,agent 在 PLAN 阶段会被拦截所有非只读工具调用。
跑完这段代码,你去workspace/plans/PLAN.md看,大致会是这样:
# 技术大会执行计划 ## Step 1 — 锁定场地 - 目标:确定可容纳 200 人的宴会厅 - 负责:行政 - 完成标准:拿到合同 + 付款凭证 ## Step 2 — 公布议程 - 目标:议程在官网公开 - 依赖:Step 1 ## Step 3 — 确认嘉宾 ...注意,这时候 agent 还没真正去订场地、发议程,它只是把计划写下来了。这就是 PLAN 阶段和 BUILD 阶段的分界。
4. 四个内置工具到底做什么
开启计划模式后,HarnessAgent 会注入四个内置工具,理解它们的分工是用好这个功能的前提。
| 工具 | 何时被调用 | 副作用 |
|---|---|---|
plan_enter | agent 判断这是多步任务 | 写入 PLAN.md 头部,进入只读计划模式 |
plan_write | 写或修订计划步骤 | 修改 workspace/plans/PLAN.md |
plan_exit | 所有计划步骤完成 | 收尾 PLAN.md,状态置为 DONE |
todo_write | 任何时候记录子任务 | 写入 AgentState 的 todo 列表 |
plan_enter是进入计划模式的入口。LLM 调了它,PlanModeMiddleware 就开始拦截所有非只读工具,返回 DENIED。这时候 agent 只能读文件、查资料,不能写文件、不能调部署接口。
plan_write负责写和改计划。你中途想调整方向,比如"把第三步的嘉宾改成都用远程连线",agent 会读当前 PLAN.md,调plan_write改对应步骤,然后输出确认。
plan_exit是退出信号。所有步骤做完,agent 调它,PLAN.md 收尾,执行能力恢复。
todo_write严格说不是计划工具,而是待办工具。plan 管长期大计划,todo 管短期可勾掉的清单。两者在计划模式下是配对的:agent 一边按 PLAN.md 推进,一边用 todo 跟踪每个子任务的完成状态。
这里有个容易踩的坑:plan_enter和plan_exit是成对的,如果 agent 调了plan_enter但任务中途失败没调plan_exit,下次会话可能还停在只读模式。排查时先看 PLAN.md 的状态字段。
5. 验证一次多步任务的行为差异
光看配置不够直观,我们跑一个对比实验。任务用"调研三个城市的咖啡店数量",这是典型的多步任务,每步都要调搜索工具。
先跑不开计划模式的版本:
HarnessAgent agent = HarnessAgent.builder() .name("researcher") .model(model) .workspace(Path.of("./workspace")) // .enablePlanMode(true) // 注释掉 .build();你会看到 agent 直接开始搜第一个城市,搜完搜第二个,中间没有任何计划输出。如果它搜到一半理解偏了,比如把"咖啡店数量"理解成"咖啡品牌数量",你只能等它全跑完才发现。
再跑开启计划模式的版本,这次加上 subagent 协作:
import io.agentscope.harness.SubagentDeclaration; SubagentDeclaration research = SubagentDeclaration.builder() .name("research") .description("做单点调研;输入主题,输出 200 字摘要") .inlineAgentsBody("你是一个研究员,每主题输出 200 字摘要。") .build(); HarnessAgent agent = HarnessAgent.builder() .name("research_lead") .sysPrompt(""" 你是一个研究主管。接到多主题调研任务时: 1. 先 plan_enter 写出计划 2. 对每个主题 async spawn research subagent 3. 用 todo_write 跟踪每个 subagent 的状态 4. 等所有 subagent 回来后 plan_exit """) .model(model) .workspace(Path.of("./workspace")) .subagent(research) .enablePlanMode(true) .build(); agent.call( List.of(new UserMessage("user", """ 请调研以下 3 个主题: 1. 杭州咖啡店数量 2. 上海咖啡店数量 3. 成都咖啡店数量 """)), RuntimeContext.empty()) .block();跑完看目录结构:
workspace/ ├── plans/ │ └── PLAN.md # 3 步计划 └── state/ └── session-*.json # 包含 todo 列表PLAN.md 里会列出三个调研步骤,session 文件里能看到 todo 的勾选状态。行为差异很明显:不开计划模式,agent 边想边做;开了计划模式,agent 先把三步写清楚,你审完再放行。
提示:计划模式下 subagent 调度不受影响,
agent_spawn、agent_send、agent_list照常可用。主 agent 可以一边写计划一边 spawn 子 agent,这是 2.0 推荐的中型工作流模式。
6. 本篇常见错排查
新手用计划模式,报错和困惑集中在几个地方,我按出现频率排一下。
问题一:agent 不进入计划模式,直接开始执行。先确认enablePlanMode(true)真的加上了,再看 sysPrompt 有没有引导它识别多步任务。有些模型对"多步"的判断偏保守,你可以在提示词里明确"超过两步的任务必须先 plan_enter"。
问题二:PLAN.md 没生成。检查 workspace 目录是否有写权限,以及路径是不是相对路径导致的定位问题。建议用绝对路径或确认工作目录。
问题三:agent 卡在只读模式出不来。大概率是plan_enter调了但plan_exit没调。打开 PLAN.md 看状态字段,如果是 PLAN 状态,手动编辑或重新发起一轮让它收尾。
问题四:想让人工介入放行。给plan_enter配一条 Permission 规则 ASK:
import io.agentscope.core.permission.*; PermissionContextState perms = PermissionContextState.builder() .mode(PermissionMode.ACCEPT_EDITS) .addAskRule("plan_enter", new PermissionRule("plan_enter", null, PermissionBehavior.ASK, "userSettings")) .build();这样每次 agent 调plan_enter,前端会弹出"agent 写了如下 plan,是否放行",这就是 HITL(Human In The Loop)模式。
问题五:从 1.x 迁移过来找不到 PlanNotebook。2.0 移除了io.agentscope.core.plan.PlanNotebook,对应关系是:PlanNotebook.createPlan(...)换成enablePlanMode(true)加 LLM 自己调plan_enter;notebook.addStep(...)换成plan_write;notebook.finishStep(idx)换成plan_write改对应步骤状态;notebook.getCurrentPlan()换成直接读workspace/plans/PLAN.md。
注意:PLAN.md 是普通 Markdown,你可以直接编辑它,下一轮 agent 推理时会读到人编辑后的版本。这个特性让"人改计划"和"agent 改计划"能无缝衔接。
7. 继续深入的方向
计划模式跑通之后,下一步可以往两个方向走。一是把 Permission 规则配细,让不同工具走不同审批策略,比如plan_enter走 ASK、只读工具走 ALLOW、写文件走 ASK。二是把计划模式和 subagent 编排结合,主 agent 负责写计划和汇总,子 agent 负责并行执行,适合调研、批量重构这类可以拆分的任务。
如果你还没配好模型接入,可以先去 TaoToken 控制台创建一个 API Key,接入文档里有 Java 的完整示例。想先直观感受计划模式的行为差异,用模型对话跑一轮多步任务,看它会不会先写计划再动手,比读文档快得多。长期做编码类 Agent 的话,Coding Plan 那边有更完整的工程化配置可以参考。