docs: Part 6.6 文档收尾 —— 迁移计划/接口矩阵/PLUGIN_DEV 全量更新

## plugin-migration-plan.md

Part 6 标记完成,并**记录实际执行与计划的偏离**而非假装一致:

原计划「逐插件迁移,随时回退」。用户决策改为彻底舍弃 .so、无回退通道,
本轮直接删 internal/plugin/cabi/。因此【V】的「.so ↔ .bin 混跑集群冒烟」
不再适用——新内核根本不认 .so。改为验证「新内核面对旧 .so 给可操作错误
且不崩溃」,已在真实二进制上确认。

最终验收清单加「结果」列,13 项逐项对账。**两项未完全达标,如实标注**:

- #9 SetToolBlocks:method 已定义并划入 CapCore,但内核侧仍返回未实现。
  C ABI 时代它也是空实现(§1.4),故不是回归,但也没兑现 §3.8 的承诺。
- #13 内存:15 进程 RSS=88.0MB,远超「基线 +29MB」。根因是每插件静态链接
  整个 Go runtime,15 个不同二进制无共同物理页(PSS/RSS 99.9% vs 基线 44%)。
  实验 5 基线用的是 2.68MB 最小插件,绝对数字不可比;结构性指标
  (均摊线程 5.5 vs 4.9)同量级。

新增两节实录:Part 6.5 生产切换(执行顺序为何不能反过来、hmap 正规通道
vs 手工拷贝的对照表、真实 QQ 消息的端到端证据链)与 Part 6.6 压测数据。

## plugin-interface-matrix.md

状态从「基线 v1」升为「完成 v2」。三个合同面逐一标注达成情况:

- 合同面 B:51 个整数 method id 已平移为 Method* 字符串常量。保留原表作
  历史对照,但注明 case 25(CoreFreeString)无对应 method(内存管理是 C 层
  特有问题),以及 io.setToolBlocks 已定义但内核侧未实现。
- 合同面 C:C1 标题从「今天」改为「迁移前」(迁移已完成,「今天」会误导);
  C2 补上「全部插件共享同一块 memfd」这个关键决定及其理由——第一版设计
  是每插件一段,那会退化成副本模型复现 lost update。
- 第六节「新获得的能力」加「实际结果」列。事件订阅标注机制已完成但
  零用户使用,故未经真实负载检验——这比只写  诚实。

「刻意不给」清单同步为带 API 后缀的新命名(SelftestAPI 等),与
capability.go 的 withheldCapabilities 对齐,并说明为何加后缀:
不加时子串匹配会把 tool.register / io.setToolBlocks 误判为泄漏 ToolAPI。

## PLUGIN_DEV.md(中英双份)

C ABI 时代的描述全部改掉:
- 「动态 .so/.dll 插件」→「子进程插件」
- 「生成 C ABI bridge(z_bridge_gen.go + z_entry.c)」→ 子进程运行时三文件
- 「go build -buildmode=c-shared」→「go build(CGO_ENABLED=0)」
- 平台二进制表:三平台统一 plugin.bin(bundle 包内按 goos.goarch 区分)
- 「不能跨 C ABI 边界序列化」→「不能跨进程序列化」
- 「ABI v2 写回」→「Stage 写回」

新增 v1.0.0 破坏性变更提示框,五条要点:.so 不再加载、业务代码不需改、
entry 字段对 Go 插件已无意义、不再需要 cgo、Windows 从 3 字段升到全字段。

保留 .so 字样的只有变更说明本身(3 处),其余全部清理。
This commit is contained in:
JianFeeeee
2026-09-03 08:12:30 +08:00
parent 670efcd426
commit 2a03a83ce0
4 changed files with 348 additions and 98 deletions

View File

@ -1,11 +1,14 @@
# 外部插件接口不变矩阵(多进程化整改基线)
> 状态:**基线 v1**2026-08-31update 分支)
> 状态:**完成 v2**2026-09-03——迁移已落地并上生产内核 v1.0.0。
> 目的:钉死「暴露给外部插件的接口不变」这一约束的**合同面**——迁移前、迁移后外部插件看到/调用的 SDK 接口完全一致;
> 所有改造落在**核心homed 侧)+ 工具链plugindev**,外部插件业务代码零改动,只需用新 plugindev 重编。
>
> 维护规则:每次改动公开 SDK 接口面 `third_party/homeagent-sdk/sdk/` 或 bridge 模板 `tools/plugindev/templates.go` 后,
> 必须同步更新本矩阵;`11.1~11.6` 任一落地后,在对应行标注「已修复」
> **结果(已验证)**`git diff third_party/homeagent-sdk/sdk/` 全程为空17 个 `example/*/plugin.go` 逐字节未改
> `git status example/` 无输出);生产 17 插件全部经子进程通道运行
>
> 维护规则:每次改动公开 SDK 接口面 `third_party/homeagent-sdk/sdk/` 或模板 `tools/plugindev/templates/` 后,
> 必须同步更新本矩阵。
>
> 权威编号plan.md 第 11 节11.1~11.9)。本文档只做接口面盘点,不做实现。
@ -111,9 +114,14 @@ type Plugin interface {
## 三、合同面 Bbridge 51 个 method id ↔ SDK 方法映射(改造基线)
> 文件:`third_party/homeagent-sdk/tools/plugindev/templates.go` 的 `tmplLinuxBridge`。
> 迁移后这些整数 method id **改为 RPC method 名**(迁移评估 3.2),语义不变、编号扔掉
> 下表是「51 个 case 平移为 method 名」的完整清单,也是新 RPC 协议的一等公民
> ⏹️ **已完成2026-09-03**:整数 method id 已全部平移为 RPC method 名字符串,
> 定义在 `internal/plugin/proc/protocol.go` 的 `Method*` 常量(共 60 个,含内核→插件方向)
> 原 `tmplLinuxBridge` 与 `meta.Core<Method>` 整数表**均已删除**
>
> 两个遗留点:`case 25``CoreFreeString`)无对应 method内存管理是 C 层特有问题);
> `io.setToolBlocks` 已定义但内核侧仍返回未实现C ABI 时代也是空实现,非回归)。
>
> 下表保留作为历史对照。
| # | method id今天 C ABI | SDK 背的方法 | 迁移后 RPC method 名(建议) |
|---|---|---|---|
@ -182,7 +190,11 @@ type Plugin interface {
## 四、合同面 CStageContext 跨 ABI 现状 → 共享内存目标
### C1. 今天C ABI 副本模型):插件只看到 10 个字段
> ✅ **已达成2026-09-03**:子进程插件现在看到全部 18 个字段(枚举见
> `internal/plugin/proc/shm.go`且可写回。生产实测sanitizer 在另一个进程里
> 改写 13590 字节文本,内核读到改写结果(`stage post_action 改写了 1 个字段`)。
### C1. 迁移前C ABI 副本模型):插件只看到 10 个字段
`stageContextWritable`templates.go:762下发/回传的字段:
@ -193,18 +205,31 @@ raw_message user_id group_id phase llm_text final_text no_memory
**看不到的 6 个字段**`ContextMsgs` / `ReasoningContent` / `TokenUsage` / `Memory` / `Extra` / `Errors`
### C2. 迁移后(共享内存 + 锁仲裁):插件可看到/改写全部 16 个字段
### C2. 迁移后(共享内存 + 锁仲裁):插件可看到/改写全部字段 — ✅ 已实现
`ShmStageCtx`(迁移评估 3.3)· 插件进程内保留原生 `StageContext`handler 照常读写,
`Lock/RLock` 映射到跨进程锁仲裁 RPC`stage.lock`/`stage.unlock`handler 返回时脏字段写回共享段。
字段级 `Slice{Off,Len}` 描述符 + 内核仲裁锁。插件进程内保留原生 `StageContext`
handler 照常读写,`Lock/RLock` 映射到跨进程锁仲裁 RPC`stage.lock`/`stage.unlock`
handler 返回时脏字段写回共享段。
**接口形式不变,能力变强**(这是「能力断层消除」合同面的一部分:外部插件拿回 ContextMsgs 等)。
**关键设计决定**:全部子进程插件共享**同一块 memfd**。第一版设计是每插件一段,
那会退化成副本模型,复现 §8.4 的 35.8~36.8% lost update。
### C3. 11.3 修复的合同面定义lost update
**接口形式不变,能力变强**(能力断层消除:外部插件拿回 ContextMsgs 等)。
今天 `stageContextWritable` **无条件回传 10 个字段的当前快照**——两个插件sanitizer 改 ToolResults +
weather 只读并行时weather 的回传会覆盖 sanitizer 的清洗结果(实测 1.6~4.3%
迁移后共享内存模型天然解决(并发改写同一对象);迁移前需 `stageContextWritable` 只回传**真正变更**的字段。
Windows 同步受益:从「只下发 3 字段、无写回」升到全字段可见 + 写回,
与 Unix 共用同一套 RPC 实现与共享段布局
### C3. lost update 的合同面定义 — ✅ 已消除
C ABI 时代 `stageContextWritable` **无条件回传 10 个字段的当前快照**——两个插件
sanitizer 改 ToolResults + weather 只读并行时weather 的回传会覆盖 sanitizer
的清洗结果(实测 1.6~4.3%,高并发下 35.8~36.8%)。
Part 0.2 先做了过渡补丁只回传真正变更的字段Part 4 的共享内存模型从根上解决
(字段级描述符 + 锁仲裁,并发改写同一对象)。
回归基线:`TestPlugin_FiveProcessesConcurrentAppendNoLostUpdate`
`TestSegment_ProductionScenario_SanitizerNotOverwrittenByWeather`
---
@ -224,36 +249,69 @@ weather 只读并行时weather 的回传会覆盖 sanitizer 的清洗结
## 六、迁移后外部插件「新获得」的能力(合同面扩展——只增不减)
| 能力 | 今天 | 迁移后 |
|---|---|---|
| 事件订阅 `Events().Subscribe`case 23/24 | ❌ 空实现 | ✅ 事件环EvtRing + eventfd + 独立游标) |
| `SetToolBlocks` 多模态注入 | ❌ 空实现 | ✅ 二进制落 arenaSlice 描述符回传 |
| `ContextMsgs`/`ReasoningContent`/`TokenUsage`/`Memory`/`Extra`/`Errors` | ❌ 看不到 | ✅ 共享内存全字段 |
| 插件崩溃隔离 | ❌ panic 带崩 homed | ✅ 子进程独立崩溃 |
| 热重载 `.so` | ❌ `DF_1_NODELETE` no-op | ✅ 同路径替换 `.bin` 即生效 |
| 工具超时取消 | ❌ cgo 不可中断(泄漏线程) | ✅ `Process.Kill()` 真取消 |
| `output_send` 结果 | ❌ 永远假成功 | ✅ 可同步等真实结果 |
| Lua/Windows DLL 路径 | ❌ 三套 ABI 分裂 | ✅ 收敛为单一 RPC 实现 |
| 能力 | 迁移前 | 迁移后 | 实际结果 |
|---|---|---|---|
| 事件订阅 `Events().Subscribe`case 23/24 | ❌ 空实现 | ✅ 事件环EvtRing + eventfd + 独立游标) | ✅ 已接线(当前零用户) |
| `SetToolBlocks` 多模态注入 | ❌ 空实现 | ✅ 二进制落 arenaSlice 描述符回传 | ⚠️ method 已定义,内核侧仍未实现 |
| `ContextMsgs`/`ReasoningContent`/`TokenUsage`/`Memory`/`Extra`/`Errors` | ❌ 看不到 | ✅ 共享内存全字段 | ✅ 18 字段全可见可写 |
| 插件崩溃隔离 | ❌ panic 带崩 homed | ✅ 子进程独立崩溃 | ✅ 测试 + 生产验证 |
| 热重载 `.so` | ❌ `DF_1_NODELETE` no-op | ✅ 同路径替换 `.bin` 即生效 | ✅ 生产实测 |
| 工具超时取消 | ❌ cgo 不可中断(泄漏线程) | ✅ `Process.Kill()` 真取消 | ✅ 整套新架构零 cgo |
| `output_send` 结果 | ❌ 永远假成功 | ✅ 可同步等真实结果 | ✅ 生产实测 `map[status:sent]` |
| Lua/Windows DLL 路径 | ❌ 三套 ABI 分裂 | ✅ 收敛为单一 RPC 实现 | ⚠️ Windows 已收敛Lua 仍独立(留待后续) |
**刻意不给**(权限梯度显式化,非技术限制):`Selftest`/`Supervisor`/`Tracker`/`Status`/`Adapter`/`Config`/`Tool`/`Indexer`/`OutputChan`/`Publish`(内核内部机制)。
**三项未完全兼得的说明**
- `SetToolBlocks``io.setToolBlocks` 已在 protocol 定义并划入 `CapCore`,但内核侧 handler
仍返回未实现。C ABI 时代它也是空实现§1.4),故**不是回归**,但也没兑现承诺。
- Lua`lua_plugin.go`/`dynamic_lua.go` 仍走自己的路径。Lua 经解释器不经 C ABI
不属于本轮要消除的 6 类缺陷,因此不阻塞。收敛第三套 ABI 是独立优化。
- 事件订阅:机制已完成(内核侧 `EvtRing` + 模板侧 `evtConsumerLoop`
但**无任何现有插件使用 `Events().Subscribe`**,所以生产上未经真实负载检验。
**刻意不给**(权限梯度显式化,非技术限制):`SelftestAPI`/`SupervisorAPI`/`TrackerAPI`/
`StatusAPI`/`AdapterAPI`/`ConfigAPI`/`ToolAPI`/`IndexerAPI`/`OutputChanRaw`/`EventPublish`
(内核内部机制)。清单与理由记在 `internal/plugin/proc/capability.go`
`withheldCapabilities``TestCapability_WithheldListIsDocumented` 守护。
这一项从「C ABI 表达能力的意外产物」变成**显式策略**:以前拿不到是因为
C 结构体不好传函数指针(那是运气,任何人给 dispatch 加个 case 就能捅穿);
现在是三道闸:类型层(`procCore` 命名字段不嵌入)+ 能力集manifest 声明)
+ RPC 边界(返回明确错误而非静默忽略)。
---
## 七、整改推进时的接口冻结检查点
## 七、接口冻结检查点(全部已通过)
1. **阶段 2子进程通道原型完成时**`plugindev``tmplProcMain` 重编 weather → `weather.bin` → 端到端跑通。
验收weather 业务代码`build/` 目录下旧 `.so` 时代的 `plugin.go` **逐字节可对比**(唯一改动是被工具链改写,非手工)。
2. **阶段 3共享内存完成时**任意改写型插件sanitizer/weather 并发)在子进程并发改写 StageContext
丢失率 = 0%(对比今天 35.8~36.8%)。
3. **阶段 5 完成时**17 个外部插件全部 `.bin` 化、cabi 删除;执行一遍全量 `go build ./...` + example 编译
4. **任何时候**`git diff` 公开 SDK `sdk/` 目录为零(接口冻结的硬证据)。
1. **阶段 2子进程通道原型**`plugindev` 重编 weather → `plugin.bin` → 端到端跑通。
验收weather 业务代码逐字节未改(`git status example/` 无输出)。
2. **阶段 3共享内存**:子进程并发改写 StageContext 丢失率 = 0%
`TestPlugin_FiveProcessesConcurrentAppendNoLostUpdate`
`TestSegment_ProductionScenario_SanitizerNotOverwrittenByWeather`
3. **阶段 5**17 个外部插件全部 `.bin` 化、cabi 删除(-3198 行);
`go build ./...` 与全仓 `go test ./...` 均通过。
4.**全程**`git diff third_party/homeagent-sdk/sdk/` 为零——接口冻结的硬证据。
生产端到端2026-09-03真实 QQ 消息):
```
input from qq → response (83293ms, tools=[qq_get_message qq_get_history
output_send__qq output_send__qq qq_mark_read])
[sanitizer] cleaned 2 bytes (before=13590 after=13588)
[proc] sanitizer stage post_action 改写了 1 个字段
tool output_send__qq result: 已通过 [qq] 通道发送: map[status:sent]
```
---
## 八、关联文档
- `docs/zh/架构迁移评估.md` — 完整论证§3.2 method id 平移、§3.3 数据面、§3.4 SDK 封装、§3.5 回调型资源)
- `docs/zh/架构迁移评估.md` — 完整论证§3.2 method id 平移、§3.3 数据面、§3.4 SDK 封装、§3.5 回调型资源、§3.8 能力对齐
- `docs/zh/plugin-migration-plan.md` — Part 0~6 执行计划与完成实录(含 Part 6.5 生产切换、Part 6.6 压测)
- `plan.md` §11 — 11.1~11.9 修复清单(唯一权威编号)
- `third_party/homeagent-sdk/sdk/` — 合同面 A 的代码实现
- `third_party/homeagent-sdk/tools/plugindev/templates.go` — bridge 模板(合同面 B 的代码实现
- `docs/zh/experiments/plugin-arch/` — 18 项可行性实验(跨进程并发改写零丢失等数字来源
- `third_party/homeagent-sdk/sdk/` — 合同面 A 的代码实现(全程零 diff
- `internal/plugin/proc/protocol.go` — 合同面 B 的代码实现(`Method*` 常量,取代已删的 bridge 模板
- `internal/plugin/proc/shm.go` — 合同面 C 的代码实现(共享段布局与 18 字段枚举
- `internal/plugin/proc/capability.go` — 权限梯度capability 组 + `withheldCapabilities`
- `third_party/homeagent-sdk/tools/plugindev/templates/` — 子进程运行时模板(三文件)
- `docs/zh/experiments/plugin-arch/` — 18 项可行性实验 + `19-migration-verify/` 迁移执行期工具