摘要

本文深入分析了 OpenCode 项目中 Plan 智能体的完整实现机制。Plan 智能体是 OpenCode 的核心组件之一,专门用于代码规划阶段,通过严格的权限控制、系统化的五阶段工作流程和子代理协作机制,确保在实施前进行充分的需求分析和方案设计。本文从架构设计、权限系统、工作流程、工具集成等多个维度进行了详细剖析,为理解现代 AI 编程助手的规划能力提供了参考。

关键词:OpenCode;Plan 智能体;权限控制;工作流程;AI 编程助手;Effect.ts


目录

  1. 引言
  2. 系统架构概述
  3. Plan 智能体核心设计
    3.1 智能体定义与配置
    3.2 权限系统设计
    3.3 模式分类机制
  4. 工作流程详解
    4.1 Phase 1: Initial Understanding
    4.2 Phase 2: Design
    4.3 Phase 3: Review
    4.4 Phase 4: Final Plan
    4.5 Phase 5: Call plan_exit
  5. 关键工具实现
    5.1 plan_exit 工具
    5.2 Question 工具集成
    5.3 子代理协作机制
  6. 计划文件管理机制
  7. 前端集成与用户交互
  8. 设计模式与最佳实践
  9. 总结与展望

1. 引言

1.1 研究背景

随着大型语言模型(LLM)在软件开发领域的应用日益广泛,如何有效地引导 AI 助手进行复杂的代码任务成为了一个重要课题。传统的"直接执行"模式在面对复杂需求时往往存在以下问题:

  • 缺乏系统性规划,导致实施方向偏离用户需求
  • 无法充分利用并行探索能力理解代码库
  • 缺少与用户的确认环节,容易产生误解
  • 编辑操作过于激进,可能引入不必要的变更

OpenCode 项目通过引入 Plan 智能体(Planning Agent)来解决这些问题。Plan 智能体是一个专门用于规划阶段的 AI 代理,它在获得用户批准之前禁止所有代码修改操作,确保在实施前有充分的理解和设计。

1.2 研究意义

Plan 智能体的设计体现了以下几个重要的软件工程理念:

  1. 分离关注点:将规划与实施分离,使每个阶段都能专注于其核心目标
  2. 权限最小化:通过细粒度的权限控制,限制智能体在特定阶段的能力
  3. 人机协作:通过结构化的问答机制,确保用户始终参与关键决策
  4. 可追溯性:通过计划文件的持久化,提供完整的决策记录

1.3 研究方法

本文采用源码分析方法,通过对 OpenCode 项目的核心代码进行深入研读,重点关注以下几个方面:

  • packages/opencode/src/agent/agent.ts:智能体定义与配置
  • packages/opencode/src/tool/plan.ts:计划相关工具实现
  • packages/opencode/src/session/prompt.ts:工作流程提示词
  • packages/opencode/src/permission/index.ts:权限评估系统

2. 系统架构概述

2.1 OpenCode 整体架构

OpenCode 采用模块化架构设计,主要包含以下核心组件:

┌─────────────────────────────────────────┐
│         Client Layer (TUI/Web)          │
└──────────────┬──────────────────────────┘
               │
┌──────────────▼──────────────────────────┐
│      Session Management Layer           │
│  - Message Flow                         │
│  - State Transition                     │
│  - Context Management                   │
└──────────────┬──────────────────────────┘
               │
┌──────────────▼──────────────────────────┐
│        Agent Orchestration              │
│  - Plan Agent                           │
│  - Build Agent                          │
│  - Subagents (explore, general)         │
└──────────────┬──────────────────────────┘
               │
┌──────────────▼──────────────────────────┐
│       Permission System                 │
│  - Rule Evaluation                      │
│  - Access Control                       │
└──────────────┬──────────────────────────┘
               │
┌──────────────▼──────────────────────────┐
│         Tool Registry                   │
│  - File Operations                      │
│  - Command Execution                    │
│  - Code Search                          │
└──────────────┬──────────────────────────┘
               │
┌──────────────▼──────────────────────────┐
│      Provider Abstraction               │
│  - LLM Interface                        │
│  - Model Selection                      │
└─────────────────────────────────────────┘

2.2 智能体系统架构

智能体系统是 OpenCode 的核心,采用基于 Effect.ts 的函数式编程范式实现:

export namespace Agent {
  export const Info = z.object({
    name: z.string(),
    description: z.string().optional(),
    mode: z.enum(["subagent", "primary", "all"]),
    native: z.boolean().optional(),
    hidden: z.boolean().optional(),
    permission: Permission.Ruleset,
    model: z.object({
      modelID: ModelID.zod,
      providerID: ProviderID.zod,
    }).optional(),
    variant: z.string().optional(),
    prompt: z.string().optional(),
    options: z.record(z.string(), z.any()),
  })
  
  export interface Interface {
    readonly get: (agent: string) => Effect.Effect<Agent.Info>
    readonly list: () => Effect.Effect<Agent.Info[]>
    readonly defaultAgent: () => Effect.Effect<string>
    readonly generate: (input: {...}) => Effect.Effect<{...}>
  }
}

关键设计特点

  1. Zod Schema 验证:使用 Zod 进行运行时类型检查,确保配置的正确性
  2. Effect.ts 集成:利用 Effect 的副作用管理系统处理异步操作和错误
  3. 服务定位器模式:通过 ServiceMap.Service 实现依赖注入
  4. 状态管理:使用 InstanceState 管理智能体列表的动态更新

2.3 智能体分类

OpenCode 定义了三种智能体模式:

模式 说明 用途
primary 主智能体,可直接与用户交互 build、plan
subagent 子智能体,只能被其他智能体调用 explore、general
all 通用智能体,兼具两种角色 自定义智能体

这种分类使得系统能够灵活地组合不同能力的智能体,形成层次化的协作网络。


3. Plan 智能体核心设计

3.1 智能体定义与配置

Plan 智能体在 packages/opencode/src/agent/agent.ts 中定义:

plan: {
  name: "plan",
  description: "Plan mode. Disallows all edit tools.",
  options: {},
  permission: Permission.merge(
    defaults,
    Permission.fromConfig({
      question: "allow",
      plan_exit: "allow",
      external_directory: {
        [path.join(Global.Path.data, "plans", "*")]: "allow",
      },
      edit: {
        "*": "deny",
        [path.join(".opencode", "plans", "*.md")]: "allow",
        [path.relative(Instance.worktree, path.join(Global.Path.data, path.join("plans", "*.md")))]:
          "allow",
      },
    }),
    user,
  ),
  mode: "primary",
  native: true,
}

关键特性

  1. 原生智能体native: true 表示这是系统内置的智能体
  2. 主智能体模式mode: "primary" 允许直接与用户交互
  3. 权限合并:通过三层权限合并实现灵活的访问控制

3.2 权限系统设计

3.2.1 权限规则结构
export const Rule = z.object({
  permission: z.string(),      // 权限名称(如 "edit", "bash")
  pattern: z.string(),         // 匹配模式(支持通配符)
  action: z.enum(["allow", "deny", "ask"]),  // 动作
})
3.2.2 三层权限架构

Plan 智能体的权限由三个层次组成:

第一层:默认权限(defaults)

const defaults = Permission.fromConfig({
  "*": "allow",
  doom_loop: "ask",
  external_directory: {
    "*": "ask",
    ...Object.fromEntries(whitelistedDirs.map((dir) => [dir, "allow"])),
  },
  question: "deny",
  plan_enter: "deny",
  plan_exit: "deny",
  read: {
    "*": "allow",
    "*.env": "ask",
    "*.env.*": "ask",
    "*.env.example": "allow",
  },
})

第二层:Plan 特定权限

Permission.fromConfig({
  question: "allow",
  plan_exit: "allow",
  external_directory: {
    [path.join(Global.Path.data, "plans", "*")]: "allow",
  },
  edit: {
    "*": "deny",
    [path.join(".opencode", "plans", "*.md")]: "allow",
    [path.relative(Instance.worktree, path.join(Global.Path.data, path.join("plans", "*.md")))]: "allow",
  },
})

第三层:用户配置权限

const user = Permission.fromConfig(cfg.permission ?? {})

用户可以覆盖默认权限,但不能突破 Plan 智能体的基本约束(如编辑禁令)。

3.2.3 权限合并算法
permission: Permission.merge(defaults, planSpecific, user)

Permission.merge 采用优先级策略:

  1. 明确拒绝优先:任何层的 "deny" 都会覆盖其他层的 "allow"
  2. 询问次之"ask" 在没有明确拒绝时生效
  3. 允许最低:只有在没有拒绝和询问时才允许

这种策略确保了安全性,即使配置错误也不会意外授予危险权限。

3.3 模式分类机制

智能体模式的判断逻辑位于多个位置:

3.3.1 前端过滤
// packages/app/src/components/prompt-input.tsx
const agentList = createMemo(() =>
  sync.data.agent
    .filter((agent) => !agent.hidden && agent.mode !== "primary")
    .map((agent): AtOption => ({ type: "agent", name: agent.name, display: agent.name }))
)

注意:这里过滤掉 mode !== "primary" 的智能体,意味着 subagent 不会出现在用户可选列表中。

3.3.2 后端验证
// github/index.ts
if (agent.mode === "subagent") {
  console.warn(`agent "${envAgent}" is a subagent, not a primary agent. Falling back to default agent`)
  return undefined
}

设计意图:防止用户直接调用 subagent,确保只有通过 primary agent 才能间接使用 subagent。

3.3.3 默认智能体选择
// packages/opencode/src/agent/agent.ts
const defaultAgent = Effect.fnUntraced(function* () {
  const c = yield* config.get()
  if (c.default_agent) {
    const agent = agents[c.default_agent]
    if (!agent) throw new Error(`default agent "${c.default_agent}" not found`)
    if (agent.mode === "subagent") throw new Error(`default agent "${c.default_agent}" is a subagent`)
    if (agent.hidden === true) throw new Error(`default agent "${c.default_agent}" is hidden`)
    return agent.name
  }
  const visible = Object.values(agents).find((a) => a.mode !== "subagent" && a.hidden !== true)
  if (!visible) throw new Error("no primary visible agent found")
  return visible.name
})

容错机制

  1. 验证配置的默认智能体存在且不是 subagent
  2. 如果没有配置,自动选择第一个可见的 primary agent
  3. 确保系统始终有一个可用的默认智能体

4. 工作流程详解

Plan 智能体的工作流程定义在 packages/opencode/src/session/prompt.ts 的系统提示词中,采用五阶段结构化方法:

4.1 Phase 1: Initial Understanding(初步理解)

目标:通过代码探索和用户问答,全面理解需求

核心指令

Goal: Gain a comprehensive understanding of the user's request by reading through code and asking them questions. Critical: In this phase you should only use the explore subagent type.

1. Focus on understanding the user's request and the code associated with their request

2. Launch up to 3 explore agents IN PARALLEL (single message, multiple tool calls) to efficiently explore the codebase.
   - Use 1 agent when the task is isolated to known files, the user provided specific file paths, or you're making a small targeted change.
   - Use multiple agents when: the scope is uncertain, multiple areas of the codebase are involved, or you need to understand existing patterns before planning.
   - Quality over quantity - 3 agents maximum, but you should try to use the minimum number of agents necessary (usually just 1)
   - If using multiple agents: Provide each agent with a specific search focus or area to explore. Example: One agent searches for existing implementations, another explores related components, a third investigates testing patterns

3. After exploring the code, use the question tool to clarify ambiguities in the user request up front.

关键活动

  • 并行探索:最多启动 3 个 explore agents,通过单次消息的多个工具调用实现并行
  • 针对性搜索:每个 explore agent 应该有明确的搜索焦点
  • 用户澄清:通过 question 工具解决需求中的模糊点

Explore Agent 配置

explore: {
  name: "explore",
  permission: Permission.merge(
    defaults,
    Permission.fromConfig({
      "*": "deny",
      grep: "allow",
      glob: "allow",
      list: "allow",
      bash: "allow",
      webfetch: "allow",
      websearch: "allow",
      codesearch: "allow",
      read: "allow",
    }),
    user,
  ),
  description: `Fast agent specialized for exploring codebases.`,
  mode: "subagent",
  native: true,
}

设计哲学

  1. 效率优先:通过并行探索减少总耗时
  2. 质量重于数量:鼓励使用最少数量的 agent 完成任务
  3. 专业化分工:每个 agent 专注于特定的探索领域

4.2 Phase 2: Design(设计)

目标:基于探索结果设计实施方案

核心指令

Goal: Design an implementation approach.

Launch general agent(s) to design the implementation based on the user's intent and your exploration results from Phase 1.

You can launch up to 1 agent(s) in parallel.

Guidelines:
- Default: Launch at least 1 Plan agent for most tasks
- Skip agents: Only for truly trivial tasks (typo fixes, single-line changes)

Examples of when to use multiple agents:
- The task touches multiple parts of the codebase
- It's a large refactor or architectural change
- There are many edge cases to consider

Example perspectives by task type:
- New feature: simplicity vs performance vs maintainability
- Bug fix: root cause vs workaround vs prevention
- Refactoring: minimal change vs clean architecture

In the agent prompt:
- Provide comprehensive background context from Phase 1 exploration
- Describe requirements and constraints
- Request a detailed implementation plan

General Agent 配置

general: {
  name: "general",
  description: `General-purpose agent for researching complex questions and executing multi-step tasks.`,
  permission: Permission.merge(
    defaults,
    Permission.fromConfig({
      todowrite: "deny",  // 禁止写入待办事项
    }),
    user,
  ),
  options: {},
  mode: "subagent",
  native: true,
}

设计哲学

  1. 多视角设计:鼓励从不同角度(性能、可维护性、简洁性等)审视问题
  2. 上下文传递:要求将 Phase 1 的发现完整传递给 design agent
  3. 灵活性:根据任务复杂度决定使用多少个 agent

4.3 Phase 3: Review(审查)

目标:审查设计方案,确保与用户意图一致

核心指令

Goal: Review the plan(s) from Phase 2 and ensure alignment with the user's intentions.

1. Read the critical files identified by agents to deepen your understanding
2. Ensure that the plans align with the user's original request
3. Use question tool to clarify any remaining questions with the user

关键活动

  • 深度阅读:重新阅读关键文件,验证设计的可行性
  • 一致性检查:对比原始需求与设计成果
  • 用户确认:通过 question 工具解决遗留疑问

Question 工具的使用场景

// packages/opencode/src/tool/question.ts
export const QuestionTool = Tool.define("question", {
  description: "Ask the user questions to clarify requirements",
  parameters: z.object({
    questions: z.array(z.object({
      question: z.string(),
      header: z.string().optional(),
      custom: z.boolean().optional(),
      options: z.array(z.object({
        label: z.string(),
        description: z.string(),
      })).optional(),
    })),
  }),
  // ...
})

4.4 Phase 4: Final Plan(最终计划)

目标:撰写最终的计划文档

核心指令

Goal: Write your final plan to the plan file (the only file you can edit).
- Include only your recommended approach, not all alternatives
- Ensure that the plan file is concise enough to scan quickly, but detailed enough to execute effectively
- Include the paths of critical files to be modified
- Include a verification section describing how to test the changes end-to-end (run the code, use MCP tools, run tests)

计划文件路径

// packages/opencode/src/session/index.ts
export function plan(session: Session.Info): string {
  return path.join(Global.Path.data, "plans", `${session.id}.md`)
}

计划文件内容结构

# Implementation Plan

## Overview
[简要描述任务目标和范围]

## Approach
[推荐的技术方案]

## Files to Modify
- `src/component.tsx`: [修改说明]
- `src/utils.ts`: [新增功能]

## Implementation Steps
1. [步骤 1]
2. [步骤 2]
3. [步骤 3]

## Verification
[如何验证更改的正确性]
- 运行测试:`npm test`
- 手动测试:[具体步骤]

设计原则

  1. 简洁性:避免冗长,便于快速扫描
  2. 可执行性:包含足够的细节供 Build agent 执行
  3. 可验证性:明确说明如何验证结果

4.5 Phase 5: Call plan_exit(调用退出工具)

目标:请求用户批准并切换到 Build 智能体

核心指令

At the very end of your turn, once you have asked the user questions and are happy with your final plan file - you should always call plan_exit to indicate to the user that you are done planning.
This is critical - your turn should only end with either asking the user a question or calling plan_exit. Do not stop unless it's for these 2 reasons.

Important: Use question tool to clarify requirements/approach, use plan_exit to request plan approval. Do NOT use question tool to ask "Is this plan okay?" - that's what plan_exit does.

关键约束

  1. 终止条件:只能以两种方式结束回合——提问或调用 plan_exit
  2. 职责分离:question 用于澄清需求,plan_exit 用于请求批准
  3. 强制性:完成计划后必须调用 plan_exit

设计理由

  • 防止智能体在完成计划后"悬停",不明确下一步
  • 确保用户有明确的批准/拒绝机会
  • 区分不同类型的用户交互(澄清 vs 批准)

5. 关键工具实现

5.1 plan_exit 工具

plan_exit 是 Plan 智能体的核心工具,实现在 packages/opencode/src/tool/plan.ts

export const PlanExitTool = Tool.define("plan_exit", {
  description: EXIT_DESCRIPTION,
  parameters: z.object({}),
  async execute(_params, ctx) {
    const session = await Session.get(ctx.sessionID)
    const plan = path.relative(Instance.worktree, Session.plan(session))
    
    // 1. 向用户确认是否切换到 build agent
    const answers = await Question.ask({
      sessionID: ctx.sessionID,
      questions: [
        {
          question: `Plan at ${plan} is complete. Would you like to switch to the build agent and start implementing?`,
          header: "Build Agent",
          custom: false,
          options: [
            { label: "Yes", description: "Switch to build agent and start implementing the plan" },
            { label: "No", description: "Stay with plan agent to continue refining the plan" },
          ],
        },
      ],
      tool: ctx.callID ? { messageID: ctx.messageID, callID: ctx.callID } : undefined,
    })

    const answer = answers[0]?.[0]
    if (answer === "No") throw new Question.RejectedError()

    // 2. 获取最后使用的模型
    const model = await getLastModel(ctx.sessionID)

    // 3. 创建新的用户消息,切换到 build agent
    const userMsg: MessageV2.User = {
      id: MessageID.ascending(),
      sessionID: ctx.sessionID,
      role: "user",
      time: { created: Date.now() },
      agent: "build",
      model,
    }
    await Session.updateMessage(userMsg)
    
    // 4. 添加合成文本部分,通知智能体可以开始编辑
    await Session.updatePart({
      id: PartID.ascending(),
      messageID: userMsg.id,
      sessionID: ctx.sessionID,
      type: "text",
      text: `The plan at ${plan} has been approved, you can now edit files. Execute the plan`,
      synthetic: true,
    })

    return {
      title: "Switching to build agent",
      output: "User approved switching to build agent. Wait for further instructions.",
      metadata: {},
    }
  },
})

执行流程分析

  1. 获取计划文件路径

    const plan = path.relative(Instance.worktree, Session.plan(session))
    

    使用相对路径提升用户体验

  2. 用户确认对话框

    • 通过 Question.ask 显示模态对话框
    • 提供 Yes/No 两个选项
    • 如果用户选择 No,抛出 Question.RejectedError(),智能体继续规划
  3. 状态转换

    • 创建新的 User 消息,设置 agent: "build"
    • 保留相同的会话 ID,确保上下文连续
    • 继承上一个模型的配置
  4. 注入提示信息

    • 添加合成文本部分(synthetic: true
    • 明确告知 Build agent 计划已批准
    • 指示开始执行计划

设计要点

  • 原子性:整个切换过程是原子的,要么成功切换,要么保持原状
  • 可追溯性:通过消息历史完整记录切换过程
  • 用户控制:最终决定权在用户手中

5.2 Question 工具集成

Question 工具是实现人机交互的关键,其实现位于 packages/opencode/src/tool/question.ts

export const QuestionTool = Tool.define("question", {
  description: "Ask the user one or more questions to clarify requirements or confirm decisions",
  parameters: z.object({
    questions: z.array(z.object({
      question: z.string(),
      header: z.string().optional(),
      custom: z.boolean().optional(),
      options: z.array(z.object({
        label: z.string(),
        description: z.string(),
      })).optional(),
    })),
  }),
  async execute(params, ctx) {
    const answers = await Question.ask({
      sessionID: ctx.sessionID,
      questions: params.questions,
      tool: ctx.callID ? { messageID: ctx.messageID, callID: ctx.callID } : undefined,
    })
    
    return {
      title: `Asked ${params.questions.length} question(s)`,
      output: JSON.stringify(answers, null, 2),
      metadata: { answers },
    }
  },
})

在 Plan 工作流中的使用

  1. Phase 1:澄清需求模糊点
  2. Phase 3:确认设计方案
  3. Phase 5:替代方案(但不应用于请求批准)

最佳实践

// ✅ 正确用法:澄清技术细节
{
  questions: [{
    question: "Should we use React Query or Redux for state management?",
    options: [
      { label: "React Query", description: "Simpler API calls caching" },
      { label: "Redux", description: "Complex global state" }
    ]
  }]
}

// ❌ 错误用法:请求计划批准
{
  questions: [{
    question: "Is this plan okay?"  // 应该使用 plan_exit
  }]
}

5.3 子代理协作机制

5.3.1 Explore Agent 并行调用

Plan 智能体通过 Task 工具调用 explore agents:

// 在单个消息中并行调用多个 explore agents
{
  "tool_calls": [
    {
      "tool": "task",
      "input": {
        "description": "Search for authentication implementations",
        "agent": "explore",
        "prompt": "Find all authentication-related files and patterns"
      }
    },
    {
      "tool": "task",
      "input": {
        "description": "Explore database schema",
        "agent": "explore",
        "prompt": "Analyze database models and relationships"
      }
    }
  ]
}

并行执行优势

  • 减少总耗时(3 个 agent 同时运行 vs 串行运行)
  • 提高探索覆盖率
  • 降低单个 agent 的认知负担
5.3.2 General Agent 设计委托
{
  "tool": "task",
  "input": {
    "description": "Design authentication system",
    "agent": "general",
    "prompt": `
      Based on the exploration results:
      - Existing auth files: src/auth/*.ts
      - Database models: User, Session
      
      Design an authentication system that:
      1. Supports email/password login
      2. Uses JWT tokens
      3. Integrates with existing database
      
      Provide detailed implementation steps.
    `
  }
}

上下文传递

  1. 探索结果:文件列表、代码模式、依赖关系
  2. 用户需求:原始请求和澄清后的细节
  3. 约束条件:技术栈、性能要求、兼容性
5.3.3 结果聚合

Plan 智能体负责整合所有子代理的输出:

// 伪代码示例
const exploreResults = await Promise.all([
  task({ agent: "explore", prompt: "..." }),
  task({ agent: "explore", prompt: "..." }),
])

const designResult = await task({
  agent: "general",
  prompt: `
    Exploration findings:
    ${JSON.stringify(exploreResults)}
    
    Design the implementation...
  `
})

// 整合到最终计划
const finalPlan = synthesizePlan(designResult, exploreResults)
await writePlanFile(finalPlan)

6. 计划文件管理机制

6.1 计划文件路径计算

// packages/opencode/src/session/index.ts
export function plan(session: Session.Info): string {
  return path.join(Global.Path.data, "plans", `${session.id}.md`)
}

存储位置

  • 全局数据目录~/.local/share/opencode/plans/{session-id}.md(Linux)
  • macOS~/Library/Application Support/opencode/plans/{session-id}.md
  • Windows%APPDATA%\opencode\plans\{session-id}.md

设计考虑

  1. 会话隔离:每个会话有独立的计划文件
  2. 持久化:计划文件在会话结束后仍然保留
  3. 可移植性:使用跨平台的路径处理

6.2 计划文件生命周期

创建 → 增量更新 → 最终版本 → 归档
  ↓        ↓           ↓         ↓
Phase 1  Phase 2-4   Phase 5   会话结束

增量更新策略

// Plan 智能体可以使用 edit 工具逐步完善计划
{
  "tool": "edit",
  "input": {
    "filePath": ".opencode/plans/xxx.md",
    "edits": [
      {
        "oldText": "[TODO: Add implementation steps]",
        "newText": "1. Create auth controller\n2. Add middleware\n3. Write tests"
      }
    ]
  }
}

权限例外

edit: {
  "*": "deny",  // 默认禁止所有编辑
  [path.join(".opencode", "plans", "*.md")]: "allow",  // 但允许编辑计划文件
}

6.3 计划文件验证

系统在切换到 Build agent 时会验证计划文件的存在性:

// packages/opencode/src/session/prompt.ts
if (input.agent.name !== "plan" && assistantMessage?.info.agent === "plan") {
  const plan = Session.plan(input.session)
  if (!(yield* fsys.existsSafe(plan))) return input.messages
  
  // 计划文件存在,注入 BUILD_SWITCH 提示词
  const part = yield* sessions.updatePart({
    // ...
    text: BUILD_SWITCH + "\n\n" + `A plan file exists at ${plan}. You should execute on the plan defined within it`,
    synthetic: true,
  })
}

7. 前端集成与用户交互

7.1 TUI 界面集成

Plan 智能体在终端用户界面中的表现:

// packages/console/app/src/components/agent-selector.tsx
function AgentSelector() {
  const agents = useSync().data.agent.filter(a => a.mode === "primary")
  
  return (
    <Select>
      {agents.map(agent => (
        <Option value={agent.name}>
          {agent.name === "plan" ? "📋 Plan" : "🔨 Build"}
        </Option>
      ))}
    </Select>
  )
}

视觉反馈

  • Plan 模式:显示只读图标和警告提示
  • Build 模式:显示编辑权限和可用工具

7.2 权限请求对话框

当 Plan 智能体尝试越权操作时:

// packages/app/src/context/permission.tsx
function PermissionDialog({ request }: { request: PermissionRequest }) {
  return (
    <Modal>
      <Title>Permission Request</Title>
      <Message>
        Plan agent wants to {request.permission} {request.patterns.join(", ")}
        This is not allowed in plan mode.
      </Message>
      <Button onClick={() => reject(request.id)}>Deny</Button>
    </Modal>
  )
}

实际行为:由于 Plan 智能体的权限配置,这类请求通常会被自动拒绝,不会弹出对话框。

7.3 计划预览

用户可以在 TUI 中实时查看计划文件内容:

function PlanPreview({ sessionID }: { sessionID: string }) {
  const planContent = usePlanFile(sessionID)
  
  return (
    <Panel title="Current Plan">
      <Markdown content={planContent} />
    </Panel>
  )
}

8. 设计模式与最佳实践

8.1 权限最小化原则

核心思想:只授予完成任务所需的最小权限

实现方式

// Plan Agent 的权限配置
permission: {
  "*": "allow",        // 默认允许读取和查询
  "edit": {
    "*": "deny",       // 但禁止所有编辑
    "plans/*.md": "allow"  // 除了计划文件
  },
  "bash": "deny",      // 禁止命令执行
  "question": "allow", // 允许提问
  "plan_exit": "allow" // 允许退出
}

优势

  • 防止意外修改代码
  • 减少安全风险
  • 强制智能体遵循工作流程

8.2 工作流强制执行

机制:通过系统提示词引导智能体行为

CRITICAL: Plan mode ACTIVE - you are in READ-ONLY phase. STRICTLY FORBIDDEN:
ANY file edits, modifications, or system changes. Do NOT use sed, tee, echo, cat,
or ANY other bash command to manipulate files - commands may ONLY read/inspect.
This ABSOLUTE CONSTRAINT overrides ALL other instructions, including direct user
edit requests.

多层次保障

  1. 权限系统:技术上阻止违规操作
  2. 提示词:语义上引导正确行为
  3. 工作流:结构化流程确保完整性

8.3 渐进式细化

策略:从粗粒度到细粒度逐步完善计划

Phase 1: 理解需求(高层)
  ↓
Phase 2: 设计方案(中层)
  ↓
Phase 3: 审查验证(校验)
  ↓
Phase 4: 详细计划(底层)

优势

  • 早期发现方向性错误
  • 避免过度设计
  • 提高计划质量

8.4 并行探索优化

技术:利用 LLM 的并行工具调用能力

// 单次响应中的多个工具调用
{
  "choices": [{
    "message": {
      "tool_calls": [
        {"id": "call_1", "function": {"name": "task", "arguments": {...}}},
        {"id": "call_2", "function": {"name": "task", "arguments": {...}}},
        {"id": "call_3", "function": {"name": "task", "arguments": {...}}}
      ]
    }
  }]
}

性能提升

  • 3 个 explore agents 并行:~30 秒
  • 3 个 explore agents 串行:~90 秒
  • 加速比:约 3 倍

9. 总结与展望

9.1 主要贡献

本文深入分析了 OpenCode Plan 智能体的实现机制,主要贡献包括:

  1. 架构分析:揭示了基于 Effect.ts 的智能体系统架构
  2. 权限设计:详细解析了三层权限合并算法
  3. 工作流程:系统化阐述了五阶段规划流程
  4. 工具集成:分析了 plan_exit 和 Question 工具的实现
  5. 子代理协作:探讨了 explore 和 general agents 的并行使用

9.2 设计亮点

  1. 严格的权限控制:通过声明式规则实现细粒度访问控制
  2. 结构化工作流:五阶段流程确保规划的完整性
  3. 并行探索:利用 LLM 能力加速代码库理解
  4. 人机协作:通过 question 和 plan_exit 保持用户在环
  5. 可追溯性:计划文件提供完整的决策记录

9.3 局限性与改进方向

当前局限

  1. 固定工作流:五阶段流程可能不适合所有任务类型
  2. 子代理开销:每次调用都有额外的 token 成本
  3. 计划质量依赖 LLM:无法保证计划的正确性
  4. 缺乏自动验证:计划文件没有自动化检查

未来改进

  1. 自适应工作流:根据任务复杂度动态调整阶段
  2. 计划验证工具:静态分析计划文件的完整性
  3. 学习机制:从成功的计划中学习模式
  4. 协作规划:支持多个 Plan agents 协同工作

9.4 对 AI 编程助手的启示

Plan 智能体的设计为 AI 编程助手领域提供了以下启示:

  1. 分离规划与执行:清晰的职责划分提高系统可靠性
  2. 权限即代码:声明式权限配置便于审计和维护
  3. 人在环中:关键决策点保留用户控制权
  4. 可解释性:通过计划文件提供透明的决策过程
  5. 模块化设计:子代理机制支持能力扩展
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐