DeepSeek Harness 的消息反馈通道:模型跑着的时候你插一句 /feedback 会怎样

2026-08-17

先说清楚这篇要回答的那个具体问题。

你在跟 agent 干活,模型正在一轮里连着调工具,你眼看着它往一个错方向走,顺手在输入框里敲了一句反馈。这时候会发生三件事里的哪几件:这一轮会不会被打断?这句话会不会被塞进模型的上下文,从而顶掉本来能复用的前缀?以及,这句话最后落到哪里去了、还能不能撤回?

DeepSeek Harness 仓库里管这块的是 packages/feedback/ 这个包组,它下面只有两个包:command-feedback/message-feedback/。两个包解决的是完全不同的两件事,名字又都叫 feedback,第一次翻很容易串。先把它们各自的路径走一遍。

需要先摆在前面的限定:该仓库 README 的 Developer preview 一节自述项目处于开发者预览阶段,并且用整句大写明写会有破坏兼容性的变更(README.md 第 9 至 11 行)。下面提到的命令、字段名、默认值随时可能变,请以仓库最新内容为准。本文对应的是版本 0.1.0-rc.5

第一条通道:/feedback,一条写进日志但模型看不见的记录

打开 packages/feedback/command-feedback/src/index.ts,整个包的公开写入口是一个五行的函数(第 72 至 76 行):

export function recordFeedback(session: Session, text: string): void {
  const normalized = text.trim()
  if (normalized.length === 0) throw new TypeError('feedback text must not be empty')
  session.append('feedback/record', { text: normalized })
}

它做的事就是把首尾空白去掉,然后往 Session 日志里 append 一条 feedback/record 事件,完。没有任何一步去启动模型工作。 斜杠命令 /feedback 的 handler executeFeedbackCommand 也只是调用它,然后拼一段确认文本回去。

所以开头那个问题的答案,在同一个包的 README 的 Model Experience 一节里写得很直白:模型什么也看不到。feedback/record 是 log-only 事件、不带 surfaceOp,因此不进 ordered surface、不进 deriveMessages()、不进 system prompt;README 还专门写了一句「在一轮进行中记录反馈,不改变这一轮剩下的请求」,以及 token 影响为零、对 KV Cache 的影响是「与模型请求路径无关」。

这里有个细节值得单独拎出来。命令注册时给了 recordInput: false

ctx.commands.register({
  name: 'feedback',
  description: 'record feedback about this session',
  input: { hint: '<text>' },
  recordInput: false,
  handler: invocation => executeFeedbackCommand(invocation, ctx),
})

dsh-commands 照例会追加它通用的 command/run / command/done 一对事件,但因为这个开关,command/run 不带 args。README 自述的理由是:反馈可能从 /feedback 之外的触发点进来,事件才是权威,把文本留在 command/run 里会变成两条记录承载同一段文字。这是文档自述,不是我们的推断。

别把「确认文本出现了」当成「已经落盘了」。 README 的已知限制里明写:确认发生在 append 之后,不是 flush 之后,所以紧接着崩溃的话,这条记录会跟着其它未 flush 的尾巴一起丢;需要落盘保证的消费方得自己去 await ctx.sessions.flush(session)。文档里给的原话是「反馈不值得为它强制一次同步磁盘写」。

确认文本本身由三段拼成:接收会话的 id、一个匿名用户 id、以及一句会话共享披露。第三段是从已挂载的遥测服务读出来的,四选一,源码里 sharingSentence 的 switch 就是这四支:

披露到的状态确认文本里那一句
fullSession sharing is enabled.
feedback-onlySession sharing is feedback-gated; recording feedback releases the session prefix for sharing.
disabledSession sharing is disabled.
没挂遥测服务Session sharing is not configured.

这张表要这么读:它说的是「你这个部署当前的共享策略」,不是「你这条反馈已经送到了谁那里」。README 对此写得很谨慎——fullfeedback-only 下记录会交给后端的非阻塞入队,批量、重试、丢失策略归 SDK 管,所以这句话不承诺任何东西真的到达了收集端。另外 README 提到,某个 harness home 第一次接受反馈时,可能会创建 $DSH_HOME/.anonymous-user-id

一处文档与代码不一致,照实记。 packages/feedback/command-feedback/README.md 第 18 行写的是通过 ctx.get('telemetry') 读这个服务,而 packages/feedback/command-feedback/src/index.ts 第 92 行里实际写的是 ctx.get('sessionTelemetry')。中文版 README 同一行同样写作 telemetry。两处不一致,以我们实读的源码为准。就到这里,不往下延伸。

这条通道的边界,README 的已知限制列得比正文还有用:没有检索面(挂载了 OTel 插件也只是拿这个事件当共享触发器)、没有结构化字段(一条就是一段自由文本,没有分类、没有严重级、没有关联事件)、没有修改或撤回(日志只追加,这个包不加墓碑,写错了只能再写一条)、只在 Web 端有(headless、ACP 自动化、JSON-RPC 都不提供命令适配器)。还有一个坑很具体:全新的空会话里执行 /feedback,事件会记下,但 Web 记录区要等会话激活后才渲染命令行,所以你看不到确认。

第二条通道:给某一条助手消息打分,走的是完全另一条路

packages/feedback/message-feedback/ 干的是另一件事:对某一条已定稿的助手消息给 positive / negative,可以带一段备注,而且可编辑、可删除。它不进 Session 日志、不是投影、不做遥测交接,docs/subsystems/feedback.md 的原话是它只待在 storage-domain 的伴随记录里。

顺着 src/index.tsput 的路径走一遍,会看到几个顺序上很讲究的点:

  1. 先校验备注,再查会话。 resolveNote 在入队之前就跑(第 207 行),空白备注直接 note-blank,超长直接 note-too-large。README 明写了这个顺序的后果:会话根本不存在时,你也可能先拿到备注类错误,因为这一步压根没碰持久化。
  2. 按 Session 串行入队。 enqueue 用一个 operationTails 的 Map 把同一 Session 的操作串成链(第 361 行起)。检查、读取、冲突判断、整行写入都在队列里,因此这套保证只覆盖单个 Host 进程内的并发。
  3. 目标必须是「非空的、append-origin 的 assistant/message」。 hasFeedbackTarget 逐条过事件,replacement-origin 的、只带 usage 的空记录、非助手记录,一律 target-not-found
  4. 写伴随记录之前先立耐久屏障。 ensureTargetDurable 对身份匹配的 live 会话先走 ctx.sessions.flush,没有耐久监听器参与就直接抛错;随后无论 live 还是 cold,都用 SessionPersistence.readFrom(id, 0) 从序列零物理复读一遍,再校验一次 header 身份和目标是否还在。文档给的结论是:目标日志的持久提交,永远早于它的伴随记录。
  5. 严格乐观并发。 ifVersion 必须与当前条目版本完全相等,哪怕这次请求根本不改值。版本是 randomUUID() 生成的不透明 token(第 132 至 134 行),只能做相等比较,调用方不能排序也不能自己合成。冲突时返回的 version-conflict 里带着权威当前条目(不存在时是 null),所以调用方不用再读一次就能对账。

值得记住的两个数值锚点:存储域名字是 message_feedbackversion: 0,声明在 src/spec.ts 末尾的 defineDomainmaxNoteBytes必填的正 safe integer,Web bundle 里配的是 8192——这个值在 packages/bundle/web-app/cordis.patch.ymlmessage-feedback 那一段(第 64 至 67 行)能直接翻到。maxNoteBytes 按 UTF-8 字节算(Buffer.byteLength(note, 'utf8')),所以中文备注能写多少字,跟这个数字不是一回事。

还有一条容易被忽略的语义:省略 note 意味着「期望值是没有备注」。也就是说一次版本匹配的实质 put 如果不带 note,会把原来那条备注清掉,而不是保持不变。

这些边界什么时候会咬到你

docs/subsystems/feedback.md 的 Boundaries 一节把话说得比较硬,挑几条落地的:

  • 队列是进程内的,storage-domain 没有跨进程条件写,多个 Host 进程写同一个存储根目录时不提供 compare-and-swap,也不保证不丢更新
  • persistence 没有持久删除接口,服务不把 session/disposed 当删除,所以带外删掉日志之后,孤儿伴随记录可能还留着。
  • cold 请求会扫完整的 Session snapshot 目录,因为 persistence 没有按 id 读元数据的操作。
  • 单个 Session 行没有条目数上限、也没有聚合字节上限,maxNoteBytes 只管每条备注。
  • Host 约定不记录已认证的 actor 或审计身份,文档直说这里假设调用方边界可信。这条决定了这个 Host 端点该暴露在什么位置上,不要想当然。
  • 只有 {createdAt, cwd} 不同时,header 身份才能识别出复用的 Session id;保留了相同 header 身份的克隆日志,这套约定区分不了。

Web 侧还有两条口径宽窄不一致,是文档自己写明的:被中断冻结的部分输出不带 messageId,渲染点在字段缺失时直接跳过反馈控件;多步 turn 里,Host 接受每一条 append-origin 步骤消息作为目标,但 UI 只在收尾那条助手消息上渲染一次操作栏,所以UI 暴露的范围比 Host 约定允许的窄。另外 trajectory 与 waterfall 视图不渲染反馈条目,尽管它们的助手节点带着同样的 messageId;伴随记录也不发布实时帧,另一个标签页的评分要等重连或下一次冲突响应才可见。

第二处不一致,同样照实记。 packages/feedback/message-feedback/README.md 的已知限制第一条写的是「Client aggregate and UI are absent」——客户端聚合与 UI 消费方缺席、延后处理;packages/feedback/README.md 结尾也是同样口径。而 docs/subsystems/feedback.md 有完整的 Web surface 一节在描述 @deepseek-ai/dsh-client-ui-message-feedback,这个包在 packages/client/ui-message-feedback 确实存在,并且在 packages/bundle/web-app/cordis.patch.yml 第 246 至 247 行被挂载。三处口径不一致,我们只陈述差异,不推断哪个是当前状态。

回到开头那个问题

如果你的诉求是「我想留一句话给这次会话,不想影响正在跑的这一轮」,走的是 /feedback:模型看不到、零 token、不动请求前缀,但写下去就改不了,也没有地方能查回来。

如果你的诉求是「这条回答不行,我要标记出来并写清楚哪不行」,走的是消息反馈:可改可删,有版本对账,但它待在伴随记录里,不进日志、不进模型、也不触发遥测释放——指望模型看到你的差评然后改,是没有的事。

最后重复一遍那层限定:以上全部字段名、事件名与默认值都取自仓库当前快照,该项目自述处于开发者预览阶段并明确会有破坏兼容性的变更,读到这篇的时候请自己回仓库对一遍再动手。


本文依据 DeepSeek Harness 官方仓库(github.com/deepseek-ai/deepseek-harness)的 README、docs/ 下的 架构与子系统文档、以及 packages/ 下的源码整理,核对日 2026-08-17,对应仓库快照 47f9438(版本 0.1.0-rc.5)。 本文内容为仓库源码与文档口径,我们没有安装、也没有运行过这个项目, 因此不涉及界面外观、操作手感与运行速度的任何描述。 该仓库 README 自述处于开发者预览阶段并明确说明未来会有破坏兼容性的变更, 文中出现的命令、配置与默认值随时可能变动,请以仓库最新内容为准。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。