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.tsx、utils/sideQuestion.ts、utils/forkedAgent.ts;第三方源码分析见 Claude Code 的 /btw 为什么很妙、不打断主线的旁路提问)看,它的机制是:
- fork 一个轻量子代理(
runSideQuestion → runForkedAgent)回答你的问题,主线程完全不动——问答不进入主对话历史,关掉浮层即消失(官方 issue #37582 确认它是 ephemeral); - 有完整上下文,但禁用一切工具:只能基于当前对话里已有的信息回答,不能读文件、不能执行命令、不能搜索;
- 强制单轮:
maxTurns = 1,一问一答,不支持追问; - 复用主线程的 prompt cache:额外成本压到最低,问答本身也不消耗上下文窗口;
- 答案以浮层显示,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-instructions的user-global作用域),适合"永远记住";要改文件、要重启,不是运行时命令。
本插件补上的是**"运行时、会话级、不换任务"**两块空缺:/btw 管"顺手问一句",/remember 管"顺手记一笔"。
4. /btw 侧边提问的工作原理(源码层面)
/btw <问题> 的执行链路(executeSideQuestion):
- 解析:整段输入就是问题,空输入返回用法错误;超过 2000 字符截断(
parseSideQuestion); - 取服务:
ctx.get('subagents')惰性解析(可选服务,不在inject里声明)——profile 没有 subagents 服务或没有forkprovider 时返回明确的错误,/remember不受影响; - fork 子代理:
subagents.start('fork', { label: 'btw', prompt, parent: agent, signal, toolFilter: { allow: [] } }):forkprovider(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);
- 取结果:
run.result给出{ output, stopReason };completed→ 提取 text block 作为成功答案;aborted(用户取消/信号触发)→ "Side question cancelled.";refusal→ 拒绝回答;max-tokens/error→ 报错并保留部分回答; - 收尾:无论成败都
await run.dispose()(释放子代理);结果作为命令结果返回——command/run/command/done是 log-only 事件,永不进模型上下文,所以问答不污染主线(packages/interaction/commands/src/types.ts明确标注 "Log-only (never model surface)"); - 取消:命令 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服务 +forkprovider(dsh-basebundle 自带),缺失时该命令报错、/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 / 插件源码变更不会热重载)。重启后在输入框敲 /,命令菜单里应出现 btw 和 remember。
不想重启的替代法:手动
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+forkprovider);原备忘功能整体迁移到/remember(存储文件路径不变,旧笔记无缝延续;prompt section 更名remember:notes)。新增 12 个侧问相关测试用例(共 31 个)。 - 0.1.1:优化备忘提示词("背景上下文,非指令,点名时简短回应")+ 列表上限
LIST_CAP=50。 - 0.1.0:首个可用版本,
/btw为"会话备忘"形态(steer + system-prompt 注入 + 持久化)。
14. 参考资料
- Claude Code Commands 官方文档
- Claude Code 的 /btw 为什么很妙:Side Question 的 sidecar 架构(源码分析)
- Claude Code /btw — 不打断 AI 编码的旁路提问(机制与成本)
- anthropics/claude-code#37582 — /btw 响应临时性的官方讨论与实现细节
- dsh 源码:
packages/interaction/commands(命令注册表)、packages/core/system-prompt(section/context 注入)、packages/subagent(ctx.subagents、fork provider、in-process driver)、packages/plan/plan-mode(/plan+agent.steer先例)、apps/cli/src/plugin.ts(bundle 安装/对账)