Skip to content

Latest commit

 

History

History
317 lines (218 loc) · 16.9 KB

File metadata and controls

317 lines (218 loc) · 16.9 KB

DeepSeek Harness 采纳方案与决策记录

基线:DeepCode main@4d56f44(0.3.0)· 调研对象 dsh@47f94385(0.1.0-rc.5, MIT) 前置阅读:调研报告 体例沿用 FLOATBOAT_ADOPTION_PLAN.md:每个候选项走正方 / 反方 / 裁决, 裁决写进文档,实现 PR 只负责执行,不重开辩论。


0. 结论先行

本轮采纳的主题是上下文经济学agent 的注意力预算花在哪、超支时怎么办

dsh 在这一层领先 DeepCode 一代。它的答案不是"把上下文做大",而是三件具体的事: 超限输出落盘可取回(spill)、无效重复被打断(护栏)、历史可检索而非只可回放(session query)。

对照之下查实了 DeepCode 的两处真实缺陷 —— WebFetch 无模型可见上限、Bash 截断即永久丢失 (证据见调研 §2.1)。它们由同一个能力修复,这构成本轮的第一优先级。

同时明确拒绝 Cordis 化重写(§2.1)。dsh 用 219 个包表达 DeepCode 用 4 个包表达的东西, 这个倍率来自它需要对外分发插件生态的产品前提 —— DeepCode 没有这个前提,抄成本不抄收益。

裁决 项目
采纳 spill · 重复调用护栏 · 逐工具超时 · 持久 shell 会话 · session 检索 · 工具渲染意图 · 结果剪枝
推迟 作业注册表统一 · goal 域 · Ralph 循环 · UI 包拆分
拒绝 Cordis 化重写 · workflow 引擎 · seam 三包拆分体例

1. 采纳项

1.1 工具输出溢出(spill)—— P0

做什么:任何工具的输出超过阈值时,全文写入 session 作用域的文件,模型收到的是 预览(头 + 尾)+ 字节数 + 取回路径。取回沿用现成的 Read(带 offset/limit),不新增工具。

正方

反方

  • 修复的是已查实的 bug,不是锦上添花:WebFetch 现在能把 5 MiB 正文灌进上下文(约 150 万 token), 一次调用即毁掉整个 session。
  • Bash 30 KB 截断后尾部无处可寻 —— 而失败测试的关键信息通常正在尾部。
  • 成本极低:DeepCode 已有 sessionDirPostToolUse 后置点,落点现成。
  • 与 DeepCode 已有的 snapshot / ledger 同构 —— 都是"把易失的东西钉在 session 目录里"。
  • 又多一类要清理的 session 产物;~/.deepcode/sessions 已经有 snapshots 了。
  • 模型未必会去 Read 那个文件,可能拿着预览就瞎猜 —— 那还不如直接截断来得诚实。
  • 阈值定错会有反效果:定低了,本该直接进上下文的中等输出被推到盘上,多花一轮 Read

裁决:采纳。 反方第 2、3 点是设计约束而非否决理由,按它们收紧设计:

  • 预览必须同时保留头和尾(dsh 只要求 bounded preview;这里加严)。测试失败、堆栈、报错 几乎总在尾部,只留头是最糟的截断方式 —— 也正是 DeepCode 今天在做的。
  • 取回提示写进结果文本本身("完整输出见 X,用 Read 的 offset/limit 取"),不靠模型自己想到。
  • 阈值可配置,默认取 Bash 现有的 30 KB —— 这样对既有行为不新增推到盘上的情况,只是把 原本丢掉的部分变成可取回。
  • 反方第 1 点接受为已知代价:spill 文件与 snapshots 同在 session 目录下,共享未来的保留期清理。

1.2 重复调用护栏 —— P0

做什么:连续以完全相同的参数调用同一工具达到阈值(3/5/8)时,向下一轮注入升级式提醒: 先短提示,再详细提示(点名工具、连续次数、参数摘要)。不拦截、不改写、不进工具列表。

正方

反方

  • 卡死循环是 agent 最贵的失败模式:不报错、不停止,安静地烧完预算。
  • DeepCode 已有 reminders/ 子系统(纯函数 builder + <system-reminder> 包装),这是零架构成本的落点。
  • 纯建议、不否决 —— 合法的重复调用一点不受影响,误判代价是几十 token。
  • 与 DeepCode 的 /cost 计价意识天然互补:省下的是真金白银。
  • 启发式,必然误伤:轮询一个文件等它变化,就是合法的同参重复。
  • 状态放内存,session 恢复后清零 —— 恢复到一半的循环要重新数。
  • 阈值是又一个要调的旋钮。

裁决:采纳。 反方三点全部接受为已知且可容忍的代价,理由是本机制的否决权为零: 误判的唯一后果是模型多读一句提醒。dsh 的取舍相同(其 README 明说 in-memory only、 "later reminders are the accepted cost"),这里沿用。

一处加严:dsh 默认排除 todo_write,理由是记账工具不该洗白循环。DeepCode 对应的 TodoWrite 同样排除,并追加排除 AskUserQuestion(等用户回答期间的重复是正常的)。

1.3 逐工具超时策略 —— P1

做什么:在中央调度点为每次工具调用武装一个 deadline,超时则以错误结果收尾并解释原因。 默认值按工具族给(网络类宽、本地类紧),可配置。

正方:今天只有 Bash 自带 timeout;一个 Grep 打到网络挂载盘、一个 WebFetch 卡在慢 TLS 握手,都能让整个 turn 无限期挂起,而用户在 REPL 里看到的只是光标在闪反方:多数工具本就有自己的超时(fetch 有、ripgrep 会退出),加一层可能与内层超时打架, 出现"两个超时谁先响"的模糊语义;且强行中止的工具可能留下半截副作用。

裁决:采纳,但明确分层。 外层 deadline 定位为兜底,默认值显著大于各工具内层超时, 使内层先响、错误信息更具体;外层只负责"内层根本没响"这一种情况。对有副作用的工具 (Edit/Write/Bash),超时后的结果文本必须明说副作用状态未知 —— 不假装什么都没发生。

1.4 持久 shell 会话 —— P1

做什么:一组工具开启/发送/读取/关闭长生命周期 shell,跨调用保留 cwd、环境变量、 shell 函数与后台进程。

正方

反方

  • 今天每次 Bash 都是全新进程:cd 白做、export 白做、source venv/bin/activate 白做。 模型的应对是把整串前缀重复拼进每条命令 —— 又长又易错。
  • 起了 dev server 之后想看增量日志,现在只能 Read 那个日志文件轮询,而轮询正是 §1.2 会触发的模式
  • 这是 DeepCode 与 dsh 之间执行形态上最实的一条差距。
  • 长生命周期进程 = 泄漏源:session 崩了、进程没收,机器上留一堆孤儿 shell。
  • 与沙箱语义纠缠:DeepCode 的沙箱是每次 spawn 时包 argv 的,一个持久 shell 在开启时确定 沙箱策略,之后策略变了它也不会重新武装。
  • node-pty 是原生依赖,要给 Tauri 打包的每个平台各编一份 —— 对一个 Mac 优先的产品是实打实的发布风险。

裁决:采纳,但不用 PTY。 反方第 3 点是决定性的:为交互式 TUI(vim、top)付出原生依赖 + 多平台编译的代价,而真实收益 90% 在"保留 cwd/env + 读增量输出"上 —— 这部分不需要 PTY

改为 marker 协议:持有一个长期 bash 进程,命令后追加哨兵 echo,读到哨兵即认为该命令结束 并取回退出码。纯 Node 管道,零原生依赖,跨平台。代价是不支持全屏 TUI 程序 —— 明确写进工具描述, 让模型知道该退回一次性 Bash

反方第 1、2 点转为硬性设计要求:

  • 会话与 session 同生命周期,agent 退出时全部收割;再加空闲超时。
  • 沙箱策略在开启时固化并记录在案;策略变更不影响已开会话,工具描述明说这一点。

1.5 session 检索 —— P1

做什么:让 agent 检索自己过去的 session:按文本搜、按 session 取回片段。

正方:DeepCode 已经把每个 session 以 JSONL 写在 ~/.deepcode/sessions —— 数据在那儿, 只是没有出口。"上次我们怎么解决这个 CI 报错的" 现在无法回答,而这正是本地 agent 相对云端 agent 的天然优势(数据全在本机)。 反方:跨 session 检索是隐私与安全的新面:A 项目的 session 可能被 B 项目的 agent 搜到, 把凭证片段、别的客户的代码带进当前上下文。dsh 用 SQLite FTS,等于再引一个原生依赖 + 一份要维护的索引与 schema 版本。

裁决:采纳,但按反方收窄两处。

  • 默认按 workspace 限定:只搜 cwd 相同(或其子目录)的 session。跨 workspace 检索需显式开启。 dsh 的工具名即 "workspace-authorized session queries",方向一致,这里作为默认而非选项。
  • 不引 SQLite:流式扫 JSONL + 正则。个人本地 agent 的 session 量级(数百到数千个文件)下, 一次扫描是几十毫秒量级,不值得为它背一个索引的一致性问题。真到量级不够时再加索引, 接口不变。

1.6 工具渲染意图 + 桌面端渲染 —— P1(UI)

做什么ToolDefinition 增加一个渲染意图声明(generic / terminal / diff / locations), 桌面端 ToolCard 按意图选择呈现:Edit/Write 出真 diff,Bash 出终端样式, Read/Grep/Glob 出可点击的文件位置列表。

正方THREE_WAY_REVIEW.md 判定"下一阶段 ROI 几乎全在 UI 出口", 而这是 UI 出口里最集中的一处:用户 90% 时间盯着工具卡片,今天它们全长一个样。 dsh 把渲染意图定为工具设计的一部分("decided up front",且呈现函数必须是 args 的纯函数), 这条纪律恰好能让 CLI 与桌面端共用同一份判断反方:把呈现关注点塞进 ToolDefinition 会污染内核 —— packages/core 一直标榜"无 UI 依赖"。 而且现有 ToolCard 已经有 diff 布尔参数,不做这层抽象也能给 Edit 出 diff。

裁决:采纳,按反方保持内核纯净。 渲染意图是枚举字符串 + 纯数据,不含任何 React/DOM 类型, 不引入 UI 依赖 —— 这与 packages/shared-ui 只放跨端类型的既有做法一致。反方的替代方案 (在桌面端硬编码 name === 'Edit')会把同一份知识在 CLI、桌面端、VS Code 各抄一遍, 下一个新工具就得改三处。声明在工具自己身上,三端各自读。

1.7 模型无关的结果剪枝 —— P2

做什么:compaction 触发前,先跑一遍不花模型调用的剪枝:丢弃早期已被同路径新结果覆盖的 文件读取、已失效的目录列表等。

正方:今天 compaction 一律走 LLM 摘要,要钱要时间;而历史里最大的一块往往是同一个文件 被读了五遍,其中四遍已经过时 —— 这部分丢弃是无损的,不需要模型判断。 反方:判断"已被覆盖"要有语义,判错就是删掉模型还需要的东西,而且静默 —— 比 LLM 摘要 更难发现出了问题。

裁决:采纳,但只做能证明无损的一类。 首版仅剪枝"同一 file_path 的更早 Read 结果, 且其后存在同路径的成功 Read/Edit/Write" —— 这一类可以从工具调用记录本身证明后者取代前者。 被剪枝的位置留一行占位说明("此处有一次已被后续读取取代的 Read"),使其可见而非静默。 其余类型不做。


2. 拒绝项

2.1 Cordis 化重写 —— 拒绝

即"一切皆插件",把 agent loop、工具注册表、session 日志都变成可从配置替换的插件行。

正方

反方

  • 这是 dsh 最核心的主张,也确实解释了它为什么能长出 52 个工具而不失控。
  • 可逆 effect(注册即返回 disposer)是真优雅,能一举解决 DeepCode 现在插件卸载残留的问题。
  • 用户明确授权了"可以整体重构"。
  • 219 包 vs 4 包。这个倍率的来源是 dsh 要对外分发插件生态dsh-plugin topic、 第三方 bundle、profile 模板)。DeepCode 没有这个产品前提 —— 抄的是成本,抄不来收益。
  • dsh 自称 developer preview 且首屏明示会破坏兼容。DeepCode 已发 0.3.0,有 npm 包、VSIX、 DMG、update feed 这些下游。拿一个自称会破坏兼容的框架重写已发布产品的地基,风险收益完全不对称。
  • Cordis 是 vendored 进 dsh 的 —— 连它自己都不敢直接依赖。DeepCode 采纳意味着要么也 vendor 一份(多一个上游要跟),要么依赖一个 0.x 外部框架。
  • 机会成本:这次重写会吃掉本轮全部预算,而 §1 的七项没有一项需要它

裁决:拒绝。 用户授权了"可以整体重构",但授权是许可不是要求 —— 判断哪里值得重构正是 这份方案该给的答案。这里的答案是:取它的纪律,不取它的框架

具体地,ctx.spillStore 式的三角色 seam 在真有第二个 provider 时才建(例如 spill 的 "本地文件 vs 未来的远程存储"),且在 DeepCode 里表现为一个模块里的接口 + 实现, 不拆成三个包。dsh 的三包拆分在 219 包的规模下自洽,在 4 包的仓库里只是目录噪声。

2.2 workflow 引擎 —— 拒绝(本轮)

模型编写编排脚本、worker thread 执行。

正方:表达力远超固定的 sub-agent 派发,能跑出真正的 fan-out/verify 结构。 反方:引入"模型写代码然后我们直接执行"这一整个新攻击面 —— 而 worker thread (dsh 自己也承认)不是安全边界。DeepCode 的 Task + TaskCreate 已覆盖多数编排场景。

裁决:拒绝本轮。 收益是"更强的编排",而 DeepCode 尚无被现有 sub-agent 卡住的实际用例。 在没有用例的情况下引入一个明知不是安全边界的代码执行路径,顺序错了。

2.3 goal 域与 Ralph 循环 —— 推迟

推迟理由:这两项改变的是"agent 什么时候停" —— 是产品取舍,不是能力补齐。 一个持久目标 + 自动续跑会显著改变 DeepCode 的交互性格(从"回合制"变成"自主推进"), 这该由用户拍板,不该由实现方在一轮技术采纳里顺手决定。列入 backlog。

2.4 作业注册表统一 —— 推迟

DeepCode 今天有两套后台机制:sub-agent 走 TaskManager,后台 Bash 走日志文件。dsh 用一个 ctx.jobs 统一。推迟理由:这是纯重构(用户可见行为不变),价值在于未来少写一套; 而 §1.4 的持久 shell 会改变后台执行的形态。先落 §1.4,等形态稳定后再统一, 否则会统一到一个即将过时的模型上。


3. PR 拆分

每个 PR 独立可回退,按依赖顺序:

# 内容 依赖 类型
1 本文档 + 调研报告 docs
2 spill:存储 + 策略 + 接入 Bash/WebFetch feature
3 重复调用护栏 feature
4 逐工具超时兜底 feature
5 持久 shell 会话(marker 协议) feature
6 session 检索(workspace 限定) feature
7 工具渲染意图 + 桌面端 diff/终端渲染 feature
8 模型无关的结果剪枝 feature

PR 2 与 PR 7 各自触及 ToolResult / ToolDefinition,若并行会在 types.ts 冲突 —— 按上表顺序合并,或后者 rebase。

4. 验证要求

每个实现 PR 必须满足:

  • 新增纯函数逻辑有单元测试(阈值边界、预览首尾保留、workspace 过滤等)
  • pnpm typecheck && pnpm lint && pnpm format:check && pnpm test 全绿
  • 触及桌面端的 PR 需在预览 harness 中实际渲染并截图自验(沿用 preview-app.html 的既有做法)
  • 不引入原生依赖(§1.4 与 §1.5 的裁决即由此约束推出)