Files
HomeAgent/plan.md
JianFeeeee b6027f3ff8 docs(plan): 按源码核实重写——剔除过时/跨仓项,只留 10 项真待办
旧 plan.md(1474 行)是「历史工单 + 路线图」混合档,混入两类错误:
1. 已实现却仍标 TODO(§13.12 L3 多模态、§11.4 Lua 快照锁、§12.2 setToolBlocks、
   §13.11 主体 11 项)
2. 根本不属于本仓的跨项目工单(§13.9 在 llmsproxy 仓、§13.10 在 agentmail 仓)

本次回到源码逐项核实(grep 符号存在性 / go build / go test / go test -race),
新档只保留经证据确认的本仓待办,每条附 file:line 与可验证的验收条件。

- §一 结论速览(10 真待办 / 4 生产验证 / 8 已关闭 / 2 跨仓)
- §二 真待办 P0-P2:RuntimeManager 分组 worker、reload 语义说谎、arena 无
  Grow/Shrink、事件环零真实负载、工具超时措辞、handleAgentAction 501、
  Lua 独立 ABI、Windows 无真机验证、homed heap 常驻、3 个测试缺陷
- §三 已关闭 8 项(附源码证据,防复活)
- §四 生产部署后验证 4 项(代码已就绪)
- §五 跨仓工单归属说明
- §六 明确「不做」的决定
2026-09-25 09:17:11 +08:00

18 KiB
Raw Blame History

HomeAgent 遗留问题清单

本文定位(2026-09-24 重写):本文只写仍未完成、且经源码核实确属本仓库的问题。 上一版 plan.md 是 1474 行的「历史工单 + 路线图」混合档,混入了大量已解决记录、 以及根本不属于本仓库的跨项目工单(见 §五),本次全部剔除。

每一条的判定方法:不是继承旧档的复选框,而是回到源码逐项核实 (grep 定符号存在性、go build ./...、go test ./...、go test -race)。 旧档里标记「未做」但代码已落地的项、以及引用别仓代码的项,统一进 §三/§五 并附证据。

本文不写:已完成项的定位过程(那是 git log 和 commit message 的职责)、 无实测支撑的性能断言、任何凭据/生产路径。


一、结论速览

类别 数量 去向
真正待办(本仓库、代码层可动) 10 项 §二
生产部署后验证(代码已就绪,需现场跑) 4 项 §四
已关闭 / 已实现(旧档误标为 TODO) 8 项 §三
跨项目工单(不属本仓,已在别处解决或归档) 2 项 §五

当前健康度:go build ./... 通过;go test ./... 有 3 个 FAIL,全部是 测试自身缺陷(见 P2-10),非产品代码问题;go test -race 于 internal/lua、 internal/plugin/** 全绿。


二、真正待办

按「影响面 × 可验证性」排序。每条给出证据(file:line)与验收。

P0-1 §13.7 RuntimeManager + 分组 worker(架构演进,唯一大件)

现状:RuntimeManager / worker_group / WorkerGroup 在全仓库 (含 .go / .json / .md,排除 plan.md 自身)零引用 —— 该能力从未实现。 当前是一插件一子进程:internal/plugin/proc/plugin.go:134 的 Spawn 按插件各起一个进程。

为什么要做:每个外部插件一个进程 → N 个插件 = N 个常驻进程 + N 份 transport,进程数随插件线性涨。目标是一个 RuntimeManager + 少量 worker + 多插件共享 transport + 每插件独立 PluginContext(独立身份,共享管道)。

证据:

  • internal/plugin/proc/plugin.go:134(Spawn,逐插件起进程)
  • manifest 无 worker_group 字段:third_party/homeagent-sdk/example/a2a/plugin.json 的键集合为 author/description/entry/name/name_en/name_zh/platforms/tags/version

子任务:

  1. RuntimeManager 类型:worker 池 + 调度
  2. Worker 类型:一个进程,承载多个插件,共享 RuntimeClient
  3. PluginContext 类型:每插件独立身份(能力门、ownerID、工具命名空间) 挂在同一 transport 上
  4. manifest 增 worker_group 字段(缺省 = 全部归同一 worker,兼容迁移)
  5. 高风险插件可声明独立 worker_group

验收:

  • 缺省分组行为与现状逐字节等价(现有 proc 测试全绿,不改协议)
  • 两个插件同 worker 时,各自 ReclaimOwner 只回收自己的 arena 块
  • 一插件崩溃不带走同 worker 的另一插件(或明确:带走,并写进文档)
  • manifest 无 worker_group 的旧插件可原样加载(向后兼容)

注:.pi/subagents/missions/ 里有一份针对本项的只读架构评审产物, 可作为设计输入;其中结论尚未落地为代码。


P0-2 §11.6 reload 语义仍在说谎

现状:plugin_install 依旧回 reload_required,而不可热重载的插件 (Go plugin.Open 路径、DF_1_NODELETE)会被假装重载成功。

证据:

  • internal/plugins/pluginmgr/plugin.go:528 / :548 / :755 —— 仍返回 reload_required
  • 全仓库无 DF_1_NODELETE / NODELETE 检测(grep 为空)
  • internal/plugin/registry.go:823-824 注释已自认:Go plugin.Open 路径 (dynamicPlugin)不可卸载,跳过

子任务:

  1. ELF 检测 DF_1_NODELETE → 标记插件「不可热重载」 (internal/plugin/dynamic_loader_unix.go)
  2. ReloadOne 对这类插件返回「需重启 homed」,停止假装成功(registry.go:785)
  3. plugin_install 返回 restart_required 替代误导性的 reload_required (pluginmgr/plugin.go)

验收:

  • 装一个 DF_1_NODELETE 插件后,调用方拿到 restart_required,不是 reload_required
  • ReloadOne 在该插件上返回明确错误,且摘除旧注册面(工具/stage/output) 仍已完成,不留悬空闭包

P1-3 §13.2 arena 容量固定,无 Grow/Shrink

现状:内核独占的变长分配器已落地(first-fit + 邻块合并 + owner 校验), 但容量固定 4MB,用尽即调用失败,不再退回内联。

证据:

  • internal/plugin/proc/arena.go:99:const arenaDefaultCapacity = 4 * 1024 * 1024
  • arena.go:263 / :283:超限直接报错

为什么当时不做:跨进程 remap 会让正在读的对端 SIGSEGV。所以这不是 「补个函数」,而是要先解决对端可见的地址稳定性。

可选路径(择一,需先定方案再动手):

  • a) 预映射大虚拟区间(未触碰页不占物理内存),容量「逻辑无限」
  • b) 段表 + 多段拼接:新块分配在新段,不必 remap 旧段
  • c) 维持固定容量,但把超限错误做成可执行指引(告诉插件该怎么分片)

验收(按所选路径定):

  • 超过当前 4MB 的单次 payload 仍能送达,或收到带指引的明确错误
  • 任何情况下对端不 SIGSEGV

P1-4 §12.3 事件环:机制完成,零真实负载检验

现状:事件环(区内 segment + eventfd)与 events.subscribe 能力已完成, 但没有一个真实外部插件订阅 stage / tool_call 事件,dropped 计数 在长跑下的行为也无人观察。

证据:

  • internal/plugin/proc/capability.go:149-150(subscribe/unsubscribe 已授权)
  • internal/plugin/proc/corehandler.go:46-58(EvtRingSubscriber 接口)
  • 无对应 example 插件(third_party/homeagent-sdk/example/ 无事件订阅样例)

子任务:

  1. 写一个订阅 stage / tool_call 事件的 example 插件,跑真实负载
  2. 长跑观察 dropped 是否异常增长(环 cap 溢出告警是否够用)

验收:

  • example 插件能稳定收到事件并正确反序列化
  • 压测下 dropped 有上界且可观测

P1-5 §11.2 工具超时措辞误导 + browser 插件超时聚集

现状:内核侧工具超时文案仍写「已取消」,但进程内 cgo 插件根本取消不了 (OS 线程永久占用)——这是措辞与事实不符。

证据:

  • internal/agent/core/toolcall.go:42: fmt.Sprintf("工具 %s 执行超时(60秒),已取消", tc.Name)
  • internal/plugin/proc/process.go:27 / :114 / :577:注释自认 cgo 路径 「超时后 OS 线程永久占用,现网已泄漏 26 次」

子任务:

  1. 措辞改「已放弃等待(插件仍在后台运行,其占用的线程无法回收)」
  2. 排查 browser 插件为何频繁 60s 超时(旧档记录 22/26 次集中于此); 真取消能力依赖子进程模型(与 P0-1 相关)

验收:

  • 超时文案不再暗示「已取消」
  • browser 超时率下降到可解释水平,或给出根因

与 P0-1 的关系:把内部 cgo 插件也迁到子进程后,这条的严重性自动消除。


P1-6 §13.11 尾项:handleAgentAction 直接 501

现状:WebUI 的 agent 操作接口所有 action 一律返回 501 Not Implemented, 是明确的未接线桩。

证据:

  • internal/plugins/webui/handler_agents.go:239: writeJSON(w, http.StatusNotImplemented, ..."action not implemented by supervisor")

子任务:决定该端点该做什么——要么接线到 supervisor 的真实动作 (stop/restart/snapshot 等),要么从 UI 撤掉入口,不要留一个必然失败的按钮。

验收:

  • UI 上不存在「点了必 501」的入口,或该入口真的能动作

说明:旧档 §13.11 的「11 项」其余各项已实现(见 §三.6),仅此一项是真缺口。


P2-7 §12.4 Lua 仍走独立 ABI(进程内解释器)

现状:Lua 插件在内核进程内跑 gopher-lua,不走 proc 通道,是三套 ABI 里唯一没收敛的。代价是:Lua 插件崩溃 = 内核崩溃,且无共享内存数据面。

证据:

  • internal/plugin/lua_plugin.go:947(makeStageHandler,进程内)
  • internal/plugin/lua_plugin.go:128 / :329(register_stage 直接注册到内核 SDK)

子任务:评估「Lua 走 proc 通道」的代价(解释器进程启动开销 vs 隔离收益), 据此决定收敛还是明确保留为独立 ABI 并写进文档。

验收:

  • 有明确决策(收敛 / 永久保留),且文档与实际一致

P2-8 §11.5 / §12.5 Windows 只有交叉编译,无真机验证

现状:Windows 侧共享内存(CreateFileMappingW)、事件通知 (CreateEventW)、命名对象传递均已实现(桩已合回平台中立文件),但 从未在 Windows 真机端到端跑过。

证据:

  • internal/plugin/dynamic_proc.go:17-27 注释:平台差异已全部封装, 桩已删除
  • 无 Windows CI / 真机记录

子任务:

  1. 找一台 Windows 机器跑端到端
  2. 特别验证命名对象的撞名防护(名字带 PID + 递增序号)

验收:

  • Windows 下 homed 能加载外部插件并完成工具调用往返
  • 并发起多个插件时命名对象不撞

P2-9 §11.9 homed 主 heap 常驻未解释

现状:旧档记录 homed 主 heap 有约 2.36GB 常驻,来源未定位。 配置侧已有缓解手段(embedding_model_path 支持 #topN 限词向量数量), 但未确认现状是否仍存在。

证据:

  • internal/config/registry.go:856:embedding_model_path 描述已写明 #topN 可「控制常驻内存」
  • 无 GOMEMLIMIT 相关设置(grep 为空)

子任务:

  1. 现场 pprof 定位常驻来源(是否仍为双模型加载)
  2. 若确认,评估是否加 GOMEMLIMIT 或默认 #topN

验收:

  • 给出常驻内存的构成分解(哪块占多少)
  • 有明确取舍结论(可接受 / 需优化 / 已优化)

P2-10 仓库卫生:三个真实的测试缺陷(3 FAIL)

现状:go test ./... 有 3 个 FAIL,根因都是测试自身缺陷, 不是「环境玄学」,也都可修:

  1. PTY 用例要求可打开的 /dev/ptmx:容器里节点存在但 open 被 EACCES 拒(实测 PermissionError: [Errno 13]),而用例没有环境探测、 直接 t.Fatal → 应用 t.Skip 或 build tag 隔离,而不是硬 FAIL
  2. 测试端口硬编码 127.0.0.1:9890 → 并行/残留实例即 bind: address already in use,波及无关用例
  3. TestRestoreFileFromBaseline 写真实系统路径且忽略错误: internal/system/system_test.go:83 的 target 是 "/etc/RestoreFileFromBaseline.test.tmp",而用例内 os.WriteFile(target, ...) 不检查 err(os.WriteFile(target, []byte("v1"), 0644)); 在 /etc 不可写的环境(实测本机 root 也被拒)里,前面的写全部静默失败, 留档内容为空/不存在,到 RestoreFileFromBaseline 就报 expected restore to happen。根因是测试写死真实路径 + 吞错误, 不是 RestoreFileFromBaseline 实现有问题。

证据:

  • internal/plugins/integration_test.go:262 / :318 / :376(PTY 三例)
  • internal/plugins/remotedevice/plugin.go:27:const defaultAddr = "127.0.0.1:9890" (测试沿用固定端口)
  • 实测输出:open /dev/ptmx: permission denied、 listen tcp 127.0.0.1:9890: bind: address already in use

子任务:

  1. PTY 用例:无 /dev/ptmx 时 t.Skip(环境能力探测,不静默)
  2. remotedevice 相关测试改用 :0 让 OS 分配端口,或测试内随机端口
  3. TestRestoreFileFromBaseline:改用可写的临时路径(并保留 IsProtectedPath 语义所需的显式前缀,用 IsProtectedPathExplicit), 且每一步 WriteFile 都检查 err;顺带审计同类「写真实系统路径」的测试

验收:

  • 在无 PTY 权限、/etc 不可写的环境里,go test ./... 全绿 (受限用例显式 skip,不是静默通过)
  • 重复/并行跑不再端口冲突

三、已关闭 / 已实现(旧档误标,防复活)

逐条给出「旧档怎么说」与「源码实际怎样」,不要再往待办里加。

1. §13.12 L3 原生多模态 —— 已实现

  • 旧档:标「未做」,要求 media 成为一等图节点 + contains/depicts/derived_from 原生边
  • 实际:internal/memory/graph.go:169 起建 memory_blocks(含 modality/payload_digest/mime/vector/fingerprint/scene)+ memory_block_edges(source_kind/target_kind/edge_type), 以及 scenes / scene_features / scene_refs; internal/agent/core/graphmedia.go(310 行)实现 migrateLegacyGraphMedia / attachBlocksToSentence / linkBlocksToDocument / commitTriplesWithMedia;graphmedia_test.go 21 个测试
  • 结论:关闭

2. §13.9 llmsproxy 上下文溢出感知 —— 不属本仓(见 §五.1)

3. §13.10 AgentMail 三个 bug —— 不属本仓(见 §五.2)

4. §11.4 Lua stage 快照缺读锁(DATA RACE)—— 已修

  • 旧档:要求加 sc.RLock()/sc.RUnlock() 包裹快照构造
  • 实际:internal/plugin/proc/shmcodec.go:42 / :326 / :425 已在 captureLocal / WriteDirty 前后持读锁
  • 实测:go test -race ./internal/lua/... ./internal/plugin/... 全绿
  • 结论:关闭

5. §12.2 io.setToolBlocks 内核侧是桩 —— 已实现

  • 旧档:要求实现内核侧 handler + 补 example
  • 实际:internal/plugin/proc/corehandler_inject.go:132 实现 MethodIOSetToolBlocks;模板 putArena → blocks_ref; e2e_template_test.go:371 用真实 SDK 模板验证
  • 结论:关闭

6. §13.11 WebUI 修复清单 —— 主体已实现,仅剩 P1-6

逐项核实:Last-Event-ID 重放(handler_chat.go:710)、请求超时 (handler_chat.go:592 等 300s)、XSS 消毒(dashboard.js:252 DOMPurify)、 renderAll 增量(dashboard.js:34 / :452 / :1361 增量游标 + 流式增量)、 handleKnowledge 不再吞错(handler_memory.go:122 起逐分支返回错误)、 CSS/DesignSystem(dashboard.css:3 起 sakura/frost 令牌)、 GUI 重构(cmd/gui/renderer/app.js)—— 上述均已落地。 仅 handleAgentAction 501 是真缺口(已列 P1-6)。

7. §12.5 Windows 桩未删 / 旧档称「交叉编译通过」 —— 已收敛

  • 实际:internal/plugin/dynamic_proc.go 已合为平台中立单文件, 平台差异封装在 shmalloc_* / evtfd_* / shmpass_* / procattr_*(各带 构建标签);旧桩已删。真机验证仍缺(已列 P2-8)

8. §12.7 cmd/ohos/.../SettingsPage.ets 有未提交改动 —— 已提交

  • 实际:git status --short 于工作区全清(含 cmd/ohos/)

四、生产部署后验证(代码已就绪,本就无法在仓库内完成)

这些不是待开发项,是「必须落到生产实例才能确认」的验收。仓库内有 脚本 scripts/verify_deploy.sh [data_dir] 可一键检查前两条。

  • §0.1 healthcheck 隔离:部署后 knowledge/、memory/graph.db、 memory/documents/ 不再出现 _hc_* 残留
  • 图记忆去重:relations 重复率归零,跑一周不新增重复
  • §13.5 / §13.6 QQ 端到端:真实 QQ 消息注入与输出经共享内存通道正常 (小 payload 内联、大 payload 走 text_ref/frame)
  • §0.2 agentcli 不泛滥:QQ 消息在 agentcli 无自喂送风暴时能被正常响应

五、跨项目工单(不属本仓,旧档误并入)

旧 plan.md 把别仓的工单写成本仓 TODO,导致「查无此代码却挂着未完成」。 移出并说明归属:

1. §13.9「llmsproxy 上下文溢出感知」

  • 旧档写:补 OVERFLOW_PATTERNS("Context window is full")、 AUTO 截断宽度 80→160、go test ./internal/ai/...
  • 事实:本仓没有 internal/ai/;OVERFLOW_PATTERNS / clientUpstreamErr / OneLine(...,80) 都在 /home/program/llmsproxy (internal/gateway/chat.go)
  • 现状:该仓已把宽度改成 160(chat.go:634 注释「160 而不是 80」)
  • 归属:llmsproxy 仓,与本仓无关

2. §13.10「AgentMail 三个 bug」

  • 旧档写:提示词修正 / InReplyTo / relay_key ≤ 64 字节
  • 事实:AgentMail 是独立仓 /home/program/agentmail (relay_key 见 server/internal/handler/permission.go:32 / :90 / :382)
  • 现状:relay_key 上限已实现为 160 字节并带测试 (permission.go:90、relay_test.go:55),in_reply_to 语义见 forward.go:225
  • 归属:agentmail 仓

若这两仓也要纳入统一管理,应各自建 plan,不要塞进本仓文档。


六、明确「不做」的决定(避免反复挂账)

  • §13.13 反向结果入共享内存:原设想把 doc.query / llm.chat 的大结果 也搬进段。核实后:本仓根本不存在 llm.chat(llm.* 只映射 listSources/setSource/currentSource);唯一可能返回大结果的 doc.query 被 CapDocMemory 能力门挡着,且无任何外部插件使用。不做, 而不是留成永久 TODO。

重写时间:2026-09-24 · 核实方式:源码 grep / build / test / race 旧档备份于 /tmp/plan.md.bak-*(仅作对照,内容已判定过时)