Skip to content
dsh.fish
Bundle

dsh-agenote

DeepSeek Harness 的 agenote 集成插件:会话开始注入知识库健康度、turn 结束检测完成信号并转交 agenote-review 流程,外加 /agenote-summarize|curate|health 斜杠命令。行为规范由 agenote-skills 定义,插件只做事件触发与命令快捷入口。

Source
ShineBreaker
License
MIT
Updated
Updated 23 days ago

Readme

# dsh-agenote

DeepSeek Harness(DSH)的 [agenote](https://github.com/ShineBreaker/agenote) 集成插件。

把 agenote 知识库接进 DSH 会话:**system prompt 注入健康度摘要**、**任务完成时提示 agent 做经验留痕**,外加三个斜杠命令快捷入口。

本插件是 `agenote-pi`(pi/omp 扩展)在 DSH 上的对等实现,二者共享同一套行为规范(`agenote-skills`)与同一个 `agenote` CLI,**不重复实现任何知识库逻辑**。

## 它做什么

| 触发点             | DSH 事件                | 行为                                                                                      |
| ------------------ | ----------------------- | ----------------------------------------------------------------------------------------- |
| system prompt 拼装 | `systemPrompt.section`  | 注入 `agenote health` 摘要(总数/孤立率/过时率/薄弱类别/记忆统计)——背景知识,不产生 turn |
| 用户消息进入       | `agent/inbox/inserted`  | 记住最近一条**真实用户**发言,用于信号判定                                                |
| 新一轮开始         | `agent/status`(running) | 作废待发的空闲计时器                                                                      |
| 工具执行完毕       | `tools/result`          | 标记本轮「真的干过活」(空闲兜底的门槛依据)                                              |
| turn 收尾          | `agent/turn-stopping`   | 检测完成信号 → 注入 review 提示;并武装空闲兜底                                           |
| 会话销毁           | `agent/disposed`        | 清计时器、释放会话状态                                                                    |

命令:

| 命令                 | 说明                                                            |
| -------------------- | --------------------------------------------------------------- |
| `/agenote-summarize` | 在当前会话触发经验总结 + 留痕(按 `agenote-review` skill 执行) |
| `/agenote-curate`    | 触发 KB 策展(按 `agenote-curator` skill 执行)                 |
| `/agenote-health`    | 直接回显 `agenote health` 报告(纯只读)                        |

前两个命令**不直接跑 CLI**——只把任务提示投进会话,流程编排与写盘决策由 agent 按对应 skill 主导。这保证了「行为规范」只有一份真相源(`agenote-skills`),不会在插件里长出第二份。

## 安装

```bash
# 在本仓库目录下,通过 DSH 的 plugin_manager 安装(推荐)
# 或在会话中让 agent 执行:plugin_manager install_bundle <本目录绝对路径>
```

安装会在当前 profile 的 `package.json` 里以 `link:` 方式登记依赖,并把 `dsh-agenote` 追加进 `dsh.profile.bundles`。

profile 为 `patchReload: live` 时(web profile 默认如此),新 bundle 会**热挂载到已存在的会话**,无需重启即可生效——实测:安装后同一会话的下一轮就出现了 `agent/created` 注入的健康度摘要。

两条注意事项:

- `install_bundle` / `set_bundle` 在**运行中**的进程上可能返回 `application: failed` + `agenote (dsh-agenote): failed to import`。这是该次调用自身的诊断,**不代表 bundle 未被采用**——请以「冷启动日志无失败」与「会话里是否真的看到注入」为准,不要据此改代码。
- **改了源码之后**需要重启:运行中的进程持有旧的模块图,重新加载要冷启动。

### 依赖

- `agenote` CLI 在 `PATH` 中(`which agenote`)。可用环境变量 `AGENOTE_BIN` 覆盖可执行名。
- 可选:`agenote-base` / `agenote-curator` / `agenote-review` skills 已安装(命令注入的提示词会引用它们)。

## 配置

配置从 cordis 行的 `config` 下发(见 `cordis.patch.yml` 注释)。全部字段可选,**默认值由插件导出的 `Config` schema 声明**(`index.js` 组合,字段定义在 `hooks.js` / `commands.js`):Cordis 加载时用它校验配置并填默认值,**非法配置在加载期直接失败**(`invalid config: ...`),不会带着错值跑起来:

```yaml
- id: agenote
  config:
    hooks:
      enabled: true # 总开关
      status: true # system prompt 注入健康度摘要
      completionSignals: true # turn 收尾检测完成信号
      idleFallback: true # 空闲兜底(无人值守场景)
      signals: ["搞定", "done."] # 覆盖内置信号清单
      debounceMs: 300000 # 触发冷却期(durable log 推导)
      idleMs: 300000 # 空闲兜底阈值
      statusTtlMs: 300000 # 健康度摘要缓存时长(0 = 每个请求重算)
    commands:
      enabled: true
```

## 设计要点

几个刻意的取舍,改动前请先读:

- **健康度摘要走 system-prompt section,不走 `agent.followup()`**。`followup` 的语义是"排一个独立后续 turn 并**唤醒 driver**"——`agent/created` 时调用它,等于插件在用户开口前替用户"发言",driver 被唤醒后模型把摘要当指令处理并回复(表现为"新会话一打开 agent 就自说自话",已修)。背景知识类注入用 `ctx.systemPrompt.section()`,随每次请求进入 system prompt,不产生 turn。
- **`agent.followup()` 只用于任务提示**(review/curate)。这两个投递点都**没有待执行的 step**;`steer` 只作用于「最近的 step」,会把消息停在 inbox 里等下一次唤醒。`followup` 才等价于 pi 的 `sendUserMessage({deliverAs:'followUp'})`。
- **subagent 豁免**(`session.header.origin === 'subagent'`)。DSH 的 subagent 是同进程独立 agent,同一个插件实例会收到它们的事件:worker 的内部对话不该投 review 提示(污染 handoff),也不该各自武装 idle 计时器(大量委派时表现为 idle 兜底"反复触发")。
- **防抖与「已触发」从 durable log 推导**,不存内存标志。内存标志在 `agent/created`(含 resume/clear/compact)时被重置——web 刷新即 resume,同一会话干完活又触发一次。改扫会话日志:`source.plugin === 'agenote-hooks'` 的 `user/message` 事件即本插件既往注入,resume/重启后答案不变。
- **每会话状态用 `Map<sessionId, state>`**,不用模块级标量。一个进程里可并存多个会话(web 多标签、subagent),模块级状态会串台——这正是 pi 版历史上修过的 bug。
- **自注入反馈环防护**。注入的提示词自身含「完成」等信号词;`agent/inbox/inserted` 只认 `source.kind === 'user'` 的消息(本插件注入的 source.kind 是 `'plugin'`),回路在来源层面断开。
- **不 import `@deepseek-ai/*`**。本包经 `link:` 部署时模块 realpath 落在源码目录,Node 的 `node_modules` 父级检索够不到 `$DSH_HOME/profiles/node_modules` 共享 fallback。需要上游纯函数(`createUserMessage`)时在 `lib.js` 里复刻并注明出处;上游改语义需手动同步。
- **配置走 Standard Schema,不手写 normalize**。插件导出 `Config`(Standard Schema v1 节点,`lib.js` 里手搓的 schemastery 最小子集,语义逐条对齐上游),Cordis 加载时用它校验 cordis.yml 行下发的 config 并填默认值;非法值(如 `debounceMs: -1`、`signals: []`)在加载期抛 `ValidationError`,而不是被静默回退。字段与默认值的单一真相源在 `hooks.js` / `commands.js` 的 `Config` 声明里,新增可调项必须加在那里。
- **空闲兜底以「跑过工具的轮次」为门槛**,不用裸 turn 数。turn 数无法区分「完成了一段工作」与「回答了一个问题」——纯对话轮同样推进 turn,会导致兜底在纯聊天上误报(实测发生过)。判据是 `workedThisTurn`(仅在该轮 `tools/result` 触发过时才成立)。
- **不重复实现行为规范**。信号清单、写入流程、卡片格式全部归 `agenote-skills`;插件只做「事件触发 + 命令快捷入口」。
- **归因**。所有 CLI 调用都带 `AGENOTE_AGENT=dsh`(`lib.js:runKb`)。该变量只给卡片打归因标签,不做写入隔离,但不带的话归因会错误落到默认 agent(`omp`)。

## 与 agenote-pi 的差异

|               | agenote-pi(pi/omp)                      | dsh-agenote                                                            |
| ------------- | ----------------------------------------- | ---------------------------------------------------------------------- |
| 形态          | 单文件 TS,运行时自动扫描                 | Cordis bundle(`package.json` + YAML patch),经 `plugin_manager` 安装 |
| 健康度摘要    | pi-ui 欢迎框显示(不注入对话)            | `systemPrompt.section`(进入 system prompt,不产生 turn)              |
| turn 收尾     | `agent_end`                               | `agent/turn-stopping`                                                  |
| 任务提示投递  | `sendUserMessage({deliverAs:'followUp'})` | `agent.followup(createUserMessage(...))`                               |
| subagent 守卫 | `isSubagentProcess()` 嗅探子进程 argv/env | `session.header.origin === 'subagent'`(持久化权威字段)               |
| 防抖依据      | 模块级内存变量                            | durable session log 推导(跨 resume/重启稳定)                         |
| 归因          | `AGENOTE_AGENT=pi`                        | `AGENOTE_AGENT=dsh`                                                    |

## 开发

无构建步骤,纯 ESM。改完源码后:

```bash
node --check index.js hooks.js commands.js lib.js   # 语法检查
node --input-type=module -e "import('./index.js').then(m=>{const v=m.Config['~standard'].validate;console.log(JSON.stringify(v(undefined)));console.log(JSON.stringify(v({hooks:{debounceMs:-1}}).issues??null))})"  # 配置 schema:全默认值 / 非法值报 issues
dsh --profile <profile> --no-open --port 3099       # 新进程冷启动,确认无激活失败
```

`plugin_manager set_bundle` 在**运行中**的进程上可能报 `failed to import`——那是该次调用自身的诊断,旧模块图仍能服务已挂载的行。判断依据看「冷启动日志无失败」+「会话里是否真的看到注入」,不要据此改代码。

## 许可

MIT。见 `LICENSE`。

Install

dsh plugin --profile web add github:ShineBreaker/dsh-agenote

Profile: web

  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source