注:文章由 codex 整理。

背景#

这里的 memory 不是操作系统内存,也不是模型推理时的 KV cache,而是 coding agent 在多轮、多 session、多项目之间保存和复用上下文的能力。

截至这次分析的源码快照,opencode 并没有内置一个类似 mem0mnemopi 或向量数据库的长期记忆系统。它的 memory 更接近三层组合:

  1. 规则型长期记忆AGENTS.md、全局 AGENTS.mdopencode.json 里的 instructions。这些内容每轮都会进入 system prompt,是最稳定、最可审计的记忆。
  2. 会话内压缩记忆compaction agent 把长会话压缩成摘要,并保留最近若干 turn,让当前 session 可以继续推进。
  3. 上下文型临时记忆:用户用 @file、MCP resource、skill、subagent 等方式临时补充上下文,主要服务当前任务。

所以分析 opencode memory 时,关键不是找一个 MemoryBackend,而是看它如何把规则文件、配置、压缩摘要和临时上下文组装进模型请求。

本文基于:

核心结论#

opencode 的 memory 设计偏“显式上下文工程(context engineering)”,而不是“自动记住一切”。

这带来一个很重要的实践判断:应该把长期稳定的项目知识写进规则文件,把当前任务过程交给 session history 和 compaction,把低置信度、临时性、可能过期的信息留在对话里,而不是沉淀到 AGENTS.md。

可以这样理解:

层次 opencode 机制 适合保存什么 不适合保存什么
长期项目记忆 项目 AGENTS.md 架构边界、测试命令、代码风格、危险操作约束 临时任务进度、一次性 debug 现象
长期个人记忆 ~/.config/opencode/AGENTS.md 个人偏好、默认工作方式、输出习惯 某个项目的私有约定
可复用规则片段 opencode.jsoninstructions 多文件规范、共享团队标准、monorepo 分包规则 需要模型自己递归解析的隐式引用
会话内记忆 compaction 当前 session 的目标、决策、已完成动作、未解决问题 跨 session 的长期知识
临时上下文 @file、MCP、skills、subagents 本轮要读的文件、外部资料、专项流程 需要每次都遵守的硬规则

实现总览#

一轮模型调用前,SessionPrompt 会把几类 system prompt 片段拼起来:

SystemPrompt.environment(model)
  -> 当前模型、工作目录、workspace root、git 状态、日期、references

Instruction.system()
  -> AGENTS.md / CLAUDE.md / CONTEXT.md / opencode.json instructions / remote instructions

SystemPrompt.mcp(agent, permission)
  -> MCP server 返回的 instructions

SystemPrompt.skills(agent)
  -> 可用 skills 的描述和加载规则

MessageV2.toModelMessagesEffect(msgs, model)
  -> 当前 session history,必要时包含 compaction 摘要

源码对应点在 packages/opencode/src/session/prompt.ts:它在处理每一步前并行取 skillsenvinstructionsmcpInstructionsmodelMsgs,然后把这些内容作为 system 传给 processor.process()

用数据流表示:

flowchart TD
    A["用户输入 / session history"] --> B["SessionPrompt"]
    C["SystemPrompt.environment"] --> B
    D["Instruction.system"] --> B
    E["SystemPrompt.mcp"] --> B
    F["SystemPrompt.skills"] --> B
    B --> G["processor.process"]
    G --> H["LLM request"]

    I["AGENTS.md / CLAUDE.md"] --> D
    J["~/.config/opencode/AGENTS.md"] --> D
    K["opencode.json instructions"] --> D
    L["Remote instruction URL"] --> D
    M["Compaction summary"] --> A

规则型记忆:Instruction 服务#

Instruction 是 opencode memory 最核心的实现文件。它负责三件事:

  1. 找到应该注入的规则文件。
  2. 读取本地或远程 instruction 内容。
  3. 当 agent 读取某个文件时,补充该文件附近的局部规则。

规则文件来源#

Instruction 初始化时定义了两组文件:

globalFiles:
  ~/.config/opencode/AGENTS.md
  ~/.claude/CLAUDE.md      # 未禁用 Claude Code 兼容时

instructionFiles:
  AGENTS.md
  CLAUDE.md                # 未禁用 Claude Code 兼容时
  CONTEXT.md               # deprecated

官方文档也给出同样语义:

  • 项目规则:项目根目录或上级目录中的 AGENTS.md
  • 全局规则:~/.config/opencode/AGENTS.md
  • Claude Code 兼容:没有 AGENTS.md 时读取 CLAUDE.md
  • 可以通过 OPENCODE_DISABLE_CLAUDE_CODEOPENCODE_DISABLE_CLAUDE_CODE_PROMPTOPENCODE_DISABLE_CLAUDE_CODE_SKILLS 关闭兼容

这里有两个实现细节值得记住:

  1. 全局规则只取第一个存在的文件。 如果 ~/.config/opencode/AGENTS.md 存在,就不会再读取 ~/.claude/CLAUDE.md
  2. 项目规则也是第一类命中优先。 它按 AGENTS.mdCLAUDE.mdCONTEXT.md 的顺序找,某一类命中后就不再继续找下一类。

这意味着迁移时最好明确选择一种规则入口。不要同时维护 AGENTS.mdCLAUDE.md,否则容易以为两个都会生效。

opencode.json instructions#

除了固定规则文件,opencode.json 还支持:

{
  "$schema": "https://opencode.ai/config.json",
  "instructions": [
    "CONTRIBUTING.md",
    "docs/guidelines.md",
    ".cursor/rules/*.md",
    "https://raw.githubusercontent.com/my-org/shared-rules/main/style.md"
  ]
}

实现上,Instruction.systemPaths() 会把非 URL 的 instructions 展开成本地路径:

  • ~/... 解析到 home。
  • 绝对路径用 basename 加 cwd 做 glob。
  • 相对路径通过 globUp() 从当前目录往 workspace root 方向找。
  • 多个 config 来源的 instructions 会合并去重,而不是简单覆盖。

远程 URL 则由 Instruction.system() 单独拉取,带 5 秒 timeout,失败时返回空字符串,不让整个 session 崩掉。

这让 instructions 很适合承载模块化规则:

  • docs/development-standards.md
  • test/testing-guidelines.md
  • packages/*/AGENTS.md
  • .cursor/rules/*.md

但也带来一个边界:远程 instruction 是运行时获取的,如果网络不可用、URL 内容漂移或被替换,agent 看到的规则也会变化。关键团队规则更适合本地 vendored 或版本锁定。

读文件时加载附近规则#

Instruction.resolve(messages, filepath, messageID) 是一个容易被忽略的机制。

当 agent 读取某个文件时,opencode 会从该文件所在目录一路向上走,寻找附近的 AGENTS.md / CLAUDE.md / CONTEXT.md。如果找到的规则文件:

  • 不是当前正在读的文件;
  • 不是已经作为 system instruction 注入过的文件;
  • 不是本轮之前已经由 read tool 加载过的文件;
  • 不是同一个 assistant message 已经 claim 过的文件;

就会把它以 Instructions from: <path> 的格式补充进结果。

这相当于一种“局部规则懒加载”。对 monorepo 很有价值:根目录 AGENTS.md 放全局原则,子包目录再放更具体的约定,只有 agent 真正进入这个子包时才加载局部规则,避免把所有包的细节都塞进上下文。

配置加载:多来源合并形成记忆层级#

Config 服务决定哪些 instructions、agent、permission、compaction 选项会生效。

官方文档列出的配置优先级是:

  1. 远程组织配置:.well-known/opencode
  2. 全局配置:~/.config/opencode/opencode.json
  3. OPENCODE_CONFIG
  4. 项目配置:项目中的 opencode.json
  5. .opencode 目录
  6. OPENCODE_CONFIG_CONTENT
  7. macOS/Linux/Windows managed config
  8. MDM managed preferences

源码里的一个重要细节是:普通配置合并用 deep merge,但 instructions 数组使用 concat + 去重。也就是说,全局 instruction 和项目 instruction 可以叠加。

这对 memory 的含义是:

  • 全局层放个人偏好和通用工作原则。
  • 项目层放 repo 约定。
  • .opencode 目录放项目内可复用 agents、commands、plugins、skills。
  • 组织远程配置可以作为企业级底座,但要注意本地项目配置可能叠加或覆盖部分字段。

会话内记忆:compaction#

compaction 是 opencode 用来处理长上下文的会话内记忆机制。它不是跨 session 的长期记忆,但对一个复杂任务能不能做完非常关键。

什么时候触发#

SessionPrompt 在每轮运行中会检查:

  • 当前 turn 是否是 compaction 任务;
  • 上一轮 assistant 的 token 是否接近或超过模型可用上下文;
  • 配置中 compaction.auto 是否关闭。

如果上下文溢出并且自动压缩开启,就创建一个 synthetic user message,里面带 compaction part,再由隐藏的 compaction agent 生成摘要。

压缩时保留什么#

SessionCompaction 会先区分两段:

  • head:较早的历史,交给模型压缩成 summary。
  • tail:最近的若干 user turn 及其后续 assistant/tool responses,原样保留。

默认保留最近 2 个 user turn,并用模型可用上下文的 25% 作为 recent budget,上限 8000 tokens,下限 2000 tokens。配置项包括:

{
  "compaction": {
    "auto": true,
    "tail_turns": 2,
    "preserve_recent_tokens": 8000,
    "reserved": 20000,
    "prune": true
  }
}

压缩前还会把过老的工具输出截断到较短文本,源码里 TOOL_OUTPUT_MAX_CHARS2000。这能避免一次巨大 cat、日志或 diff 把 compaction 本身挤爆。

压缩后的继续执行#

压缩完成后,如果是自动压缩,opencode 默认会追加一条 synthetic user message:

Continue if you have next steps, or stop and ask for clarification if you are unsure how to proceed.

这说明 compaction 的目标不是“生成一段摘要给用户看”,而是让 agent 继续执行当前任务。对 memory 来说,summary 必须保留目标、约束、已完成动作和未解决问题,否则自动续跑会丢方向。

和向量库 memory 的差异#

opencode 当前的 memory 不是检索增强记忆,而是 prompt 结构化管理。它的优缺点很鲜明。

优势:

  • 可审计:长期知识就在 Markdown / JSON 配置里,团队可以 code review。
  • 确定性强:规则文件每轮注入,不依赖 embedding 召回是否命中。
  • 权限边界清楚:项目规则、全局规则、组织配置有明确层级。
  • 适合代码仓库:repo 约定本来就应该随代码一起版本化。

不足:

  • 不会自动沉淀经验:一次 debug 结论不会自动变成未来记忆。
  • 没有语义召回:不会根据当前问题从历史会话里检索相似经验。
  • 容易被写胖AGENTS.md 越写越长会直接吃上下文。
  • 远程 instruction 有漂移风险:URL 内容变化会改变 agent 行为。

所以 opencode 的最佳实践不是“打开 memory 开关”,而是建立一个人类可维护的规则沉淀流程。

最佳实践#

1. 把 AGENTS.md 当成项目操作手册,不是知识库#

适合写进 AGENTS.md 的内容:

  • 项目结构中不容易从文件名看出来的边界。
  • 常用 build、lint、test 命令,以及命令顺序。
  • 必须遵守的代码风格、架构约束、接口兼容要求。
  • 危险操作、部署操作、数据迁移的审批要求。
  • 已经稳定下来的“踩坑规则”。

不适合写进去的内容:

  • 当前任务的临时 TODO。
  • 尚未验证的猜测。
  • 一次性日志分析。
  • 过长的背景材料。
  • 只是为了让 agent 知道“有这个文件”的目录清单。

一个好的 AGENTS.md 应该短、强、可执行:

# Project Instructions

## Commands

- Run `pnpm test --filter api` before changing `packages/api/**`.
- Run `pnpm lint` before final response when TypeScript files changed.

## Architecture

- `packages/core` must not import from `packages/web`.
- API handlers should call domain services instead of accessing database tables directly.

## Safety

- Do not run production migration commands unless the user explicitly asks.
- Never edit generated files under `src/generated/**`; update the schema and regenerate.

2. 用 instructions 拆分规则,而不是把一个文件写成百科全书#

当规则超过几屏后,把它拆成明确主题:

{
  "$schema": "https://opencode.ai/config.json",
  "instructions": [
    "AGENTS.md",
    "docs/agent/testing.md",
    "docs/agent/api-style.md",
    "packages/*/AGENTS.md"
  ]
}

拆分原则:

  • 根规则写“总原则和路由”。
  • 测试规则、API 风格、前端设计规则分别成文。
  • 子包自己的约定放在子包目录。
  • 大型 monorepo 优先用 glob,而不是在根文件里手工列所有细节。

3. 子目录规则要写局部差异,不要重复全局规则#

因为 opencode 会在读文件时懒加载附近 instruction,子目录的 AGENTS.md 很适合写:

# packages/billing Instructions

- Money values are stored in cents.
- All Stripe webhook handlers must be idempotent.
- Use `pnpm test --filter billing` after changing this package.

不要复制根目录已有的通用规则。重复规则会让上下文变胖,也会在未来改规则时产生不一致。

4. 把经验沉淀成“触发条件 + 行动”,而不是流水账#

低质量 memory:

- 上次修 billing 测试时遇到 mock 很麻烦。

高质量 memory:

- When changing billing date logic, run `pnpm test --filter billing -- recurrence`
  because snapshot-only tests missed timezone edge cases before.

好的长期记忆应该包含:

  • 什么时候触发。
  • 为什么重要。
  • agent 应该做什么。
  • 可验证命令或文件位置。

5. 把不稳定信息留在 task note 或 issue,不要写进长期规则#

很多信息“现在有用”,但不应该成为每次调用都注入的长期规则:

  • 某个 bug 当前排查到第几步。
  • 某个 PR 的临时决策。
  • 某个外部依赖今天的版本状态。
  • 还没有通过测试验证的推断。

这些更适合放在 issue、task note、PR description 或当前 session 里。等它变成稳定约束后,再浓缩进 AGENTS.md

6. 对 compaction 友好地表达任务状态#

长任务里,agent 最容易在压缩后丢失三类东西:

  • 完成条件。
  • 已经验证过的证据。
  • 仍然不能动的边界。

因此复杂任务可以显式维护一个短状态块:

Current task state:
- Goal: add OAuth callback validation.
- Done: route added, unit tests for invalid state passed.
- Remaining: integration test for expired code.
- Constraints: do not change provider SDK wrapper public API.
- Verification: `pnpm test --filter auth`.

这类结构比长篇自然语言更容易被 compaction 摘要保留下来。

7. 远程 instructions 要版本锁定或低风险化#

opencode 支持从 URL 拉取 instructions,但最佳实践是:

  • 用 raw GitHub commit URL,而不是浮动 main
  • 远程规则只放低风险、通用、可替换的内容。
  • 核心安全规则放本地仓库,接受 code review。
  • 远程拉取失败时要允许 agent 继续工作,不要让关键流程只存在远端。

8. 明确区分个人偏好和团队约定#

个人偏好放:

~/.config/opencode/AGENTS.md

例如:

  • 默认回答语言。
  • 是否先给计划。
  • final response 风格。
  • 自己常用工具路径。

团队约定放:

<repo>/AGENTS.md
<repo>/opencode.json
<repo>/.opencode/

团队约定要随仓库版本化,个人偏好不要污染团队 repo。

一个推荐模板#

项目根目录 AGENTS.md 可以从这个结构开始:

# Agent Instructions

## Project Shape

- Short description of the system and major packages.
- Non-obvious ownership boundaries.

## Commands

- Install:
- Lint:
- Unit test:
- Focused test:
- Build:

## Working Rules

- Keep changes scoped to the requested module.
- Prefer existing helpers over new abstractions unless duplication is real.
- Update tests when behavior changes.

## Verification

- For backend changes:
- For frontend changes:
- For schema or generated-code changes:

## Safety

- Never run:
- Ask before:
- Do not edit:

## Local References

- More testing rules: `docs/agent/testing.md`
- API style: `docs/agent/api-style.md`

如果规则继续增长,再把 Local References 移到 opencode.jsoninstructions 中,让 opencode 自动加载。

总结#

opencode 的 memory 实现可以概括为一句话:

它把长期记忆做成可版本化的 instruction,把会话延续做成 compaction,把临时上下文交给文件引用、MCP、skills 和 subagents。

这是一种保守但工程上很稳的选择。它牺牲了“自动记住所有经验”的魔法感,换来可审计、可 review、可调试的上下文系统。

实践上最重要的是维护纪律:

  1. AGENTS.md 只写稳定、可执行、会反复影响 agent 行为的规则。
  2. instructions 用来模块化和共享规则。
  3. 子目录规则写局部差异。
  4. 任务状态用结构化短块,帮助 compaction 保留目标和证据。
  5. 不把临时猜测沉淀成长期记忆。

如果未来 opencode 加入语义检索型 memory,这套规则型 memory 仍然不会过时。因为向量召回解决“找得到过去经验”的问题,AGENTS.md 解决“每次都必须遵守什么”的问题,两者不是同一个层次。