OpenCode 学习笔记 04:高级功能

多 Agent、权限系统、重试机制、Snapshot 与子任务

January 22, 2026·8 min read·Yimin
#AI#Agent#OpenCode#TypeScript#Permission#Snapshot

从单一 Agent 到多 Agent 协作,从无约束到精细权限控制,从脆弱到健壮。

本篇目标

目标实现
多 Agent✅ tiny-agent 实现
权限系统✅ tiny-agent 实现
错误重试✅ tiny-agent 实现
Snapshot📖 原理介绍
子任务并发📖 原理介绍

一、多 Agent 系统

1.1 为什么需要多 Agent?

问题:单一 Agent 什么都做,但不是什么都做得好。

用户: 帮我写一个排序算法
AI: [写代码] ✅ 擅长

用户: 搜索项目里哪里用了这个函数
AI: [搜索代码] 勉强可以,但不够高效

用户: 帮我做个计划
AI: [规划] 需要特殊约束,否则会到处改文件

解决方案:不同任务使用不同的 Agent。

1.2 OpenCode 的 Agent 类型

Agent用途特点
build默认 Agent,编写代码全能,可以读/写/执行
plan规划任务只能写 .opencode/plans/*.md
explore搜索代码库只读,不能修改文件
general通用子任务作为子 Agent 被调用
compaction压缩上下文隐藏,系统自动调用
title生成标题隐藏,系统自动调用

1.3 Agent 定义结构

interface Agent {
  name: string           // 名称
  description?: string   // 描述(给 AI 看)
  mode: "primary" | "subagent" | "all"  // 模式
  hidden?: boolean       // 是否隐藏
  prompt?: string        // 专用 System Prompt
  permission: Ruleset    // 权限规则
  model?: { providerID, modelID }  // 指定模型
  temperature?: number   // 温度
}

1.4 Primary vs Subagent

┌─────────────────────────────────────────────────────────┐
│                    Primary Agents                        │
│  ┌─────────┐  ┌─────────┐  ┌─────────┐                 │
│  │  build  │  │  plan   │  │ custom  │  ← 用户直接交互  │
│  └────┬────┘  └────┬────┘  └────┬────┘                 │
│       │            │            │                       │
│       ▼            ▼            ▼                       │
│  ┌─────────────────────────────────────────────────┐   │
│  │                  Subagents                       │   │
│  │  ┌─────────┐  ┌─────────┐                       │   │
│  │  │ explore │  │ general │  ← 通过 task 工具调用  │   │
│  │  └─────────┘  └─────────┘                       │   │
│  └─────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────┘

1.5 explore Agent 的约束

// explore 只允许只读操作
permission: {
  "*": "deny",           // 默认禁止所有
  grep: "allow",         // 允许搜索
  glob: "allow",         // 允许列表
  list: "allow",         // 允许目录
  bash: "allow",         // 允许命令(查看用)
  read: "allow",         // 允许读取
  websearch: "allow",    // 允许网页搜索
  codesearch: "allow",   // 允许代码搜索
  webfetch: "allow",     // 允许获取网页
}

1.6 tiny-agent 实现

简化设计:支持 build/explore 两种模式切换。

You: mode explore
📋 切换到 explore 模式(只读)

You: mode build
📋 切换到 build 模式(默认)

二、权限系统

2.1 为什么需要权限?

AI: 我来帮你删除这个文件... rm -rf /
用户: 😱

需求

  1. 防止危险操作
  2. 保护敏感文件(如 .env)
  3. 让用户确认某些操作

2.2 权限动作

动作含义行为
allow允许直接执行
deny拒绝抛出错误
ask询问等待用户确认

2.3 权限规则结构

interface Rule {
  permission: string  // 权限类型(工具名或特殊权限)
  pattern: string     // 匹配模式(支持通配符)
  action: "allow" | "deny" | "ask"
}

// 示例规则
const rules: Rule[] = [
  { permission: "*", pattern: "*", action: "allow" },      // 默认允许
  { permission: "read", pattern: "*.env", action: "ask" }, // 读 .env 要确认
  { permission: "bash", pattern: "rm *", action: "deny" }, // 禁止 rm
]

2.4 规则匹配顺序

规则列表:
1. { permission: "*", pattern: "*", action: "allow" }
2. { permission: "read", pattern: "*", action: "allow" }
3. { permission: "read", pattern: "*.env", action: "ask" }

匹配时从后往前找,最后一条匹配的规则生效:
- read("src/index.ts") → 匹配 #2 → allow
- read(".env.local")   → 匹配 #3 → ask

2.5 特殊权限类型

权限说明
edit写/编辑文件
bash执行命令
external_directory访问项目外目录
doom_loop检测死循环
task启动子任务
skill使用 skill
question向用户提问
plan_enter/exit进入/退出规划模式

2.6 OpenCode 的默认权限

const defaults = {
  "*": "allow",                    // 默认允许所有
  doom_loop: "ask",                // 死循环要确认
  external_directory: {
    "*": "ask",                    // 访问外部目录要确认
    "/tmp/truncate/": "allow",     // 截断目录允许
  },
  question: "deny",                // 禁止提问(build agent)
  plan_enter: "deny",              // 禁止进入规划
  read: {
    "*": "allow",
    "*.env": "ask",                // .env 文件要确认
    "*.env.*": "ask",
    "*.env.example": "allow",      // 但 example 允许
  },
}

2.7 tiny-agent 实现

简化设计:基于工具名的简单规则。

interface Permission {
  tool: string
  action: "allow" | "deny" | "ask"
}

// explore 模式的权限
const explorePermissions: Permission[] = [
  { tool: "read", action: "allow" },
  { tool: "grep", action: "allow" },
  { tool: "glob", action: "allow" },
  { tool: "bash", action: "ask" },   // 命令要确认
  { tool: "write", action: "deny" },
  { tool: "edit", action: "deny" },
]

三、错误处理与重试

3.1 AI 调用会失败

常见失败原因:
├── 网络问题
├── Rate Limit(请求太频繁)
├── Provider 过载(Overloaded)
├── Token 超限
└── 服务器错误

3.2 重试策略

指数退避(Exponential Backoff)

重试 1: 等待 2s
重试 2: 等待 4s
重试 3: 等待 8s
重试 4: 等待 16s
...
最大等待: 30s(无 header)或按 header 指定

3.3 OpenCode 的重试实现

// 计算等待时间
function delay(attempt: number, error?: APIError) {
  // 1. 优先使用 API 返回的 retry-after-ms
  if (error?.responseHeaders?.["retry-after-ms"]) {
    return parseFloat(error.responseHeaders["retry-after-ms"])
  }
  
  // 2. 或者使用 retry-after(秒)
  if (error?.responseHeaders?.["retry-after"]) {
    return parseFloat(error.responseHeaders["retry-after"]) * 1000
  }
  
  // 3. 否则使用指数退避
  const base = 2000  // 2秒
  const factor = 2
  return Math.min(base * Math.pow(factor, attempt - 1), 30000)
}

3.4 可重试的错误类型

错误是否重试说明
OverloadedProvider 过载
too_many_requestsRate Limit
rate_limitRate Limit
server_error服务器错误
no_kv_spaceKV 缓存空间不足
invalid_request请求参数错误
authenticationAPI Key 错误

3.5 tiny-agent 实现

async function callWithRetry<T>(
  fn: () => Promise<T>,
  maxRetries = 3
): Promise<T> {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await fn()
    } catch (error) {
      if (!isRetryable(error) || attempt === maxRetries) {
        throw error
      }
      const delay = 2000 * Math.pow(2, attempt - 1)
      console.log(`⚠️ 重试 ${attempt}/${maxRetries},等待 ${delay}ms...`)
      await sleep(delay)
    }
  }
  throw new Error("Unreachable")
}

四、Snapshot 与回滚

4.1 为什么需要 Snapshot?

AI: 我来帮你重构代码...
AI: [修改了 50 个文件]
用户: 不对!我要撤销!
AI: ...

问题:AI 可能做出错误的修改,需要能够回滚。

4.2 OpenCode 的 Snapshot 设计

核心思想:使用独立的 Git 仓库跟踪文件变化。

项目目录                    Snapshot Git
/project/                   ~/.local/share/opencode/snapshot/{projectID}/
├── src/                    ├── objects/
├── package.json            ├── refs/
└── ...                     └── ...

每次 AI 操作前:
1. git add .               (添加所有文件)
2. git write-tree          (写入 tree 对象,返回 hash)
3. 保存 hash 到消息

4.3 Snapshot 操作

操作说明
track()记录当前状态,返回 hash
patch(hash)获取从 hash 到现在的变更文件列表
restore(hash)恢复到 hash 状态
revert(patches)按 patch 列表逐文件恢复
diff(hash)获取从 hash 到现在的 diff

4.4 为什么用独立 Git?

优点:
├── 不影响用户的 Git 历史
├── 不需要用户 commit
├── 可以跟踪 .gitignore 的文件
└── 独立于用户的 Git 操作

缺点:
├── 需要额外存储空间
└── 可能和用户 Git 操作冲突(极少)

4.5 回滚流程

用户: undo

1. 获取当前消息的 snapshot hash
2. 获取变更的文件列表 (patch)
3. 对每个文件:
   - 如果文件在 snapshot 中存在:恢复内容
   - 如果文件在 snapshot 中不存在:删除文件
4. 更新会话状态

4.6 tiny-agent 不实现 Snapshot

原因

  1. 需要复杂的 Git 操作
  2. 用户可以用 git checkout 或编辑器撤销
  3. MVP 阶段不需要

五、子任务与并发

5.1 什么是子任务?

Task 工具:让主 Agent 启动子 Agent 执行特定任务。

用户: 帮我分析这 5 个文件的代码质量

主 Agent:
├── Task(explore, "分析 file1.ts")  ─┐
├── Task(explore, "分析 file2.ts")   │ 并发执行
├── Task(explore, "分析 file3.ts")   │
├── Task(explore, "分析 file4.ts")   │
└── Task(explore, "分析 file5.ts")  ─┘
        │
        ▼
    收集结果,生成报告

5.2 Task 工具参数

const parameters = z.object({
  description: z.string(),    // 简短描述(3-5 词)
  prompt: z.string(),         // 详细任务说明
  subagent_type: z.string(),  // 子 Agent 类型
  session_id: z.string().optional(),  // 继续已有会话
})

5.3 子任务特点

特点说明
无状态每次调用独立,除非传 session_id
权限继承子 Agent 不能超越父 Agent 权限
结果返回子 Agent 的输出返回给父 Agent
并发执行多个 Task 可以同时运行

5.4 并发 vs 顺序

并发(推荐):
User: 分析 5 个文件
AI: [同时调用 5 个 Task]  ← 快

顺序:
User: 先分析 A,根据结果分析 B
AI: [Task A] → [等结果] → [Task B]  ← 有依赖时必须顺序

5.5 tiny-agent 不实现子任务

原因

  1. 需要复杂的会话管理
  2. 需要并发控制
  3. MVP 阶段复杂度太高

六、完整流程

┌─────────────────────────────────────────────────────────┐
│                     用户输入                             │
└─────────────────────────────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────┐
│  1. 选择 Agent (build / plan / explore / custom)        │
└─────────────────────────────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────┐
│  2. 检查权限 (allow / deny / ask)                        │
└─────────────────────────────────────────────────────────┘
                           │
              ┌────────────┼────────────┐
              ▼            ▼            ▼
           allow         ask          deny
              │            │            │
              │      等待用户确认        │
              │            │         抛出错误
              ▼            ▼
┌─────────────────────────────────────────────────────────┐
│  3. 创建 Snapshot (记录当前状态)                         │
└─────────────────────────────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────┐
│  4. 执行工具 (可能失败 → 重试)                            │
└─────────────────────────────────────────────────────────┘
                           │
              ┌────────────┴────────────┐
              ▼                         ▼
           成功                       失败
              │                         │
              │                    可重试?
              │               ┌────┴────┐
              │              是         否
              │               │          │
              │         等待后重试    抛出错误
              ▼
┌─────────────────────────────────────────────────────────┐
│  5. 返回结果 / 用户可撤销 (undo)                          │
└─────────────────────────────────────────────────────────┘

七、OpenCode vs tiny-agent 对比

功能OpenCodetiny-agent
多 Agent6 种内置 + 自定义build/explore 切换
权限系统复杂规则 + 通配符简单工具级别
错误重试指数退避 + header指数退避
Snapshot独立 Git 仓库❌ 不实现
子任务Task 工具 + 并发❌ 不实现

八、测试方法

测试模式切换

You: mode explore
📋 切换到 explore 模式

You: 写入一个文件  ← 应该被拒绝
AI: ❌ explore 模式不允许写入

You: mode build
📋 切换到 build 模式

You: 写入一个文件  ← 应该允许
AI: ✅ 已创建文件

测试权限

# explore 模式下
You: 执行 ls
AI: ⚠️ 需要确认执行命令 (y/n)?

测试重试

# 模拟 Rate Limit(需要故意触发)
⚠️ 重试 1/3,等待 2000ms...
⚠️ 重试 2/3,等待 4000ms...
✅ 请求成功

九、总结

完成项说明
✅ 多 Agentbuild/explore 模式切换
✅ 权限系统工具级别的 allow/deny/ask
✅ 错误重试指数退避重试机制
📖 Snapshot原理介绍(不实现)
📖 子任务原理介绍(不实现)

核心收获

  1. 多 Agent 分工:不同 Agent 擅长不同任务,权限也不同
  2. 权限三态:allow(放行)、deny(拒绝)、ask(确认)
  3. 优雅重试:指数退避 + 响应 header 指导
  4. Snapshot:独立 Git 跟踪,支持任意回滚
  5. 子任务并发:Task 工具启动子 Agent,并行执行