DSH ↔ agentmemory 会话记忆桥:把会话生命周期镜像到本地 agentmemory 守护进程(REST),提供 memory_recall / memory_remember 工具,并通过 agent/pre-step 按会话注入记忆上下文窗口。
安装
# GitHub 源码(首次需按提示配置 allowBuilds 构建授权后重试)
dsh plugin --profile web add github:Yiipu/dsh-agentmemory
装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络,工具审批管不到它。GitHub 来源的插件还会在安装时执行构建脚本——pnpm 默认拦截,所以安装可能停在 ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED 或 ERR_PNPM_IGNORED_BUILDS;dsh 会打印出需要添加的确切键名,把它加进该 profile 的 pnpm-workspace.yaml 的 allowBuilds 下,重跑一次即可装上。放行构建本身就是一次信任判断:请只安装可信来源,并尽量锁定 commit(github:owner/repo#sha)。
README
一个 DSH(DeepSeek Harness)的 Cordis 插件,让每个 dsh 会话都拥有 agentmemory —— 一个本地自托管、带 REST API 的记忆守护进程 —— 中的持久、可搜索记忆。
English | 中文
⚠️ 已废弃。 本插件不再维护。agentmemory 0.9.29+ 自带官方 dsh 连接器,请迁移:
agentmemory connect dsh --with-hooks官方连接器提供自动捕获(hook 经第一方
dsh-hooks-claude-code桥接)与 MCP 记忆工具;本插件的agent/pre-step上下文注入无官方等价物。已写入的记忆与观察留在 daemon 中(按 project/agentId 组织),迁移后仍可查询——但官方连接器写入的 project/agentId 可能与本插件不同(本插件用 git toplevel 项目名、dshagentId)。过渡期内每个 profile 二选一:daemon 去重无法合并两路捕获,同时开启会产生重复行。
dsh 会话是短暂的:会话一结束,agent 学到的一切——做过的工具调用、恢复过的错误、定下的决策——都随之消失。agentmemory 用跨会话、跨 harness 的持久化解决这一点(它已在服务 Claude Code、OpenCode、Hermes 等)。本插件就是 dsh 侧的那一环:把每个会话的活动写入 daemon 成为观察记录,在恰当的时机把记忆读回模型,并向 agent 暴露显式的记忆工具。
一个插件,三个面:
| 面 | 做什么 |
|---|---|
| 写(观察桥) | 把每个 DSH 会话事件映射为 agentmemory 标准 hookType,让 daemon 的压缩管线、搜索索引、viewer 都能读到真实内容——见映射注册表 |
| 读(上下文注入) | 经 agent/pre-step waterfall 追加 sourced user/message:每会话一次的项目 recall 窗口、可选的逐消息语义 recall、压缩前的补注入 |
| Agent 工具 | memory_recall / memory_remember,注册为模型工具,用于显式精确召回与主动写入 |
agentmemory 是硬依赖。 插件在加载时、注册任何能力(工具、监听器)之前就先检查
<baseUrl>/agentmemory/livez。如果守护进程不可达或未报告status: ok,插件会加载失败并抛出明确错误——绝不静默降级。
所有配置都放在插件 cordis.yml 行的 config 字段里。没有浏览器 UI,也没有持久化的配置文件。
安装(静态组合)
先把 agentmemory daemon 跑起来——插件的加载硬门要求它在线。daemon 默认 REST 端口为 3111;安装方式见 agentmemory 快速上手(npx -y @agentmemory/agentmemory@latest)。已在 agentmemory 0.9.29 上验证;要求 ≥ 0.9.29:
- 更早的 0.9.x 会在
/agentmemory/remember上丢弃agentId,agent 作用域的 recall 会因此漏掉所有已保存的记忆。- 更早的 0.9.x 的 observe 去重会把不带
tool_input的行(本插件的 approval → notification 行)折叠到同一把 key、5 分钟窗口内静默丢弃第二次审批。
然后从 GitHub 把包装进 profile:
dsh plugin --profile web add github:Yiipu/dsh-agentmemory
(本地 dev 检出也可用:dsh plugin --profile web add /path/to/checkout。)
把 cordis-row.example.yml 里的行挂进 host 组合 cordis.yml(或 ${DSH_HOME:-$HOME/.dsh}/.agent-presets/<id>/ 下的每会话 preset 组合)。最小行就是 {name}——Cordis 按插件的 Config schema 校验并填默认值(scoped 包名以 @ 开头时需加引号,因为 @ 是 YAML 保留标量起始):
- insert:
- id: agentmemory-bridge
name: "dsh-agentmemory"
config:
baseUrl: http://localhost:3111
enabled: true
需要 Node >= 20、一个
shell能力接缝(capability seam,标准 bash/pwsh 执行器),以及与 package.json 中peerDependencies范围匹配的 dsh harness(已验证版本:dsh-tools 0.1.1-rc.2 与 0.1.2-rc.1)。行可接受的全部键见配置键。校验命令见验证。
DSH 事件 → agentmemory 映射注册表
这张表是桥接向 daemon 发送内容的权威注册表。每条观察都使用 agentmemory 的标准 hookType:daemon 的 mem::observe 只对 prompt_submit / post_tool_use / post_tool_failure 提取可搜索字段(prompt、tool_name、tool_input、tool_output);其他 hookType 的数据留在 raw.raw,永远进不了搜索与压缩读取的合成 narrative。因此本插件不使用任何自定义 hookType——每条记录都落在 daemon 本就认识的桶里。
| DSH 事件 | agentmemory 调用 | hookType | data 字段 |
dedup 判别 |
|---|---|---|---|---|
session/created |
POST /agentmemory/session/start(enableSessionStartEnd 开启时);否则在 injectContext 开启时直接 POST /agentmemory/context |
— | sessionId、project、cwd、agentId;响应中的 context 被缓存用于注入 |
— |
user/message |
observe |
prompt_submit |
prompt=content、source(另含原始 content) |
tool_input=content(同 prompt 自然合并) |
assistant/message |
observe |
post_tool_use |
tool_name='assistant_message'、tool_output=content、provider、model(另含原始 content) |
tool_input='#'+seq(唯一;内容不进 input) |
tool/call |
不发观察行(见下) | — | — | — |
tool/result(ok) |
observe |
post_tool_use |
来自 callMeta stash 的 tool_name/tool_input/tool_output(tool_name 兜底取事件的 name;tool_input 兜底为 'result#'+callId),另含 callId、原始 content、isError: false、errorName: '' |
tool_input=args |
tool/result(err) |
observe |
post_tool_failure |
同上,但 isError: true、errorName 有值 |
tool_input=args |
turn/end |
observe |
post_tool_use |
tool_name='turn_end'、tool_output=reason(如 completed) |
tool_input='turn#'+seq(唯一) |
approval/asked |
observe |
notification |
notification_type='permission_prompt' + tool_name、request_id、call_id、reason |
无 tool_input:dedup 哈希覆盖整个 data,其中唯一的 request id 区分不同审批 |
compaction/start |
POST /agentmemory/context 刷新 + 补注入标记(injectContext + injectContextOnCompaction 开启时) |
— | — | — |
compaction/summary |
POST /agentmemory/remember(compactionBridge 开启时) |
— | content='[dsh compaction] '+summary、type='fact'、concepts=['compaction']、project、agentId |
— |
session/flush |
缓冲观察落库 | — | — | — |
session/disposed |
最终 flush;POST /agentmemory/session/end(enableSessionStartEnd 开启时) |
— | — | — |
未列出的事件(boundaries、chunks、todo/write、request/*)是纯日志噪音,不产生任何输出。
为什么 tool/call 不发观察行
tool/result 行已经携带 name + args(经 callMeta,在 call 事件经过时 stash)加上输出,合成一条标准 post_tool_use。单独的 call 行要么落在 daemon 提取的字段范围之外(见表格上方的说明),要么与结果行撞 dedup key(相同 tool_name + args)导致结果行的输出被静默丢弃。所以 call 被记录,但只作为结果行的元数据。
为什么 turn/end 借壳 post_tool_use
纯粹为了让 turn 的 reason 进 tool_output、从而进可搜索的合成 narrative(自定义 hookType 会让它不可见——同上)。assistant/message 对其内容做同样的事。
为什么 approval/asked 映射为 notification
notification 正是 Claude Code 的 Notification hook 与 OpenCode 的 permission 事件产生的 hookType,dsh 的审批提示因此落入 daemon 本就认识的同一个类型化、可渲染桶。daemon 设计上不对 notification 做内容索引,所以这里的字段无需为提取而摆放。
为什么 compaction/summary 走 /remember
蒸馏出的摘要是免费的、daemon 侧的记忆——把它持久化为 durable fact,意味着它在产生它的那次历史压缩之后依然存活,而不是随被压缩的 transcript 一起消亡。上限 6000 字符,compactionBridge: false 可关闭。
去重安全设计(Dedup-safe)
agentmemory 的 mem::observe 对 sha256(sessionId, tool_name||hookType, tool_input[0..500]) 做 5 分钟 TTL 去重,命中即整条丢弃。桥接用每会话单调 seq 与自然内容/callId 做 tool_input 判别,保证同类多条都落库,同时保留「相同 prompt / 相同 (tool,args) 结果」的自然合并。tool_input 缺失时 daemon 改为哈希整个 data 对象。
记忆注入(读侧)
桥接走 DSH 原生的 agent/pre-step 注入通道(与 harness 内置的 dsh-time-context 插件同一范式),把 sourced user/message append 到进入 step 的消息批次尾部。两条路线都做事件级去重,不在每个 tool step 重复注入。前端把每次注入渲染为独立的「上下文注入」块(ContextMessageNode)。
路线 1 —— 项目 recall(form: 'recall',默认开)
每会话把 agentmemory 的 /context 项目级跨会话窗口(「这个项目之前干过啥」,排除当前会话)注入一次。session/created → /session/start 从响应缓存 context;enableSessionStartEnd 关闭(或 cwd 为空)时改为直接 POST /agentmemory/context 取窗口。agent/pre-step 的首个有缓存的 step append 一次(injectedContext 去重);每条 user/message 仍异步刷新缓存。
路线 2 —— 语义 recall(form: 'semantic',默认关)
每条 user/message 用原文(查询截断到 2000 字符)做一次 /smart-search(BM25+向量+图),把召回记忆的标题组装成一条注入消息(semanticSeq 逐消息去重)。精确召回(/search)仍留给 agent 显式调用 memory_recall 工具。
压缩前补注入(injectContextOnCompaction,默认开)
compaction/start 触发一次 /context 刷新,并在下一个 agent/pre-step 把最新项目窗口再注入一次,避免项目背景随历史压缩丢失。
模型工具
| 工具 | 参数 | 说明 |
|---|---|---|
memory_recall |
query (必填), limit? (默认 8,钳制到 1–50), project?, agentId? |
按调用会话自动定位 project,跨会话召回。按 project 过滤,可选按 agentId(不传 = 项目的所有 agent,包含历史无 agent 的记录) |
memory_remember |
content (必填), type?, concepts? (最多 20 个), ttlDays? |
主动固化记忆;type ∈ pattern/preference/architecture/bug/workflow/fact。写入行带插件 agentId(见配置键) |
工具用 defineTool 定义、经 ctx.tools.register 注册,守护进程传输走 host shell seam(inject: ['tools', 'shell']);二者随插件 Fiber 生命周期自动清理。execute 失败返回 {ok:false,error} 而非抛错。
配置键
所有键都放在行的 config 下;省略的键会自动填默认值——无需把默认值手抄进行里。Cordis 原生校验:非法值(例如 curlTimeoutMs: -5)会让插件加载失败并给出明确错误。
| 键 | 默认值 | 范围 | 用途 |
|---|---|---|---|
baseUrl |
http://localhost:3111 |
— | daemon 基地址 |
secret |
'' |
— | Bearer token——明文或环境变量引用(见下) |
enabled |
true |
— | 生命周期桥总开关;false = 不再产生观察、session 行与注入数据源(模型工具仍可用) |
enableTools |
true |
— | 注册 memory_recall / memory_remember |
enableSessionStartEnd |
true |
— | 镜像 session/start 与 session/end 行 |
compactionBridge |
true |
— | 经 /remember 持久化压缩摘要 |
agentId |
'dsh' |
— | 写入行的身份标识;解析顺序:env AGENT_ID → 该值 |
curlTimeoutMs |
4000 |
1–60000 | 单次 curl 超时 |
observeBatchLimit |
20 |
1–200 | 缓冲观察达到该数量时落库 |
maxContentChars |
4000 |
1–100000 | 单条观察内容上限 |
maxArgsChars |
2000 |
1–50000 | 工具调用参数上限 |
injectContext |
true |
— | 项目 recall 开关(路线 1) |
injectContextMaxChars |
6000 |
1–200000 | 注入项目窗口文本上限 |
injectContextOnCompaction |
true |
— | 压缩前补注入 /context |
injectSemantic |
false |
— | 语义 recall 开关(路线 2) |
injectSemanticMaxResults |
8 |
1–50 | smart-search 结果并入语义块的上限 |
injectSemanticMaxChars |
3000 |
1–100000 | 渲染后语义 recall 文本上限 |
secret:明文或环境变量引用
secret 支持两种形式——明文或环境变量引用(后者有三种变体)。注意明文 secret 会原样留在 cordis.yml 里——若该文件会被共享或纳入版本管理,请优先用环境变量引用:
| 形式 | 行为 |
|---|---|
secret: "xxx" |
明文,原样用作 Bearer token |
secret: '${AGENTMEMORY_SECRET}' |
读环境变量;未定义 → 加载失败并报错 |
secret: '${AGENTMEMORY_SECRET:default}' |
读环境变量;未定义 → 用 default |
secret: '${AGENTMEMORY_SECRET:?goes nowhere}' |
读环境变量;未定义 → 报错 goes nowhere |
环境变量经 shell 能力接缝在 apply() 开始时解析(沙箱无直接 env 访问)。
模型体验
模型看到什么。 桥接不改动已接收的输入;其影响是追加式的——每个去重边界附加一条 sourced user/message(见记忆注入),加上 enableTools 为 true 时的两个模型工具。
Token 影响。 有条件。注入只在去重边界贡献额外输入 token——项目 recall 每会话一次、开启语义 recall 时每条用户消息一次、每次压缩一次,而非每个 tool step。上限由 injectContextMaxChars / injectSemanticMaxChars 控制。enableTools 为 true 时工具 schema 增加少量固定 token 成本。工具响应正常回到模型的上下文窗口。
KV cache 影响。 追加式、前缀稳定。注入消息 append 到 step 消息批次的尾部,因此之前的前缀保留且可复用;再注入只改变后缀。去重条件(injectedContext、injectedSemanticKey、compactionInject)防止同一块在同一边界重复追加,所以桥接自身的活动不会使稳定前缀失效。
传输与失败语义
- 沙箱无
fetch/require/timers;出网走shell能力接缝,每次调用一个 curl,JSON body 走 stdin(--data-binary @-)。 - 缓冲:观察按会话缓冲,达到
observeBatchLimit(20)条、session/flush或session/disposed时逐条落库;多个并发会话各自持有独立缓冲。 - 绝不 veto 生命周期:生命周期监听器有错误防护(观察发送失败在 flush 循环内被捕获);工具调用尊重
AbortSignal。 - 基础设施失败(curl 非零退出)重排队列、下一个 checkpoint 重试。daemon 返回的 HTTP 错误响应目前不会为观察行检查——这类行会被静默丢弃;一次性调用(压缩 / context / session 行)则记录后丢弃。
- flush 竞态:flush 在途时新事件经
dirty标志被在途循环补发,不丢。
日志。 所有运行时错误都以 [agentmemory] 前缀写入 dsh 宿主进程的 stdout/stderr——如 daemon unreachable at …(加载门)、secret env … is unset(secret 解析)、observe failed, re-queuing tail(发送失败)、session/start failed / context refresh failed(一次性调用)。加载横幅([agentmemory] bridge active: …)确认 apply() 成功。
验证
node scripts/boot-check.mjs # 模块 + Config schema + daemon livez + peer deps(7 项)
node test/smoke.mjs # 端到端(需 daemon 在 :3111)
冒烟测试覆盖上表注册表的大部分内容:schema 校验、livez 硬门、工具注册、secret 三种 env 引用、观察落库(title/type/narrative 形状)、approval → notification 行、turn/end reason 可在 narrative 搜索、无幽灵 tool/call 行、compaction → /remember 桥接、以及 agent/pre-step 注入形状。未覆盖:tool/result 错误路径(post_tool_failure)与压缩前补注入。
本地开发——peer 依赖链接、冒烟 fixture 清理、ESM 缓存——见 DEVELOPMENT.md。
已知边界
- 工具侧与桥接侧的 project 解析不一致:观察桥按
AGENTMEMORY_PROJECT_NAME环境变量 → git toplevel → cwd basename 解析 project,而memory_recall/memory_remember默认只取会话 cwd basename。当会话 cwd 位于 git 仓库子目录时,观察落在 toplevel 项目名下、工具却查 cwd basename——此时请显式传project。 - 无只读模式:
enabled: false同时切断观察桥与注入数据源(只有模型工具保持可用)。只注入上下文、不写观察目前做不到。 - 记忆默认跨会话共享(观察记录打的是插件 agentId,不是 DSH session id);需要隔离某个 agent 时,设置
agentId或让memory_recall按它过滤。 - Agent 隔离为可选:写入记录都带插件 agentId(见配置键);早于 agentId 标记功能的插件版本写入的记录
agentId为 undefined,不传过滤时仍可召回。 - 环境变量(
AGENT_ID、AGENTMEMORY_PROJECT_NAME)经shellseam 从 dsh 宿主进程环境读取——在启动 dsh 的地方设置,而不是写在cordis.yml里。 - 未改动任何
@deepseek-ai包;未改动随 dsh 发布的 preset 安装目录。
许可证
MIT——见 LICENSE。
链接
同类插件
vectorize-io/hindsight#coding-agents★ 41184
Hindsight:会学习的 Agent 长期记忆系统,自动召回/保存、知识页、深度反思与按仓库隔离的记忆银行。
volcengine/OpenViking#examples/dsh-memory-plugin★ 38914
面向 DeepSeek Harness 的 OpenViking 记忆与上下文插件:pre-step 自动召回与画像注入、会话捕获、`viking://` URI 防护,以及对接 OpenViking 服务端的 recall/write 记忆工具。
agentscope-ai/ReMe#dsh★ 3530
将 DeepSeek Harness 接入 ReMe 本地优先、自进化的个人知识库:自动把已完成的主 Agent 对话沉淀为用户掌控的 Markdown 记忆,通过 reme_search 结合 BM25、可选向量检索和 wikilink 展开搜索对话与资料,并按日整理长期记忆。
zilliztech/memsearch#MemSearch★ 2679
供 DSH 与其他编程 Agent 共享的 Markdown 记忆,支持自动捕获、步骤前上下文注入、搜索召回,以及通过审阅面板实现 memory-to-skill 自进化。
vshulcz/deja-vu#extensions/dsh★ 1081
读取本机上其他三十三个编程智能体已经写下的会话文件——Claude Code、Codex、Cursor、VS Code Copilot Chat、opencode、OpenClaw、Hermes、Kimi、Cline、Zed 等,包括安装之前的历史:提供 deja_recall、deja_session、deja_blame、deja_fix、deja_how、deja_remember 六个工具与 /deja 命令,并可选地把召回结果加入运行时上下文。本地 BM25 索引,不用大模型,不用向量嵌入,不联网(dsh plugin --profile web add dsh-deja)。
adoresever/graph-memory★ 631
DeepSeek Harness 的可追溯、可检索跨会话记忆:把对话知识沉淀为带类型的图节点(任务/技能/事件)与关系边。
社区评论
评论公开保存在 GitHub Discussions。加载评论会连接 GitHub 和 Giscus;发表内容需要 GitHub 账号。