Back to home

wensincai

btw4DeepseekHarness

/btw system command for deepseek harness

Stars
1
Language
JavaScript
Created
Aug 15, 2026
Updated
Aug 15, 2026

Introduction

dsh-btw — 给 DeepSeek Harness 加 Claude Code 原版 /btw 侧边提问 + /remember 会话备忘

一个零依赖的 dsh 插件包(bundle),为你的 profile 注册两个斜杠命令:

  • /btw <问题> —— Claude Code 原版形态的侧边提问(side question):fork 一个禁工具的轻量子代理、用当前会话上下文回答、答案作为命令结果显示,不打断主线、不进主对话历史、不落盘
  • /remember <备忘> —— 原来的备忘功能整体搬到这里:记录一条 by-the-way 笔记,让模型在本次会话里一直记得它(steer 即时回应 + 每次请求注入 + 磁盘持久化)。
/btw 刚才那个配置文件叫什么来着?   → 子代理在后台回答,主线不中断
/btw                                → 提示用法(必须带问题)

/remember 用户姓张,称呼"张工"      → 记录备忘,模型本轮之后一直记得
/remember                           → 列出本会话已记录的备忘
/remember clear                     → 清空本会话备忘
/remember forget 2                  → 删除第 2 条备忘

1. Claude Code 原版 /btw 是什么

Claude Code 2.0 引入的 /btw 命令,官方描述是 "Ask a quick side question without interrupting the main conversation"——侧边提问(side question),不是记笔记。从源码(commands/btw/btw.tsxutils/sideQuestion.tsutils/forkedAgent.ts;第三方源码分析见 Claude Code 的 /btw 为什么很妙不打断主线的旁路提问)看,它的机制是:

  1. fork 一个轻量子代理runSideQuestion → runForkedAgent)回答你的问题,主线程完全不动——问答不进入主对话历史,关掉浮层即消失(官方 issue #37582 确认它是 ephemeral);
  2. 有完整上下文,但禁用一切工具:只能基于当前对话里已有的信息回答,不能读文件、不能执行命令、不能搜索;
  3. 强制单轮maxTurns = 1,一问一答,不支持追问;
  4. 复用主线程的 prompt cache:额外成本压到最低,问答本身也不消耗上下文窗口;
  5. 答案以浮层显示,Space / Enter / Esc 关闭,不落盘、不可恢复。

一句话:共享上下文但不共享控制权,共享缓存但不污染主线。

2. 本插件与 Claude Code 原版的对应关系

v0.2.0 起,插件按原版形态重做:

Claude Code 原版dsh-btw(本插件)
/btw <问题>:fork 子代理、禁工具、单轮、答案不进历史/btw <问题>(原版形态)ctx.subagents fork provider、toolFilter: { allow: [] } 全禁工具、in-process driver 天然单轮、答案作为命令结果(log-only,永不进模型上下文)、不落盘
——(原版没有对应物)/remember <备忘>:原 /btw 的备忘语义,steer 即时回应 + system-prompt 每次注入 + $DSH_HOME/btw/notes.json 持久化

两套机制在 dsh 里的具体差异:

维度/btw 侧边提问/remember 会话备忘
本质提问 → 一次性答案记录 → 会话级记忆
执行体fork 出的轻量子代理(sidecar)主线程本身(agent.steer + system-prompt 注入)
工具完全禁用toolFilter: { allow: [] }注入主线后,后续轮次有全部工具
是否进历史不进(command/run·command/done 是 log-only,永不进模型上下文)(先作为一条 user message,之后每次请求都注入)
轮次单轮(in-process driver 一次 turn 一个 result)持续生效,直到 /remember forget / /remember clear
成本子代理一次请求(继承父上下文前缀)每轮多注入一段 section(上限 20 条、单条 500 字符)
持久化$DSH_HOME/btw/notes.json,resume 可恢复
管理命令无(一问一答)/remember 列表、/remember clear/remember forget <n>

3. dsh 原本有没有类似的命令?

没有内置 /btw 我在 dsh 源码(packages/interaction/commands)和 dsh-cc-tui 里全文检索过 btw,没有任何实现。dsh 的斜杠命令体系是插件注册制:任何插件用 ctx.commands.register({ name, description, input, handler }) 就能挂一个 /命令,Web 端输入框敲 / 会弹出命令菜单(ui-input-trigger 的 slash 菜单)。

当前 web profile 里实际可用的内置命令(逐包核对源码):

命令作用注册包
/compact压缩旧对话历史@deepseek-ai/dsh-command-compact
/goal查看/设置长任务目标@deepseek-ai/dsh-command-goal
/plan [off|message]进入/退出计划模式@deepseek-ai/dsh-plan-mode
/feedback <text>记录反馈@deepseek-ai/dsh-command-feedback
/permission <preset>切换权限预设@deepseek-ai/dsh-permission-presets
/export导出会话日志 ZIP@deepseek-ai/dsh-session-log-export

最接近的官方替代品:

  • /goal <目标> —— "让模型记住一件事"的持久语义最接近(跨多轮自动延续),但它是任务目标,不是随手笔记;
  • ~/.dsh/AGENTS.md(用户级全局指令)—— 会被注入每一个会话(dsh-agent-instructionsuser-global 作用域),适合"永远记住";要改文件、要重启,不是运行时命令。

本插件补上的是**"运行时、会话级、不换任务"**两块空缺:/btw 管"顺手问一句",/remember 管"顺手记一笔"。

4. /btw 侧边提问的工作原理(源码层面)

/btw <问题> 的执行链路(executeSideQuestion):

  1. 解析:整段输入就是问题,空输入返回用法错误;超过 2000 字符截断(parseSideQuestion);
  2. 取服务ctx.get('subagents') 惰性解析(可选服务,不在 inject 里声明)——profile 没有 subagents 服务或没有 fork provider 时返回明确的错误,/remember 不受影响;
  3. fork 子代理subagents.start('fork', { label: 'btw', prompt, parent: agent, signal, toolFilter: { allow: [] } })
    • fork provider(packages/subagent/subagent-fork-in-process)会把父会话已完成的 turn 前缀作为 seed 注入子会话——子代理"有完整上下文";
    • toolFilter: { allow: [] } —— allow 语义是"只保留列出的工具",空数组 = 全部工具消失且拒绝执行(fork 支持 toolFilter 能力);
    • 单轮由 subagent-in-process-driver 保证("owns exactly one turn with one result");
    • prompt 文本(renderSideQuestionPrompt)带固定指令:这是 side question、直接简洁回答、不要调用任何工具、不要承诺采取行动(对应 Claude Code 的 system reminder);
  4. 取结果run.result 给出 { output, stopReason }completed → 提取 text block 作为成功答案;aborted(用户取消/信号触发)→ "Side question cancelled.";refusal → 拒绝回答;max-tokens/error → 报错并保留部分回答;
  5. 收尾:无论成败都 await run.dispose()(释放子代理);结果作为命令结果返回——command/run/command/donelog-only 事件,永不进模型上下文,所以问答不污染主线packages/interaction/commands/src/types.ts 明确标注 "Log-only (never model surface)");
  6. 取消:命令 invocation 的 signal 直接传给子代理,UI 请求中止时子代理同步取消。

5. /remember 会话备忘的工作原理(源码层面)

/remember <note> 一次做三件事:

行为机制效果
① 即时响应把笔记作为一条用户消息注入会话(agent.steer模型下一轮就会回应它
② 会话记忆笔记注入本会话每一次模型请求的 system prompt(ctx.systemPrompt.section,name remember:notes,order 60)模型在本会话持续"记得",不靠上下文里的偶然提及
③ 跨重启持久写入 $DSH_HOME/btw/notes.json(按 session id 分桶)resume 会话后笔记还在

② 的 section 文本同时带行为引导:笔记是"背景上下文,不是指令"——无关时不要复述、被用户点名时简短回应不展开。这是对"顺手记一条却换来长篇大论"这类模型行为的抑制杠杆(插件无法强制模型,但每轮注入的引导能显著压低这种倾向)。

命令语法

/remember                列出本会话已记录的备忘(1-based 编号;超过 50 条只显示前 50 并提示其余)
/remember <note>         记录备忘 + 注入给模型;超过 500 字符截断
/remember clear          清空本会话全部备忘
/remember forget <n>     删除第 n 条备忘

clear / forget精确匹配的控制词:/remember clear the cache 会被当作一条普通备忘(与 /goal 的解析规则一致)。

6. 兼容性

  • 零运行时依赖:只 import node:*,通过注入的 commands/systemPrompt 服务和惰性的 ctx.get('subagents') 与 harness 交互——web profile 的 node_modules 里没有 @deepseek-ai/* 包,任何带 @deepseek-ai/* import 的插件都会 ERR_MODULE_NOT_FOUND;本插件任何 profile 都能直接加载;
  • /btw 需要 profile 提供 subagents 服务 + fork provider(dsh-base bundle 自带),缺失时该命令报错、/remember 照常工作;
  • peerDependencies 范围为 >=0.1.0-rc.5 <0.2.0(含 @deepseek-ai/dsh-subagent),全部标记 optional(运行时零 import,声明仅作文档用途);
  • Web、TUI、headless 等一切带命令注册表的 profile 都可用(dsh-cc-tui 会把注册表命令合并进它的 / 菜单)。

7. 目录结构

D:\dsh\btw\
├── package.json        # dsh.bundle.patch → cordis.patch.yml;零依赖
├── cordis.patch.yml    # bundle 补丁:insert 一行 { id: btw, name: dsh-btw }
├── src\
│   └── index.js        # 插件本体(纯 ESM + JSDoc,零构建;两个命令 + section)
├── test\
│   └── btw.test.mjs    # node:test 单测 + 桩 ctx 集成测试(零依赖,31 个用例)
└── README.md

8. 安装(web profile)

dsh 源码仓库根目录D:\dsh\deepseek-harness)执行:

cd D:\dsh\deepseek-harness
pnpm dsh plugin --profile web add "link:D:\dsh\btw"
  • link:链接安装:改 D:\dsh\btw 源码即生效(重启后),适合迭代;想要拷贝一份独立安装可改用 file:D:\dsh\btw
  • 纯本地路径,不需要网络、不需要 git/SSH(避开你环境里 github 连不上的问题);
  • 该命令会在 profile 的 package.json 加上 dsh-btw 依赖,并自动把它并入 dsh.profile.bundles

验证:

pnpm dsh --profile web --dump-config   # 应看到 # == dsh-btw 层

然后重启客户端(bundle 层在启动时组装,package.json / 插件源码变更不会热重载)。重启后在输入框敲 /,命令菜单里应出现 btwremember

不想重启的替代法:手动 pnpm add 该包后,把 - id: btw / name: dsh-btw 这两行写进 profile 的 cordis.patch.yml——profile 自己的补丁层支持热重载(watchUserPatches)。

9. 使用示例

你: /btw 刚才那个配置文件叫什么来着?
dsh: (子代理在后台用当前会话上下文回答)
     配置文件是 src/config.ts,第 12 行导出 CONFIG 对象。
     (主线继续干活,这段问答不进主对话、不落盘)

你: /remember 用户姓张,称呼"张工"
dsh: Noted (1 by-the-way note this session).
模型: 好的,记下了——后续涉及称呼我会用"张工"。

你: /remember
dsh: By-the-way notes (2):
1. 用户姓张,称呼"张工"
2. 测试环境地址是 http://10.0.0.5:8080

你: /remember forget 2
dsh: Forgot note 2.

10. 测试

cd D:\dsh\btw
node --test        # 31 个用例:侧问解析/提示/子代理流(含取消/拒绝/部分回答/失败兜底/始终 dispose)、
                   # 备忘语法、存储/持久化/损坏回退、渲染上限、apply() 装配、steer 失败兜底

11. 卸载

pnpm dsh plugin --profile web remove dsh-btw

(同时会从 dsh.profile.bundles 移除该层;如需清理数据,删除 C:\Users\hukou\.dsh\btw\ 目录。)

12. 限制与已知问题

  • /btw 的上下文是"上一个已完成 turn":fork provider 的 seed 截止到父会话最后一次 turn/end(当前进行中的 tool-call turn 无法回放为合法子会话)。主线正在干活时提问,子代理看到的是最近一个完整回合的上下文——与 Claude Code"每轮结束后快照"的取舍一致;
  • /btw 不做 prompt cache 复用:dsh 的 fork 子代理是独立的一次请求(继承了上下文前缀,但不会像 Claude Code 那样保证与主线程字节级一致的 cache key),成本低于开新会话但高于纯缓存命中;子代理自身的请求不写回主线上下文窗口
  • /btw 答案的呈现:作为命令结果行显示(相当于浮层),关闭/翻页即不可恢复;想要保留就复制,或用 /remember 记下来;
  • /remember 注入每次模型请求:带备忘的会话每个请求都会携带 section 文本,有 token 成本;maxNotes(默认 20 条)和单条 500 字符上限约束它。想彻底清空用 /remember clear
  • 持久化是进程内快照 + JSON 文件:并发写多个会话时以最后一次 save() 为准;$DSH_HOME/btw/notes.json 可手工编辑,但建议通过命令操作;
  • 数据存明文notes.json$DSH_HOME 下,别往里写密钥;
  • TUI 注意dsh-cc-tui 自带一套本地命令(/clear/cost 等)且需要 TTY——你的 client 是无 TTY 的 Web 会话,用的是本插件的 registry 命令,两者不冲突。

13. 版本历史

  • 0.2.0(本次重构):/btw 改为 Claude Code 原版形态的侧边提问(fork 子代理 → 禁工具 → 单轮 → 答案作为命令结果,不进主对话、不落盘,需要 profile 提供 subagents + fork provider);原备忘功能整体迁移到 /remember(存储文件路径不变,旧笔记无缝延续;prompt section 更名 remember:notes)。新增 12 个侧问相关测试用例(共 31 个)。
  • 0.1.1:优化备忘提示词("背景上下文,非指令,点名时简短回应")+ 列表上限 LIST_CAP=50
  • 0.1.0:首个可用版本,/btw 为"会话备忘"形态(steer + system-prompt 注入 + 持久化)。

14. 参考资料