Plan-and-Solve 不应被设计成“Planner 输出字符串列表,Executor 逐条调用 LLM”的工作流,而应作为 Harness 中一种可插拔的决策策略(Agent Mode):
- Planner:决定“要完成什么步骤、每一步期望达成什么”。
- Executor:将步骤编译为具体 Action,路由至 Tool / Provider / 子能力,并将 Observation 写回上下文。
- Harness:统一管理运行生命周期、状态、上下文、事件、工具、安全与失败策略。
- Plan-and-Solve Mode:不直接持有底层 Tool 实现,不绕过 Harness 管理状态。
一个需要提前纠正的假设:并非每个 Step 都应该直接映射为 Tool 调用。某些步骤是推理、归纳、生成或判断,应使用 llm.generate 一类的标准能力;另一些才是 file.read、web.search、db.query 等外部工具。统一的关键是:每一步都最终执行为标准化 Action,并产生 Observation。
User Request
│
▼
Agent Harness
├── Run / Lifecycle Manager
├── State Store
├── Context Manager
├── Tool Registry + Tool Router
├── Provider Manager
├── Event Bus
├── Policy / Permission / Error Handling
│
▼
PlanSolveAgent (Agent Mode)
├── Planner
├── Plan Validator
├── Step Scheduler
├── Action Resolver
├── Executor Orchestrator
└── Recovery Controller
│
▼
Standard Action Invocation
│
├── Tool Action: file.read / web.search / db.query
├── LLM Action: llm.generate / llm.classify / llm.extract
└── Control Action: ask_user / wait / finish
│
▼
Observation → Context Update → Next Step / Recovery / Completion
| 模块 | 职责 | 不应负责 |
|---|---|---|
| Agent Harness | Run 生命周期、状态持久化、Context 存取、工具注册和调用、Provider 选择、事件、鉴权、超时、重试、审计 | 具体计划策略、如何拆任务、何时重规划 |
| PlanSolveAgent | 规划、计划校验、Step 调度、Action 决策、根据 Observation 判断下一步、失败恢复策略 | 直接访问工具 SDK、直接修改全局状态存储、直接处理 Provider 底层细节 |
| Planner | 生成或修订结构化 Plan | 执行工具 |
| Executor | 将当前 Step 解析为可执行 Action,并消费 Observation | 独立维护完整 Agent 生命周期 |
| Tool Router | 根据 Action 路由到具体工具 | 决定业务步骤或重规划 |
| Tool Registry | 工具元数据、参数 Schema、权限、能力描述 | Agent 推理逻辑 |
这种划分可避免未来每种 Mode 各自实现一套工具调用、状态机和错误处理,最终形成不可维护的三套 Agent Runtime。
建议 Harness 只依赖统一的 AgentMode 协议,而不是依赖 Plan-and-Solve 的内部对象。
AgentMode
├── initialize(run_context) -> ModeState
├── next(mode_context) -> AgentDecision
├── on_observation(mode_context, observation) -> AgentDecision
├── on_error(mode_context, error) -> AgentDecision
├── can_resume(mode_context) -> bool
└── finalize(mode_context) -> FinalAnswer
其中 AgentDecision 为 Mode 输出给 Harness 的统一指令:
AgentDecision
├── type: EXECUTE_ACTION | WAIT | REPLAN | ASK_USER | COMPLETE | FAIL
├── action?: Action
├── plan_patch?: PlanPatch
├── message?: string
└── reason_code?: string
Harness 执行 AgentDecision,将结果封装为 Observation 回调给 Mode。这样 ReAct、Plan-and-Solve、Reflection 都可在同一 Runtime 中运行。
1. Harness 创建 AgentRun 与 Context
2. PlanSolveAgent.initialize()
3. Planner 生成 Plan
4. Plan Validator 校验并持久化 Plan
5. Scheduler 选取当前 Step
6. Executor 将 Step 解析成 Action
7. Harness Tool Router 调用 Tool / Provider
8. Harness 生成 Observation 并写入 Context
9. PlanSolveAgent.on_observation()
10. 标记 Step 状态,继续下一 Step / 修订计划 / 询问用户 / 完成
11. Harness 统一输出结果、事件与审计记录
建议所有对象具备稳定 ID、版本号、时间戳和可序列化能力,便于持久化、断点恢复、前端展示及审计。
Plan
├── id: string
├── version: integer
├── goal: string
├── assumptions: string[]
├── steps: Step[]
├── status: PlanStatus
├── created_at: datetime
├── updated_at: datetime
├── created_by: "planner" | "replanner" | "human"
├── parent_plan_id?: string
└── metadata: object
字段说明:
| 字段 | 作用 |
|---|---|
id |
支持关联 Run、日志、事件和前端展示。 |
version |
支持重规划,防止直接覆盖原计划导致无法审计。 |
goal |
明确计划对应的用户目标,防止执行偏离。 |
assumptions |
显式记录路径、权限、数据可用性等前提。 |
steps |
有序或依赖式的执行单元。 |
status |
DRAFT / ACTIVE / SUPERSEDED / COMPLETED / FAILED。 |
parent_plan_id |
新计划可追溯到被替换的旧计划。 |
建议第一期使用有序步骤;但 Step.depends_on 应预留,后续可支持 DAG、并发与条件分支。
Step
├── id: string
├── order: integer
├── title: string
├── objective: string
├── action: ActionSpec
├── depends_on: string[]
├── status: StepStatus
├── retry_policy: RetryPolicy
├── failure_policy: FailurePolicy
├── expected_output: OutputContract
├── result_ref?: string
├── observation_refs: string[]
├── started_at?: datetime
├── completed_at?: datetime
└── metadata: object
关键点:
title用于人类可读展示,例如“读取项目入口文件”。objective表达目的,而不只是命令,方便失败后判断是否能换路径完成。action是结构化执行意图,而不是自然语言字符串。expected_output定义结果要求,例如“输出入口模块路径及其主要职责”。failure_policy决定失败后可重试、跳过、局部重规划还是全局重规划。result_ref与observation_refs使用引用,避免 Context 和 Plan 中大量重复存放大文本。
推荐状态:
PENDING → READY → RUNNING → SUCCEEDED
├→ RETRYING → RUNNING
├→ BLOCKED
├→ SKIPPED
└→ FAILED
需要区分“计划阶段的 ActionSpec”与“运行阶段的 Action”。
ActionSpec
├── kind: "tool" | "llm" | "control"
├── name: string
├── arguments: object
├── argument_sources: object
├── timeout_ms?: integer
├── idempotency_key?: string
└── confirmation_required?: boolean
Action
├── id: string
├── run_id: string
├── step_id: string
├── kind: "tool" | "llm" | "control"
├── name: string
├── resolved_arguments: object
├── status: ActionStatus
├── tool_call_id?: string
├── started_at?: datetime
├── completed_at?: datetime
└── trace_id: string
示例:
{
"kind": "tool",
"name": "file.read",
"arguments": {
"path": "README.md"
}
}对于依赖上一步结果的参数,不能把模板字符串散落在 Prompt 中。建议使用显式变量引用:
{
"kind": "tool",
"name": "file.read",
"arguments": {
"path": {"$ref": "context.artifacts.entry_file.path"}
}
}这样可以在执行前校验引用存在、类型正确且符合工具 Schema。
Observation
├── id: string
├── action_id: string
├── source: "tool" | "llm" | "system" | "user"
├── status: "SUCCESS" | "ERROR" | "PARTIAL"
├── data: object
├── summary: string
├── error?: ErrorInfo
├── artifacts: ArtifactRef[]
├── created_at: datetime
└── metadata: object
Observation 表示外部世界或执行环境返回的事实;Result 则是 Step 层面经过解释或归纳后的产物。
例如:
file.read原始内容是 Observation。- “该项目入口为
src/main.py”是 Step Result。 - “项目使用 FastAPI”可作为结构化 Artifact 写入 Context。
必须避免将工具原始输出、LLM 解释、最终结论混为同一字段,否则会降低可调试性,也会让重规划时缺少可靠事实来源。
AgentContext
├── run_id: string
├── user_task: UserTask
├── state: AgentState
├── active_plan_ref?: string
├── current_step_id?: string
├── step_results: map<string, ResultRef>
├── observations: ObservationRef[]
├── artifacts: ArtifactStore
├── conversation: MessageRef[]
├── constraints: Constraint[]
├── tool_scope: ToolScope
├── budget: ExecutionBudget
└── memory: ModeMemory
统一 Context 需覆盖:
- 用户任务:原始请求、补充信息、用户偏好。
- 当前 Plan 与 Plan 版本。
- 当前 Step。
- 历史 Step Result。
- Tool Result / Observation。
- 约束:权限、文件范围、预算、截止时间、风险等级。
- 工具范围:本次 Run 可用工具及权限。
- 对话消息:用于保留人机协作过程。
建议采用“双层上下文”:
Persistent Context:完整事实、原始 Observation、事件、Plan 版本,支持恢复和审计。Prompt Context View:按当前 Step 裁剪、摘要和引用后的上下文,控制 Token 成本与噪声。
AgentState
├── phase: AgentPhase
├── run_status: RunStatus
├── current_step_id?: string
├── waiting_reason?: string
├── failure?: FailureInfo
├── retry_count: integer
└── updated_at: datetime
建议状态分层:
AgentPhase:业务运行阶段。RunStatus:系统运行状态,例如RUNNING / PAUSED / CANCELLED / TERMINATED。StepStatus:局部步骤状态。ActionStatus:单次调用状态。
不要让一个枚举同时表达所有层次;例如 “Tool 调用等待中” 不等于 “整个 Run 失败”。
Executor 不是“再调用一次 LLM 来解释 Step”。它是一个面向执行的编排器:
Step
→ Action Resolver
→ Argument Resolver
→ Policy Check
→ Tool Router
→ Tool Invocation
→ Observation
→ Result Extractor
→ Context Update
→ Step Transition
推荐优先由 Planner 直接生成 ActionSpec。Executor 只负责:
- 校验当前 Step 状态及其依赖是否满足。
- 根据 Context 解析 Action 参数引用。
- 根据 Tool Registry 校验工具、参数、权限和风险等级。
- 将
ActionSpec变为带追踪信息的Action。 - 交给 Harness 执行。
- 保存 Observation,生成 Step Result。
- 判断步骤完成、失败或等待。
对于 Planner 无法预先确定参数的步骤,可允许 Executor 使用受控的 Action Resolver 进行一次 LLM 辅助决策;但它只能从 Tool Registry 提供的受限工具集合中选择,不能自由生成任意工具名或参数。
ToolDefinition
├── name: string
├── version: string
├── description: string
├── input_schema: JSON Schema
├── output_schema: JSON Schema
├── permission: PermissionLevel
├── risk_level: RiskLevel
├── timeout_ms: integer
├── retryable_errors: string[]
├── idempotent: boolean
└── handler: ToolHandler
Registry 是工具的唯一能力来源。Planner Prompt、Action Validator、前端工具说明与实际 Router 均应复用同一份定义,避免“Prompt 说有工具、运行时没有”的漂移问题。
工具命名建议使用领域命名空间:
file.read
file.search
code.symbol_lookup
web.search
database.query
llm.generate
user.ask
Router 负责:
- 从 Registry 查找 Tool。
- 校验 Action 名称与输入参数 Schema。
- 执行权限和风险策略。
- 注入
run_id、trace_id、超时、取消信号。 - 标准化 Tool 输出和错误。
- 发布 Tool 调用事件。
统一返回:
ToolExecutionResult
├── status: SUCCESS | ERROR | PARTIAL
├── output: object
├── artifacts: ArtifactRef[]
├── error?: ErrorInfo
├── usage?: UsageInfo
└── metadata: object
| 内容 | 存储位置 | 原因 |
|---|---|---|
| Tool 原始返回 | Observation Store / Artifact Store | 可审计、可重放、避免丢失事实。 |
| 大文件、网页正文、附件 | Artifact Store | 防止 Context 膨胀。 |
| 对当前任务有用的提炼结论 | Step Result / Context Artifacts | 后续规划与最终回答可高效使用。 |
| Prompt 摘要 | Prompt Context View | 控制 Token,不替代原始记录。 |
关键原则:LLM 不应成为唯一事实存储。原始 Tool Observation 必须可追溯。
Planner 不应只看到用户一句话。输入应包含:
- 用户任务及澄清信息
- 当前 Context 摘要
- 可用 Tool Registry 摘要
- 权限和约束
- 当前 Plan(重规划时)
- 已完成步骤及关键 Observation
- 输出 Plan Schema
Planner System Prompt 应明确:
- 目标是生成可执行计划,而不是直接回答。
- 每个 Step 必须有明确目标、Action、参数、期望输出和失败策略。
- 只能使用 Tool Registry 中允许的 Action 名称。
- 对不确定信息必须先用信息获取类步骤验证,不能虚构路径、URL、数据或工具结果。
- 不执行高风险操作,除非标注需确认。
- 计划应保持最小充分:避免把“思考”“分析”“总结”拆成大量无操作价值的步骤。
- 允许使用
llm.generate,但必须给出输入来源、输出契约和用途。
建议要求模型输出 JSON,并使用 Schema 校验:
{
"goal": "分析项目入口和主要模块",
"assumptions": ["工作目录可读"],
"steps": [
{
"id": "step_01",
"order": 1,
"title": "读取项目说明",
"objective": "获取项目结构和运行方式",
"action": {
"kind": "tool",
"name": "file.read",
"arguments": { "path": "README.md" }
},
"expected_output": {
"type": "structured_summary",
"fields": ["project_name", "run_command", "source_roots"]
},
"failure_policy": "REPLAN_STEP"
}
]
}在工程上,JSON Mode / Structured Output 可以提高格式稳定性,但不能取代业务校验。
Plan Validator 至少覆盖四层:
- 语法校验:JSON 是否符合 Plan Schema。
- 能力校验:Action 是否存在于 Registry。
- 参数校验:参数是否符合工具 JSON Schema;引用是否合法。
- 语义与策略校验:
- Step 是否存在空目标或无效步骤。
- 是否有循环依赖。
- 是否访问超出授权范围的资源。
- 是否包含高风险操作而未要求确认。
- 计划是否超过步数、成本或时间预算。
验证失败的策略:
可自动修正 → 修正后继续
可要求 Planner 重生 → 带 Validator 错误进行一次受控重试
不可修复 / 高风险 → 进入 FAILED 或 ASK_USER
不要无上限地要求 LLM “重新生成直到正确”;需要限定 Planner Retry Budget。
CREATED
↓
PLANNING
├── WAIT_USER
├── FAILED
└── EXECUTING
↓
WAIT_TOOL
↓
EXECUTING
├── REPLANNING
│ ├── EXECUTING
│ ├── WAIT_USER
│ └── FAILED
├── COMPLETED
├── WAIT_USER
└── FAILED
建议状态含义:
| 状态 | 含义 |
|---|---|
CREATED |
Run 已创建,尚未开始。 |
PLANNING |
Planner 生成初始计划。 |
EXECUTING |
调度并执行当前 Step。 |
WAIT_TOOL |
已发起异步 Tool 调用,等待 Observation。 |
WAIT_USER |
缺少必要信息、权限或确认。 |
REPLANNING |
计划不能继续,需要生成新版本或局部 Patch。 |
COMPLETED |
目标已满足,生成最终答复。 |
FAILED |
在既定恢复策略和预算内无法继续。 |
CANCELLED |
用户或系统取消。 |
WAIT_TOOL 可作为 Harness 通用状态;Plan-and-Solve 仅决定何时发起 Action、收到 Observation 后如何继续。
PLANNING → EXECUTING:Plan 已通过验证,至少一个 Step 可执行。EXECUTING → WAIT_TOOL:异步 Tool Action 已提交。WAIT_TOOL → EXECUTING:收到成功或可恢复失败的 Observation。EXECUTING → REPLANNING:Step 目标仍有效,但原 Action 或后续计划失效。EXECUTING → WAIT_USER:缺少用户提供的信息、权限或确认。EXECUTING → COMPLETED:所有必要 Step 完成且最终答案生成。- 任意活动状态 →
FAILED:不可恢复错误、预算耗尽、策略拒绝或关键依赖不可用。
以 read_file("main.py") 文件不存在为例:
Tool Error: FILE_NOT_FOUND
↓
Failure Classifier
├── 可替代路径:file.search("main.py") → 修改当前 Step 参数
├── 目标仍成立但路径假设错误:局部 Replan
├── 项目结构未知:追加“列出目录/读取 README”步骤
├── 用户必须决定:WAIT_USER
└── 无法完成:FAILED,并保留事实和已完成结果
建议 FailurePolicy:
FAIL_FAST
RETRY
RETRY_WITH_BACKOFF
REPLAN_STEP
REPLAN_REMAINING
ASK_USER
SKIP_IF_OPTIONAL
重规划应该尽量局部化:优先保留已成功 Step 与有效 Observation,仅替换失败 Step 及其依赖后续步骤;避免每次错误都从头生成全计划。
三种模式共享 Harness 运行时,仅差异化“下一步决策策略”。
| Mode | 决策特点 | Harness 交互 |
|---|---|---|
| ReAct | Observation 驱动,边思考边行动 | 每轮输出下一 Action 或最终答案。 |
| Plan-and-Solve | 先有 Plan,按 Step 执行,必要时重规划 | 输出 Plan、当前 Step Action、Plan Patch。 |
| Reflection | 对既有执行过程或答案进行评估、修正 | 输出评估、修订 Action 或重新执行建议。 |
统一抽象:
Mode → AgentDecision → Harness executes → Observation → Mode
其中差异仅体现在 Mode 内部状态:
ReActMemory
├── thought summary
├── action / observation trace
└── iteration budget
PlanSolveMemory
├── active plan
├── current step
├── completed step results
└── replan history
ReflectionMemory
├── draft answer / prior trace
├── critique
├── revision plan
└── verification results
进一步建议未来定义能力型接口,而非依赖 Mode 名称:
PlanningCapable
ReflectiveCapable
RecoverableCapable
ToolUsingCapable
这样后续可以实现 “Plan-and-Solve + Reflection” 的组合策略,而无需重构 Harness。例如:完成 Plan 后由 Reflection Mode 进行结果验收和必要的补充执行。
agent/
├── harness/
│ ├── runtime/
│ │ ├── agent_run.py
│ │ ├── lifecycle_manager.py
│ │ └── state_machine.py
│ ├── context/
│ │ ├── context_manager.py
│ │ ├── prompt_context_builder.py
│ │ └── artifact_store.py
│ ├── tools/
│ │ ├── registry.py
│ │ ├── router.py
│ │ ├── contracts.py
│ │ └── builtins/
│ ├── providers/
│ │ ├── llm_provider.py
│ │ └── provider_manager.py
│ ├── events/
│ │ ├── event_bus.py
│ │ └── event_types.py
│ ├── policies/
│ │ ├── permission_policy.py
│ │ ├── risk_policy.py
│ │ └── retry_policy.py
│ └── contracts/
│ ├── mode.py
│ ├── action.py
│ ├── observation.py
│ └── errors.py
│
├── modes/
│ ├── plan_solve/
│ │ ├── agent.py
│ │ ├── planner.py
│ │ ├── executor.py
│ │ ├── scheduler.py
│ │ ├── action_resolver.py
│ │ ├── plan_validator.py
│ │ ├── recovery_controller.py
│ │ ├── prompts/
│ │ │ ├── planner.system.md
│ │ │ └── replanner.system.md
│ │ └── schemas/
│ │ ├── plan.schema.json
│ │ └── step.schema.json
│ ├── react/
│ └── reflection/
│
├── domain/
│ ├── plan.py
│ ├── step.py
│ ├── context.py
│ ├── run.py
│ └── enums.py
│
├── storage/
│ ├── repositories/
│ ├── migrations/
│ └── serializers/
│
├── observability/
│ ├── tracing.py
│ ├── metrics.py
│ └── audit_log.py
│
├── api/
│ ├── http/
│ ├── websocket/
│ └── dto/
│
└── tests/
├── unit/
├── integration/
├── contract/
└── scenario/
建议按以下顺序实施:
- 先定义 Harness 通用 Contract:
AgentMode、Action、Observation、AgentDecision、State Machine。 - 实现 Tool Registry、Router 和标准化错误模型。
- 将 Plan-and-Solve 的 Plan / Step / Context 持久化模型落地。
- 实现 Planner 的结构化输出与 Validator。
- 实现 Executor、局部重规划与断点恢复。
- 最后接入事件流、前端执行过程展示、审计和指标。
需要特别避免的风险:
- 不要让每个 Mode 自己调 Tool,否则 Harness 会失去统一价值。
- 不要把所有中间数据塞进 Prompt;应采用 Artifact 引用与上下文裁剪。
- 不要把 Plan 当作不可变圣旨;执行中必须允许局部修订。
- 不要把 LLM 输出视为可信执行指令;所有 Action 都必须经过 Registry、Schema、权限和策略校验。
- 不要只记录最终答案;Plan、Action、Observation、失败原因和重规划过程都是生产级可观测性的一部分。
该方案的核心收益是:Plan-and-Solve 能独立演进其规划策略,同时完全复用统一 Harness 的工具、状态、上下文和运行治理能力,并为 ReAct、Reflection 及未来混合式 Agent 提供一致的工程基础。