dsh-clear-tool-results
Manifest validdsh-clear-tool-results
维护状态:【弃用/归档】
0.7.0 为最后一版,不再做功能维护。 原因与适用边界见下节:在 DeepSeek-V4.1-Flash 的真实峰价下,本插件的成本收益不成立;
DSH 宿主插件:把工具结果按轮归档并从对话上下文中清除以减少 Token 消耗;模型可用 read_tool_result_log 按轮次或时间自主取回原文。
维护状态与适用边界
已归档,0.7.0 为最后一版。归档原因:成本口径下不成立。
DeepSeek-V4.1-Flash 官方峰价(官方价目):输入命中 $0.006/M、输入未命中 $0.30/M、输出 $1.20/M——命中价只有未命中的 1/50(c = 0.02)。在这个价目结构下,按 10 轮 × 10 步、每步 1 Think + 1 个 1K 工具结果(T = 1000/步、占位符 p = 35 token)推算:
| 单步 Think | 省(占基线 input 成本) |
|---|---|
| 0(无思维链) | 47.2% |
| 300 | 28.0% |
| 1000 | 5.6%($0.0066;占含输出的总账单 2.8%) |
| ≥ 1,312 | 0(盈亏平衡点) |
| 2000 | −8.3% |
两个让 5.6% 在真实使用中进一步归零的结构性原因:
- 「不清除」的基线本身命中率已达 98%(9,702,000 / 9,900,000):前缀天然稳定,「保住缓存」没有可保的空间,清除只可能引入重算。每轮边界都要把上一轮尾部(
9T + 10p)按全价重算一次,9 次边界即 66,150 token;而重算代价随T线性增长——带tools的请求必须回传历史reasoning_content并拼进上下文(官方 thinking_mode 文档),所以每步 Think 都真占 prompt。 - 一次取回就能吃掉大半收益:取回一条 5K 结果 ≈ 5K 走全价 + 一个额外助手回合的 Think 输出 ≈ $0.0027,相当于 10 轮省额的 41%。
它仍然成立的地方:
- 上下文头寸,与价格无关:同一算例的上下文峰值 198,000 → 111,150(−44%)。会话本来会撞上下文上限时,它买的是「跑得完」,不是「省钱」。
- 短思维链 / 无思维链:
T = 0省 47.2%、T = 300省 28.0% 的 input。 - 命中价占比更高的模型:
c = 0.25时省基线 input 的 39.5%,回到与 token 省额同阶的量级;c越小越不划算,DeepSeek 的 1:50 是最极端的一档。
下文「缓存命中与成本分析」里的 4.3%~20.3% 是按 c = 0.25 假设算的,不是 DeepSeek 真实峰价;按 c = 0.02 重算会显著下调、部分情形转负。该节保留为测量记录——断点位置(上一轮第一个工具结果)、保留率 33%~47%、占位符需字节级固定等约束都是实测结论,仍然有效;原始 token 净省额 4,342,500 与 Think 长度无关,也仍然成立。但它不再作为价值主张。
代码仍可安装使用;归档只是不再把它当省钱工具宣传,也不再跟进 DSH 核心变化。
兼容性
同一份代码支持三代核心,无需改配置或按环境区分:
| 核心代数 | 差异 | 插件行为 |
|---|---|---|
| 老核心 | 事件数组为 session.events | eventsOf() 回退读 session.events |
| ≥ 0.1.2-rc.1 | 事件数组改为 session.log | eventsOf() 优先读 session.log |
| ≥ 0.1.5-rc.1 | surface replace 键名改为 startSeq/endSeq | 按会话头版本选键名,被拒时换另一代重试一次 |
安装
dsh plugin --profile web add dsh-clear-tool-results
在 ~/.dsh/profiles/web/cordis.patch.yml 注册:
- insert:
- id: clear-tool-results-host
name: 'dsh-clear-tool-results'
使用
| 命令 | 效果 |
|---|---|
/clear-tool-results on | 启用:工具结果按轮归档,并在下一轮开始前从对话清除 |
/clear-tool-results off | 停用:保留工具结果、不再归档 |
/clear-tool-results status | 显示启用状态与插件版本 |
状态存于 $DSH_HOME/clear-tool-results.json(DSH_HOME 未设置时回退 ~/.dsh):{ "enabled": true };旧的 { enabled, mode } 仍可读,mode 被忽略。
功能
0.7.0 保留的能力就是下面这几条:每轮归档 → 按轮清除(留占位符)→ 模型按需用 read_tool_result_log 取回。
- 归档:每轮结束时,从追加式会话日志(而非改写后的 surface)取出该轮原始
tool/result,保留轮次/步骤号、工具名与匹配的tool/call,写入round-NNNN.json并登记index.json;以 index 为准、幂等,可补归档中途启用或重启前的轮次。 - 清除:
turn/end把该轮 surface 节点替换为占位符,例如[第 3 轮工具结果已清除归档:bash → git status(1.2k),可用 read_tool_result_log(turn: 3) 读取]。 - 归档上限 49000 字节(UTF-8):超限结果不归档——harness 的 spill 策略在 50000 字节处把结果换成「首尾预览 + 通知」并从中间掐掉,存了也取不回完整原文。占位符写成「已清除(该结果 49.5k 字节,超过 49000 字节上限,未归档)…。未保存原文,如需请重新执行原工具获取」,不给取回坐标;同轮若还有可归档结果则保留坐标并追加「本轮另有 N 条超 49000 字节的结果未归档,需要时请重新执行原工具」。规则同时写进工具描述,随工具注入 Agent。
- 占位符索引:占位符带紧凑索引——工具名 → 关键参数(命令/路径/模式)+ 规模 + 是否失败,整行压到 60 字以内;同一步的多条合并成一行(最多列 2 条,其余归入「等 N 条」);PTC(
run_code)下优先显示里面的子调用(bash → git status),而不是 run_code 的代码前缀。让模型先知道「里面有什么」,再决定要不要取回。 - 取回:
read_tool_result_log注册为模型工具,按turn或time读取。返回紧凑纯文本(非 JSON):每条以--- turn N step S · 工具名 · 参数摘要 · 第 A-B 行 / 共 T 行 · N 字符 / M 字节 ---开头(参数摘要为空时省略;offset越过末尾时行窗口显示为「第 T 行之后无内容(共 T 行)」),后接原文。 - 输出预算 48000 字节:按整份载荷计(含表头与末尾通知 ⇒ 整份 ≤ 48400 字节,永不触发 harness 截断);正文 ≤47.7k 字节可一次取回;更大的结果需带
offset/limit分段,被截断的条目会附「续取:offset=…」坐标;若单条超出预算且未分段,则跳过该条并提示改用offset/limit。 - 依赖:仅 Node 内置模块;适用于所有会话与 agent preset;与 DSH 内置 compaction 兼容。
0.7.0 的功能面 = 每轮归档 + 按轮清除(占位符索引)+
read_tool_result_log取回,外加 49000 字节归档上限与分页。overclock 模式、配套核心补丁、归因埋点与逐轮追踪日志均已移除。每步清除解决不了「模型把自己的输出当缓存」:实测 ≤49000 字节的已清除结果里,事后只有 5.5% 走取回、52.6% 靠记忆代偿(41.2% 抄进推理、11.3% 抄进可见正文),而推理会被适配器以reasoning_content回灌上下文。
缓存命中与成本分析
本插件对工具结果的处理是每轮结束后原地替换为固定占位符,而非直接删除。但省 Token 的主力是「清除」这件事本身:历史不再携带全部工具结果。占位符的作用是保住 tool_call/result 配对,并给模型留下取回索引。
前缀缓存:断点落在上一轮第一个工具结果处
DeepSeek 的上下文缓存是块对齐的前缀缓存(实测会话里 cacheReadTokens 全部是 64 的整数倍):从第 1 个 token 开始连续完全一致的部分才能复用 KV,一旦某个位置不匹配,从该位置往后全部未命中。
清除发生在 turn/end:上一轮的工具结果被替换成占位符,而占位符与真实结果字节不同——所以下一轮首个请求必然在「上一轮第一个工具结果」处断开,与直接删除断在同一个 token 位置。
Agent 多步调用中,工具结果天然穿插在助手消息之间:
S U1 A1 T1 A2 T2 A3 T3
S:系统提示 + 工具定义
U1:用户消息
A1 A2 A3:助手思考/调用
T1 T2 T3:工具结果
下一轮首个请求里,上一轮的 T 已经变成占位符 P:前缀比对到 A1 结束时撞上 P1 ≠ T1,于是 A2 P2 A3 P3 U2 … 全部按全价重算。
实测(~/.dsh/sessions 中清除真正生效的会话;命中 = cacheReadTokens,保留率 = 命中 / 上一轮末 prompt):
| 会话 | 上一轮步数 | 上一轮末 prompt | 下轮首请求 prompt | 命中 | 保留率 |
|---|---|---|---|---|---|
| session-2b193d28 | 10 步 | 38437 | 28081 | 12672 | 33.0% |
| session-ce28482c | 13 步 | 34399 | 27539 | 11392 | 33.1% |
| session-3e1783ea | 2 步 | 32345 | 17849 | 15232 | 47.1% |
| session-d19ab3bd | 15 步 | 36768 | 28696 | 14464 | 39.3% |
判定式:命中 ≈ 上一轮首请求 prompt + 上一轮首条助手消息(9 个边界误差 <1.5%)——命中的最后一个 token 恰好是上一轮首条助手消息的末尾,紧接着就撞上被替换掉的工具结果。
- 「每轮请求命中上一轮结束时的完整序列、只需计算新增的
U_n A_n T0」不成立:上一轮尾部(该轮全部工具结果 + 其后的助手消息)每一轮都要按全价重算一次。多步轮(10~13 步)保留率只有 33%~47%,上一轮步数越多越低;上一轮只有 1 步时工具结果就在末尾,保留率才接近 100%。 - 保留率接近 100% 的会话是清除没有生效(旧版本语义,结果仍留在 prompt 里:下轮首 prompt / 上轮末 prompt ≈ 1.03)。那是「不清除」的性质,不是占位符带来的。
关键前提:占位符必须字节级固定,不能包含时间戳、随机数或变化的摘要——占位符本身内容一旦变化,前缀同样会断。本插件生成的占位符格式固定(轮次号 + 工具名 + 参数摘要 + 字节数),符合这一要求。
省在哪里:长期体积,而不是前缀稳定性
省在 prompt 的长期体积上:不清除时每个历史工具结果会被后续每一步按命中价反复读;清除后 durable history 只剩助手消息 + 占位符。用同一批实测 token 数重建「不清除」对照(每步 prompt 都带上全部历史结果、命中上一请求的前缀):
| 会话 | 轮 / 步 | c | 实测成本 | 对照(不清除) | 省 |
|---|---|---|---|---|---|
| session-2b193d28 | 4 / 31 | 0.25 | 452484 | 564057 | 19.8% |
| session-3e1783ea | 3 / 6 | 0.25 | 81375 | 102087 | 20.3% |
| session-ce28482c | 3 / 16 | 0.25 | 126634 | 132346 | 4.3% |
| session-e0725c8c | 3 / 8 | 0.25 | 91250 | 101768 | 10.3% |
c = 0.1 时省 2.7%~16.0%。三点提醒:
- 第 1 轮没有历史可清,两种做法完全相同;从第 2 轮起才有净收益,轮数越多被清掉的历史越久,省得越多。
- 省的比例取决于每轮步数(每轮边界要全价重算上一轮尾部,步数越多这笔越贵)与命中价
c;上表是 3~4 轮短会话,只说明方向与量级。 - 「压缩成本恒为不压缩的 30%」那类换算属于理想化模型(假设除新增助手消息外全部命中),与实测结构不符,别当结论用。
为什么用固定占位符而不是直接删除
- 配对约束:助手消息里的
tool_call必须有配对的tool_result,直接删结果就得连助手消息一起改写——断点前移、推理文本丢失(结构推演:成本比占位符高 52%~65%)。占位符保住了配对结构。 - 可见索引:占位符是模型判断「要不要取回」的唯一线索——工具名 → 关键参数 + 规模 + 是否失败,外加
read_tool_result_log(turn: N)坐标。 - 字节级固定:占位符写入后不再变化(不含时间戳、随机数或变化的摘要),从下一轮起长期可命中;若占位符内容每轮都变,前缀同样会断。
read_tool_result_log 工具
| 参数 | 说明 |
|---|---|
turn | 轮次编号(1 起),如 read_tool_result_log({ turn: 3 }) 读取第 3 轮 |
time | ISO 8601 时间或毫秒时间戳,读取该时刻所在轮次 |
offset / limit | 可选:只取原文的第 offset 行起、最多 limit 行。大结果用它分段取 |
| 都不传 | 已归档轮次列表 |
turn接受数字或纯数字字符串(schema 为integer/string)。step参数自 0.7.0 起移除;仍传step会返回明确提示,请改用turn取回整轮。- 返回体是紧凑纯文本,不是 JSON:每条为「表头 + 原文」,约 1.0×。旧版输出整条归档条目(
JSON.stringify),同一份原文出现两次、体积 3–5 倍,会被 harness 从中间切开并落盘成 spill。 - 归档上限 49000 字节:超限结果不保存,占位符写明尺寸(按字节)并要求重新执行原工具,不给取回坐标;同轮若还有可归档结果则保留坐标并追加「本轮另有 N 条超 49000 字节的结果未归档,需要时请重新执行原工具」。
工作原理
- 监听
session/event的turn/end与turn/start;另监听tool/ptc-dispatch,只为占位符索引登记run_code内部真正干活的子调用。 turn/end:从追加式日志收集该轮原始tool/result,按callId解析工具名,写round-NNNN.json与index.json;再把该轮 surface 节点替换为占位符(保持 tool-result 包装结构)。turn/start:只补归档中途启用或重启前未归档的轮次,不在此清除;清除统一在上一轮的turn/end执行,因此下一轮 prompt 组装时该轮结果已不可见。read_tool_result_log从调用方会话目录读取归档并返回原文。- 归档与清除失败只写 warning 到
$DSH_HOME/clear-tool-results.log(只有异常路径才写,正常路径零 I/O;0.7.0 起不再有 per-turn/per-step 追踪行),不牵连彼此的流程。
Demo:验证 read_tool_result_log 跨轮取回
目的:证明工具结果被清除后,模型能在后续轮次用 read_tool_result_log 取回原文,而不是靠上一轮的记忆复述。
关键设计——随机 token:固定串(如 TOPSECRET-12345)模型在生成它的那一轮见过,可能靠记忆答对;随机串无法预知,只有真正取回才能答对。
前置:/clear-tool-results on(/clear-tool-results status 应显示 enabled)。
第 1 轮:生成随机 token,要求不复述
发送:
执行
python3 -c "import secrets; print('DSH-DEMO-' + secrets.token_hex(8))"。 只回复「完成」,不要复述命令输出,也不要在思考或正文里出现任何 token。
工具结果先显示完整值,随后被替换为占位符:
[第 1 轮工具结果已清除归档:bash → python3 -c "..."(26),可用 read_tool_result_log(turn: 1) 读取]
✅ 检查点 1:第 1 轮回复里不得出现
DSH-DEMO-。一旦出现,说明 token 已写进对话,之后可能靠记忆而非取回答对。
第 2 轮:显式跨轮取回
发送:
调用
read_tool_result_log({ turn: 1 })取回第 1 轮那条命令的原始输出,把完整 token 原样发我;不要用 bash/read 翻文件。
预期发起的工具调用与返回(工具名/行数/字节数随调用方式与输出浮动;直接调用 bash 时表头显示 bash,PTC/run_code 下显示 run_code):
查询:第 1 轮
--- turn 1 step 1 · bash · {"command":"python3 -c \"...\""} · 第 1-N 行 / 共 N 行 · … 字符 / … 字节 ---
DSH-DEMO-xxxxxxxxxxxxxxxx
取回提示:归档在每轮结束时写入,之后随时可读;如需引用多轮原文,可在总结前逐轮取回。
通过标准
- 第 2 轮确实调用
read_tool_result_log({ turn: 1 }),而不是用 bash/read 绕过。 - 返回体以
查询:第 1 轮开头,--- turn 1 step 1 · … ---表头之后是原文。 - 模型回答的 token 与第 1 轮归档里的 token 逐字相同——随机值意味着不可能是背出来的。
可选扩展
- 空参数
read_tool_result_log({})→已归档轮次:turn 1(…步,…条),确认归档已登记。 - 中间多聊几轮后再问同一问题 → 仍取回同一 token,证明取回与轮次间隔无关。
- 用
time参数(取index.json中该轮的timeFrom毫秒值)→ 命中同一轮。
一键核对
grep -o 'DSH-DEMO-[0-9a-f]*' ~/.dsh/sessions/*/*/tool-result-logs/round-0001.json
输出应与第 2 轮回答中的 token 一致。
验证
跨轮取回:见上一节 Demo(随机 token + 通过标准)。
超限回归:跑一条 >49000 字节的输出(如 python3 -c "print('中'*16600)")→ 占位符应写明「该结果 49.8k 字节,超过 49000 字节上限,未归档」,且 tool-result-logs/ 下不得出现该条目的归档记录。
文件检查:
ls ~/.dsh/sessions/*/*/tool-result-logs/
cat ~/.dsh/sessions/*/*/tool-result-logs/round-0001.json
卸载
- 删除
cordis.patch.yml中的注册行; dsh plugin --profile web remove dsh-clear-tool-results;- 可选:删除
$DSH_HOME/clear-tool-results.json、$DSH_HOME/clear-tool-results.log与各tool-result-logs/目录。
链接
- GitHub: https://github.com/stultuss/dsh-clear-tool-results
- npm: https://www.npmjs.com/package/dsh-clear-tool-results
License
MIT
Comments
Loading…
From the same category
by volcengine
Self-evolving Context Database for AI Agents. Unify Agent Memory, Knowledge RAG and Skills.
★ 38.8k
↓ 11.2k/wk
AGPL-3.0
Python
Sep 27, 2026
dsh plugin --profile agent add @openviking/dsh-memory-pluginHindsight, agent memory that learns: long-term project memory with auto recall and retain, knowledge pages, deep reflection, and per-repo memory banks.
★ 22.8k
MIT
Python
by plastic-labs
Memory library for building stateful agents
★ 7.4k
AGPL-3.0
Python
Sep 25, 2026
by agentscope-ai
ReMe: Memory Management Kit for Agents - Remember Me, Refine Me.
★ 3.5k
↓ 154/wk
Apache-2.0
Python
Sep 25, 2026
dsh plugin --profile agent add @agentscope-ai/remeby zilliztech
A persistent, unified memory layer for all your AI agents (e.g. Claude Code, Codex, DSH), backed by Markdown and Milvus.
★ 2.7k
↓ 419/wk
MIT
Python
Sep 24, 2026
dsh plugin --profile agent add @zilliz/memsearch-dshby mem9-ai
Unlimited memory for OpenClaw
★ 1.2k
↓ 84/wk
Apache-2.0
TypeScript
Sep 16, 2026
dsh plugin --profile agent add @mem9/dsh-plugin