给 DeepSeek Harness 装上代码库的持久记忆:Hindsight Coding Agents 集成实战与源码解析
【免费下载链接】hindsightHindsight: Agent Memory That Learns项目地址: https://gitcode.com/GitHub_Trending/hindsight2/hindsight
本篇文章讲解如何为 DeepSeek Harness(dsh,一个基于 Cordis 插件框架的编码智能体)接入 Hindsight 的长期记忆能力:一条命令完成原生 Cordis 插件安装,让仓库的 git 历史与对话在后台自动沉淀为记忆库,并在下一次会话开始时被自动召回。读完本文,你将掌握安装、内存后端选择、配置分层、按仓库路由记忆库的原理,以及hindsight_*工具集在 dsh 生命周期事件中的真实接线方式。
为什么编码智能体最需要记忆
以离散会话工作的智能体擅长专注,却天然缺乏连续性——每一次新会话都要重新理解你的技术栈、重新踩一遍同样的坑、重新问一遍你已经回答过的问题。正如 hindsight-docs/blog/2026-08-14-deepseek-harness-memory.md 指出的:大部分真实修复可以从代码推导出来,但"最后一公里"往往取决于完全不在代码里的项目级决策——一个舍入规则、一份重试白名单、一条命名约定、一个平局裁决标准。这些决策存在于 git 历史和过往对话中,记忆的作用就是把它们放回智能体开始工作时的面前。
DeepSeek Harness 的插件优先设计("everything is a plugin")恰好给了解决途径:既然一切皆插件,那么给 Harness 补上记忆这件事,也可以用一个插件完成,而不是引入额外的 MCP 服务器或外部进程。本项目通过 hindsight-integrations/coding-agents 包实现这一点——一个共享的"反射 + 注入"核心,为每个智能体提供极薄的接入入口。
安装:一条命令
Harness 把一切视为插件,因此集成本身也是一个插件。安装并接线dsh只需:
npx @vectorize-io/hindsight-coding-agents install dsh该命令会在$DSH_HOME/cordis.patch.yml(默认~/.dsh)中注册一行 Cordis 插件记录,被每个dshprofile 组合加载,使用原生工具而非 MCP。安装时终端会询问记忆存放在哪里——Hindsight Cloud、你自建的服务端,或本机本地守护进程——只需选择一次。脚本化安装可改用--server cloud|self-hosted|daemon参数:
npx @vectorize-io/hindsight-coding-agents install dsh --server cloud --api-token <token> npx @vectorize-io/hindsight-coding-agents install dsh --server self-hosted --api-url http://localhost:8888 npx @vectorize-io/hindsight-coding-agents install dsh --server daemon安装器把运行时复制到~/.hindsight/coding-agents,再指向各智能体的接线,因此与你在哪里执行命令无关;它是幂等的(重复执行安全),会为触碰过的文件保留.hindsight-backup备份,uninstall只移除自己的条目。日常更新也无需额外操作:默认开启的autoUpdate会让每次会话启动时在后台检查 npm 并重新暂存较新版本;想固定版本可设"autoUpdate": false。
如果偏好从已发布包安装,官方还提供了dsh plugin --profile web add @vectorize-io/hindsight-coding-agents这条等价路线——包内自带 profile 补丁层(见 cordis.patch.yml),无需再手工编辑任何配置。
版本前提:Harness 把会话日志写为 Zstandard 帧格式的 JSONL,读取它需要 Node 22.15+。这也影响历史会话的导入(
--import-conversations)——旧版 Node 会跳过导入并说明原因,而不是静默导入空数据。
安装后没有"capture"命令需要记忆。从下一个会话开始,记忆是自动的。
源码级原理:Cordis 插件如何接线记忆生命周期
集成不是通过 hook 二进制桥接,而是直接绑定 dsh 的 Cordis 生命周期事件。src/dsh.ts 的注释解释了原因:dsh 的 Claude Code / Codex hook 桥只是同一套类型化生命周期事件的翻译器,直接绑定事件能保留 transcript、session id 与 awaited 的停止边界。四个事件的映射如下:
| Cordis 事件 | 对应行为 | 代码入口 |
|---|---|---|
agent/session-start | 冷检查 + 后台 git/代码库种子(seedIfCold) | hooks.sessionStart |
agent/pre-step | 召回(onPrompt)+ 以带来源的消息注入记忆 | hooks.preStep |
agent/turn-stopping | 完成回合的写回(onSessionIdle) | hooks.turnStopping |
agent/disposed | 释放该 session 的实时引用 | hooks.disposed |
三个值得注意的实现细节:
- 一个 dsh 进程服务多个仓库。与 opencode/Kilo/Cline 的"一进程一项目"不同,dsh 的 Web UI 可以在任意目录为每个会话创建工作区,
session.header.cwd各不相同。因此workspacesMap 按工作区根目录缓存RuntimeCore(bank、client、seed),而不是每进程一份;记忆按仓库解析,与进程无关。 - 注入消息带显式来源。
preStep在next()返回enter决策且用户确有新输入时,才调用onPrompt并追加一条注入消息——source: { kind: "plugin", plugin: "hindsight", form: "recall" }。form: "recall"是 dsh 自己的"已检索上下文"词汇,UI 会将其渲染为召回材料而非用户输入;prepend: true让本监听器置于最外层,保证记忆块排在所有其他插件的消息之后、最接近模型的回合。 - 子代理会话被跳过。
workspaceForAgent对origin === "subagent"的会话直接返回空——为其注入会为每次委派多付一次召回,为其保留则会把父会话已完整存储的片段重复归档。
所有监听器都是 fail-open 的:RuntimeCore从不抛出异常,无法解析工作区只会让该会话不获得记忆,而不会破坏智能体本身的运行。
教一次:项目规则如何被保留
在一个正常的工作会话中,告诉 Harness 这个仓库的规则即可。例如记录两条项目约定(包管理用pgm而非npm、PR 标题必须遵循 Conventional Commits),Hindsight 会将其保留为持久记忆:
这些保留内容落在作用域限定为该仓库的 Hindsight bank 中,并带有产生它们的 harness 标签。可以在 Control Plane 中实时观察它们到达:
整个过程无需手动导出。会话写回依赖 src/core/transcript-dsh.ts 对 dsh 会话日志的规范化:dsh 把会话存为追加式SessionEvent类型化日志(而非聊天数组),插件通过session.snapshotEvents()(alpha.4+ 的公开访问器)或旧版events属性直接读取实时日志。在约 30 种事件类型中,只有三种携带对话内容:
user/message——人类提示词或注入的插件上下文,只有source.kind === "user"的真实人类消息会被保留;assistant/message——单步回复;tool/call——模型的工具调用,渲染为紧凑的 action 回合。
tool/result被刻意跳过:其载荷是原始工具输出,actionLine约定会把这类内容挡在记忆库之外。注入的召回块(source.kind === "plugin")同样会被过滤掉,避免"把机器脚手架当成用户的话",也避免把召回的记忆再喂回下一次抽取。
下一个会话:它记得
稍后开启全新会话,询问项目约定。Harness 不再猜测,而是从记忆库中召回被教过的规则:
包管理规则与 Conventional Commits 要求得以逐字返回,因为它们被存储为"已核对一致的记忆"(reconciled memory),而不是依赖每个会话都会重置的上下文窗口。第一节课的学习成果成为第五十节课的起始上下文。从实现上看,这是preStep在用户提示到达时调用onPrompt触发召回、再把getInjection生成的记忆块以带来源消息追加进消息列表的结果;测试 src/dsh.test.ts 精确断言了这一行为——召回只针对人类提示("why did we roll back?"),工具续回合(只有 plugin 消息)不触发第二次召回,被拒绝或被中止的步骤原样通过。
超越召回:自愈的 Knowledge Pages
集成做的远不止保留和召回单个事实。在冷仓库上,它会运行一次只读调查(codebase survey),为架构、约定与进行中的计划(in-flight initiatives)播种 Knowledge Pages,并在你持续工作的过程中保持它们的最新状态。Harness 在开始任务前会阅读这些页面,并把新工作记录为被追踪的页面——文档因此实现了自我书写与自我修复。
这些页面由服务端从 bank 的记忆中合成(见 src/core/survey.ts 的注释),并使用delta refresh:每次刷新编辑页面而非重建。刷新时机由配置决定——默认pageTriggerCron: "H * * * *"每小时错峰刷新一次(每个页面通过 bank id 与页面名哈希出自己的分钟槽,参见 README 中 JenkinsH语法的说明),pageTriggerType: "auto-refresh"则回到每次整合后刷新(成本更高:每次整合每个页面一次 LLM 合成)。冷仓库调查由当前 harness 自己的 CLI 无头运行(Claude 配方下可用surveyModel与surveyBudgetUsd控制模型与预算),surveyRefreshCommits: 20控制架构持续变动时每隔多少提交重跑一次。
共享记忆库:教一个,全队皆知
因为记忆存放在 Hindsight bank 中而非 Harness 内部,它是可移植的。同一个 bank 既被 Harness 填充,也会被 Claude Code、Codex、Cursor 以及其他十余个编码智能体(本项目支持的完整名单见 hindsight-integrations/coding-agents/README.md)召回。教一个,其余整个工具舰队都知道。
这背后的机制是按仓库解析 bank。默认模板"coding-agent::{gitProject}"对 harness 中立——opencode、Claude Code、Codex、dsh 共享同一仓库的记忆;改用"{harness}-{gitProject}"则按智能体拆分。解析顺序为:
mapPathToBank——最长匹配的绝对路径前缀(映射仓库根目录即覆盖其下所有子目录;覆盖任何显式bankId);- 静态——设置了
bankId(或dynamicBankId: false); - 动态——
bankIdTemplate占位符展开:{gitProject}是 worktree 感知的仓库名(git rev-parse --git-common-dir把所有 linked worktree 解析为主 worktree 的 basename),{project}是工作目录 basename,{harness}是接入入口,{channel}/{user}来自环境变量。
gitIngest控制 git 深度:"message"只取提交消息(HEAD 移动时重 upsert 一条文档),"full"额外按"最新优先、渐进"抓取逐提交 diff,"none"关闭 git 摄入。
配置:一个 JSON 文件的分层覆盖
所有配置集中在一个文件:~/.hindsight/coding-agent.json。分层按字段覆盖,后者胜出:
- 内置默认值;
- 环境变量(
HINDSIGHT_API_URL、HINDSIGHT_API_TOKEN及每个标量设置对应的HINDSIGHT_<FIELD_IN_CAPS>,用于容器/CI); - 文件顶层;
harnesses.<name>段——按智能体覆盖;banks.<resolvedBankId>段——按仓库覆盖(bank 解析后应用,因此与仓库位置无关、目录移动后依然有效)。
环境变量只是回退:文件设置了值就以文件为准。retainTags、optInPaths等列表型设置接受逗号分隔值;mapPathToBank、harnesses、banks、retainMetadata等映射型设置为文件专用。配置在进程启动时读取(文件不被监听):hook 型 harness 每个 hook 调用读一次(下次提示即生效),插件型 harness(含 dsh)每次加载插件时读一次(重启智能体后生效)。唯一例外是apiToken——服务端拒绝请求时每个 host 都会重读它,因此轮换密钥无需重启。
| 字段 | 默认值 | 含义 |
|---|---|---|
apiUrl | https://api.hindsight.vectorize.io | Hindsight API 基址(本地服务端设为http://localhost:8888) |
apiToken | — | Bearer 令牌(Cloud 模式) |
bankIdTemplate | "coding-agent::{gitProject}" | 动态 bank id 格式 |
mapPathToBank | — | 绝对路径 → bank,最长前缀胜出 |
optInOnly/optInPaths | false/ — | 仅白名单项目启用记忆,其余完全惰性(不建 bank、不保留、不播种) |
retainTags/retainMetadata | — / — | 给每份文档加盖来源标签/元数据,支持{gitProject}等占位符 |
observationScopes | "shared" | 观测整合的作用域:每个 bank 一个全局无标签作用域;也可用"per_source"把"提交说的"与"对话决定的"分开整合 |
autoSeed/seedLimit | true/300 | 冷仓库从 git 历史自动播种 / 最近 N 条提交上限 |
codebaseSurvey/surveyRefreshCommits | true/20 | 冷仓库结构调查 / 每积累多少提交重跑调查 |
retainSessions | true | 会话写回总开关 |
gitIngest | "message" | git 摄入深度:message/full/none |
manageBankConfig | true | 让插件塑造 bank 自身配置(retain 策略、knowledge实体标签组、缺失时的 missions);只做增量添加,绝不覆盖既有内容 |
disabled | false | 硬关闭开关(惰性插件/hook) |
autoUpdate | true | 每日后台检查并重暂存运行时代 |
按仓库精细控制同样在这个文件里,以解析后的 bank id 为键。例如把coding-agent::secret-client加入黑名单、把旧 bank 收敛到共享 bank、对大型 monorepo 开启完整 git 摄入,都只需在banks段声明;"两个仓库共享一个 bank"既可按 id 收敛(banks映射到同一字面目标),也可按路径前缀(一条mapPathToBank覆盖目录下所有仓库)。
验证与排障
集成在仓库内配有完整的验证链路:
- 单元测试src/dsh.test.ts 覆盖 pre-step 注入、写回、session-start 恰好一次播种、alpha.4+ 的
snapshotEvents回退,以及toDshParameters把 Zod 原始 schema 投影为 dsh 参数 JSON Schema(仅支持字符串参数,非字符串会显式报错); - 端到端测试e2e/Dockerfile.dsh 与 e2e/dsh-stub-model.cordis.yml 用一个本地 echo 模型(
hindsight-stub,openai-completions协议)替代 DeepSeek API,使 E2E 无需真实账户即可验证完整的生命周期接线,e2e/run-harness.sh负责在容器中执行安装命令并驱动会话。
日常排障时注意:失败永不破坏智能体——一次失败的反射、页面抓取或保留会退化为一次普通的无记忆回合并记入日志。所以"没有记忆"是一个日志问题:检查结构化诊断文件($TMPDIR/hindsight-plugin.log,可用HINDSIGHT_DIAG_FILE覆盖)中session_start与deepen_started是否为该 bank 触发过;reflect_failed/pages_failed表示一次无记忆的运行。hindsight_sync_status工具(脚本用dist/status.js)直接回答"记忆就绪了吗":"synced": true表示播种的记忆可查询。要重置某个仓库的记忆,只需删除服务端上对应的 bank——bank 是这个集成保持的唯一状态,删除后下一次会话就是真正的首次打开,种子与调查会从头再来。
小结
从一条npx命令到原生 Cordis 插件,从agent/session-start的冷启动播种到agent/pre-step的按需召回,再到agent/turn-stopping的会话写回,DeepSeek Harness 通过 Hindsight 获得了真正跨会话的代码库记忆:git 历史与对话在后台沉淀,约定与决策在下一次会话被自动摆在智能体面前,Knowledge Pages 让架构文档自我维护,而共享 bank 让记忆在所有编码智能体之间流动。相关实现细节可继续深入 hindsight-integrations/coding-agents/README.md 与 src/dsh.ts。
【免费下载链接】hindsightHindsight: Agent Memory That Learns项目地址: https://gitcode.com/GitHub_Trending/hindsight2/hindsight
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考