364 lines
24 KiB
Markdown
364 lines
24 KiB
Markdown
# ASR 管线 Agent 解耦 — 重构实施文档
|
||
|
||
**版本**:v1(2026-09-18)
|
||
**上游 review**:`docs/agent-decoupling-review.md`
|
||
**执行者**:子 agent(Claude Code / Codex / 其他具备文件与 shell 能力的 agent)
|
||
**授权边界**:本文档 Stage 1–4 为已授权范围。超出范围的改动(尤其是 §8 禁止项)必须先回报 Kai 并取得确认。
|
||
|
||
---
|
||
|
||
## 0. 给执行 agent 的前置须知(先读完本节再动手)
|
||
|
||
### 0.1 这套系统正在生产运行,且无 git 保护
|
||
|
||
`/home/claw/claude-workspace` **不是 git 仓库**(已验证:`git rev-parse` 返回 `fatal: not a git repository`)。这意味着:
|
||
|
||
- **没有 `git checkout` 可以回滚**。任何改动前必须手动备份。
|
||
- 备份约定沿用本项目既有风格(目录中已有 7 个 `*.bak.*` 文件):
|
||
```bash
|
||
cp -a <file> <file>.bak.$(date +%Y%m%d-%H%M%S)
|
||
```
|
||
- 磁盘余量充足(43G available),备份成本可忽略。
|
||
|
||
同时注意:`~/obsidian-vault` **是**独立 git 仓库,管线会向它 commit+push。本次重构**不应触碰 vault 内容**,只改 `OBSIDIAN/` 下的基础设施代码与 systemd unit。
|
||
|
||
### 0.2 当前 baseline(改动前请先自行复现,作为对照)
|
||
|
||
```bash
|
||
cd /home/claw/claude-workspace/OBSIDIAN/asr-pipeline
|
||
PYTHONPATH=. .venv/bin/python -m unittest discover -s tests -v
|
||
# 期望:Ran 28 tests ... OK
|
||
```
|
||
|
||
注意:venv 里**没有 pytest**,必须用 `unittest`。这 28 个测试全部离线(`test_core.py` 在 import 前就把 `ASR_STATE_DIR`/`ASR_VAULT` 重定向到 tempdir),可以安全反复跑。
|
||
|
||
**任何 stage 结束时这 28 个测试必须仍然全绿**,且新增功能需补充测试(见各 stage 的验收标准)。
|
||
|
||
### 0.3 当前运行状态(改动前实测,供对照)
|
||
|
||
| 单元 | 状态 | 说明 |
|
||
|---|---|---|
|
||
| `obsidian-recording-watch.service` | active | 音频监听 + 转录 + 桥接 |
|
||
| `obsidian-asr-refine.path` | active | 监听 `.asr-inbox/` |
|
||
| `asr-review-web.service` | active | Web 审核/上传 UI,`.145:8765` |
|
||
| `obsidian-asr-refine.service` | inactive | 旧 `claude -p` 链路,**保留供 rollback** |
|
||
|
||
`127.0.0.1:8644` 已确认无监听(`ss -tlnp | grep 8644` 为空)。`.48` ICMP 可达,但 8644 等端口从 `.145` 探测为 closed/filtered,且 **SSH 到 `.48` 被拒(publickey)**——执行 agent 无法自行登录 `.48` 查看其配置。**这是本次重构的关键约束,见 §1.3。**
|
||
|
||
### 0.4 上游 review 的一处补充(执行时必须一并修掉)
|
||
|
||
在准备本实施文档时复核代码,发现 review 未记录的一个**真实 bug**,与本次解耦同属一处代码,应合并修复:
|
||
|
||
`asrpipe/vault.py:135` 把第一个位置参数写死成字符串 `"草稿已生成,待确认"`:
|
||
|
||
```python
|
||
r = subprocess.run(["python3", str(n), "草稿已生成,待确认", title, body], ...)
|
||
```
|
||
|
||
而 `meeting-status-notify.py` 的 argv 契约是 `(status, filename, detail)`。于是所有 5 处 `vault.notify()` 调用的语义都被错位丢弃:
|
||
|
||
| 调用点 | 调用方本意(第 1 参数) | 实际发出的 status |
|
||
|---|---|---|
|
||
| `revise.py:273` | "ASR 正式记录已发布" | 草稿已生成,待确认 ❌ |
|
||
| `cli.py:109` | "ASR Web 审核启动失败" | 草稿已生成,待确认 ❌ |
|
||
| `cli.py:160` | "ASR Web 审核启动失败" | 草稿已生成,待确认 ❌ |
|
||
| `review_web.py:49` | "ASR 快速确认" | 草稿已生成,待确认 ❌ |
|
||
| `review_web.py:271` | "ASR 快速确认" | 草稿已生成,待确认 ❌ |
|
||
|
||
即:**"发布成功"和"启动失败"在 Telegram 上都显示为"草稿已生成,待确认"**。调用方传的 `title` 被挤到 `filename` 位、`body` 被挤到 `detail` 位。这个 bug 目前被 8644 不通掩盖着(消息根本没发出去),一旦按本文档恢复通知,它会立刻变成可见的错误告警。**Stage 2 必须连带修掉。**
|
||
|
||
---
|
||
|
||
## 1. 重构目标与设计约束
|
||
|
||
### 1.1 目标
|
||
|
||
把"管线向人(Kai)发通知"这件事,从**"调用 Hermes 网关某个 IP:port 上的私有 webhook"**,改造成**"调用一个稳定的本地通知接口,Hermes 只是它的一种后端"**。
|
||
|
||
判据(重构完成后应当成立):
|
||
|
||
1. Hermes 再次换主机/换端口/换协议时,**只改一个配置文件,不改任何一行 Python、不改任何 systemd unit**。
|
||
2. 通知目标不可用时,管线行为与今天一致:**记日志、继续跑、不阻塞、不丢数据**。
|
||
3. 通知发送逻辑(HMAC 签名 + POST)在代码库里**只有一份实现**。
|
||
4. 管线**不再读取 `~/.hermes/` 下的任何文件**。
|
||
5. 上述 4 条对两个调用方(`meeting-watch.py` 和 `asrpipe`)同时成立。
|
||
|
||
### 1.2 设计风格必须与现有代码一致
|
||
|
||
这个项目有很清晰的既有风格,重构**必须沿用**而不是另立一套:
|
||
|
||
- 配置集中在 `asrpipe/config.py` 的 `Config` dataclass,每项都是 `_env("ASR_XXX", default)` 形式,env 可覆盖(见该文件已有的 `use_termbank` / `use_hindsight` / `gate_enabled` 等)。
|
||
- 失败降级优先于抛异常:通知失败只 `log(..., level="WARN")`,绝不中断管线(`vault.py:139` 已是此风格,`meeting-status-notify.py` 的 docstring 也明确写了 "Silent no-op (exit 0) ... never block the pipeline")。
|
||
- 注释说明"为什么"而非"是什么",且保留实测数据(参考 `config.py` 里 clean_model 的选型注释、`llm.py` 的 temperature 注释)。中文注释与英文注释在本项目中混用,**按所改文件的既有语言为准**。
|
||
- 凭证走 600 权限的独立 env 文件,不进代码、不进 git(`bin/.asr-refine.env` 是既有先例)。
|
||
|
||
### 1.3 关键约束:`.48` 的新地址当前未知
|
||
|
||
执行 agent **无法** SSH 到 `.48`,因此**无法自行确定 Hermes webhook 的新地址**。
|
||
|
||
这不阻塞重构——恰恰相反,这正是要做接口层的理由。处理方式:
|
||
|
||
- Stage 1–3 的全部工作**不需要知道 `.48` 的地址**,只需要把地址变成"一个配置文件里的一个字段"。
|
||
- 配置文件的初始值填 `.145` 的旧地址(即当前的 `http://127.0.0.1:8644/...`),并在文件里注释清楚"此地址在 2026-09-15 Hermes 迁移至 .48 后已失效,待 Kai 提供新地址后更新此处"。
|
||
- 重构完成后,**由 Kai 填入 `.48` 的真实地址**,或按 Stage 4 的 `direct` 后端绕过 Hermes。
|
||
- **禁止猜测端口后写进配置当作"已修复"**。宁可保留失效地址 + 明确注释,也不要写一个看似能跑实际错误的值。
|
||
|
||
---
|
||
|
||
## 2. 实施总览
|
||
|
||
| Stage | 内容 | 依赖 | 是否需要 Kai 输入 |
|
||
|---|---|---|---|
|
||
| **1** | 建立通知配置层 + 单一发送实现 | — | 否 |
|
||
| **2** | 两个调用方收敛到该实现 + 修 argv 错位 bug | 1 | 否 |
|
||
| **3** | 清理 systemd 硬编码 + 文档同步 | 2 | 否 |
|
||
| **4** | (可选)`direct` 后端,彻底不依赖 Hermes | 3 | 是(需 token 授权决定) |
|
||
|
||
Stage 1–3 应**一镜到底连续执行**,中间不要停下来问;每个 stage 结束跑一次测试即可。Stage 4 涉及凭证使用决策,**必须停下来问 Kai**。
|
||
|
||
---
|
||
|
||
## 3. Stage 1 — 建立通知配置层与单一发送实现
|
||
|
||
### 3.1 新增配置文件 `OBSIDIAN/bin/notify-target.json`
|
||
|
||
这是本次重构的**唯一真相源**。结构建议(执行时可按需微调字段名,但必须保持"后端可切换 + 目标可配置"两个语义):
|
||
|
||
```json
|
||
{
|
||
"backend": "webhook",
|
||
"_comment": "backend: webhook | none | direct(见 Stage 4)。切换后端不需要改代码。",
|
||
|
||
"webhook": {
|
||
"url": "http://127.0.0.1:8644/webhooks/meeting-recording-status",
|
||
"_url_comment": "⚠️ 此地址在 2026-09-15 Hermes 由 .145 迁移到 .48 后已失效。待 Kai 提供 .48 上的新地址后更新此字段。保留失效值而非猜测端口,是为了让故障显式可见。",
|
||
"secret": "",
|
||
"_secret_comment": "HMAC-SHA256 共享密钥。留空则不签名(对端若校验会拒绝)。历史上此值取自 ~/.hermes/webhook_subscriptions.json 的 meeting-recording-status.secret;现在改为本项目自持,不再读 Hermes 私有配置。",
|
||
"timeout": 10
|
||
}
|
||
}
|
||
```
|
||
|
||
权限必须是 600(含密钥):
|
||
```bash
|
||
chmod 600 /home/claw/claude-workspace/OBSIDIAN/bin/notify-target.json
|
||
```
|
||
|
||
**迁移密钥**:从 `~/.hermes/webhook_subscriptions.json` 的 `meeting-recording-status.secret` 复制一次到上面的 `secret` 字段。这是**一次性拷贝**,之后管线不再读 Hermes 的文件。(该文件当前结构已确认为 `{route: {secret, deliver, deliver_extra:{chat_id}, ...}}`。)
|
||
|
||
### 3.2 改造 `OBSIDIAN/bin/meeting-status-notify.py` 为唯一实现
|
||
|
||
保持它现有的**双重身份**——既是 CLI(供 shell 脚本调用),也要能被 Python import(供 `asrpipe` 直接调用,省掉一次 subprocess)。要求:
|
||
|
||
- 读 `notify-target.json` 决定后端与目标;**删除**对 `~/.hermes/webhook_subscriptions.json` 的读取(`SUBSCRIPTIONS` 常量与 `secret()` 函数)。
|
||
- 保留 env 覆盖能力,优先级 `env > notify-target.json > 内置兜底`。env 变量名沿用既有前缀风格(如 `MEETING_STATUS_WEBHOOK_URL` 已在 `meeting-watch.py` 中使用,继续复用同名,不要另起新名)。
|
||
- `backend: "none"` 时直接记日志返回,不发网络请求——这让"临时静音"不需要改代码,也让测试可以完全离线。
|
||
- 暴露一个可 import 的函数,签名保持与 CLI 契约一致:
|
||
```python
|
||
def send(status: str, filename: str = "", detail: str = "") -> bool
|
||
```
|
||
返回是否送达成功;**内部吞掉所有异常**,绝不向调用方抛(维持"never block the pipeline")。
|
||
- `__main__` 分支保持 `argv = (status, filename, detail)` 契约不变——`bin/obsidian-asr-refine.sh:45` 仍以此方式调用它(旧 rollback 链路),**不要破坏该契约**。
|
||
|
||
### 3.3 验收标准
|
||
|
||
```bash
|
||
cd /home/claw/claude-workspace/OBSIDIAN
|
||
|
||
# 1. backend=none 时静默成功、不发网络请求
|
||
python3 - <<'EOF'
|
||
import json, pathlib
|
||
p = pathlib.Path("bin/notify-target.json")
|
||
d = json.loads(p.read_text()); d["backend"] = "none"
|
||
p.write_text(json.dumps(d, ensure_ascii=False, indent=2))
|
||
EOF
|
||
python3 bin/meeting-status-notify.py "测试" "f.mp3" "detail" # 期望:退出码 0,日志说明 backend=none
|
||
|
||
# 2. backend=webhook 且目标不通时,仍然退出码 0(不阻塞)
|
||
# (把 backend 改回 webhook 后重跑,期望打印类似 "skipped: Connection refused",退出码仍为 0)
|
||
echo "exit=$?"
|
||
|
||
# 3. 确认不再引用 Hermes 私有配置
|
||
grep -rn "webhook_subscriptions\|\.hermes" bin/meeting-status-notify.py
|
||
# 期望:无输出(注释里提及历史来源可以,但不能有真实读取)
|
||
|
||
# 4. 旧链路 CLI 契约未破坏
|
||
bash -n bin/obsidian-asr-refine.sh && echo "refine.sh syntax ok"
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Stage 2 — 两个调用方收敛 + 修 argv 错位
|
||
|
||
### 4.1 `asrpipe` 侧
|
||
|
||
**`asrpipe/config.py`**:
|
||
|
||
- 新增 `notify_backend` 字段,风格与既有项一致:`notify_backend: str = _env("ASR_NOTIFY_BACKEND", "")`(空 = 交由 `notify-target.json` 决定;显式设置则覆盖)。
|
||
- 保留现有 `notify: bool`(`ASR_NOTIFY=0` 的总开关)——这是既有行为,`doctor` 和文档都依赖它,不要删。
|
||
- `notifier` property 保留(`cli.py:207` 的 doctor 检查在用),但其含义从"要 subprocess 调用的脚本"变为"通知实现模块的位置"。若改动了 doctor 的检查语义,需同步更新 `cli.py` 的 `chk("notifier", ...)`。
|
||
|
||
**`asrpipe/vault.py` 的 `notify()`**:
|
||
|
||
- **修掉 argv 错位 bug**:不再把 `"草稿已生成,待确认"` 写死为 status。调用方传入的第一个参数(语义上就是 status/事件名)必须原样传递。
|
||
- 建议直接 import `meeting-status-notify.py` 暴露的 `send()`,取代 `subprocess.run(["python3", ...])`——省一次进程启动,也让类型契约显式。若因文件名带连字符不便 import,可用 `importlib.util.spec_from_file_location`(路径来自 `CFG.notifier`),或把共享逻辑提取为 `OBSIDIAN/bin/notify_lib.py` 再由两边 import。**三种都可接受,选一种并在注释里说明理由。**
|
||
- 保留 `redact_log` 参数行为(`review_web.py:271` 传了 `redact_log=True`,因为 body 含一次性 capability URL,**绝不能进日志**)。这是安全相关行为,不要动。
|
||
- 保留"notifier 不存在时只记日志"的降级分支。
|
||
|
||
**5 处调用点**(`revise.py:273`、`cli.py:109`、`cli.py:160`、`review_web.py:49`、`review_web.py:271`):修好 `notify()` 后语义即自动正确,**调用点本身不需要改**。改完后请逐一确认它们发出的 status 与调用方本意一致(对照 §0.4 的表格)。
|
||
|
||
### 4.2 `meeting-watch.py` 侧
|
||
|
||
- 删除自建的 HMAC 签名与 POST 逻辑:`webhook_secret()`(L177)、`post_webhook()`(L187)、`post_status()`(L205)。
|
||
- `post_status` 改为薄封装,转调 Stage 1 的单一实现;3 个调用点(L272 "开始转录"、L286 "转录完成,正在生成粗整理"、L304 "处理失败")的调用形式**保持不变**。
|
||
- 删除 `WEBHOOK_URL` / `WEBHOOK_ROUTE` / `WEBHOOK_SECRET` / `STATUS_WEBHOOK_URL` / `STATUS_WEBHOOK_ROUTE` / `SUBSCRIPTIONS` 这些常量(L32–L37)。
|
||
|
||
**关于 `meeting-recording` 路由(携带正文那条)**:`post_webhook()` 在当前代码里**只被 `post_status()` 调用过**,即 `meeting-recording` 路由从未真正被 POST(已 grep 确认)。它是旧 Hermes agent 精修架构的残留,`CLAUDE.md` 明确记载该架构"已**取代**"。**删除这条死路径**,不要为它保留接口。(`~/.hermes/webhook_subscriptions.json` 里那条带 `prompt`/`skills: [memo-inbox]` 的 `meeting-recording` 订阅同属旧架构遗留,但那是 Hermes 侧的配置,**不在本次授权改动范围**,只在 §7 报告里提一句即可。)
|
||
|
||
另外注意 `meeting-recording-status` 订阅带 `deliver_only: True`——它只做消息投递、不唤起 agent。这印证了"状态通知本质上只是一条消息,不需要 agent 参与",也是 Stage 4 `direct` 后端在语义上完全等价的依据。
|
||
|
||
### 4.3 补充测试(必须新增)
|
||
|
||
在 `asr-pipeline/tests/test_core.py` 中新增(沿用该文件既有的 `unittest` + `mock` 风格,注意它已在 import 前把 state/vault 重定向到 tempdir):
|
||
|
||
1. **status 透传测试**(覆盖 §0.4 的 bug):mock 掉实际发送,断言 `vault.notify("ASR 正式记录已发布", "body")` 传出的 status 是 `"ASR 正式记录已发布"`,**不是** `"草稿已生成,待确认"`。这是防止该 bug 回归的关键测试。
|
||
2. **降级测试**:目标不可达(mock 抛 `URLError`)时 `vault.notify()` 不抛异常、正常返回。
|
||
3. **backend=none 测试**:不产生任何网络调用。
|
||
4. **`redact_log` 测试**:`redact_log=True` 时日志中不含 body 原文(防止 capability URL 泄漏进日志)。
|
||
|
||
### 4.4 验收标准
|
||
|
||
```bash
|
||
cd /home/claw/claude-workspace/OBSIDIAN/asr-pipeline
|
||
PYTHONPATH=. .venv/bin/python -m unittest discover -s tests -v
|
||
# 期望:28 + 新增用例,全绿
|
||
|
||
# 单一实现确认:HMAC 签名逻辑只应出现在一个文件里
|
||
cd /home/claw/claude-workspace/OBSIDIAN
|
||
grep -rn "hmac.new" --include="*.py" . | grep -v -E "__pycache__|\.venv|\.bak\."
|
||
# 期望:只有 1 处(notify 实现所在文件)
|
||
|
||
# 管线不再读 Hermes 私有配置
|
||
grep -rn "webhook_subscriptions" --include="*.py" --include="*.sh" . | grep -v -E "__pycache__|\.venv|\.bak\."
|
||
# 期望:无真实读取(注释提及历史来源可接受)
|
||
|
||
# doctor 仍通过(会联网测两个模型,属正常)
|
||
asr-pipeline/bin/asr-refine doctor
|
||
```
|
||
|
||
---
|
||
|
||
## 5. Stage 3 — systemd 与文档同步
|
||
|
||
### 5.1 systemd
|
||
|
||
`~/.config/systemd/user/obsidian-recording-watch.service` 删除这一行(第三处硬编码):
|
||
|
||
```ini
|
||
Environment=MEETING_WEBHOOK_URL=http://127.0.0.1:8644/webhooks/meeting-recording
|
||
```
|
||
|
||
理由:该 URL 对应的 `meeting-recording` 路由已在 Stage 2 删除;状态通知目标现由 `notify-target.json` 统一提供,不应再由 unit 注入。
|
||
|
||
**保留** `MEETING_INBOX_BASE` 与 `MEETING_ASR_BACKEND=qwen3cam`——这两个是真实生效的部署配置,与本次解耦无关(后者还带着"ASR backend trial"的回滚注释,不要动)。
|
||
|
||
生效并验证:
|
||
```bash
|
||
systemctl --user daemon-reload
|
||
systemctl --user restart obsidian-recording-watch.service
|
||
systemctl --user is-active obsidian-recording-watch.service # 期望 active
|
||
journalctl --user -u obsidian-recording-watch.service -n 30 --no-pager
|
||
```
|
||
|
||
`asr-review-web.service` 与 `asr-review-revise@.service` **不需要改**(它们通过 `asrpipe` 间接受益;后者的 `EnvironmentFile=.asr-refine.env` 只管模型凭证)。
|
||
|
||
### 5.2 文档
|
||
|
||
按"一份权威来源 + 其余引用"的结构收敛(与 vault 侧 `AGENTS.md` 作索引的模式一致):
|
||
|
||
| 文件 | 动作 |
|
||
|---|---|
|
||
| `asr-pipeline/docs/agent-protocol.md` | **新建**。本次解耦的权威文档:通知接口契约(status/filename/detail 三段语义)、`notify-target.json` 字段含义、后端切换方式、"Hermes 只是一种后端"的定位、任何 agent 想接入时该读什么。Kai 早在 2026-07-17 就规划过这份共用协议文档但从未落地——这次补上。 |
|
||
| `asr-pipeline/HANDOFF.md` | 第 71 行 "Telegram 通知(hermes-gateway :8644 签名 webhook)" 改为指向通知接口层,不再把 Hermes 端口写进设计文档;链接到 `agent-protocol.md`。 |
|
||
| `OBSIDIAN/CLAUDE.md` | 管线段落中的 `meeting-status-notify.py` 描述更新为"可切换后端的通知接口";补一行指向 `agent-protocol.md`。保持该文件"只留 Claude + 本服务器专属基础设施"的既有定位,不要把协议细节搬进来。 |
|
||
| `~/.claude/skills/memo-inbox/references/webhook-toolset-diagnostics.md` | **标注废弃**。它描述的"webhook agent 直接处理 `meeting-recording` 路由 + 需要 `hermes-webhook`/`file`/`skills` toolset"架构已于 2026-07-25 被取代,该路由在 Stage 2 已删除。建议在文件顶部加醒目废弃声明 + 指向 `agent-protocol.md`,而不是直接删除(保留历史排障脉络)。**注意:这是 skill 目录,改动会影响 Claude 的行为,改完在 §7 里明确告知 Kai。** |
|
||
| `~/.claude/skills/memo-inbox/references/asr-refine-pipeline.md` | 五阶段表格等与 `HANDOFF.md` 逐字重复的内容改为链接引用;**保留** skill 独有的纪律性内容("第一轮只出粗整理,不直接写正式纪要"、S3 fidelity gate 存在的理由与实测数据、模型选型不得随意改动的警告)——这些是 Kai 的工作纪律,不是可去重的冗余。 |
|
||
|
||
### 5.3 验收标准
|
||
|
||
```bash
|
||
# 确认 8644 不再出现在任何活跃代码/配置里(文档中作为历史说明可保留)
|
||
cd /home/claw/claude-workspace/OBSIDIAN
|
||
grep -rn "8644" --include="*.py" --include="*.sh" . ~/.config/systemd/user/*.service 2>/dev/null \
|
||
| grep -v -E "__pycache__|\.venv|\.bak\."
|
||
# 期望:仅 notify-target.json 的待更新地址 + 注释
|
||
|
||
# 端到端(不触碰 vault、不 commit)
|
||
asr-pipeline/bin/asr-refine dry-run <任一 .asr-inbox/*.txt>
|
||
# 期望:五阶段跑通,产物落 asr-pipeline/dry-run/<slug>/,通知步骤降级但不报错
|
||
```
|
||
|
||
---
|
||
|
||
## 6. Stage 4 —(可选,需 Kai 决策)`direct` 后端
|
||
|
||
Stage 1–3 完成后,通知链路仍然经过 Hermes:管线 → Hermes 网关 webhook → Telegram。只要 Hermes 不在线,Kai 就收不到通知。
|
||
|
||
**可选的彻底解耦**:增加 `backend: "direct"`,直接调 Telegram Bot API(`https://api.telegram.org/bot<token>/sendMessage`),不经过 Hermes。
|
||
|
||
技术上已确认可行:
|
||
|
||
- `~/.hermes/.env` 中存在 `TELEGRAM_BOT_TOKEN`;`chat_id`(`1093241065`)在 `webhook_subscriptions.json` 的 `deliver_extra` 里已确认。
|
||
- `meeting-recording-status` 订阅标记为 `deliver_only: True`,即 Hermes 对这条消息**只做投递、不唤起 agent**。因此 `direct` 后端在语义上与现状**完全等价**,不损失任何 agent 能力。
|
||
|
||
**但这需要 Kai 决策,不要擅自实施**,因为:
|
||
|
||
1. 要把 Telegram bot token 复制进本项目自持的凭证文件(或新增一份 600 env 文件)。这扩大了 token 的存放面,是安全决策,须 Kai 同意。
|
||
2. 绕过 Hermes 后,Telegram 侧的消息格式/限流/重试不再由 Hermes 统一处理,需自行承担。
|
||
3. Kai 可能有意保留"所有对外消息都经 Hermes"的架构约定。
|
||
|
||
**执行方式**:Stage 3 完成后,在 §7 报告中列出此选项与上述权衡,**等 Kai 明确答复再动手**。届时若 Kai 同意,`direct` 只是 `notify-target.json` 里加一个后端分支 + 一个 sender 函数,改动很小——这正是 Stage 1 接口层带来的收益。
|
||
|
||
---
|
||
|
||
## 7. 完成后的回报要求
|
||
|
||
Stage 1–3 结束后,向 Kai 报告,包含:
|
||
|
||
1. **改了哪些文件**,每个文件一句话说明,附备份文件名。
|
||
2. **测试结果原文**(`Ran N tests ... OK`)。若有任何失败,**如实说明失败项与原因,不要含糊**。
|
||
3. **§0.4 的 argv 错位 bug 已修复的证明**:新增测试的名称与断言内容。
|
||
4. **仍然待办的事**,明确区分:
|
||
- 需要 Kai 提供 `.48` 上 Hermes webhook 新地址,填入 `notify-target.json` 的 `webhook.url`(在此之前通知仍不通,但已降级为"改一个配置字段")。
|
||
- Stage 4 `direct` 后端的决策请求(附 §6 的三点权衡)。
|
||
- Hermes 侧 `~/.hermes/webhook_subscriptions.json` 中 `meeting-recording` 那条旧订阅(带 `prompt`/`skills: [memo-inbox]`)属旧架构遗留、已无调用方,建议 Kai 自行清理——**本次未改动,因为不在授权范围**。
|
||
5. **动过 `~/.claude/skills/` 的明确告知**(若执行了 §5.2 的 skill 文档改动)——这会影响 Claude 后续行为,Kai 需要知道。
|
||
|
||
---
|
||
|
||
## 8. 明确禁止(越界前必须先问)
|
||
|
||
- ❌ **不要猜 `.48` 的端口后填进配置当作"已修复"**。宁可留失效地址 + 注释。SSH 到 `.48` 已确认被拒,无法自行核实。
|
||
- ❌ **不要往 `asrpipe/` 核心五阶段(`segment`/`flagger`/`refine`/`fidelity`/`revise`)里加任何 agent 相关代码**。`cli.py` 与 `config.py` 头部的 "no Claude Code, no opencode, no agent runtime" 声明是这套系统最值得保留的架构资产。
|
||
- ❌ **不要改模型选型**(`clean_model` / `draft_model`)。`config.py` 与 `HANDOFF.md` 都记录了实测依据(flash-lite 闸门失败 19/87 vs haiku 78/87,且 haiku 有 69 次过度压缩)。与本次解耦无关。
|
||
- ❌ **不要关闭或削弱 S3 fidelity gate**。它防的是静默信息丢失(无闸门时英文术语丢失达 22%、数字 11%)。
|
||
- ❌ **不要删除 rollback 链路**(`bin/obsidian-asr-refine.sh` + `obsidian-asr-refine.service`)。它们当前 inactive 但被 `obsidian-asr-refine.path` 的注释和 `HANDOFF.md` 记录为回滚路径。只需保证 `refine.sh:45` 调用 `meeting-status-notify.py` 的 CLI 契约不被破坏。
|
||
- ❌ **不要动 `~/obsidian-vault/` 的内容**,也不要在本次重构中产生 vault commit。本次只改 `OBSIDIAN/` 基础设施与 systemd unit。
|
||
- ❌ **不要清理那 7 个 `*.bak.*` 文件**。它们是 2026-07-26 重构的历史留存,清理属独立决策,需 Kai 单独批准。
|
||
- ❌ **不要引入新的第三方依赖**。`asr-pipeline/.venv` 目前只有 pypinyin / rapidfuzz / numpy,通知功能用标准库 `urllib.request` + `hmac` + `json` 足够(现有实现已证明)。
|
||
- ❌ **不要为解耦搭建通用消息总线/插件框架**。当前只有一个通知需求,`notify-target.json` 里一个 `backend` 字段 + 几个分支就够。过度工程化会让下一个维护者更难读。
|
||
|
||
---
|
||
|
||
## 9. 执行顺序速查
|
||
|
||
```
|
||
0. 读 §0 全部 → 跑 baseline 测试(28 OK)→ 备份将改动的文件
|
||
1. Stage 1:notify-target.json + meeting-status-notify.py 单一实现 → 验收 §3.3
|
||
2. Stage 2:config.py / vault.py(修 argv bug)/ meeting-watch.py + 4 个新测试 → 验收 §4.4
|
||
3. Stage 3:systemd 去硬编码 + agent-protocol.md + 4 份文档同步 → 验收 §5.3
|
||
4. 回报 Kai(§7),等 .48 地址与 Stage 4 决策
|
||
```
|
||
|
||
Stage 1–3 连续执行,不要中途停下来确认;Stage 4 必须等 Kai 答复。
|