OpenCode 的 Agent Runtime:从本地状态到 Agent Loop

OpenCode 的核心不是把用户文本转发给模型,而是一个以 Session 为边界的本地 Agent Runtime。它在本地保存任务事实,构造每轮上下文,限制模型可用工具,执行真实动作,再依据结果决定继续还是结束。

本文按运行时依赖方向,从底向上 讲:上一层只建立在下一层已经提供的能力之上。packages/opencode/src/session 承载当前 Loop 主线;packages/core/src/session 是正在演进的 V2 Session 持久化实现,文中单独标注。

OpenCode 六层主栈图 :。

六层主栈与代码锚点

自底向上 一句话说明 主要代码
1. 本地状态、Memory 与执行 把可恢复的会话事实和可实际操作的工作区留在本地。 src/storage/src/git/、shell/PTY 工具、packages/core/src/database/
2. 模型与外部能力接入 把不同模型厂商和 MCP 都收敛成统一的模型流与工具能力。 src/session/llm/src/mcp/request.ts
3. 工具与权限 把模型给出的 JSON 动作转换为受控的真实执行。 tools.tssrc/tool/permission
4. Session 与本轮上下文 把历史、规则和工具重新编译为这一轮模型真正能读的输入。 session.tsprocessor.tsmessage-v2.ts
5. Agent Loop 决定何时调模型、何时继续、何时压缩/等子任务,以及何时结束。 prompt.tsrun-state.ts
6. Runtime 接入与运行控制 把同一套 Runtime 提供给 CLI、UI、SDK,并发布过程事件。 server.tsagent.tsprompt.ts

如何把分层与流程对应起来

房子图解释"能力放在哪里";主流程解释"一条输入如何跑完"。两者通过 Session 汇合:每个模块都向 Session 读写事实,Agent Loop 每次回环都重新从它取数。

后文每一层都会附"最小代码骨架":保留真实源码的入口、核心数据和下一跳;/* ... */ 代表为阅读而省略的字段或分支,示例用于对照流程,不是可直接粘贴运行的完整实现。

text 复制代码
用户输入
  → Session 写入 User Message
  → 读取状态与记忆:历史、摘要、Todo、规则、工作区事实
  → 编译 system / messages / tools / params
  → 模型流
      ├─ 文本 / 推理 → 写入 Assistant Part
      └─ tool-call → 权限 → 本地或 MCP 工具 → ToolResult → 写入 tool Part
  → Loop 重读 Session
  → 继续下一模型回合,或满足终态后 idle

贯穿主栈的状态与 Memory

这里的 Memory 不是单独的向量库层,而是四类状态的组合:

类型 owner 下一轮是否直接送模型
会话记忆 SQLite 中的 Session、Message、Part、Todo 是,重放后进入 messages
压缩记忆 compaction 生成的摘要 Message 与保留边界 是,以摘要替代旧历史
外部事实 工作区文件、Git、快照、终端输出 否;模型通过工具再次读取
进程运行态 runner、AbortController、MCP client、流式拼接缓冲 否;进程重启即丢失

因此,Session 是 Agent 的主要持久记忆;工作区是外部事实源;进程内 Map 只是暂态。当前源码没有"自动抽取偏好并做 embedding / 向量检索、跨 Session 注入"的内建长期记忆链路。MemoryState 仅是流式事件处理期间的内存消息投影,不是长期记忆库。SQLite 表结构内存投影

1. 本地状态、Memory 与执行

这一层提供两类本地能力:一类是工具实际操作工作区所需的 filesystem、process/PTY、Git、snapshot;另一类是会话事实的 SQLite 持久化。

一句话:它既保存"这个 Agent 已经知道和做过什么",也提供"它现在能在电脑上实际做什么"。

默认数据库在 Global.Path.data/opencode.dbOPENCODE_DB 可改为其他绝对路径,也可设为 :memory:数据库路径

持久化不是一个大 JSON,而是三层关系:

text 复制代码
Session
  ├─ Message[]       用户 / assistant 的一轮消息
  │    └─ Part[]     text / reasoning / tool / 文件 / step
  ├─ Todo
  └─ 元数据:目录、Agent、模型、权限、token/cost、摘要、revert

session 表保存会话元数据;message 表按 session_id 保存轮次;part 表按 message_id 保存文本、工具调用和工具结果,主体字段以 JSON 保存。表结构

流式中,Text.Delta 是即时事件,供订阅者增量渲染;完整文本或工具结果结束后变成 PartUpdated,由 projector upsert 到 SQLite 的 part 表。模型事件发布Part 投影

因此,重启后可以重读历史 Session;但模型连接、AbortController、MCP client、未完成流式缓冲和当前 loop 协程是进程内运行态,不能原地续跑。每 Session 串行协调器

核心流程

这一层把模型产生的 tool-call(name, args) 翻译为一次可审批、可观测、可回放的本机动作:

text 复制代码
tool-call(name, args)
  → 解析输入:command/workdir 或 filePath/content/patch
  → 解析本地目标:cwd、绝对路径、worktree 边界
  → 权限:read / edit / bash;越界时 external_directory
  → 执行器:ChildProcessSpawner.spawn(...) 或 FileSystem API
  → 收集结果:输出流、exit code、diff、diagnostic、截断信息
  → 发布 FileSystem / Watcher / LSP 事件
  → ToolResult
  → 上层写为 Session 的 tool Part,并成为下一轮上下文

最小代码骨架

下面按 shell 工具执行器 删减;省略输出截断、超时、取消和审批细节:

ts 复制代码
// tool-call 的 args 已在上层转换为 command / cwd / timeout
const handle = yield* spawner.spawn(cmd(shell, command, cwd, env))

// 进程 stdout / stderr 持续更新本次工具调用的运行 metadata
yield* Stream.runForEach(Stream.decodeText(handle.all), (chunk) =>
  ctx.metadata({ metadata: { output: preview(chunk) } }),
)

const exit = yield* handle.exitCode
return {
  title: command,
  metadata: { exit, truncated: false },
  output: collectedOutput,
} satisfies ToolResult

shell 的执行器是 OS 子进程;readwriteapply_patch 直接调用文件系统 API。它们共享同一工具协议:参数 Schema、Tool.Context(Session、abort、审批)、输出截断和 ToolResult 形状。工具协议shell 启动进程批量 patch 的审批后落盘

目录边界、动作权限和执行后事件是三件不同的事:前者限制目标位置;中者决定本次 read/edit/bash 是否可执行;后者通知文件观察者/LSP。它们都发生在本地工具层,但 Session 持久化只消费最终的 ToolResult,而不替代文件事件。

2. 模型与外部能力接入

这里合并原先的"外部网络接入"和"模型接入":模型 Provider 负责请求/流式返回,MCP 负责把外部能力接成工具;模型回合编译负责将两者接到 Session。

一句话:无论底下是 OpenAI、Claude、Azure,还是一个远程 MCP,上一层只看到"模型能流式回答、工具能被调用"。

Provider 与 MCP 通道

核心流程

text 复制代码
Provider:本轮模型请求 → Provider adapter / AI SDK → 供应商 HTTP 流 → 统一 LLMEvent → Session
MCP     :stdio | Streamable HTTP | SSE → MCP Client → 工具发现 → client.callTool → ToolResult → Session

最小代码骨架

网络层只保留"建立外部通道"和"交给统一接口"两件事;Provider 与 MCP 的真实协议细节留在 adapter / client 内部。Provider 流入口MCP 建连与发现

ts 复制代码
// Provider:OpenCode 不直接拼各厂商 HTTP body
const result = streamText({ model, messages, tools, headers, providerOptions })
for await (const chunk of result.fullStream) {
  yield* LLMAISDK.toLLMEvents(state, chunk) // 统一为 LLMEvent
}

// MCP:先按 stdio / HTTP / SSE 建连,再发现工具定义
const client = yield* connectTransport(transport, timeout)
const defs = yield* McpCatalog.defs(client, timeout)
// defs 后续会转换为普通 Tool 的 Schema + execute

Provider:统一的是模型接口,不是 HTTP body

Provider 层保存模型目录、认证、variant、headers、provider options 等全局接入配置;一次 Session 调用只携带"本轮模型、上下文、工具、采样参数"。LLMRequestPrep.prepare() 先把它们整理为 { system, messages, tools, params, headers },再交给 streamText(...)请求准备调用

text 复制代码
Session 本轮上下文
  → LLMRequestPrep.prepare()
  → AI SDK streamText({ model, messages, tools, headers, providerOptions, ... })
  → 指定 Provider adapter
  → 实际 URL / 鉴权 / HTTP body / 流协议
  → result.fullStream(异步事件序列)

AI SDK 是这里的适配层:它替 OpenCode 对接不同模型供应商。OpenCode 因而不维护一份跨供应商通用 HTTP JSON;实际 wire-format 由具体 adapter 决定。很多 Provider 使用 SSE,但 OpenCode 消费的是 AI SDK 暴露的 fullStream,不能把 SSE 当作所有 Provider 的固定实现。

fullStream 的每个 chunk 会被 LLMAISDK.toLLMEvents 转成统一 LLMEvent,再进入 Session 处理器。AI SDK 事件适配流消费

text 复制代码
Provider 流事件
  → text / reasoning / tool-input / tool-call / tool-result / step-finish
  → LLMEvent
  → SessionProcessor.handleEvent
  → text、reasoning、tool、usage、finish 等 Session Part / Message 更新
  → 订阅者实时渲染;完成态持久化

这解释了"模型回调后做什么":不是直接通知 UI,而是先绑定到当前 Session 的 message/part;UI 只是 Session 事件的订阅者。text-delta 形成文本增量,tool-input-* 形成 pending 参数,完整 tool-call 才交给工具层,step-finish 写 token、成本、finish 等轮次状态。处理器

MCP:三种 transport 收敛为同一个工具调用

MCP 的本地模式用子进程 stdio;远程模式优先 Streamable HTTP,必要时退回 SSE。三者都先建立 MCP client,随后发现 server 的工具定义和 instruction;连接对象、工具目录、instruction 缓存在 Instance/MCP 运行态,而不是复制进每个 Session。本地连接远程连接发现与缓存

text 复制代码
local cmd / Streamable HTTP / SSE
  → MCP Client
  → getServerCapabilities().tools
  → MCP definition:name + inputSchema
  → McpCatalog.convertTool(...)
  → AI SDK dynamicTool({ inputSchema, execute })
  → client.callTool({ name, arguments })

因此模型看到的 MCP 工具和内建工具没有本质差别:都是 description + JSON Schema + execute。模型不需要知道某个工具位于哪个 server,也不需要自己拼 transport 请求。

"三种 MCP 回复在哪里统一处理"分两步:

  1. catalog.tsconvertTool 用统一 client.callTool(...) 接收 CallToolResult,处理协议级 isError
  2. session/tools.ts 的执行包装把 MCP content 统一成 OpenCode ToolResult:文本、图片、resource/blob 附件都会被规范化;随后进入当前 assistant message 的 tool Part
text 复制代码
MCP CallToolResult
  → OpenCode ToolResult / attachments
  → Session tool Part(completed 或 error)
  → 下一轮模型历史

这也是 Provider 与 MCP 的共同边界:连接配置和 client 是运行时全局状态;一次模型输出或一次 MCP 工具结果才是当前 Session 的事实,并在完成后被持久化。

Provider / MCP 通道负责连接、transport 和结果回流边界;本层接着讨论 OpenCode 如何把这些能力组织成统一模型回合。

模型回合:统一请求和统一事件

上一层完成 Provider 网络连接;本层不关心 URL 或 SSE 细节,只定义 OpenCode 的"标准模型回合"如何变成 AI SDK 调用,以及如何把返回流统一为 LLMEvent

核心流程

text 复制代码
Session 历史、system、候选工具和模型选择
  → StreamInput(OpenCode 的标准模型回合)
  → prepare() 编译 system / messages / tools / params / headers
  → run() 选择 native runtime 或 AI SDK runtime
  → Provider 发起流式模型调用
  → 原始流事件转换为 LLMEvent
  → SessionProcessor 消费事件,更新 Message / Part

最小代码骨架

这是 请求编译默认 runtime流事件适配 的主干;省略 Provider 兼容分支:

ts 复制代码
const prepared = yield* LLMRequestPrep.prepare(input)
const result = streamText({
  model: language, // 已按 Provider / 模型配置解析的 AI SDK LanguageModel
  messages: prepared.messages,
  tools: prepared.tools,
  headers: prepared.headers,
  providerOptions: ProviderTransform.providerOptions(model, prepared.params.options),
})

const state = LLMAISDK.adapterState()
for await (const raw of result.fullStream) {
  for (const event of LLMAISDK.toLLMEvents(state, raw)) {
    yield* processor.handleEvent(event)
  }
}

通用模型回合

llm.tsStreamInput 是 Host 侧模型请求,不是 Provider HTTP body:

ts 复制代码
{
  user, sessionID, parentSessionID?,
  model, agent, permission?,
  system, messages, tools,
  retries?, toolChoice?
}
数据组 字段 用途
本轮身份 usersessionIDparentSessionID 当前任务、会话归属、子 Session 关联;还会进入 header、日志、trace。
模型选择 modelagent 确定 Provider 模型、Agent prompt、采样参数和默认权限。
上下文 systemmessages 系统规则和由 Session 历史转换出的 ModelMessage[]
能力 toolspermissiontoolChoice 候选工具、可见性规则,以及 auto/required/none 调用策略。
控制 retries 重试、取消、遥测等 Host 控制。

其中 sessionIDagentpermission 并不原样发给模型:它们分别影响请求 header/观测、system/options、工具过滤。真正交给 AI SDK 的通用数据是 model + messages + tools + generation params + providerOptions + headers

prepare():把抽象模型回合编译为本次真实请求

request.tsLLMRequestPrep.prepare() 只负责准备请求:不发网络请求、不写 Session、不控制 loop。

text 复制代码
Host 侧标准回合 + Agent / Provider / Session / Plugin 配置
                         │
                         ▼
                    prepare()
                         │
                         ▼
本轮最终请求:system · messages · tools · params · headers

输入来自不同 owner:Agent 提供 prompt、采样参数和默认权限;Session 提供环境、instruction 与历史;User 提供本轮 system、variant、工具开关;Model/Provider 提供默认 options、headers 与能力约束。

输出 Prepared Request 的结构是:

ts 复制代码
{
  system,
  messages,
  tools,
  params: { temperature, topP, topK, maxOutputTokens, options },
  messageTransformOptions,
  headers,
}
输出字段 编译规则 这一步解决什么
system Agent prompt / 模型默认 prompt + input.system + user.system 得到模型本轮最高优先级规则。
messages 通常为 system message + Session history 得到模型实际看到的历史;特殊 Provider 可改用专用 instructions
tools 候选工具 − Agent/Session 禁用项 − user 显式关闭项 得到模型本轮真正可调用的能力。
params Provider base → model.options → agent.options → user variant 得到最终温度、token 上限和 Provider options;右侧覆盖左侧。
headers 模型 headers + Plugin headers + Session 标识 将会话关联、客户端标识和 Provider 接入信息带到请求边界。

Plugin 的改写点也是分开的,而不是任意改整个请求:

Hook 允许改写
experimental.chat.system.transform system
chat.params 采样参数、token 上限、Provider options
chat.headers headers

兼容分支的含义是:

分支 为什么存在
OpenAI OAuth:system → instructions 该接口路径使用专用 instructions 字段,而不是普通 system message。
Azure completion URL:删除字段 这类 endpoint 不接受 reasoningSummaryinclude
OpenAI / Azure / Bedrock:strict: false 动态/MCP Schema 可能不符合 Provider 的严格 structured-output 约束;OpenCode 仍在工具执行边界校验参数。
Copilot:补 _noop 历史含 tool call 但本轮无可用工具时,该 Provider 仍要求 tools 字段存在。

这些是 Provider API 差异的隔离层,不是 Agent Loop 的分支。

run():接入依赖、选择 runtime、发起流

run() 是模型调用的执行入口:它拿到 prepare() 的结果后,选择可用 runtime,并返回模型流;不写 Session,也不判断 loop 是否结束。

text 复制代码
LLM.stream(input)
  → 创建本轮 AbortController
  → run(input, abort)
      → 取得 model adapter、Provider、Auth、Config
      → prepare(input)
      → 尝试 Native runtime
      → 否则 AI SDK streamText(...)
  → Native stream 或 AI SDK fullStream
  → 统一为 Stream<LLMEvent>

真正进入 Provider 网络层的边界是 streamText(...)run

streamText 字段组 字段 用途
模型与上下文 modelmessages 选择具体 adapter;提供本轮推理上下文。
工具能力 toolsactiveToolstoolChoice 工具完整定义、启用工具名、auto/required/none 调用策略。
生成控制 temperaturetopPtopKmaxOutputTokensmaxRetries 采样、输出上限、重试。
Provider / Host 控制 providerOptionsheadersabortSignal 供应商特有参数、请求头、取消信号。

transformParams 在最后发送前,把通用 ModelMessage[] 改写成当前 Provider 所需的消息格式。

LLM.Service 依赖的 Auth.Service | Config.Service | Provider.Service | Plugin.Service | Permission.Service | EventV2Bridge.Service | LLMClientService 是 Effect 的依赖声明,不是发给 Provider 的请求字段。

依赖 本层用途
Auth / Config 凭证、全局配置、遥测开关。
Provider 根据 model 找语言模型 adapter、Provider 配置。
Plugin / Permission 改写请求,过滤模型可见工具。
EventV2Bridge 事件体系桥接;不属于 Provider 网络请求。
LLMClient 给实验 Native LLM runtime 使用的底层客户端。

工具调用在流中发生

模型不必等整条回答结束才调用工具:

text 复制代码
text/reasoning delta
  → tool-input-delta(参数 JSON 尚未完整)
  → tool-call(工具名和参数完整)
  → AI SDK 调用 tools[name].execute(args)
  → 本地工具或 MCP 工具返回
  → tool-result
  → 模型基于结果继续下一个推理 step / 后续模型回合

对模型语义来说,工具结果后的推理就是"下一次回答";对 Runtime 来说,它可能是同一流的后续 step,也可能由外层 Loop 发起下一次模型请求。共同点是工具结果先写入 Session,下一次模型看到的是这份真实历史。

流事件字段 含义
tool-input-{start,delta,end} { id, name, text }text 是尚未完成的 JSON 参数片段。
tool-call { id, name, input }input 已完整,可执行。
tool-result { id, name, result };与同一 tool-call.id 对应。
providerExecuted / providerMetadata 标识调用是否由 Provider 托管执行,以及 Provider 附带元数据。

流事件适配:分发 + 拼接

Stream<LLMEvent> 只是"异步依次产出标准事件"的类型名。实现流程只有一条:

text 复制代码
AI SDK fullStream 的一个原始 event
  → 按 event.type 进入 switch 分支
  → 必要时读取/更新 adapterState
  → 输出 0、1 或多个标准 LLMEvent
  → 下一个原始 event
text 复制代码
native.stream 已经是 LLMEvent
AI SDK fullStream → toLLMEvents(state, event) → LLMEvent
                                      ↑
                       只用于拼接 text / reasoning / tool 分片

流收口事件 switch

原始 AI SDK event 适配动作 输出
text-*reasoning-* 用当前 block ID 关联前后分片 对应 text-*reasoning-*
tool-input-*tool-calltool-result/error 记录或查询 toolCallId → toolName 对应工具事件。
finish-stepfinish 记录 step 序号、读取 usage/finish reason,结束时 reset step-finishfinish
startrawsourcefile 不需要交给 SessionProcessor,或只提取内部 metadata 不输出事件。

adapterState 只保存拼接所需的临时索引:当前 text/reasoning block ID、toolCallId → toolName 和 step 序号。连续 text-delta 要归到同一个 text Part;工具结果可能只带 call ID,需靠映射找回工具名。finish 后立刻 reset,不跨模型流保存业务状态。

LLMEvent 表示什么 SessionProcessor 的落点
text-start/delta/end 普通文本片段 assistant text part;delta 可即时展示。
reasoning-* 推理片段 reasoning part。
tool-input-* 工具参数 JSON 正在生成 pending tool part。
tool-call 工具名和完整参数已就绪 工具层开始执行。
tool-result/tool-error 工具结束 completed/error tool part。
step-finish/finish 一个模型步骤或整条流结束 finish、token、成本、快照。

模型层到此只输出事件,不写 SQLite、不渲染 UI、不决定是否继续 loop;SessionProcessor 消费事件,Agent Loop 再据此判断继续或结束。

3. 工具与权限:从 JSON Schema 到真实动作

工具层不负责决定"下一步做什么";它负责把模型给出的 tool-call(name, args) 变成一次受控的真实动作,并把结果写回本轮 Session。这里有三个不能混淆的动作:

一句话:模型只能提出"调用什么、参数是什么";这一层负责决定能不能做、怎么做、结果如何留下来。

text 复制代码
注册(全局有哪些工具)
  ≠ resolve(当前这轮允许模型看见哪些工具)
  ≠ execute(模型已调用某工具后,真正执行动作)

核心流程

text 复制代码
1. 启动时注册:ToolRegistry 注册内建 / Plugin 工具;MCP 连接后发现远端工具。
2. 每个模型回合前:SessionTools.resolve() 根据当前 Agent、Session、模型能力和 MCP 状态生成候选工具表。
3. resolve 过滤掉当前 Agent 或 Session 禁用、以及静态规则完全 deny 的工具;其余工具转为 AI SDK schema。
4. 同时为每个工具生成 execute 闭包,闭包绑定 sessionID、messageID、toolCallID、AbortSignal 和权限上下文。
5. 模型流产生完整 tool-call(name, args) 后,AI SDK 才调用这个 execute 闭包。
6. 闭包构造 Tool.Context,进入 Plugin hook、具体本地/MCP 工具的执行包装和细粒度权限判断。
7. 工具返回 ToolResult;模型流产生 tool-result/tool-error,SessionProcessor 将其写成当前 assistant message 的 tool Part。
8. 结果进入下一模型 step 或下一轮上下文;是否继续由 Agent Loop 判断。

最小代码骨架

下面是 SessionTools.resolve() 中最重要的"本轮工具表 + execute 闭包"结构;省略 Plugin hook、附件处理和特殊 MCP resource 工具:

ts 复制代码
const tools: Record<string, AITool> = {}

for (const item of yield* registry.tools({ agent, modelID, providerID, permission })) {
  tools[item.id] = tool({
    description: item.description,
    inputSchema: jsonSchema(ProviderTransform.schema(model, ToolJsonSchema.fromTool(item))),
    execute(args, options) {
      return run.promise(Effect.gen(function* () {
        const ctx = context(args, options) // 绑定 session / message / call / abort / ask
        return yield* item.execute(args, ctx)
      }))
    },
  })
}

MCP 的连接、发现、协议结果归一化已在上一层完成;到这一步,它已被转换为同样的工具定义,和本地工具共享同一工具表、权限边界和 ToolResult 回写路径。

1. resolve():为本轮编译工具表

SessionTools.resolve() 不是注册工具,也不在此时实际调用工具。它为这一轮模型请求组装 AI SDK 能消费的映射:

ts 复制代码
Record<toolName, {
  description: string
  inputSchema: JSONSchema
  execute(args, options): Promise<ToolResult>
}>

其中 descriptioninputSchema 会随模型请求发出,使模型知道可调用什么、参数如何组织;execute 不会发给模型,而是留在 OpenCode 进程中,等待模型真的发出同名 tool-call 时被 AI SDK 调用。

resolve() 读取的运行时输入及其用途如下:

输入 在本轮工具表中的作用
当前 Agent 根据 Agent 的 tool / permission 配置筛掉不可用工具。
Session 与当前 message 把工具执行结果归属到正确的会话和 assistant message。
当前模型 按 Provider / 模型能力把输入 schema 转成兼容格式。
MCP client 把已发现的远端工具加入同一候选集合。
processorAbortSignal 让工具能够发布执行进度、响应取消并回写本轮事件。

2. 模型调用后:执行包装如何落到真实工具

工具参数并不是一次性凭空拿到的。流中先有 tool-input-delta,表示参数 JSON 仍在增量生成;完整 tool-call 到达后,AI SDK 才执行第 4 步创建的闭包:

text 复制代码
model tool-call
  → tools[name].execute(args, options)
  → 由闭包构造 Tool.Context
  → plugin before hook
  → 本地工具 item.execute(args, context) 或 MCP client.callTool(...)
  → plugin after hook
  → ToolResult
  → tool-result / tool-error 事件
  → Session 的 tool Part

Tool.Context 是真实执行的运行时边界,包含 sessionIDmessageIDcallIDagentabort、历史消息,以及两个回调:metadata() 用于更新执行中的工具 Part,ask() 用于在需要时发起权限审批。也就是说,resolve() 创建的是"带会话身份的执行入口";第 5 步的模型调用才会触发真正的 shell、读写文件或 MCP RPC。

3. 权限有两道边界

时机 规则 结果
resolve() 工具被规则完全 deny 不进入本轮 schema,模型根本看不见。
execute() 对具体命令、文件路径或参数判断 allow / ask / deny allow 直接执行;deny 拒绝;ask 发布审批事件并等待 UI/SDK 回复。

前一道是在降低模型可见的能力面;后一道面对的是实际参数,因此能精确限制"哪条 shell 命令""哪个文件路径"。ask 不是 UI 线程阻塞:权限服务发布请求事件后等待一个 Deferred 结果,收到用户的 allow/deny 回复再恢复这次工具调用。权限判断

4. 结果如何回到模型与 Session

工具的统一返回形状包含 titlemetadata、文本 output 和可选 attachments。它不是由工具直接"回调模型";先进入 AI SDK 的流,再成为 Session 的可持久化事实,最后由下一次模型请求重放。

text 复制代码
1. 模型输出 tool-call(name, args)
2. 找到本轮 resolve() 预先包装好的 execute 闭包
3. 闭包绑定 sessionID / assistant messageID / toolCallID / 权限上下文
4. 执行真实工具
5. 工具结果包装为 ToolResult
6. ToolResult → tool-result 事件 → 写入原 assistant message 下的 tool Part
7. Loop 重新读取 Session,把该 tool Part 编译进下一次模型请求
8. 模型根据工具结果:
   - 再发 tool-call:重复 2--7
   - 输出正常文本并 stop:Loop 结束

其中第 2--6 步是一次工具调用的执行链;第 7--8 步将工具结果送回模型,构成"模型 → 工具 → Session → 模型"的外层循环。

text 复制代码
真实工具执行
  → ToolResult
  → AI SDK fullStream: tool-result / tool-error
  → OpenCode LLMEvent
  → SessionProcessor 更新 tool Part
  → Agent Loop 重新读取 Session
  → tool Part 编译为 ModelMessage
  → 下一次模型请求

工具执行包装已经持有三类关联键:sessionID 指明会话,messageID 指明这次 assistant 回答,toolCallID 对应模型发出的具体调用。SessionProcessortoolCallID 找到此前由 tool-input-* 创建的 pending Part;收到 tool-call 后将其更新为 running,收到 tool-result 后将其更新为 completed,收到 tool-error 则标记 error。

text 复制代码
tool-input-start  → pending
tool-call          → running
tool-result        → completed
tool-error         → error

完成后的事实形状可理解为:

ts 复制代码
{
  type: "tool",
  tool: "read",
  callID: "call_xxx",
  state: {
    status: "completed",
    input: { filePath: "src/a.ts" },
    output: "...文件内容...",
    attachments: []
  }
}

这份 tool Part 挂在当前 assistant message 下,因此 UI/TUI 可以显示运行中状态和最终结果;更重要的是,外层 runLoop() 不会仅因 Provider 返回 stop 就退出。它重新读取 Session:只要还发现工具调用,就继续编译历史。继续条件

MessageV2.toModelMessagesEffect() 会把 completed tool Part 转成模型通用的工具输出块:

ts 复制代码
{
  type: "tool-read",
  state: "output-available",
  toolCallId: "call_xxx",
  input: { filePath: "src/a.ts" },
  output: "...文件内容..."
}

Provider adapter 再将这个通用块转换成对应厂商的 tool-result 协议。因此模型在下一次 请求中看到的是已绑定、已完成的工具结果;它可以再发 tool-call,流程从执行包装再次开始,也可以输出普通文本并终止 Loop。工具调用形成的是"模型 → 工具 → Session → 模型"的外层循环,而不是工具和模型之间的一次直接回调。

工具主干的代码锚点为:全局注册每轮 resolve 与执行闭包工具 ABI流事件转换Session 回写历史重放权限服务

代码定位也属于这一层的工具反馈回路:

text 复制代码
glob:按文件名模式找候选路径
grep:按内容返回真实路径、行号、命中片段
read:读取已定位文件或目录

相对路径由后端锚定到 instance 工作目录。模型依靠工作目录、搜索结果和失败反馈收敛到正确文件,而不是天然记住仓库每个路径。readgrepglob

4. Session 与本轮上下文:事实中心和 prompt 编译器

Session 既是持久化事实中心,也是每轮模型输入的来源。它不会把全部本地状态直接送给模型,而是从历史和配置中选择、转换并编译出一轮上下文。组装位置

一句话:它把散落的历史、规则、工具结果整理成模型此刻真正看得懂的一包输入。

核心流程

text 复制代码
1. 用户输入先写成 Session 中最新的 User Message(U1)。
2. Agent Loop 重新读取该 Session 的 Message + Part;已被 compaction 摘要替代的旧历史被过滤。
3. 从 U1 取得本轮 Agent、模型和 Session 权限,并创建空的 Assistant Message(A1)作为本次模型流的输出容器。
4. MessageV2.toModelMessagesEffect() 将已持久化的历史转为 ModelMessage[]:文本、推理、历史 tool-call 和 tool-result 都在这里重放。
5. 同时构建本轮运行上下文:环境、项目 instruction、MCP instruction、Skill 引导,并 resolve 可用工具表。
6. 将 system + ModelMessage[] + tools + model / agent 等输入交给 handle.process();随后 prepare() 编译成真实 Provider 请求。
7. 模型流的 text / reasoning / tool 事件持续写入 A1 下的 Part;工具结果也写回 A1 的 tool Part。
8. 若模型结束且无工具待继续,A1 成为终态回答;若仍需工具结果继续推理,Loop 回到第 2 步,重放 U1 + A1,并创建 A2 接收下一模型回合的输出。

最小代码骨架

以下对应 prompt.ts 的每轮编译,省略 Agent 查找、Plugin 注入和结构化输出分支:

ts 复制代码
const msgs = yield* MessageV2.filterCompactedEffect(sessionID)
const lastUser = MessageV2.latest(msgs).user!

const msg = {
  id: MessageID.ascending(),
  parentID: lastUser.id,
  role: "assistant",
  agent: agent.name,
  mode: agent.name,
  sessionID,
  modelID: model.id,
  providerID: model.providerID,
  time: { created: Date.now() },
  // path / tokens / cost 等初始字段省略
}
yield* sessions.updateMessage(msg) // 先创建本轮输出容器 A1

const [skills, env, instructions, mcpInstructions, modelMsgs] = yield* Effect.all([
  sys.skills(agent),
  sys.environment(model),
  instruction.system(),
  sys.mcp(agent, session.permission),
  MessageV2.toModelMessagesEffect(msgs, model),
])
const system = [...env, ...instructions, ...(mcpInstructions ? [mcpInstructions] : []), ...(skills ? [skills] : [])]
const tools = yield* SessionTools.resolve({ agent, session, model, processor: handle, messages: msgs })
yield* handle.process({ user: lastUser, system, messages: modelMsgs, tools, model })

每次实际发给模型的核心输入可简化为:

text 复制代码
system   = Agent 规则 + 环境 + 项目 instruction + MCP / Skill 引导
messages = Session 历史重放:用户消息、assistant 消息、已完成工具结果
tools    = 本轮允许模型调用的工具 description + JSON Schema
params   = model、toolChoice、temperature、maxOutputTokens 等控制参数

每个模型回合先创建输出容器

模型流不是结束后才一次性写入 Session。每次发起模型调用前,Loop 会先创建一条空的 assistant message,并将它交给 SessionProcessor;随后到达的 text-delta、reasoning、tool-call、tool-result 都按 messageID 写入它下面的对应 Part。创建容器流事件回写

text 复制代码
U1:请读 a.ts 并解释
│
├─ A1:本次模型回合预先创建的空 assistant message
│  ├─ text Part:我先读取文件
│  └─ tool Part:read(a.ts) → 文件内容
│
└─ A2:工具结果需要继续推理时,下一模型回合创建的空 assistant message
   └─ text Part:基于 a.ts 的分析与最终回答

因此"创建 assistant message"不是提前生成下一条用户输入;它是为当前模型流 预留的输出归属。A1 的 tool Part 完成后,外层 Loop 从 Session 重放 U1 + A1,并在下一次模型调用前创建 A2 接收继续输出。这样每个流式事件都能确定写到哪一个 assistant message、哪一个 Part;工具结果也成为下一回合可恢复、可重放的历史事实。

Session 存储模型与模型消息之间的重放

模型不能直接理解 OpenCode 的数据库对象,例如 SessionV1.ToolPart。Session 的 Message + Part 是面向持久化、事件订阅和 UI 展示的内部存储模型;Provider 需要的是面向对话协议的 ModelMessage[]MessageV2.toModelMessagesEffect() 是二者之间的翻译层:每轮都从持久化的 Session 历史重新生成模型可读消息,而不是依赖上一轮请求的内存。消息重放

text 复制代码
Session 存储模型                         通用模型消息模型
──────────────────────────────────────────────────────────────
User Message + text Part             →  role=user 的 text content
Assistant Message + text Part        →  role=assistant 的 text content
Assistant Message + reasoning Part   →  role=assistant 的 reasoning content
Assistant Message + completed tool   →  tool-<name> / output-available
File Part / tool attachment          →  可被模型支持的 media content

例如 Session 中的一条 read 工具事实:

ts 复制代码
// 持久化 / UI 使用的 ToolPart
{
  type: "tool",
  tool: "read",
  callID: "call_xxx",
  state: {
    status: "completed",
    input: { filePath: "src/a.ts" },
    output: "...文件内容..."
  }
}

重放后变成模型通用的工具输出块:

ts 复制代码
// 发送给 Provider adapter 前的 ModelMessage content
{
  type: "tool-read",
  state: "output-available",
  toolCallId: "call_xxx",
  input: { filePath: "src/a.ts" },
  output: "...文件内容..."
}

Provider adapter 再将这个通用形状转换为 OpenAI、Anthropic、Gemini 等各自的实际协议。这里的"重放"只是在重建模型上下文,不会再次执行工具;它保证重试、断开恢复、压缩或下一模型回合仍能依据同一份工具结果继续推理。

text 复制代码
Agent 系统规则
+ 环境:工作目录、workspace root、仓库、平台、日期
+ 项目 instruction:AGENTS.md / CLAUDE.md / 配置 instruction
+ MCP instruction 与可发现的 Skill
+ 当前 Session 历史与已完成工具结果
+ 本轮获准工具的 description + JSON Schema
+ 最新用户目标

Skill 正文默认不会全量进入 system prompt;模型通常先知道有哪些 Skill 及如何加载,需要时再读取。Skill system prompt

"写 Session"也不是直接调用 UI 的 appendText。模型/工具的事件先变成 Session 的 message/part 更新;UI、TUI 和 SDK 都订阅这些事件。已完成的工具输出属于当前 message 的 tool Part;Provider 配置、模型目录、MCP client 和工具注册表则是全局运行配置,不会为每个 Session 复制一份。

5. Agent Loop:唯一的编排和终态判断点

一句话:它是总调度员,反复驱动"读状态 → 调模型/工具 → 写结果",直到这一轮任务真的结束。

核心流程

SessionPrompt.prompt() 写入用户消息、取得该 Session 的串行 runner 后进入主循环。入口循环

text 复制代码
用户输入
  → 写 user message
  → 读取 Session 历史和任务状态
  → 优先处理 subtask / compaction / revert
  → 编译本轮上下文与工具表
  → 调模型并消费 LLMEvent
  → 模型 / 工具结果写回 Session
  → 是否有未完成工具或需要 continue?是则回到"读取 Session"
  → 否则 Session idle

最小代码骨架

以下是 runLoop() 的循环骨架;真正代码还包含标题生成、提醒注入、重试和错误处理:

ts 复制代码
while (true) {
  const msgs = yield* MessageV2.filterCompactedEffect(sessionID)
  const { user: lastUser, assistant: lastAssistant, tasks } = MessageV2.latest(msgs)
  const hasToolCalls = /* 当前 assistant 是否仍有需回传模型的 tool Part */

  if (
    lastAssistant?.finish &&
    !["tool-calls"].includes(lastAssistant.finish) &&
    !hasToolCalls &&
    lastAssistant.parentID === lastUser.id
  ) break

  const task = tasks.pop()
  if (task?.type === "subtask") { yield* handleSubtask({ task, msgs }); continue }
  if (task?.type === "compaction") { yield* compaction.process({ msgs, sessionID }); continue }

  // 以下进入第 5 节:创建 assistant 容器 → 编译上下文 / 工具 → handle.process()
}

形式上是 while (true),但每轮都重新从 Session 读取历史。因此工具结果、子任务结果、压缩结果都会成为下一轮真实模型上下文。

结束条件不只是 Provider 返回 stop:还要确认最近 assistant 是否对应最新 user message、是否已经终态、是否带 tool-calls,以及是否存在 pending/running tool part。只有"正常结束 + 无待处理工具 + 对应当前用户"才 idle。

Loop 状态机

状态载体 关键状态 职责
Session runner idlebusyretry 同一 Session 串行、取消和运行态展示。
Assistant message finish=stop/tool-calls/error 判断模型轮次是否可终止。
Tool part pendingrunningcompletederror 判断真实动作是否仍在执行。
SessionProcessor 返回值 continuecompactstop 消费完一次模型流后,建议外层 Loop 继续、先压缩或立即停止。
text 复制代码
空闲(`idle`) → 接收用户输入 → 运行(`busy`)
运行 → 优先处理子任务 / 上下文压缩 → 重新读取会话
运行 → 组装本轮模型输入 → 调用模型与执行工具 → 终态判断
终态判断 → 继续运行(仍有工具或需要继续)| 空闲(满足 `stop` 条件)

OpenCode Agent Loop 状态机

图中的"终态判断 → 本次结束"是唯一的 stop 出口。它不是"模型供应商返回了 stop"这一条信号,而是必须同时满足:assistant 已有 finish、finish 不是 tool-calls、没有仍需回传模型的工具 Part,且该 assistant 属于最新 user message。任一条件不满足都沿"继续"回到"读取当前会话",由下一回合重新编译上下文。

task、控制结果与终态的边界

Loop 从 MessageV2.latest(msgs) 取出的 task,不是泛指"模型做的任务",而是 Session 中待处理的控制 Part:subtask 表示先执行一条子 Agent 委派,compaction 表示先压缩上下文。它们是持久化历史的一部分,Loop 每次重读 Session 都会优先调度它们。任务提取调度分支

不要把 continue / compact / stop 与模型事件混为一谈。text-deltatool-callfinish 是模型流事件;pending/running/completed/error 是工具 Part 状态;而 continue/compact/stopSessionProcessor.process() 消费完一次模型流 后返回给外层 Loop 的控制结果。它分别由"正常完成""需要压缩""被阻断或模型错误"等处理状态导出。处理器结果

一次外层终止表示"当前 Session 的这一次 prompt run 完成,runner 回到 idle",并不表示 Agent 模板生命周期结束;用户下一条输入仍可用同一 Agent、同一 Session 重新启动 Loop。子 Agent 也是如此:子 Session 先终止并将结果写回父 Session 的 task tool Part,父 Loop 是否终止仍要由父会话自己的终态条件决定。

因此,正常情况下"一次 Loop 迭代约等于一次父 Agent 的模型调用",但并不绝对:遇到 subtask 或 compaction 时,这一轮只处理控制任务,不发父 Agent 的正常模型请求。OpenCode 具备 Session 持久化和循环恢复基础,但没有固定的 Planner、统一 Evaluator 或严格确定性重放机制;它是 Agent Runtime,而不是完整的声明式工作流引擎。

6. Runtime 接入与运行控制

这一章有两个方向:接入 回答"用户和 UI 怎样进入、怎样看到 Runtime 的变化";运行控制 回答"同一次 Session run 依据什么策略选择模型、规则、工具和权限"。二者最终都汇入 SessionPrompt → Agent Loop

一句话:它把同一个 Agent Runtime 交给 CLI、Web、Desktop、SDK 使用,并让它们看到同一份过程状态。

本节总结

text 复制代码
1. 同步 prompt 和异步 prompt 启动的是同一个 Agent Loop。
   区别只是:同步请求等本次 run 完成;异步请求立即返回,随后靠事件看进度和结果。

2. 模型和工具不直接更新 UI。
   它们先更新 Session 的 Message / Part;Runtime 发布变化事件;UI 订阅这些变化后再渲染。

3. Provider、MCP、工具、项目规则等基础配置先在初始化阶段建立 Runtime 能力。
   每一轮再按当前 Agent、Session 权限和模型筛选出本轮有效策略;最后才编译为模型输入。

核心流程

text 复制代码
CLI / TUI / Web / Desktop / SDK
  → 进程内 Server 或 HTTP Server
  → prompt、取消、审批、查询等 Session 服务
  → SessionPrompt / Agent Loop
  → Message / Part 更新并发布事件
  → 进程内订阅或 SSE 订阅
  → UI 渲染同一份 Session 状态

最小代码骨架

同步与异步入口共享同一个 promptSvc.prompt();区别只有 HTTP handler 是否等待。事件端则把 Runtime 事件队列编码为 SSE。prompt handler事件 handler

ts 复制代码
// 同步:本 HTTP 请求等待 Session Loop 完成
const message = yield* promptSvc.prompt({ ...payload, sessionID })
return HttpServerResponse.stream(Stream.make(JSON.stringify(message)).pipe(Stream.encodeText))

// 异步:同一调用在后台运行,立即返回
yield* promptSvc.prompt({ ...payload, sessionID }).pipe(Effect.forkIn(scope))
return HttpApiSchema.NoContent.make()

// UI 订阅:事件 → 队列 → SSE;UI 再按 Message / Part 重新渲染
const queue = yield* Queue.unbounded<EventV2.Payload>()
const unsubscribe = yield* events.listen((event) => Queue.offerUnsafe(queue, event))
return HttpServerResponse.stream(Stream.fromQueue(queue).pipe(Stream.pipeThroughChannel(Sse.encode())))

请求进入与事件返回

Server 的 HTTP handler 将 prompt payload 直接交给 SessionPrompt.prompt();SDK/CLI 也可调用进程内 Server.Default.fetch(),不需要监听 HTTP 端口。进程内 Serverprompt handler

调用方式 Server 行为 客户端如何取得结果
同步 prompt 等待 promptSvc.prompt() 完成本次 Session run,再返回最终 message。 HTTP 响应拿最终结果;也可同时订阅事件展示过程。
异步 prompt 将同一 promptSvc.prompt() 放到后台执行,HTTP 立即返回。 订阅事件或后续查询 Session。

同步与异步不代表两套 Agent 执行逻辑;它们启动的是同一个 Session、同一个 Agent Loop、同一套模型/工具/权限路径。异步入口

模型和工具并不直接操作 UI。它们先更新 Session 的 Message / Part,Runtime 再发布事件;进程内客户端可直接监听,Web 等远程客户端由 Server 将同一事件流编码为 SSE 后订阅。事件 handler 在注册监听后建立队列、按 workspace 过滤事件,并发送连接事件与心跳。SSE 事件桥接

text 复制代码
模型 / 工具
  → Session Message / Part 更新
  → Runtime 事件
  → 进程内订阅 或 SSE
  → TUI / Web / Desktop 渲染

权限审批也走同一条返回路径:工具发起 ask,UI 订阅到审批事件;用户允许/拒绝后调用 permissionRespond,Server 再调用 Permission.reply() 解除或拒绝被暂停的工具执行。审批回复

运行控制:初始化能力 → 本轮策略 → 模型请求

"配置、Agent、项目规则、Plugin、Skill、MCP、Provider、工作区"不是一个会原样发送给模型的大对象,而是三层来源:

text 复制代码
1. 初始化能力:系统有什么能力
   Provider / 账户、MCP client、工具注册表、数据库、Server、工作区

2. 本轮有效策略:当前这次允许用什么
   当前 Agent + Session 权限 + 项目规则 + 当前模型

3. 模型请求包:真正给模型的内容
   system + messages + tools + params

初始化阶段加载并合并基础配置,建立长期运行的 Provider、MCP、工具注册和工作区能力;它们不会整体进入 prompt。Agent 服务再合并默认值与用户配置,得到模型、prompt、采样参数、mode、steps、options、permission 等策略。配置合并Agent 合并

每次 Loop 运行时,才依据当前 Agent、当前 Session、当前模型再次筛选和编译:项目 instruction、Skill、MCP instruction、Session 历史与工具表汇入模型输入。也就是说,初始化决定"有什么能力",每轮编译决定"当前能启用什么",最终只有编译产物进入模型。每轮汇合点

控制来源 本轮的实际影响
Agent 选择模型、Agent prompt、采样参数、最大 steps、默认权限。
Session 权限 与 Agent 权限合并,筛选工具并约束真实执行。
项目规则 / Skill / MCP 形成 system 引导;MCP 已发现工具可加入候选工具表。
Plugin 改写 messages、system、params、headers,或包裹工具执行。
Provider / 账户 提供模型 adapter、认证、headers、provider options。
工作区 提供 cwd、worktree、Git、目录边界和环境提示。

子 Agent 与后台任务

Agent 是模板;Session 是一次具体运行。开子 Agent 不只是把父会话加一个角色,而是创建带 parentID 的独立子 Session,并运行自己的 Loop:

text 复制代码
父 Session S0 → task(subagent_type, prompt)
  → 子 Session S1(parentID=S0,agent=指定 Agent)
  → S1 的 模型 → 工具 → 模型 loop
  → 最终 text → 父任务的 task tool result
  → 父 Session 下一轮模型调用

TaskTool.execute() 检查委派权限和深度、选择子 Agent 与模型、创建子 Session 并调用它的 prompt()TaskTool

子 Session 的直接背景来自 task.prompt,不复制父 Session 全量历史;它使用自己的权限,并承接父任务的 deny 与外部目录限制,默认限制再次开 task,避免无限递归。子 Agent 权限

模式 父任务行为 结果回传
前台(默认) task 等子 Session job 完成 作为当前 tool result,父 loop 再调模型。
后台(实验功能) task 立即返回 running job 完成后写 synthetic user message 到父 Session,再唤醒父 loop。

补充:长任务支撑机制(Agent Loop 的扩展)

这不是第七层;它是第 5 层为了处理长上下文、回退和后台工作而使用的持久化辅助机制。

一句话:任务太长时,不让 Loop 丢失现场,而是把"下一步要恢复什么"也写进 Session。

核心流程

text 复制代码
正常 Session Loop
  → 发现上下文溢出、用户 revert、需要标题/摘要等条件
  → 创建或触发对应的持久化辅助任务 / 快照操作
  → 下一次 Loop 优先处理 compaction、subtask、revert 等状态
  → 更新 Session 历史或工作区事实
  → 回到正常模型 → 工具 → Session 循环

最小代码骨架

压缩不是在 Loop 外另开一个不可见任务;它先写成持久化的 compaction Part,随后由 Loop 优先处理。创建压缩任务Loop 分支

ts 复制代码
// 1. 先把"需要压缩"写入 Session,重启后仍能发现
const message = yield* session.updateMessage({ role: "user", sessionID, agent, model })
yield* session.updatePart({
  messageID: message.id,
  sessionID,
  type: "compaction",
  auto,
})

// 2. 下一次 Loop 读取 task 后优先处理
if (task?.type === "compaction") {
  const result = yield* compaction.process({ messages: msgs, sessionID, parentID: lastUser.id })
  if (result === "stop") break
  continue
}

上下文压缩:生成可重放的摘要历史

压缩本身是一次特殊模型调用,不是直接删除或截断 Session 历史。它先把旧历史划分为需要总结的 head 和原样保留的近期 tailtail 会按 token 预算和 tail_turns 配置从最新回合向前选择。head / tail 选择

text 复制代码
原历史:U1 → A1 → U2 → A2 → U3 → A3 → U4 → A4

1. Loop 写入 C1:系统生成的 compaction pseudo-user message + Compaction Part。
2. Loop 优先调度 C1;compaction Agent 将旧段 head 连同压缩提示交给模型。
3. 模型生成 S1:agent=compaction、summary=true 的 assistant 摘要消息。
4. C1 的 Compaction Part 记录 tail_start_id,指出 U4 / A4 等近期事实从哪里开始保留。
5. 下一次普通模型调用重放:C1 + S1 + tail + 后续用户消息,而不再发送完整旧历史。

因此压缩后的下一轮模型上下文可理解为"系统生成的压缩任务 + 模型生成的摘要 + 最近原始历史 "。C1 不是用户真实输入;它是 Runtime 写入的持久化控制任务。自动压缩成功后,Runtime 还可能写入一条 synthetic "继续执行"用户消息来唤醒正常 Loop。

text 复制代码
旧历史 head 仍保留在数据库,用于审计和追溯;
filterCompacted() 决定下次模型请求不再重放它,而是发送 summary + tail。

这与展示用途的标题、文件 diff 摘要不同:compaction summary 会改变后续模型看到的上下文;标题和文件 diff 仅用于展示、检索或审计。历史重放过滤

机制 作用 代码
Revert 恢复文件快照,并清理被放弃分支后续历史,保证文件与 prompt 历史一致。 revert.ts
Compaction 接近上下文窗口时写入 compaction pseudo-user task,优先压缩后继续。 compaction.ts
标题与摘要 后台生成会话标题、文件差异摘要,供展示、恢复与审计。 summary.ts
消息转换 将持久化文本、附件、工具调用/结果转为 provider-ready messages。 message-v2.ts

推荐阅读路径

  1. 本地数据库Session 表:先明确本地事实边界。
  2. MCPLLM 请求:外部通道。
  3. 工具桥接权限:动作如何落地。
  4. 上下文消息转换:模型实际看到什么。
  5. 主 loop子 Agent:最后理解编排。

范围

基于静态源码 2859603;未执行真实 Provider/MCP 调用。Loop、工具和子 Agent 主线以 packages/opencode/src/session 的现有 runtime 为准;Session 本地持久化补充 packages/core 的 V2 实现。不同 Provider 的实际 wire-format、不同 MCP server 的行为仍应以 adapter 实现或真实运行证据为准。

相关推荐
leoZ23133 分钟前
AI+前端提效-09 AI赋能前端测试:单元测试、E2E测试自动生成,提升覆盖率
前端·人工智能·opencv·目标检测·数据挖掘·单元测试·语音识别
算了吧956940 分钟前
从“黑箱”到“透明”:答序科技如何用“诊断型”技术架构重构品牌AI可见度
人工智能·科技·架构
2601_9499506341 分钟前
考研英语资料太散?用小程序把真题、词汇和错题放到一起
人工智能·学习·考研·小程序·刷题·小程序推荐
ShineWinsu41 分钟前
对于OpenClaw:核心命令以及飞书钉钉等渠道的解析
人工智能·钉钉·飞书
牧羊人.3331 小时前
动手学深度学习 02 | 手写数字识别
图像处理·人工智能·深度学习·算法
熊猫钓鱼>_>1 小时前
鸿蒙AI Agent新范式:从“对话式辅助”到“工程化代理”的Harness架构实战解析
人工智能·笔记·学习·华为·架构·harmonyos
晓窗科技1 小时前
AI基座哪家好
大数据·人工智能·python
paopao_djshddhdj1 小时前
钉钉AI培训系统详解:适用行业与落地实践
人工智能
suaizai_1 小时前
LangChain核心概念一文全解析:从Prompt到Agent
人工智能