☰
opencode 整体架构分析
2026/10/15 10:45:56 网站建设 项目流程

opencode 整体架构文档

opencode 是一个开源的 AI 编程 Agent(对标 Claude Code):终端 / 桌面 / Web 多端 UI + 本地后台服务 + 多 Provider LLM 接入 + 插件 / MCP / Skill 扩展生态。
本文从结构、流程、上下文、通讯、特色工具五个维度说明其整体设计,并重点拆解子代理、skill/MCP、三层记忆与上下文压缩的内部实现。


一、整体结构

1.1 仓库布局

Bun monorepo(Turborepo 管理),约 60 个包,分为三大板块:

板块包职责
引擎schema纯类型与事件定义(零逻辑)
protocolRPC 协议分组(对外的 HTTP API 契约)
core引擎本体:会话、Agent、指令、工具、MCP、skill、权限、事件总线
aiLLM 客户端抽象(多 provider、压缩、tool-history、图像等模态)
plugin插件公共 API(给第三方写插件用)
util/effect-drizzle-sqlite/http-recorder共享工具、DB 封装、录制回放
服务与接入serverHTTP API 服务端,持有引擎与数据库
client生成的前端客户端(代码生成产物,禁手改)
sdk对外 SDK,组合 Client/Core/Server
function/enterpriseFaaS / 企业部署形态
codemodeEffect 原生的沙箱代码执行
界面cli命令入口(启动 TUI / 无头 / 后台服务)
tui终端 UI(opentui + Solid)
appWeb / 桌面渲染前端
desktop桌面壳(Electron)
session-ui/ui/theme共享组件、组件库、主题 token
gui-extensions编辑器扩展(GUI 集成)
console/stats管理后台 / 统计数据

另有services/(官网、更新服务)与sdks/(VS Code 扩展)。

1.2 分层依赖(严格单向)

schema(纯类型,零逻辑) ↑ ↑ protocol core ──→ ai ↑ ↑ client ←──────── server ↑ cli / tui / app(只准依赖 client + schema + protocol,禁止触碰 core/server)

依赖纪律(AGENTS.md 强制约束):

  • UI 层永远通过协议访问引擎,不做进程内直连;
  • client是生成代码:协议或 ServerHttpApi变更后bun run generate重新生成,不允许手改;
  • 运行时依赖方向:Schema → Protocol → Server单向,Client 可依赖 Schema/Protocol,sdk组合三者;
  • 默认分支v2,新分支/worktree/PR 默认对齐v2。

1.3 核心领域概念

概念说明
Session一次会话的聚合根(Root),所有消息/事件/状态以 Session 为边界,跨进程按 ID 定位
Agent行为配置:系统提示、可用工具、权限、模型、步数上限。分primary(独立会话的主代理)与subagent(子代理)两种模式
Location工作目录作用域。工具、权限、文件系统、MCP、技能、虚拟环境均按 Location 隔离——同一进程可同时服务多个项目目录互不串扰
Project / Workspace项目与工作区的持久化身份,管理目录归属与配置层级
Job后台任务注册表,承载子代理、后台 shell 的启动与崩溃恢复
Bus事件总线:durable 事件先落库再广播,事件溯源(Event Sourcing)入口
State/Transform可重放的注册表模式:工具 / 技能 / MCP / Agent 的"注册-覆盖-卸载"全走同一机制——后注册覆盖先注册,卸载自动回退到上一个

二、核心流程

2.1 一个回合(Turn)的生命周期

用户输入 │ ▼ ① 准入(Admission):prompt 先作为 durable 事件写入收件箱(session inbox) │ —— 先落库,再调度,崩溃不丢 ▼ ② 唤醒(Wake):执行协调器被 advisory 唤醒 │ 同 Session 的重复唤醒合并;不同 Session 并发执行(有写前 claim) ▼ ③ Drain:进入对应 Location,加载历史、Agent、模型、指令、工具 ▼ ④ Step 循环(一个 Step = 一次逻辑 LLM 调用) │ ├─ 每步前重新加载历史 + 估算 token,超预算先压缩 │ ├─ 组装 system + messages + tools,发起流式调用 │ ├─ 工具调用:多个可并发,但每个结果 durable 落库后才继续 │ └─ 工具结果回填 → 下一个 Step(编排从不交给内存态 tool loop) ▼ ⑤ 结算:步数用尽 / 无更多工具调用 / 用户打断 → Session 空闲,释放写前 claim

两个关键机制:

  • 一个 Step 多次物理尝试(physical attempt):限流重试、流中断续传、超长触发压缩重建,都不消耗新的逻辑步额度;assistant 消息 ID 在泛化重试时保持不变。
  • 写前执行声明(claim):进程开始忙一个 Session 时落一条恢复标记;正常结束(完成/失败/用户打断)释放,进程死亡则保留。启动时据此恢复中断的顶层会话,带每条执行的持久化 attempt 计数。

2.2 输入交付词汇(steer vs queue)

新输入有两种交付模式,在安全步边界生效:

  • steer(默认):插队转向,优先于队列项,最多插到第一次转向控制之前;在空闲边界 steer 也优先;
  • queue:排队,Session 空闲边界逐条投递(空闲边界若无 steer 则每轮投递一条再让运行器决定是否续跑)。

手动/compact、会话搬迁等控制指令也走同一收件箱,构成交付边界。收件箱项在"排队与转向之间"允许取消/变更;新用户输入表态会重置所选 Agent 的步数额度。

2.3 子代理流程(subagent)

主 Agent 调用subagent工具时,内部实现如下(详见第五节):

  1. 校验嵌套深度:沿 parentID 向上数祖先,depth ≥ 配置上限(默认 1,即默认不允许嵌套)即失败;
  2. 创建独立子 Session(记 parentID),模型选择优先级:显式指定 model > Agent 配置的 model > 继承父会话 model;新子会话注入开场白"You are a subagent spawned by another session.",不带父上下文——调用方需自带全部背景;
  3. 前台:阻塞(jobs.block)等子会话跑完,取子会话最后一条 assistant 文本返回,包装进<subagent sessionID=... state="completed">;若父被中断则同时中断并取消子代理;
  4. 后台:立即返回status="running"+ sessionID(批注:不要轮询);子会话完成后,向父会话投递一条synthetic 通知消息(<subagent sessionID=... state="...">,带 source/meta),唤醒父级继续;去重键childSessionID:startedAt;
  5. 续聊同一子代理:传入sessionID且该会话的 parentID 等于当前会话即复用,仅追加新 prompt(不加开场白);期间模型/Agent 可被切换;
  6. 动态清单:每次请求前把"当前可用的子 Agent 清单 + 描述"拼进工具描述里(过滤掉 primary/hidden/被权限拒绝的),提示里明确"用户点名的陌生名字多半是模型名,应传入模型参数"。

2.4 配置发现流程

启动时从全局目录(~/.config/opencode,兼容~/.claude、~/.agents)和项目目录(.opencode、.agents、.claude及opencode.json/.jsonc)向上查找配置文件与指令文件(AGENTS.md 等),自带文件 watcher 热重载。配置系统由大约 20 个"配置插件"组成,每个领域(Agent / MCP / Skill / Provider …)一个,用与第三方插件完全相同的机制喂数据。


三、上下文(Context)管理

3.1 一次 LLM 请求的上下文构成

┌─────────────────────────────────────────┐ │ System 提示 = 内置指令(日期/环境) │ │ + 发现的指令(AGENTS.md…) │ │ + Agent 自定义 prompt │ │ + 技能清单(<available_skills>)│ │ + MCP server 指令 │ │ (以上均为 Instructions 组的渲染)│ ├─────────────────────────────────────────┤ │ Messages = 指令 epoch 基线(渲染的初始值) │ │ + 历史的 System 更新消息(delta) │ │ + 会话历史(用户/助手/工具,文本或工具结果)│ │ + (若压缩过)<conversation-checkpoint> 占首条 │ ├─────────────────────────────────────────┤ │ Tools = 内置工具 + 插件工具 + MCP 工具 │ │ (请求时快照,注册变化不影响在途请求) │ └─────────────────────────────────────────┘

3.2 记忆分层(重点)

全部记忆共用同一个 SQLite(drizzle)后端,三层差异在作用域与持久性:

层机制生命周期实现落点
短期每次请求对指令源做一次并发read()后拼装上下文(内置core/date、core/environment就是典型"每次现读"的源),外加会话消息投影与收件箱待发项单次请求instructions源的实时read()
中期指令值增量(value delta):每类指令源算 SHA-256 哈希,只持久化"变更的 delta 与新增 blob",正文按内容寻址去重存储(AGENTS.md 改一个字不复制整文件);投影到instruction_state/instruction_entry;指令 epoch以压缩为边界推进,epoch 基线作为每次请求首条渲染值单会话内跨压缩instruction-state/instruction-entry投影 +instruction_blob表
长期①durable 事件日志(事件溯源:所有状态可折叠重放,UI 崩溃恢复靠它);②KV 存储(sqlite 键值库,插件storage域的底座,get/set/remove/scan);③指令文件(AGENTS.md / 全局配置,跨会话永久注入);④ Agent/Skill/MCP 配置本身跨会话永久bus事件日志 +kv表 + 全局/项目指令发现

中期记忆的关键设计是"值增量":每次模型调用前读全部指令源、哈希、只持久化变更的 delta。epoch 只在压缩结束时推进——epoch_start前移、initial_values基线滑动到当前值;会话搬迁也保留 epoch(让目标会话的指令变更保持时序),提交回滚则清除。

3.3 上下文压缩(Compaction)

触发:

  • auto:每步前对完整请求估算 token(estimateContext),达到模型窗口上限(默认 10% 余量)即压缩;紧邻已完成的压缩之后、或 checkpoint 后的首个实测 prompt 之前不压缩;
  • overflow:provider 明确拒绝"上下文超长"(且输出尚未开始),允许一次机会压缩后重建同一逻辑 Step,第二次溢出则失败终止;
  • manual:用户/compact命令进收件箱,投递时触发。

两条策略(按模型compaction设置选择):

  1. 结构化总结(默认):用专门的内置compactionAgent把旧历史总结进固定模板,模板分节为
    Objective / Requirements / Decisions / Work State / Next Move / Relevant Files / Important Context,
    采用滚动式合并旧 checkpoint;同时保留最近约 15k token 的原文(recent)。之后每次请求开头加一条<conversation-checkpoint>(含<summary>与<recent-context>)。
  2. 原生压缩:provider 自带的上下文续接机制(trigger / endpoint 两种机制),由 AI 客户端层(LLMClient.compact/canCompact)承载;引擎只管路由、重试、用量与 checkpoint 持久化,需要稳定的 route provenance。

溢出重试预算:SHRINK_STEPS = [0.7, 0.5, 0.35]——首次溢出前目标就已压到估算值的 70%,每次"too long" 拒绝按上次 × step再次缩小,35% 用尽仍失败则终止。

EPOCH 联动:压缩结束Compaction.Ended写 durable 消息行,同时调用advanceEpoch推进指令 epoch。

不可变性:完整转写永不删除——压缩只改写"模型可见的活跃历史",所有消息持久化在session_message表,历史加载以最近一次完成的压缩为第一条消息,但存储记录完整保留。


四、通讯

4.1 客户端-服务端架构

┌──────┐ HTTP RPC ┌──────────┐ 进程内 ┌───────┐ │ TUI │ ──────────→ │ Server │ ──────────→ │ Core │ → LLM Provider │ Web │ (生成客户端)│ (HttpApi)│ (Effect 服务)│(引擎) │ → MCP / FS / Shell │ 桌面 │ ←────────── │ │ │ │ └──────┘ 事件流(SSE) └──────────┘ └───────┘
  • 后台服务持有引擎与数据库;多端 UI 只是协议消费者;
  • 协议按领域分组(session / message / mcp / skill / tool / config …约 30 组),与引擎领域一一对应。

4.2 事件体系(双通道)

  • durable 事件:先写 sqlite 再广播,带聚合内单调序列号;投影器在发布事务内同步执行(事件与投影原子提交);支持log重放(断线后按 watermark 续传),UI 刷新 / 崩溃恢复都靠它;
  • live 事件:流式文本、reasoning、工具进度等瞬态增量,只在本实例内广播、不保证重放。

4.3 MCP 接入

  • 传输两种:
    • stdio(自研 transport):帧上限 16 MiB;stderr 排空保留 1000 字符尾;关闭时先SIGTERM、2 秒后升级SIGKILL;出站帧走有界队列(cap 64);
    • Streamable HTTP:MCP-Session-Id会话 + terminate 清理。
  • 工具注册:server 名做净化(非[A-Za-z0-9_-]替换为_),工具全名<server>_<tool>,注册进统一工具注册表,与本地工具同构(同一 schema / 权限 assert / 输出截断管道);
  • MCP 发布ToolsChanged时 debounce 100ms 后整表 reload,保持插件覆盖优先级;
  • MCP prompts:可调用模板(带 server 标记 + PromptsChanged);
  • MCP resources:暴露为opencode_list_mcp_resources/opencode_read_mcp_resource两个内置工具,底层把resources与resourceTemplates合并成资源目录;
  • OAuth / elicitation:远程 server 注册为 OAuth 集成(按sha1(name\0url)键控),凭据变化自动重连;elicitation 表单把 JSON-schema 属性映射为表单字段,支持外链 URL 模式。

4.4 插件体系

  • 插件 =define({ id, effect }),拿到约 20 个 domain 的上下文(tool / skill / session / mcp / permission / storage / vcs / worktree / websearch …);
  • 内置功能本身也是插件:subagent、skill、bash 与第三方工具走完全相同的注册路径,没有特权代码;
  • 三种插件源:本地 Bun 模块、Node 模块、npm 包;
  • 工具 / 技能 / MCP / Agent 注册全部走可重放 Transform 机制,支持热插拔与覆盖回退。

五、特色工具(专项实现)

5.1 subagent —— 子代理派发

详见 2.3。要点:独立子会话 + 模型优先级(显式 > Agent 配置 > 继承父)、前台阻塞 / 后台 synthetic 通知、sessionID续聊可切模型、嵌套深度默认 1、动态子 Agent 清单注入工具描述。会话分类:general/explore→ subagent 模式,build/compaction/title/summary→ primary 模式。

5.2 skill —— 技能加载

本质是指令注入,不是代码执行——技能目录里的脚本由模型用普通 shell 工具自己跑。

  • 发现:扫描配置目录里{*.md, **/SKILL.md},解析 frontmatter + Markdown 正文;技能 id 为目录名(SKILL.md)或文件名;autoinvoke由元数据opencode/autoinvoke决定(disable-model-invocation强制为关);
  • 来源:本地目录(含watch监听、100ms debounce 重扫)与远程下载(读远端index.json,缓存到~/.cache/skills/<hash>,带.opencode-version版本化原子更新);
  • 注入:读取SKILL.md内容 +采样文件清单(前 10 个文件),渲染成<skill_content name=...>块(含# Skill:、正文、基目录、采样提示、<skill_files>),作为技能的结果返回或直接被调用时注入;
  • 运行清单:系统提示里始终维护<available_skills>清单,变更用 diff 增量下发——新增渲染"New skills are available…“,移除渲染"no longer available and must not be used”;有变更就把整表作废重发。

5.3 question —— 结构化提问

向用户发起带选项的表单提问(支持多问、多选、推荐项标注),用于执行中获取确认/澄清,代替模型自问自答或胡乱猜。

5.4 patch / file-diff —— 代码修改与差异

  • patch:对文件做受控修改(含 BOM 处理、格式化器联动、写后校验);
  • file-diff:生成文件差异视图,模型与 UI 双方共用统一 diff 语义。

5.5 opencode 工具族 —— 自省与自管理

models(列出/筛选可用模型)、session_rename/session_move(改名与搬迁)、worktree_create / list / remove(Git worktree 管理,配合隔离实验)。

5.6 mcp-resource 工具族

opencode_list_mcp_resources/opencode_read_mcp_resource:枚举与读取 MCP server 暴露的资源(自定义 URI scheme 的文档、记录等),适合"用户提到一个非本地文件的东西"的场景。

5.7 基础工具箱

  • 文件:read / write / edit / glob / grep(Location 作用域内,走权限系统);
  • shell:后台进程管理、输出尾部保留 + 全量输出落盘引用(超长输出不爆上下文);
  • webfetch / websearch:网页抓取(渲染为 Markdown,外部内容视为数据)与网络搜索;
  • 所有工具输出统一做模型侧截断(默认头尾保留、中间省略标记),超长内容进托管存储。

六、安全与权限

  • 统一权限系统:每个工具调用前assert(action + resources + 可记忆的允许规则),用户可放行一次或永久保存;
  • Location 作用域的文件系统访问,工具拿不到作用域外的路径;
  • UI 层与引擎层严格隔离,客户端无进程内直连引擎的通道;
  • MCP 输入做路径穿越 / URL 校验;技能下载有版本化原子更新(staging + backup 回滚)。

七、小结

opencode 的架构可浓缩为四句话:

  1. 事件溯源 + 投影:一切先落 durable 事件、投影原子提交,状态靠折叠;崩溃可恢复、UI 可重放。
  2. Location 作用域:一个进程服务多目录,工具 / 权限 / 上下文按目录隔离。
  3. 一切皆插件:配置、内置工具、MCP、技能走同一套可重放注册机制,无特权路径。
  4. 上下文即预算:指令是值增量、记忆分三层、压缩双策略 + 阶梯重试——引擎对"模型能看什么"的每个字节都有明确账本。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询