OpenCode智能体系统实现机制分析
摘要
OpenCode是一个基于大语言模型的AI编程助手框架,其核心创新在于多智能体协作系统的设计。本文深入分析了OpenCode中Plan智能体和Build智能体的实现机制,揭示了其在任务规划与执行解耦、权限控制、工具调用管理等方面的架构设计。
关键词:智能体系统、任务规划、大语言模型、权限控制、工具调用
1. 系统架构概述
OpenCode采用Effect函数式编程框架构建整个系统,核心模块包括:
| 模块 | 路径 | 功能 |
|---|---|---|
| Agent | packages/opencode/src/agent/agent.ts |
智能体定义与管理 |
| Session | packages/opencode/src/session/ |
会话状态管理 |
| LLM | packages/opencode/src/session/llm.ts |
大语言模型调用封装 |
| Tool | packages/opencode/src/tool/ |
工具注册与执行 |
| Permission | packages/opencode/src/permission/ |
权限控制 |
1.1 架构层次图
2. Build智能体实现机制
2.1 智能体定义
Build智能体在agent.ts定义为:
build: {
name: "build",
description: "The default agent. Executes tools based on configured permissions.",
options: {},
permission: Permission.merge(
defaults,
Permission.fromConfig({
question: "allow",
plan_enter: "allow",
}),
user,
),
mode: "primary",
native: true,
}
关键特性:
mode: "primary"- 作为主要执行智能体native: true- 内置原生智能体- 权限配置:允许使用question和plan_enter工具
2.2 权限系统
Build智能体的权限规则通过Permission.merge组合三层配置:
2.2.1 默认规则 (agent.ts)
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",
},
})
权限评估机制在permission/index.ts实现:
export function evaluate(permission: string, pattern: string, ...rulesets: Ruleset[]): Rule {
return evalRule(permission, pattern, ...rulesets)
}
2.2.2 权限动作类型
| 动作 | 含义 |
|---|---|
allow |
允许执行,无需确认 |
deny |
拒绝执行 |
ask |
询问用户确认 |
2.3 工具调用流程
Build智能体的工具执行通过SessionPrompt.resolveTools管理:
- 从
ToolRegistry获取可用工具列表 - 根据权限过滤可用工具
- 包装为AI SDK格式的工具定义
- 在执行上下文中注入权限检查
// session/prompt.ts
const resolveTools = Effect.fn("SessionPrompt.resolveTools")(function* (input) {
const tools: Record<string, AITool> = {}
for (const item of yield* registry.tools({...})) {
tools[item.id] = tool({
id: item.id,
description: item.description,
inputSchema: jsonSchema(schema),
execute(args, options) {
// 权限检查
yield* permission.ask({...})
// 执行工具
const result = yield* Effect.promise(() => item.execute(args, ctx))
return result
},
})
}
return tools
})
3. Plan智能体实现机制
3.1 智能体定义
Plan智能体在agent.ts定义:
plan: {
name: "plan",
description: "Plan mode. Disallows all edit tools.",
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",
},
}),
user,
),
mode: "primary",
native: true,
}
关键限制:
- 禁止所有编辑工具:通过
edit: { "*": "deny" }实现 - 仅允许编辑计划文件:
.opencode/plans/*.md - 允许使用question工具:用于向用户提问
3.2 Plan模式工作流程
Plan模式的工作流程在session/prompt.ts的insertReminders函数中定义,包含五个阶段:
Phase 1: 初始理解
Phase 2: 设计
Phase 3: 评审
Phase 4: 最终计划
Phase 5: 调用plan_exit
3.3 Plan Exit工具实现
tool/plan.ts定义了PlanExitTool:
// src/tool/plan.ts
export const PlanExitTool = Tool.define("plan_exit", {
description: EXIT_DESCRIPTION,
parameters: z.object({}),
async execute(_params, ctx) {
const answers = await Question.ask({
sessionID: ctx.sessionID,
questions: [
{
question: `Plan at ${plan} is complete. Would you like to switch to the build agent?`,
header: "Build Agent",
options: [
{ label: "Yes", description: "Switch to build agent and start implementing" },
{ label: "No", description: "Stay with plan agent to continue refining" },
],
},
],
})
const answer = answers[0]?.[0]
if (answer === "No") throw new Question.RejectedError()
// 用户选择Yes,创建build类型的消息切换智能体
const userMsg: MessageV2.User = {
id: MessageID.ascending(),
sessionID: ctx.sessionID,
role: "user",
agent: "build", // 关键:切换到build智能体
model,
}
await Session.updateMessage(userMsg)
return {
title: "Switching to build agent",
output: "User approved switching to build agent.",
}
},
})
3.4 计划文件管理
计划文件路径由Session.plan()函数生成:
// session/index.ts
export function plan(input: { slug: string; time: { created: number } }) {
const base = Instance.project.vcs
? path.join(Instance.worktree, ".opencode", "plans")
: path.join(Global.Path.data, "plans")
return path.join(base, [input.time.created, input.slug].join("-") + ".md")
}
4. 子智能体系统
4.1 Subagent架构
OpenCode通过task工具支持子智能体调用:
// tool/task.ts
export const TaskTool = Tool.defineEffect(id, Effect.gen(function* () {
const run = Effect.fn("TaskTool.execute")(function* (params, ctx) {
// 获取子智能体配置
const next = yield* agent.get(params.subagent_type)
// 创建子会话
const nextSession = yield* Session.create({
parentID: ctx.sessionID,
title: params.description + ` (@${next.name} subagent)`,
permission: [...],
})
// 执行子任务
const result = yield* SessionPrompt.prompt({
sessionID: nextSession.id,
agent: next.name,
...
})
return { output: result.parts[...].text, ... }
})
}))
4.2 预定义子智能体
| 智能体名称 | 用途 | 权限范围 | 模式 |
|---|---|---|---|
general |
通用研究和多步骤任务执行 | 大部分工具 | subagent |
explore |
代码库快速探索 | 仅读取工具 | subagent |
compaction |
会话压缩生成摘要 | 无工具 | primary |
title |
生成会话标题 | 无工具 | primary |
summary |
生成会话摘要 | 无工具 | primary |
4.3 Explore智能体提示词
// src/agent/prompt/explore.txt
You are a file search specialist. You excel at thoroughly navigating
and exploring codebases.
Your strengths:
- Rapidly finding files using glob patterns
- Searching code and text with powerful regex patterns
- Reading and analyzing file contents
Guidelines:
- Use Glob for broad file pattern matching
- Use Grep for searching file contents
- Do not create any files or modify system state
4.4 智能体模式对比
| 模式 | 描述 | 使用场景 |
|---|---|---|
primary |
主智能体,直接响应用户 | build、plan |
subagent |
子智能体,通过task工具调用 | explore、general |
all |
灵活模式 | 自定义智能体 |
5. 关键实现机制
5.1 会话处理流程
SessionProcessor(session/processor.ts)处理LLM流式响应:
// session/processor.ts
const handleEvent = Effect.fn("SessionProcessor.handleEvent")(function* (value) {
switch (value.type) {
case "start":
yield* status.set(ctx.sessionID, { type: "busy" })
return
case "tool-call":
// 检测doom loop
if (recentParts.length === DOOM_LOOP_THRESHOLD) {
yield* permission.ask({ permission: "doom_loop", ... })
}
return
case "tool-result":
yield* completeToolCall(value.toolCallId, value.output)
return
case "finish-step":
// 更新token计数,检查溢出
if (isOverflow({ tokens: usage.tokens, model: ctx.model })) {
ctx.needsCompaction = true
}
return
}
})
5.2 上下文溢出处理
当token数量超过阈值时触发压缩:
// session/compaction.ts
export const PRUNE_MINIMUM = 20_000
export const PRUNE_PROTECT = 40_000
const PRUNE_PROTECTED_TOOLS = ["skill"]
const prune = Effect.fn("SessionCompaction.prune")(function* (input) {
let total = 0
const toPrune: MessageV2.ToolPart[] = []
// 遍历消息,收集需要清理的旧工具输出
loop: for (let msgIndex = msgs.length - 1; msgIndex >= 0; msgIndex--) {
for (let partIndex = msg.parts.length - 1; partIndex >= 0; partIndex--) {
const part = msg.parts[partIndex]
if (part.type === "tool" && part.state.status === "completed") {
const estimate = Token.estimate(part.state.output)
total += estimate
if (total > PRUNE_PROTECT) {
toPrune.push(part)
}
}
}
}
// 清理选定的工具输出
if (pruned > PRUNE_MINIMUM) {
for (const part of toPrune) {
part.state.time.compacted = Date.now()
}
}
})
5.3 消息转换
MessageV2.toModelMessagesEffect将内部消息格式转换为AI SDK格式:
// session/message-v2.ts
export const toModelMessagesEffect = Effect.fnUntraced(function* (input: WithParts[], model: Provider.Model) {
const result: UIMessage[] = []
for (const msg of input) {
if (msg.info.role === "user") {
// 转换用户消息
for (const part of msg.parts) {
if (part.type === "text") {
userMessage.parts.push({ type: "text", text: part.text })
}
if (part.type === "file" && !isMedia(part.mime)) {
userMessage.parts.push({ type: "file", url: part.url, mediaType: part.mime })
}
}
}
if (msg.info.role === "assistant") {
// 转换助手消息
for (const part of msg.parts) {
if (part.type === "tool" && part.state.status === "completed") {
assistantMessage.parts.push({
type: ("tool-" + part.tool) as `tool-${string}`,
state: "output-available",
toolCallId: part.callID,
input: part.state.input,
output: part.state.output,
})
}
}
}
}
return yield* Effect.promise(() => convertToModelMessages(result, { tools }))
})
5.4 工具执行上下文
工具执行上下文定义在tool/tool.ts:
// src/tool/tool.ts
export type Context<M extends Metadata = Metadata> = {
sessionID: SessionID
messageID: MessageID
agent: string
abort: AbortSignal
callID?: string
extra?: { [key: string]: any }
messages: MessageV2.WithParts[]
metadata(input: { title?: string; metadata?: M }): void
ask(input: Omit<Permission.Request, "id" | "sessionID" | "tool">): Promise<void>
}
6. 架构优势分析
6.1 Plan-Build解耦设计
6.2 权限粒度控制
通过规则集实现细粒度权限控制:
// 权限规则示例
const ruleset = [
{ permission: "*", pattern: "*", action: "allow" }, // 允许所有
{ permission: "edit", pattern: "*", action: "deny" }, // 禁止编辑
{ permission: "edit", pattern: ".opencode/plans/*.md", action: "allow" }, // 例外
{ permission: "*.env", pattern: "*", action: "ask" }, // 环境文件询问
]
6.3 工具截断机制
Truncate.output防止工具输出过长:
// tool/tool.ts
toolInfo.execute = async (args, ctx) => {
const result = await execute(args, ctx)
const truncated = await Truncate.output(result.output, {}, agent)
return {
...result,
output: truncated.content,
metadata: {
...result.metadata,
truncated: truncated.truncated,
...(truncated.truncated && { outputPath: truncated.outputPath }),
},
}
}
6.4 Doom Loop检测
防止智能体在相同工具调用上无限循环:
// session/processor.ts
case "tool-call": {
const recentParts = parts.slice(-DOOM_LOOP_THRESHOLD)
if (
recentParts.length === DOOM_LOOP_THRESHOLD &&
recentParts.every((part) =>
part.type === "tool" &&
part.tool === value.toolName &&
JSON.stringify(part.state.input) === JSON.stringify(value.input)
)
) {
yield* permission.ask({
permission: "doom_loop",
patterns: [value.toolName],
sessionID: ctx.sessionID,
...
})
}
return
}
7. 数据模型
7.1 会话数据库结构
-- session表
CREATE TABLE session (
id TEXT PRIMARY KEY,
project_id TEXT NOT NULL REFERENCES project(id),
parent_id TEXT,
title TEXT NOT NULL,
permission TEXT, -- JSON Ruleset
time_created INTEGER,
time_updated INTEGER,
time_compacting INTEGER,
time_archived INTEGER
);
-- message表
CREATE TABLE message (
id TEXT PRIMARY KEY,
session_id TEXT NOT NULL REFERENCES session(id),
time_created INTEGER,
data TEXT NOT NULL -- JSON MessageInfo
);
-- part表
CREATE TABLE part (
id TEXT PRIMARY KEY,
message_id TEXT NOT NULL REFERENCES message(id),
session_id TEXT NOT NULL,
time_created INTEGER,
data TEXT NOT NULL -- JSON Part
);
7.2 消息类型层次
MessageV2.Part
├── TextPart -- 文本内容
├── ToolPart -- 工具调用
│ ├── pending
│ ├── running
│ ├── completed
│ └── error
├── ReasoningPart -- 推理过程
├── FilePart -- 文件附件
├── StepStartPart -- 步骤开始
├── StepFinishPart -- 步骤完成
├── SnapshotPart -- 快照
├── PatchPart -- 补丁
├── AgentPart -- 智能体切换
├── SubtaskPart -- 子任务
├── CompactionPart -- 压缩标记
└── RetryPart -- 重试标记
8. 结论
OpenCode的Plan和Build智能体系统展示了一个精心设计的AI编程助手架构:
8.1 核心创新
- 职责分离:Plan智能体专注规划,Build智能体专注执行
- 权限隔离:通过规则集实现工具级别的访问控制
- 子智能体协作:支持专业化子智能体处理特定任务
- 上下文管理:自动压缩和清理机制保证长会话可用性
8.2 技术亮点
| 特性 | 实现位置 | 说明 |
|---|---|---|
| Effect框架 | 全局 | 函数式编程,依赖注入 |
| 权限系统 | permission/ |
通配符模式,规则集 |
| 工具注册 | tool/registry.ts |
动态加载,插件支持 |
| 会话压缩 | session/compaction.ts |
自动上下文管理 |
| Doom Loop检测 | session/processor.ts |
防止无限循环 |
8.3 设计启示
- 分层解耦:智能体、权限、工具分离,便于扩展
- 规则驱动:权限和配置通过规则描述,而非硬编码
- 事件流处理:使用流式处理管理LLM响应
- 可组合性:通过Effect的Layer实现模块组合
这种架构为构建复杂的多智能体AI系统提供了优秀的参考实现。
参考文献
| 文件 | 路径 | 说明 |
|---|---|---|
| 智能体定义 | packages/opencode/src/agent/agent.ts |
Agent服务核心实现 |
| 工具注册 | packages/opencode/src/tool/registry.ts |
工具发现和初始化 |
| 会话处理 | packages/opencode/src/session/processor.ts |
LLM流式响应处理 |
| 权限系统 | packages/opencode/src/permission/index.ts |
权限评估和请求 |
| Plan工具 | packages/opencode/src/tool/plan.ts |
计划退出工具 |
| 会话压缩 | packages/opencode/src/session/compaction.ts |
上下文溢出处理 |
| 消息管理 | packages/opencode/src/session/message-v2.ts |
消息格式转换 |
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)