你的 DeepSeek 智能体能回看历史聊天。
30.7/100
综合分
1
GitHub 星标
64
npm 周下载
—/5
用户评分
数据截至 2026/10/18 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add dsh-elephant-memorynpm 源 · dsh-elephant-memory 的发布者账号已校验与本仓库一致
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:已验证
是什么
dsh 原生插件 · chat
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 17 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过展开查看检查明细 ↓
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
✓npm 包dsh-elephant-memory @ 0.1.1
✓Node 引擎要求 >=22 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明
验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/13 04:26:45
安全扫描自动 · 静态分析源码v2.7.0🟢 扫描:未发现敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描未发现敏感能力:最多只发网络请求、读取自身元数据或只读文件,未发现执行命令、文件写入或敏感信息读取。
扫描于 2026/11/2(1 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
依赖的 DSH / Cordis 模块
@deepseek-ai/dsh-compaction@deepseek-ai/dsh-home-paths@deepseek-ai/dsh-llm@deepseek-ai/dsh-tools@deepseek-ai/schemastery给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
功能亮点
- 回看历史聊天
- 无限记忆
为 DeepSeek harness 提供无限记忆:你的智能体永不遗忘。基于 Victor Taelin 的 OptChat 规范。
最近更新
在 GitHub 查看全部 →v0.3.02026-10-11
排序召回返回带日期的来源段落,并加入有界自动上下文。查询扩展在失败时保留原查询并转发取消。Endless 预设启用增量决策日志,支持持久批次、重启恢复和来源变更失效。编码、原始搜索和缩放工具仍然可用。召回行可打开、关闭,重载后保留内容。此版本支持 harness 0.2.1-alpha.1 或更新版本,低于 0.3。
数据来自仓库的 GitHub Release,可能与插件实际版本不一致。
README
由 DeepSeek 最新模型翻译生成Elephant Memory
你的 DeepSeek 智能体能回看历史聊天。
Elephant Memory 是 DeepSeek harness(dsh)的插件。你在某个文件夹里的每次聊天都会汇成一段长记忆:每轮新对话开始时,先带上早前轮次的有界摘要、匹配到的原文片段,以及一份带日期的决策日志。智能体能从任意一行摘要一路下钻到原话。保留历史并不保证模型一定能找到或用上每一条事实。
排序召回与决策日志
Endless 预设保留常规编码工具和此前所有回看工具,另外新增 recall(question):对说过的话做排序搜索,并用会话自己的模型和账号做简短的查询扩展。扩展有六秒时限,失败时原查询照样可用。它绝不会去搜别的项目的记忆。
在一轮对话首次请求模型之前,自动召回最多可加入二十个片段,内容预算 40,000 字节,另加框架开销。片段跟随摘要视图,而不是常驻上下文。无关消息不会加任何块。手动召回会在扩展前冻结来源,并把取消信号转发给模型请求。原始 recall_search 和 zoom 仍然可用,供工具轨迹使用——排序召回有意排除这类内容。
预设还会在后台从已结束的口语轮次中提取决策、规则、反转和未完成工作。四轮对话,或拆分长消息后累计 16,000 个字符,构成一个提取窗口。每批保存的结果都绑定确切的来源和前驱;来源一变,依赖它的缓存就失效。写入失败则进度不前进。
展开完整文档(README 剩余 1 节)
Elephant Memory
你的 DeepSeek 智能体能回看历史聊天。
Elephant Memory 是 DeepSeek harness(dsh)的插件。你在某个文件夹里的每次聊天都会汇成一段长记忆:每轮新对话开始时,先带上早前轮次的有界摘要、匹配到的原文片段,以及一份带日期的决策日志。智能体能从任意一行摘要一路下钻到原话。保留历史并不保证模型一定能找到或用上每一条事实。
排序召回与决策日志
Endless 预设保留常规编码工具和此前所有回看工具,另外新增 recall(question):对说过的话做排序搜索,并用会话自己的模型和账号做简短的查询扩展。扩展有六秒时限,失败时原查询照样可用。它绝不会去搜别的项目的记忆。
在一轮对话首次请求模型之前,自动召回最多可加入二十个片段,内容预算 40,000 字节,另加框架开销。片段跟随摘要视图,而不是常驻上下文。无关消息不会加任何块。手动召回会在扩展前冻结来源,并把取消信号转发给模型请求。原始 recall_search 和 zoom 仍然可用,供工具轨迹使用——排序召回有意排除这类内容。
预设还会在后台从已结束的口语轮次中提取决策、规则、反转和未完成工作。四轮对话,或拆分长消息后累计 16,000 个字符,构成一个提取窗口。每批保存的结果都绑定确切的来源和前驱;来源一变,依赖它的缓存就失效。写入失败则进度不前进。重启后会从已保存的进度继续。不附带任何对话或静态研究日志。
请求携带的日志上限 16,000 字节。它的缓存可以随历史增长。这是派生出来的历史,不是新指令,也不代表仓库当前状态;它可能落后于新进来的轮次。摘要必须在一轮对话开始前就绪,但日志还在写不会卡住这一轮。只靠召回的问答走记忆;要改文件的请求仍然得走正常流程。日志由模型撰写:来源引用有效,不代表它的转述或说话人归属就一定准确。准确性要紧时,请回看原话;光有来源哈希,并不能证明语义上站得住。
对于自定义宿主,ledger 默认关闭,需显式启用。预设会启用 ledger、lookBackRules、recall、autoRecall 和 lineTools。宿主可以用 autoRecall: false 关闭自动段落,或用 ledger: false 关闭日志提取。recallMost 接受 1–50;ledgerBytes 接受 512–64,000。编码会话不受三步上限约束,也不强制使用无工具回答模式。
这是 Victor Taelin 的 UniiChat 规范(最初以 OptChat 之名发布)的独立实现。源码、issue 和发布版本见:<https://github.com/Shtukbase/elephant-memory>。
安装
源码发布版本是 0.4.0。请从对应 tag 安装这个确切版本;npm registry 上的版本可能落后于源码发布:
git clone --branch v0.4.0 --depth 1 https://github.com/Shtukbase/elephant-memory.git elephant-memory
dsh plugin --profile web add ./elephant-memory
registry 命令 dsh plugin --profile web add dsh-elephant-memory 安装的是当前发布在那里的版本,不一定是这个源码发布版本。
插件支持 Node 22 或更高版本;验收运行使用的是 Node 25.2.1。当前 harness 命令行请使用该运行时:Node 23.10.0 缺少 import.meta.main,可能还没启动就退出。你的 PATH 中需要有 pnpm(harness 的插件命令会调用它),并且 harness 版本需为 0.2.1-alpha.1 或更新,低于 0.3(npm install -g @deepseek-ai/dsh@0.2.1-alpha.1 安装的是经过测试的版本)。
1. 把插件加入配置档。 dsh web 启动的是 web 配置档;如果它还不存在,该命令会创建它:
本次发布请使用上面的源码文件夹命令,或使用 registry 命令安装其单独发布的版本。
add 也接受包含此包的文件夹,或由 npm pack 生成的 .tgz。由于包内附带 bundle 补丁,add 也会启用该补丁。harness 会在安装时和每次启动时检查包的 peerDependencies,并拒绝 0.2.x 之外的 harness。
2. 启动或重启 harness: dsh web。它会打印页面地址;在浏览器中打开即可。
版本 0.4.0
默认视图大小翻倍:viewBytes 从 128,000 变为 256,000,viewFloorBytes 从 64,000 变为 128,000。长对话因此会在合并最旧的行之前持续更久,更多内容会被逐字读取,同一段历史的摘要也会被更频繁地读回。这一改动背后的测量来自一段 544 条消息的实时记忆,其中视图显示了 173 行;磁盘上已有的记忆,一旦其视图低于新的下限,就会重建到新的上限。
版本 0.3.0
排序召回和自动源段落补充了现有的摘要、原始搜索和缩放工具。查询扩展有界,失败时保留原始查询。Endless 预设启用一个有界决策日志,具备持久进度、重启恢复和源变更失效。普通编码工具仍然可用;插件不设置 effort 或调用上限。
手动召回有可读的转录行,覆盖开头、结尾和重新加载。支持的 harness 最低版本是 0.2.1-alpha.1。
选择 Endless 预设
预设就是 harness 所说的模式。插件新增一个预设 endless,在新会话的模式列表里显示为 Endless。它就是 harness 的 Creator 模式,只是用这套记忆替代了通常的压缩(压缩是 harness 缩短长历史的方式;这套记忆取代了它)。
1. 新建会话。在消息框上方、工作区旁边打开模式选择器(显示为 Standard mode),在发送第一条消息前选 Endless。会话的模式在第一条消息之后无法更改,每个新会话又会从默认模式开始。
2. 照常工作。每轮结束时,内容会逐字保留,并在后台生成摘要。之后每一轮都从摘要开始。
要让 Endless 成为新会话的默认模式,编辑 $DSH_HOME/profiles/web/cordis.patch.yml($DSH_HOME 是 harness 的主目录,默认 ~/.dsh,除非你另行设置)。文件初始是空列表 [];把它替换成下面这个列表,或把该项加到已有列表里:
- id: agent-preset-registry
config:
default: endless
配置模型
记忆使用聊天自身的模型:会话运行所用的模型和服务商,也负责写摘要。你在输入框里切换服务商或模型后,摘要从下一轮起跟随:写入方使用该轮请求所用的路由,而不是聊天创建时的路由。预设的模型上下文窗口至少要有 122,560 tokens。仅做摘要的主机需要 65,536;默认的自动召回(不含日志)需要 106,560。这些保守下限为配置的附加项预留了空间。更小的模型按普通聊天运行,不读也不写这套记忆。
只选适配器实际声明的 reasoning effort。在召回测试中,研究适配器的 medium 设置比 high 设置更便宜。官方 DeepSeek 适配器声明的是 off、low、high 和 max,没有 medium;插件不会凭空造出这一档,也不会改动聊天的 effort。
1. DeepSeek 官方 API 开箱即用:在 harness 页面的「Add an API key」对话框里填入你的密钥,或在 dsh web 之前设置 DEEPSEEK_API_KEY。
2. OpenAI 兼容网关走 harness 的 dsh-llm-pi-ai 适配器。下面这个例子声明 OpenCode Go 的端点(它要求 x-opencode-session 请求头),并把它设为默认模型。把条目加到同一个 cordis.patch.yml 列表里:
- id: llm-pi-ai
config:
providers:
opencode-go:
displayName: OpenCode Go
apiKeyEnv: OPENCODE_GO_API_KEY
api: openai-completions
baseURL: https://opencode.ai/zen/go/v1
headers:
x-opencode-session: my-machine
compat:
thinkingFormat: deepseek
models:
- id: deepseek-flash
name: DeepSeek Flash
contextWindow: 1000000
reasoningEfforts:
off:
high: high
- id: agent-default-model
config:
provider: opencode-go
model: deepseek-flash
apiKeyEnv 指定的是凭据名,不是密钥本身:把它设在环境变量里,或写进 $DSH_HOME/.credentials.yaml(version: 1,然后 refs: 下写 OPENCODE_GO_API_KEY: "<your key>",文件权限 0600)。空的 off: 档让摘要写入器关掉思考(deviation 13)。模型没声明这一档也能用:写入器读取模型声明的档位,取最低的一档;模型完全没声明推理档位时就不思考,并记录一次日志。这样的模型可能每次摘要都思考,会消耗输出 token。
记忆的归属规则
一份记忆属于一个工作区文件夹里的一个模式。同一工作区里每个 Endless 会话读写同一份记忆,所以那里新开的会话已经知道之前的会话,还能说出某句话是在哪个会话里说的。另一个工作区的会话有自己的一份记忆。工作区就是模式选择器旁边显示的文件夹;网页里的第一个,Default workspace,是 ~/Documents/deepseek-harness/default-workspace。
记忆就是 $DSH_HOME/endless/ 下的普通文件。想忘掉一份记忆,删掉它在那里的文件夹(见文件在哪里)。
驱动 harness 的程序(「宿主」)可以在会话第一轮之前,把会话绑定到自己的 key、智能体和项目上;见面向宿主。
历史
打开右侧边栏,在起始页选 History,或在输入框里输入 /history。历史显示当前会话所在文件夹的记忆,选择器可以切换到其他记忆。
1. Conversation 是每个会话的每一轮,按天分组。搜索框在记忆里任意位置找原始词并高亮。
2. Remembers 是智能体开始下一轮时依据的摘要行列表。每行展开成生成它的两行,一直追到具体那条消息。
用 Esc 停掉的轮次也会保留:你的消息、智能体已经说出的内容(被截断的回复保留到截断处)、它用过的工具和到目前为止的结果,最后加一条你停掉它的说明。如果停掉时它还在等记忆、智能体什么都还没说,这条也成立:你的消息只保留一次,不会自己再发一遍。harness 停止运行导致中断的轮次同样保留,末尾加一条被中断的说明。
回看
智能体有四个工具用来查记忆:
1. zoom(id, n) 把一条摘要行展开成生成它的两行;zoom(id, 1) 给出完整的一条消息。
2. date(id) 给出某条消息的日期和时间。
3. recall_search(query) 逐字搜索所有写过的消息,查找某个短语(不区分大小写)或 /regular expression/。最多返回 20 条命中。
4. recall(question) 给口述来源段落排序,在选定路线上扩展短查询,返回带日期的原话,而不是新生成的回答。
在对话记录里,每次调用显示为一行,Looked back · «what» · date · in «chat»,点开就是答案。记忆轮次不会标记「Context compacted」,因为没有丢失任何东西;普通聊天里真正的 /compact 仍照常标记。
成本与缓存
这些数字来自一次实测运行:2026-10-08 的 201 个脚本化轮次,模型 deepseek-flash,走 DeepSeek 官方 API,harness 0.2.0-rc.2。成本按该次运行的 token 数、以 DeepSeek 对 deepseek-flash 的标价计算,标价于 2026-10-09 从定价页读取。高峰时段每百万 token:缓存输入 $0.006,其他输入 $0.30,输出 $1.20。非高峰是各自的一半:$0.003、$0.15、$0.60。高峰为 UTC 周一至周五 01:00-04:00 和 06:00-10:00,中国法定节假日除外。DeepSeek 可能调整这些价格,依赖之前请再读一遍页面。
| 指标 | 结果 |
| 每轮输入中从服务商缓存读取的比例 | 中位数 98.4%(196 个普通轮次,最低 95.3%) |
| 每个合并批次之后的那一轮(本次运行有两轮) | 约 35% 从缓存读取 |
| 轮次开始前的等待(摘要仍在写入) | 中位数 5.1 秒,90% 在 7.5 秒以内 |
| 智能体轮次的成本 | 高峰 $0.46,非高峰 $0.23 |
| 写摘要的成本(3,133 次调用) | 高峰 $1.50,非高峰 $0.75 |
| 一轮加上其摘要的成本 | 高峰 0.98 美分,非高峰 0.49 美分 |
| 精确引用回忆问题 | 7 个中 7 个逐字答对 |
摘要写入占了账单的 76%。那次运行之后,写入器每行最多尝试三次而不是五次,并接受超出上限 1.25 倍的行(偏差 12,见下文),重试时按上限的 0.75 请求(偏差 16,见下文),这应该能减少调用次数。一次更新的运行测了这些:2026-10-08 的 235 个脚本化轮次,使用已发布的这个包(0.1.1),干净的 harness 0.2.0-rc.2,默认 128,000 字节上限和 64,000 字节下限,deepseek-flash 走 OpenCode Go。OpenCode Go 是订阅制,所以其成本按该次运行的 token 数、以同样的 DeepSeek 标价计算。写入器调用了 2,119 次,每轮 9.0 次,此前是 15.6 次,高峰成本 $1.23(非高峰 $0.61);智能体轮次成本 $0.51($0.26)。一轮加上其摘要高峰 0.74 美分,非高峰 0.37 美分,写入器占其中 71%。
每次摘要调用还会重读约 16k 缓存 token:该轮的系统提示词和工具,然后是压缩视图。从 0.2.0 起,一次调用最多写六行摘要,它们同时就绪(writer.batch,偏差 20 。一次运行在 2026-10-08 的 17:51-18:51 UTC 测量了它。它用了同样的 235 个脚本化回合、同样的 harness、同样的 128,000/64,000 字节视图,以及通过 OpenCode Go 使用的 deepseek-flash,按同样的 DeepSeek 标价计费。写入器发起了 1,498 次调用,每回合 6.4 次,而 0.1.1 是 9.0 次;峰值花费 $0.995(非峰值 $0.50),而 0.1.1 是 $1.23。智能体的回合花费 $0.47($0.24)。一个回合连同它的摘要,峰值花费 0.62 美分,非峰值 0.31 美分,比 0.1.1 少 16%。写入器裁剪了 87 行,而 0.1.1 是 94 行;两次运行中全部 12 个召回检查点都答对了。
同一天做了一次简短的实机检查,用的是已发布的这个包,从它的 .tgz 安装到一个干净的 harness 0.2.0-rc.2 中,配合通过 OpenCode Go 使用的 deepseek-flash:四个 Endless 聊天,六个回合。第一个聊天里给出的一条规则,在第二个聊天的第一个回合被回忆起来,连同聊天的名字。从第一个回合之后起,每个回合开始时都有 96.9% 到 98.3% 的输入来自缓存,只有一个回合例外,那是在插件的系统提示词刚被改动之后。摘要写入器发起了 22 次调用,其中 94.4% 的输入来自缓存,每次调用写入约 145 个 token,裁剪了 3 行。
配置
要改动某项设置,把 presets/endless.patch.yml 里的 preset-endless 行复制到你配置档的 cordis.patch.yml 中,去掉它的 - insert: 行,然后编辑 endless-memory 行。补丁会替换整行的 config,所以每个键都要保留。
| 键 | 默认值 | 含义 |
| agentName | Assistant | 在无人绑定的聊天里,摘要行用来称呼智能体回复的名字。已绑定的聊天用宿主给出的显示名,或从智能体地址读出的名字(team/agents/release-captain 得到 "Release Captain")。不能有冒号,不能有换行。 |
| you | you | 摘要行用来指代本人的词。 |
| viewBytes | 256000 | 摘要的字节上限:一旦超过,一批合并就把它降到 viewFloorBytes。 |
| viewFloorBytes | viewBytes 的一半 | 这批合并降到的下限(默认 128,000)。 |
| lineBytes | 512 | 一条摘要行的目标大小,单位字节。 |
| granularity | turn | turn 每回合保留一条 trace 消息;step 把每次工具调用和结果各保留为一条消息(偏差 1)。 |
| writer.model | '' | 用聊天自身服务商的另一个模型来写摘要;留空表示用聊天的模型。writer.provider 键会被拒绝。 |
| writer.batch | 6 | 同时有多条就绪时,一次调用最多写多少条摘要行(偏差 20)。1 表示每次调用写一行,与 0.2.0 之前一样。 |
| writer.reasoningEffort | 'off' | 摘要调用的推理强度(偏差 13)。留空表示 off。聊天模型未声明的级别会回退到 off,否则回退到它声明的最低级别,再否则不用,并记录一次。 |
| recallSearch | true | 提供 recall_search 工具。 |
| recall | true | 提供 recall(question),在口述的源消息上按相关度排序。 |
| autoRecall | true | 在第一个模型步骤之前先搜索;会改变上下文下限。 |
| lineTools | true | 保留 zoom、date 和原始搜索,包括轨迹。 |
| recallMost | 20 | 排序后段落的最大数量,1–50;每段预留 2,000 字节。 |
| lookBackRules | false | 把直接回答的指引限定在仅靠回忆的问题上;预设会启用它。 |
| ledger | false | 在后台提取带日期的日志;预设会启用它。 |
| ledgerBytes | 16000 | 请求日志预算,512–64,000 字节;会改变上下文下限。 |
| root | '' | 存放记忆的文件夹;留空表示 $DSH_HOME/endless。 |
| contextKinds | [] | 宿主自身常驻上下文的消息类型,像 harness 自己的上下文消息一样,保留在摘要前面。 |
要让另一个预设也拥有这份记忆,把这个组放进它的 plugins 列表,替换掉它的 compaction 组(一个预设只有一个 compaction 服务):
- id: endless
name: cordis:group
group: true
isolate:
compaction: true
config:
- id: endless-memory
name: dsh-elephant-memory
config:
agentName: Assistant
限制
1. 小模型。 低于配置的上下文下限时,一轮对话按普通聊天运行,日志里会说明一次。DeepSeek 官方模型远高于这个下限。
2. 会消耗 token。 摘要由聊天自己的模型在后台写入,用的是聊天自己的服务商,绝不会换成别的服务商。查询扩展和日志同样消耗 token。每次写入调用都会记录,并在记忆的 usage.jsonl 里汇总。
3. 动态追加会改变缓存后缀。 合并批次之后的第一轮,从第一条合并的行开始就无法命中缓存。日志更新和不同的回忆段落也可能改变后续输入。上面那些只含摘要的历史缓存占比,不能保证新预设也有同样的占比。
4. 一轮对话可能被拒绝。 如果 180 秒后摘要行仍然缺失,这一轮会在聊天的拒绝行里用一句话结束:「更早消息的记忆还没准备好,所以这条消息会随你的下一条一起发送。」它的消息留在聊天的队列里,随你的下一条消息一起发出;聊天仍然可用。写入方很慢,和它每次尝试都立刻失败,表现完全一样。没有摘要就什么都不发送,也不会转给别的服务商。拒绝的原因在日志里([ENDLESS_TURN],以及写入方的 [ENDLESS_WRITER] 行)。
5. 一切都保留。 记忆以纯文件形式存在你的磁盘上,保存每一轮的每一个字,包括你粘贴的内容或工具打印的内容,摘要由你聊天的服务商写入。目前还没有「忘掉这个」:删掉记忆的文件夹就等于忘掉它。
6. 部分工具输出会被截断。 工具结果保留它的开头和结尾 15,000 字符,一轮的完整工具轨迹保留首尾各 30,000 字符(偏差 1)。你的消息和智能体的最终回复永远不会被截断。
7. 每个记忆只允许一个进程。 同一 home 上第二个 harness 进程会被拒绝,只要第一个进程还持有该记忆。
8. 仅英文。 回看行和 History 用英文绘制。
9. 测试环境:一个 harness 及其 web UI。 排序召回针对 @deepseek-ai/dsh 0.2.1-alpha.1 编写并运行,更早的测量基于 0.2.0-rc.2,web UI 由 dsh web 提供。记忆本身在任何配置档下都能工作,但回看行、静默压缩和 History 只由 web UI 绘制。DSH Desktop 加载同一个浏览器 bundle,但尚未测试。换用其他 harness 版本时,可能需要 SEAMS.md 里的检查。
10. 服务商。 在 DeepSeek 官方 API(201 轮的那次运行)和 OpenCode Go 的 OpenAI 兼容端点上运行过,后者通过 dsh-llm-pi-ai(短时实测)。写入方会请求推理强度 off,前提是模型声明了该档位;否则请求模型声明的最低档位;再否则不请求任何档位(偏差 13)。想在别的服务商上压低摘要成本,就声明一个 off 档位(见配置模型)。
11. 检索和证据使用可能失败。 自动摘录是搜索结果,不保证所有必需的词都找到了。智能体仍可手动回看。提示词会告诉它:摘要行绝不是任何人的原话,引用前要先打开消息;短时实测中它确实这么做了。在加上这句话之前,同一个模型曾把摘要行当成说话人自己的话引用。
12. 两行。 harness 只为挂在配置档顶层的插件提供浏览器 bundle,所以补丁还额外加了 endless-client 这一行。如果后续 harness 会读取预设内部的 bundle,并因此拒绝该包两次,就在那一行上设 disabled: true。
13. /history 是普通名字。 如果 harness 将来自带 /history,这个命令会让位,侧边栏条目保留。
14. 辅助智能体没有自己的记忆;它们的回看工具读取的是启动它们的那个聊天的记忆。
工作原理
设计和每个常量都来自 Taelin 的规范;这里做个简短导览。
1. 日志。 harness 自己的会话日志就是历史。一轮结束时,它的消息会完整追加到记忆的轮次顺序里,并编号。
2. 树。 后台把每条消息变成约 512 字节的摘要行。相邻两行合并成一行,再两两合并,依此类推:一棵二叉树。短到能放下的消息就是它自己那一行,逐字保留。
3. 视图。 一份覆盖全部历史的行列表,近期消息一行一条,更早的多条一行。每来一条消息,它就增长一行。
一旦超过 256 KB,就用一批合并最超期的配对,直到回到 128 KB。配对的「超期」分数按它结束至今的时间计算,以自身大小为单位:(T - last) / 2^l。这样复现了 Taelin 回滚列表的合并顺序。
4. 每轮都从新开始。 在一轮的第一步之前,此前的对话会被替换成一条消息,其中包含视图和可选日志,后面跟着匹配到的召回片段和新消息。视图只在每批之间、于末尾变化。动态追加的内容跟在稳定前缀之后;它们占多少缓存取决于服务商和本次运行。
5. 摘要按顺序进行。 最多同时跑 8 个摘要调用。一条消息的行,在它前面未写的行少于 8 条时开始;合并在一半都齐了之后开始。每次调用先发送聊天自己的系统提示词和工具,再发送一个更小的视图(16-32 KB)作为上下文,然后发送任务,附带一把 512 个短横线的尺子,因为模型数不了字节。聊天的第一轮在等待时还没有系统提示词(harness 在等待结束后写入),所以它的调用发送同一记忆下另一个聊天的系统提示词和工具。只有记忆里没有其他打开的聊天时,比如重启后的第一个聊天,这些最初的调用才发送插件自己的段落、不带工具;这一点在所有服务商上都一样。
它如何接入 harness,含文件和行号引用,见 SEAMS.md。TRY.md 是一份在隔离的 harness home 里试用它的操作指南。
谢谢你,Victor。 日志、树、构建顺序、合并规则、提示词和常量都来自 Taelin 于 2026-10-08 重写的规范。本包是该想法的一种实现。
与规范的偏差
每一处都有规范尚未回答的理由。
1. 粒度 turn(默认)。 规范把每次工具调用和结果都保留为独立消息(这里叫 step,仍然可用)。turn 按轮保留:每个人的每条消息、一条包含本轮工具调用和结果的轨迹消息,以及最终回复。为什么:摘要调用少得多,同一视图里每行一轮的轮数更多。代价:一轮很长时,轨迹只保留首尾各 30,000 个字符(每个结果本就已经截到 30,000),所以一轮里有多个超大工具输出时,中间会被切掉。在 step 模式下,一次工具调用无论多长都是一条消息。
2. recall_search。 对整个历史做原始搜索。为什么:摘要丢掉的内容仍能找到。/pattern/ 搜索 2 秒后停止,因为模式匹配可能耗时指数级。
3. 视图是独立消息。 规范把视图和新消息作为一条用户消息发送。这里视图是放在新消息之前的一条消息,因为 harness 从会话日志构建每个请求。harness 的上下文消息(技能列表、沙箱事实、工作区指令)仍然留在视图前面,因此也会从缓存中读取。
4. 纯标签。 各行给条目打标签时用 "you:"、智能体的名字、"did:"、"result:"、"colleague:" 和 "note:",而不是 user、unii、tool、echo、work 和 note。
5. 没有第二份日志,只有一个锁文件。 历史就是 harness 的会话日志;插件只保留已完成轮次的顺序和树。每个记忆由一个锁文件保证只有一个写入进程。
6. 没有 spawn 和 tell。 委派使用 harness 自带的工具,也不提供 zoom("Name")。
7. 被拒绝的轮次会重新排队它的消息,并以失败结束。 否则 harness 会丢弃被拒绝步骤的消息。该轮次以失败结束,只给一句平实的话,而不是作为一个被拒绝的步骤,因为 Web UI 会把被拒绝的步骤画成一个什么都没说的已完成轮次,并且把那个人的消息一直画成还在开始中。重新排队的消息不带提交 id,所以 Web UI 会结算它挂起的那份副本;这些消息随下一条消息一起送达,没有任何东西会自己启动一个轮次。
8. 没有缓存标记。 DeepSeek 按前缀缓存,不需要标记。需要显式标记的服务商(Anthropic)从这个版本得不到任何标记。
9. 视图每轮记录一次,记录在 harness 的会话日志里。这是 harness 接缝的代价(SEAMS.md)。
10. 多个聊天,一个记忆。 规范里只有一个对话。这里一个文件夹里的每个聊天都加入同一个记忆,一次一整个轮次,并且每轮的第一条消息都标上它所在聊天的标题("in «title»")。
11. 写入者的提示词和 "Compaction:" 标记。 摘要指令放在插件的系统提示词区段里,轮次和摘要调用共用这个区段,正如规范所要求。一次 201 轮的运行曾经把一个人的短消息当成摘要任务来回答,所以该区段规定:只有当一条调用的最后一条消息以 "Compaction:" 开头时,它才是摘要任务。两个任务都加一句话,要求用消息的语言来写,再加一句要求保留那个人的规则(偏差 18)。合并任务还加一句:只使用各行及其消息中出现的事实,因为早先某个提示词的示例行把编造的事实泄漏进了真实记忆。
12. 三次尝试,然后裁剪;1.25 倍容差。 规范允许五次尝试。在 201 轮的运行中,写入者平均每行调用 2.65 次,大多是 "Too long" 重试,而且 1,182 行里有 325 行在五次之后仍然超过 512 字节。这里,一行只要不超过上限的 1.25 倍(512 时即 640 字节)就直接接受,不重试也不裁剪。一行有三次尝试;如果都不满足 1.25 倍上限,就把最短的那行在词边界处裁剪,并以 "…" 结尾,日志会记下这次裁剪。重试要求少于上限(偏差 16)。视图的预算是按字节算的,所以行更长只意味着行数更少。
13. 写手推理强度设为 off。 规范里用的是便宜模型,但推理强度开得很高。在 DeepSeek 上,强度设为「high」(适配器的默认值)时,每 512 字节一行,输出 token 中位数达到 8,800,单轮要等两分钟。所以除非 writer.reasoningEffort 另有指定,写手一律请求 off。它只请求聊天所用模型声明支持的强度(即 harness 的模型
catalog, resolveModelInfo): where the wanted one is not declared it takes
off, else the lowest declared, else names none, and logs that once per
model. Before this, a route whose model declared no off refused every
summary before it left, and the memory stopped at the first long message.
14. The language guard. In the 201-turn run 6% of the lines were written in
Chinese from sources with no Chinese text. A try that uses a Chinese,
Japanese or Korean script its sources do not use is rejected with "Wrong
language" feedback; with no valid try, the line fails and is tried again.
15. Retries every 10 s. The specification retries a failed call at the next
message. Here a failed line is tried again every 10 s, because the next turn
waits for it. A turn waits at most 180 s.
16. A "Too long" retry asks for 0.75 of the limit. The specification's retry
cuts the line at the limit. In a 235-turn run on deepseek-flash, 66% of the
new summary lines were over 512 bytes on their first try, and 29% fit in no
try even at 640 bytes, so they were cut with "…": retries aimed at 512 did not shrink enough. Here a try past 640 bytes gets the same message with the limit and the cut mark moved to 384 bytes, 0.75 of 512 (RETRY_TARGET in lib/config.js); the first try still gets the full limit and its ruler. Why 0.75: a model overshoots a stated length by up to about a
quarter (the 90th percentile was 610 bytes at 512 in the 201-turn run), and
aiming at 384 lets a retry overshoot by up to 1.67 times and still land under
640. A smaller factor would cost more of every retried line (0.5 halves it).
The factor comes from those numbers and has not been measured over a long run
yet. The message calls 384 "the limit", which is how the model is steered
below the real one.
17. Heads are stripped from every line of a try. A summary call shows each
line as id+n|text, and a model sometimes copies the head. The specification
does not say what to do. Here every try loses a head at the start of each of
its lines before it is measured or stored. The first version stripped only
the start of the reply, and a merge reply that copied both of its input lines
stored 625+1| on its second line (1 of 214 new lines in the 235-turn run).
A head in the middle of a line is left alone, because it cannot be told from
the line's own words.
18. Both tasks ask to keep what the person states. Each task adds: "Keep every
rule, instruction, decision or preference the person states, word for word
where short, before any other detail." The shared instructions already rank the
person's own words first (lib/system-section.js),
but the task, which comes last in the conversation, says nothing of it. A
real run (round 5 of the live walk, 2026-10-08) compressed a long first
message, a rule ("release notes list only what ships") followed by a
ten-item checklist, into a line that held the checklist and dropped the
rule; the rule survived only in the agent's own memory-write line. The
sentence sits after the language sentence and before the input
(lib/prompts.js, KEEP). It asks and does not guarantee:
the line limit still applies, so a very long rule can be shortened.
19. The writer reads look-backs in words. The order file keeps a turn's trace
as the harness gave it, and for the memory's own look-back tools that is
machine form: did: zoom {"id":0,"n":1}, a line head 0+0|, a search's
id|kind|date|snippet hits. A writer told to tag items in plain words copied
it into real summary lines ("Looked back id 0 n 1", "message 0", "(1, 7, 8)";
round 5 of the live walk). The writer, and a short trace's own line in the
view, now read did: looked back at «subject» in «chat» (a date as did:
looked back at when a message was written: …; nothing found as … and found nothing), made by lib/look-back-words.js from the stored trace, which is not changed: zoom(id, 1) still gives it whole, recall_search still searches it, and the History view still reads it. Every tool call, these too, keeps the one tag did:. Granularity turn only: a step entry holds a call without its answer. Other tools' own answers are not touched, so an id the harness's own tool prints (undo-1 from the harness's memory_add) can still reach a line.
20. Several lines per summary call. The
specification writes one line per call. Every call re-reads the turn's
system prompt and tools and the compaction view, about 16k cached tokens, so
in a 235-turn run the writer was 74% of the bill at 1.97 calls per tree
node. Here (writer.batch, 6 by default) the nodes the queues make ready at the
same moment share one call, up to that many. Usually these are a turn's
messages, or the merges one stored call made ready. Only ready nodes go in,
so the build order, the 8-unbuilt rule and the views do not change. A node
ready alone still gets the one-line call. The call opens with "Compaction:"
and the jobs it holds ("compress message 5; merge lines 0+2 and 2+2"), then
each job's own task, whole and numbered: its ruler, the language and keep
sentences and its . Each job aims at 0.75 of the limit (384 bytes
at 512), in its ruler, its "at most" and its reply tag
(<line id="n">job n's line, at most 384 bytes</line>). A line is judged
alone, exactly as a one-line try: heads stripped, the CJK guard, and the
1.25× tolerance of the true limit. A line that fits is stored at once. A
line too long, in an invented script or missing is asked for again alone,
in the same conversation, with the per-line feedback ("Too long" with its
cut mark at 0.75 of the limit, "Wrong language", or "Missing"). After three
tries in all, the shortest valid try is trimmed, as for one line. A call
that fails fails each of its lines, and they come back together after 10 s.
The context is the longest of the jobs' own contexts, so a merge can see
built lines after its own messages; its task still says to use only the
facts of its lines and messages. The shared system section still says to
output only the line; the batch task, which comes last, asks for the tags.
The first design (2026-10-08) asked first tries for the full limit and
carried a retry into a new call that did not hold the model's earlier line.
In a live run 81% of first tries and 64% of retries came back over 640
bytes, and it trimmed 31 lines where one line per call trimmed 6, so it was
stopped at turn 27. With the aim at 0.75 and the retry in the same
conversation, a run of all 235 scripted turns on the same day measured these
numbers (deepseek-flash through OpenCode Go, DeepSeek list prices at peak
of $0.006 / $0.30 / $1.20 per million cached input, other input and output
tokens):
- 2.5 lines per batched call.
- 30% of first tries over 640 bytes, and 76% of retries then fitting.
- 1,498 writer calls against 2,119 for 0.1.1 on the same script.
- 87 trimmed lines against 94.
- A 19% cheaper writer and a 16% cheaper run.
- The same recall: 12 of 12 checkpoints.
21. Lines keep decision dates, and the repository beats memory. Lite run 1
(2026-10-09) showed two losses. A decision's date, said in the message
("confirmed this morning, March 3rd"), was dropped from its line, so "when
did we decide" had no answer. And the agent acted on a remembered ruling
that a teammate's later commit had changed. Both tasks now add, after the
keep sentence: "Keep each decision, rule or change with its date when the
messages give one; when lines merge, keep the date of every decision they
hold, or, if space runs short, the latest one, marked "latest"."
(lib/prompts.js, DATES). The shared instructions
copy dates exactly too. The line limit is unchanged, and no code edits a
line. The system section adds: before acting on a remembered decision or
a remembered fact about files, check git log and git status; if the
repository disagrees with memory, the repository wins, and the agent says
so (lib/system-section.js).
For hosts
A program on the same computer can read and bind memories through six methods,
served by the harness's web server as POST /api/endless.<name> in the
harness's Remote envelope: {type: 'client-request', rpcId, method, payload:
{args}} answered by {type: 'server-response', rpcId, result}. Only callers on
this computer are answered, and a request with an Origin header (which every
browser page sends) is refused. Times are ISO 8601.
| Method | Arguments | Answer |
| endless.bind | sessionId, agent, project, agentName? | {memoryId} |
| endless.memories | agent? | {memories: [{memoryId, agent, project, turns, firstAt, lastAt, writerUsage}]} |
| endless.view | memoryId | {lines: [{id, n, text, from, to}]}; text is null while a line is not written |
| endless.zoom | memoryId, id, n | {lines} (the two halves), or for n = 1 {message: {id, kind, text, at, sessionId, sessionTitle}} |
| endless.history | memoryId, before?, limit? ≤ 200 | {turns: [{at, sessionId, sessionTitle, entries: [{id, kind, text}]}], next?}; pages backwards, oldest first inside a page, and a page stops before 2 MB of text |
| endless.search | memoryId, query, limit? ≤ 50 | {hits: [{id, at, sessionTitle, kind, snippet}]}, newest first, raw words, any case |
kind is user, trace, reply or work. writerUsage is {calls,
cacheRead, miss, output}. A refusal is {ok: false, error: {code, message}}
with the code endless/bad-request, endless/unknown-memory, endless/no-line
or endless/already-bound (the chat's turns are already in another memory).
The harness's own page reads the five read methods (not bind) through
POST /api/endless-ui.<name>, behind the harness's own Host and Origin check
and its signed browser cookie.
Where the files are
<root> is $DSH_HOME/endless unless root says otherwise. memoryId is the
first 16 hex digits of sha256(agent + "\n" + project); for a chat nobody bound,
agent is the id of the mode it runs (endless) and project its workspace
folder.
| What | Where |
| The memory's key | <root>/memories/<memoryId>/key.json |
| Finished turns, in order | <root>/memories/<memoryId>/order-turn-v1.jsonl |
| Summary lines | <root>/memories/<memoryId>/tree-turn-v1/YYYY-MM-DD.jsonl |
| The view and the writer's view | view-turn-v1.json, compaction-view-turn-v1.json in the same folder |
| Writer spend | usage.jsonl in the same folder, one line per call |
| One-writer lock | lock in the same folder |
| A bound chat | <root>/sessions/<session>.json |
Every line is written with one write and fsync. A torn last line is skipped
at load. Nothing is edited or deleted. The view is saved after every change and
loaded at start, never rebuilt while its file is usable, because a rebuilt view
would differ and lose the cache. With granularity: step the file names say
step instead of turn.
Contributing
Issues and pull requests are welcome at
<https://github.com/Shtukbase/elephant-memory>.
The package has no dependencies of its own. Its tests import the harness's
packages, which are peers, so install them first:
npm install --no-save @deepseek-ai/dsh-compaction@0.2.0-rc.2 @deepseek-ai/dsh-home-paths@0.2.0-rc.2 \
@deepseek-ai/dsh-llm@0.2.0-rc.2 @deepseek-ai/dsh-tools@0.2.0-rc.2 @deepseek-ai/schemastery@^3.18.4
node scripts/build-client.js --check
node --test --test-timeout=20000 --test-force-exit test/*.test.js
The browser half is built: client/ holds its sources, and
node scripts/build-client.js writes lib/client.js (it also runs on
npm pack). Commit both. The tests run the real lib/client.js against a fake
of the harness's client, and the engine against a fake of the harness's session
and context owners. A real harness is needed to see anything drawn; see
TRY.md. When the harness changes version, walk the recheck list at
the end of SEAMS.md.
Changelog
Unreleased
1. Summary lines keep the date of each decision, and a merge keeps the dates
of both its lines, or the latest, marked.
2. Before acting on a remembered decision or fact about files, the agent checks
git log and git status; the repository wins over memory.
0.2.0
1. One summary call writes up to six summary lines that are ready at the same
time (writer.batch, default 6). Each line aims at 0.75 of the limit and is
judged alone. A line that does not fit is asked again in the same
conversation. Over 235 scripted turns on deepseek-flash the writer made
29% fewer calls and trimmed fewer lines, and a turn with its summaries cost
16% less, with the same recall. Set writer.batch: 1 for one line per call.
0.1.1
1. A turn stopped with Esc is saved to memory: the person's message, what the
agent already said, its tool uses with their results so far, and a closing
note, "stopped by the person". Before, a turn stopped while it waited for
the memory lost the person's message and was not saved at all, and a turn
stopped later was saved without saying it had been stopped.
2. A turn cut off because the harness stopped running keeps the person's
message the same way, and ends with the note "interrupted".
0.1.0
1. First release.
License
MIT. See LICENSE.预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
Shtukbase/elephant-memory
你的 DeepSeek 智能体能回看历史聊天。
相关插件 · 对话 / 记忆
上下文中枢oceanbase/powercontext
上下文跟着工作走 —— 跨会话、跨智能体、跨人。
★ 1,487已汉化DSH
73
智能体记忆库plastic-labs/honcho
Honcho 是记忆基础设施,用来构建有状态的智能体。这些智能体能理解不断变化的人、智能体、群组、项目和想法。
★ 7,894已汉化DSH
71.7
长程智能体控制面loopx-project/loopx
给 Agent 一个目标,让工作持续向前。
★ 6,358已汉化DSH
71.3
智能体上下文数据库volcengine/OpenViking
给 AI 智能体用的上下文数据库
★ 41,379已汉化DSH
71.3
长效机器人团队Fei2411/DS-Bot
DS Bot 是一个基于 DeepSeek Harness简称 dsh的 bot 插件,插件将 dsh 改造为 bot…
★ 5,084已汉化DSH
71.1
可携带记忆层EverMind-AI/EverOS
为什么选择 EverOS
★ 13,778已汉化DSH
71.1
这是你的仓库?认领条目