OpenCode 持久记忆实战指南:用 @vectorize-io/opencode-hindsight 让 Agent 跨会话记住一切
【免费下载链接】hindsightHindsight: Agent Memory That Learns项目地址: https://gitcode.com/GitHub_Trending/hindsight2/hindsight
OpenCode 是终端原生的 AI 编码智能体,但默认情况下每个会话都是"冷启动"——昨天定下的技术决策、项目约定与架构背景在下一次会话中全部丢失。本文基于 Hindsight 项目官方博客与技术文档,深入讲解@vectorize-io/opencode-hindsight插件的安装配置、自动 Hook 与显式工具的底层实现,帮助你为 OpenCode 搭建一套持久化长期记忆系统。
TL;DR
- OpenCode 会话默认无状态,每次对话都从零开始
@vectorize-io/opencode-hindsight通过三个显式工具(hindsight_retain、hindsight_recall、hindsight_reflect)和自动 Hook 提供持久记忆- 记忆会在会话开始时被注入系统提示词,让 Agent 在你说出第一句话之前就拥有上下文
- 会话空闲时自动捕获对话,记忆可跨越上下文窗口压缩(compaction)而保留
- 支持 Hindsight Cloud(零配置)或自托管两种模式
问题:会话之间没有任何持久记忆
OpenCode 是一个强大的终端原生编码智能体,你可以安装插件、接入各种模型供应商并完成真实工作。但每个会话都是相互隔离的——没有任何机制让 Agent 记住之前会话中发生的事情。
让 Agent 使用特定的测试框架,它照做了;下一次会话,它完全不记得。向它解释项目架构、部署流程、命名约定,会话一结束全部消失。
对于一次性任务这没问题;但对于依赖连续性的长期项目工作,这是实实在在的短板。
方案:Hindsight 记忆层 + OpenCode 插件
Hindsight 是 AI Agent 的记忆层:它从对话中抽取事实(facts),在查询时进行语义检索,并能基于累积的上下文综合出有依据的回答。@vectorize-io/opencode-hindsight插件把 Hindsight 直接接入 OpenCode 的插件系统,支持两种工作模式:
自动模式—— Hook 在后台处理一切:
- 会话开始时召回相关记忆(注入系统提示词)
- 会话空闲时保留(retain)对话内容
- 上下文窗口压缩时保留记忆
显式模式—— Agent 可直接调用的三个工具:
hindsight_retain—— 存储特定的事实或决策hindsight_recall—— 检索记忆中相关的上下文hindsight_reflect—— 基于累积记忆综合出有依据的回答
Session start │ ▼ ┌─────────────────────────────────────┐ │ System prompt │ │ + recalled memories (automatic) │ ├─────────────────────────────────────┤ │ OpenCode Agent │ │ ├── hindsight_retain (explicit) │ │ ├── hindsight_recall (explicit) │ │ └── hindsight_reflect (explicit) │ └─────────────────────────────────────┘ │ ▲ retain recall │ │ ▼ │ ┌───────────────────────────────┐ │ Hindsight bank │ └───────────────────────────────┘自动 Hook 覆盖大多数使用场景;显式工具则用于 Agent(或你)需要精细控制的时候。
安装与配置
1. 安装插件
npm install @vectorize-io/opencode-hindsight2. 在 opencode.json 中注册
{ "plugin": ["@vectorize-io/opencode-hindsight"] }注意:根据 hindsight-integrations/opencode/README.md,OpenCode 会在启动时自动安装
"plugin"数组中列出的插件,此时无需手动执行 npm install。opencode.json可以是项目级配置,也可以是全局配置~/.config/opencode/opencode.json。
3. 指向一个 Hindsight 服务端
方案 A:Hindsight Cloud(零配置)
在 Hindsight Cloud 注册,生成 API Token,然后设置两个环境变量:
export HINDSIGHT_API_URL="https://api.hindsight.vectorize.io" export HINDSIGHT_API_TOKEN="hsk_your_token"方案 B:自托管
本地或在自己的基础设施上运行 Hindsight,参考仓库内的 安装说明 启动服务:
uvx hindsight-server export HINDSIGHT_API_URL="http://localhost:8888"自托管模式下 API Token 为可选项。插件源码中的默认值是DEFAULT_HINDSIGHT_API_URL = "https://api.hindsight.vectorize.io"(见 src/config.ts),因此插件实例化本身不会因 URL 未配置而失败;如果 Cloud URL 未配置 API Key,请求会在调用时失败并返回清晰可操作的错误信息,而不是静默禁用插件。
4. 启动 OpenCode
opencode插件会自动激活。在第一次会话中,它会连接 Hindsight 并开始捕获上下文。
运行机制:一次会话的完整生命周期
插件初始化时(见 src/index.ts),会完成三件事:加载配置、创建HindsightClient、通过deriveBankId解析出当前记忆库(bank)ID,然后注册三个工具与三个 Hook。模块级PluginState会跨会话存活(OpenCode 服务进程只加载一次模块),用于记录回合数、已注入回忆的会话、各会话最后一次保留的回合。
以下是插件激活后一次会话的生命周期:
- 会话开始——
session.created事件触发,标记此会话用于记忆注入 - 首次 LLM 调用——
experimental.chat.system.transformHook 执行一次召回查询("project context and recent work"),并把匹配的记忆注入系统提示词 - 会话进行中—— Agent 可按需显式调用
hindsight_retain、hindsight_recall或hindsight_reflect - 会话空闲——
session.idle事件触发对话记录的自动保留(auto-retain) - 上下文压缩—— 如果上下文窗口填满,插件先保留当前对话,再把召回的回忆注入压缩上下文,确保重要内容不丢失
自动保留使用由retainEveryNTurns(默认 3)控制的滑动窗口,避免冗余存储的同时保持近期上下文的新鲜度。
值得注意的实现细节
- recall 不依赖事件顺序:源码注释(src/hooks.ts)明确指出,
session.created与system.transform的相对触发顺序是 OpenCode 未文档化的实现细节,在不同版本间有差异,因此自动召回完全由system.transformHook 驱动,而不是session.created。同时用recalledSessions集合做去重标记——只有 API 往返成功(即使结果是 0 条)才会标记,瞬时失败会在下一条消息时重试。 - 注入位置:召回内容会折叠进
system[0]第一段系统提示词而非追加新段落(src/hooks.ts)。因为 OpenCode 会把system[]的每一项作为独立的系统消息发出,而部分模型只认第一条——追加的段落可能被静默丢弃。 - 压缩时先保留后召回:压缩 Hook 先调用与空闲保留相同的
retainSession逻辑,再基于最后一条用户消息构造召回查询(src/hooks.ts)。
配置体系详解
插件支持三个层级的配置(后者覆盖前者,即"later wins"):
- 配置文件——
~/.hindsight/opencode.json,用户级默认值 - 插件选项——
opencode.json内联配置,项目级设置 - 环境变量—— 用于 CI/CD 或按机器覆盖
这个加载顺序在 src/config.ts 的loadConfig中实现:默认值 → 用户配置文件 → 插件选项 → 环境变量覆盖。源码还会校验枚举类字段:非法的retainMode、recallTagsMatch、recallBudget会打印错误并回退到默认值。
插件选项示例
{ "plugin": [ ["@vectorize-io/opencode-hindsight", { "hindsightApiUrl": "http://localhost:8888", "bankId": "my-project", "recallBudget": "high", "retainEveryNTurns": 5, "bankMission": "You are a coding assistant for a TypeScript monorepo. Focus on architecture decisions, test patterns, and deployment procedures." }] ] }核心配置项
| 配置项 | 默认值 | 作用 |
|---|---|---|
autoRecall | true | 会话开始时注入记忆 |
autoRetain | true | 会话空闲时捕获对话 |
recallBudget | mid | 召回的回忆条数:low、mid、high |
retainEveryNTurns | 3 | 自动保留频率(按用户回合计) |
retainMode | full-session | full-session每次整体 upsert 整段对话;last-turn创建分块窗口 |
bankMission | (无) | 指导 Hindsight 抽取什么、如何 reflect |
dynamicBankId | false | 从项目/Agent 上下文推导 bank ID |
完整环境变量参考
以下变量在 src/config.ts 的ENV_OVERRIDES映射中定义:
| 变量 | 说明 | 默认值 |
|---|---|---|
HINDSIGHT_API_URL | Hindsight API 基础地址 | https://api.hindsight.vectorize.io |
HINDSIGHT_API_TOKEN | 认证 API Key | (无,Cloud 必需) |
HINDSIGHT_BANK_ID | 静态记忆库 ID | opencode |
HINDSIGHT_AGENT_NAME | 动态 bank ID 中的 Agent 名 | opencode |
HINDSIGHT_AUTO_RECALL | 会话开始时自动召回 | true |
HINDSIGHT_AUTO_RETAIN | 会话空闲时自动保留 | true |
HINDSIGHT_RETAIN_MODE | full-session或last-turn | full-session |
HINDSIGHT_RECALL_BUDGET | 召回预算:low/mid/high | mid |
HINDSIGHT_RECALL_MAX_TOKENS | 召回结果的最大 token 数 | 1024 |
HINDSIGHT_RECALL_MAX_QUERY_CHARS | 召回查询的最大字符数 | 800 |
HINDSIGHT_RECALL_CONTEXT_TURNS | 召回查询附带的历史上下文回合数 | 1 |
HINDSIGHT_RECALL_TAGS | 逗号分隔,过滤召回结果 | (无) |
HINDSIGHT_RECALL_TAGS_MATCH | 标签匹配模式:any/all/any_strict/all_strict | any |
HINDSIGHT_RETAIN_TAGS | 逗号分隔,附加到每次保留 | (无) |
HINDSIGHT_RETAIN_EVERY_N_TURNS | 自动保留频率 | 3 |
HINDSIGHT_RETAIN_OVERLAP_TURNS | last-turn 模式的窗口重叠回合数 | 2 |
HINDSIGHT_RETAIN_CONTEXT | 保留记录附带的上下文标签 | opencode |
HINDSIGHT_DYNAMIC_BANK_ID | 启用动态 bank ID 推导 | false |
HINDSIGHT_BANK_MISSION | Bank 使命/上下文 | (无) |
HINDSIGHT_BANK_ID_PREFIX | Bank ID 前缀 | (空) |
HINDSIGHT_CHANNEL_ID | 动态 bank 的渠道维度 | (无) |
HINDSIGHT_USER_ID | 动态 bank 的用户维度 | (无) |
两个值得注意的点:一是recallMaxTokens与recallMaxQueryChars共同约束了召回的成本与上下文占用——查询过长时truncateRecallQuery会优先保留最新用户消息、从最旧的历史上下文开始裁剪(见 src/content.ts);二是debug选项刻意不提供环境变量(源码注释 src/config.ts 说明:环境变量在 OpenCode 的插件运行时中不可靠,尤其在 Windows 上)。调试请通过opencode.json插件选项或~/.hindsight/opencode.json设置"debug": true。插件日志走 OpenCode 自己的日志流(service=hindsight),用opencode --print-logs或在 OpenCode 日志文件中查看;错误信息与解析出的 API URL/bank 默认就会打印,无需开启任何开关。
三个显式工具
插件通过createTools(src/tools.ts)注册三个工具,Agent 可按需调用:
hindsight_retain(content, context?)—— 存入长期记忆。参数content为要记住的具体且自包含的信息,可选context说明信息来源。工具描述建议 Agent 记忆时"Be specific — include who, what, when, and why"。调用时会将retainContext、retainTags、retainMetadata一并传入。hindsight_recall(query)—— 搜索长期记忆。工具描述建议 Agent 在回答涉及过往对话、用户偏好、项目历史的问题前主动调用("When in doubt, recall first")。无结果时返回 "No relevant memories found.",有结果时返回Found N relevant memories (as of ... UTC)并附格式化条目。hindsight_reflect(query, context?)—— 与 recall 返回原始记忆不同,reflect 会把记忆综合成连贯答案,适合"关于这个用户你知道什么?"或"总结我们的项目决策"这类问题,返回response.text作为综合回答。
三个工具都会通过ensureBankMission确保 bank 的使命(mission)在首次使用时被设置(源码 src/bank.ts 用内存中的missionsSet去重,插件是长生命周期进程,不会重复调用createBank)。
用 Bank ID 隔离记忆
默认情况下所有会话共享一个opencodebank。需要项目隔离时有两种方案:
静态 Bank ID—— 每个项目一个 bank:
export HINDSIGHT_BANK_ID="my-project"动态 Bank ID—— 从上下文自动推导:
export HINDSIGHT_DYNAMIC_BANK_ID=true动态模式下,bank ID 由粒度字段(默认agent::project)拼接而成。例如位于/home/user/my-app的项目会得到 bank IDopencode::my-app。
多用户场景:
export HINDSIGHT_DYNAMIC_BANK_ID=true export HINDSIGHT_CHANNEL_ID="team-backend" export HINDSIGHT_USER_ID="alice"这会产生opencode::my-app::team-backend::alice—— 每个用户、每个项目、每个渠道完全隔离。
动态 bank 的源码细节
deriveBankId(src/bank.ts)支持五个粒度字段:agent、project、gitProject、channel、user。
其中gitProject是project的进阶替代:它通过git rev-parse --path-format=absolute --git-common-dir解析主工作树(main worktree)的名字,使得同一仓库的所有 linked worktree 共享同一个记忆库;当 git 不可用或目录不在仓库中时回退到工作目录名。如果你用git worktree开发,建议用以下配置让各 worktree 共享记忆:
{ "dynamicBankId": true, "dynamicBankGranularity": ["agent", "gitProject"] }另外要注意:bank ID 只在插件加载时推导一次,维度是进程级的——在运行中的 OpenCode 进程内不会随会话变化。因此按用户隔离时,要在启动每个用户的 OpenCode 实例之前设置好HINDSIGHT_CHANNEL_ID/HINDSIGHT_USER_ID环境变量。仓库内还有一份更完整的多用户记忆模式参考:cookbook/README.md。
踩坑与边界情况
记忆反馈回路。插件在保留前会从对话记录中剥离<hindsight_memories>与<relevant_memories>标签(stripMemoryTags,见 src/content.ts),防止循环放大。如果你构建了把 OpenCode 输出直接喂给 Hindsight 的自定义工具,需要注意这一点。
首次会话冷启动。新 bank 的第一次会话没有可召回的回忆,Agent 从空白开始,记忆从第二次会话起才累积。要预填充 bank,可以直接使用 Hindsight API 或 CLI(参考 hindsight-api/README.md)。
压缩时机。压缩 Hook 会先保留当前对话再召回相关上下文。如果 Hindsight 服务端响应慢或不可达,压缩照常进行、只是不注入记忆——插件永远不会阻塞 Agent。
上下文窗口预算。召回的回忆会消耗上下文窗口的 token。recallBudget: "high"意味着更多上下文,但留给对话的空间更少。长会话中mid(默认值)是较好的平衡。
权衡与替代方案
什么时候不要用:
- 如果会话总是一次性问答、没有连续性,记忆捕获与召回的开销不值当
- 如果处理敏感代码库、对话内容不应离开本机,请使用自托管的 Hindsight 实例
替代方案:
- 手工上下文文件—— 在项目根目录维护
CONTEXT.md或CLAUDE.md。可用,但需要人工维护,且无法跨会话扩展 - Git 历史—— 提交信息与 PR 记录的是"改了什么",而不是"为什么这么决定"
- 会话导出/回放—— 有些工具支持导出会话并回放,但你无法把 50 个历史会话都塞进一个上下文窗口
插件在"决策、偏好和上下文在数天或数周内持续累积"的长期项目工作中价值最大。另外,仓库内已有一个功能更全面的替代品值得了解:官方文档指出该插件已被 Coding Agents 插件 取代(一个覆盖 Claude Code、Codex、opencode、Cursor 等 CLI Agent 的包,使用所有 Agent 共享的 per-repo 记忆库)。本页面与已发布的 npm 包仍然可用,只是不再继续开发。
总结
- OpenCode 会话默认无状态
- Hindsight 插件通过自动 Hook(开始即召回、空闲即保留、压缩中保全)和显式工具(retain、recall、reflect)提供持久记忆
- 配置分层:配置文件、插件选项、环境变量(后者优先级最高)
- 记忆通过 bank ID 隔离——简单场景用静态 ID,多项目/多用户场景用动态 ID
- 插件非阻塞,Hindsight 不可达时优雅降级
下一步
- 安装插件:
npm install @vectorize-io/opencode-hindsight - 阅读 插件完整 README 获取全部配置细节
- 参考 OpenCode 集成参考文档 了解迁移到 Coding Agents 插件的方式
- 查看 插件源码 与 测试用例 深入理解 Hook 行为
- 设置一个贴合项目主题的
bankMission—— 它显著提升抽取与召回的针对性 - 在 cookbook 中探索更多跨 Agent 框架的记忆模式
【免费下载链接】hindsightHindsight: Agent Memory That Learns项目地址: https://gitcode.com/GitHub_Trending/hindsight2/hindsight
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考