HologramAgent — 家政型 AI 管家内核(多通道 Agent、插件体系、工具并行内核)|插件 SDK 文档 sdk.homeagent.jianfgit.xyz
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 1 天前 | ||
feat(security): 插件按工具声明内容策略 + 目录名统一 ## 一、插件声明层(用户裁定) > 「敏感词策略也应该是插件声明啊,每个插件声明自己轮次以及自己工具的 > 敏感词策略」 SecurityProfile 新增两个字段: ToolSensitiveWords map[string][]string // key=工具名模式(尾 *),value=词表 DetectInjection bool // 开启内核内置注入迹象识别 语义是**追加不覆盖**:整轮 SensitiveWords 仍然生效。 取宽不取窄的理由:当前处置是 B 档(标记+警示,不阻断), 多标一条只是多一句警示,漏标则是安全缺口。 ## 二、修掉我自己造的「莲藕式分层」 前一版把 *security.Round 从 runInputTask 一路搬到工具链 (prepareInputTask(evt, round) → TaskFrame.SecRound → executeToolCallOutcomeWithRound),并在 rtchild 里另补一条。 那是把**分层策略实现成跨层传参**:每多一条工具执行路径就要求调用方 多传一次句柄。profileActivator 的设计注释早就写明了正路: > 工具调用发生在 LLM 决定之后,此时插件已不在调用栈上。 > 唯一可靠的传递方式是**内核在输入进来时记住**,在该轮的工具判定里使用。 改为 security 自取: Runtime.ScanContentForAgent(agentID, isChild, ins, text) → ForAgent(agentID, isChild) // per-agent 视图,天然不串档 → EffectiveProfile() // session > longtime → 合并 全局词表 + 档位词表 + 工具词表 + DetectInjection core 只递交**事实**(我是谁 / 哪个工具 / 什么文本),不认识 Round。 与 CheckToolForAgent 同一套身份推断,不引入第二个入口语义。 TaskFrame.SecRound / executeToolCallOutcomeWithRound 全部撤除。 ## 三、目录名统一 third_party/homeagent-sdk → third_party/hologramagentsdk module 与远端仓早已是 hologramagentsdk,只剩物理目录是遗留名。 同步改了 go.mod replace、构建/打包/部署脚本、5 处测试里的分段路径。 注意:这里改的是**主仓 vendored 的 SDK 源码路径**,example/ 由 SDK 独立仓自己跟踪,主仓不涉及。 ## 判据 TestToolSensitiveWordsAppendOnlyForThatTool 词表只对目标工具生效 + 追加语义 TestToolSensitiveWordsUseAgentScope security 按 agent scope 自取生效 (变异验证:删掉 ForAgent 查询 → 红) ## 验证 go build ./... → 0 go vet ./... → 0 go test ./... -count=1 -race → 62 ok / 0 FAIL / 0 DATA RACE gofmt -l internal/ → 干净 | 3 天前 | |
| 1 天前 | ||
feat(security): 插件按工具声明内容策略 + 目录名统一 ## 一、插件声明层(用户裁定) > 「敏感词策略也应该是插件声明啊,每个插件声明自己轮次以及自己工具的 > 敏感词策略」 SecurityProfile 新增两个字段: ToolSensitiveWords map[string][]string // key=工具名模式(尾 *),value=词表 DetectInjection bool // 开启内核内置注入迹象识别 语义是**追加不覆盖**:整轮 SensitiveWords 仍然生效。 取宽不取窄的理由:当前处置是 B 档(标记+警示,不阻断), 多标一条只是多一句警示,漏标则是安全缺口。 ## 二、修掉我自己造的「莲藕式分层」 前一版把 *security.Round 从 runInputTask 一路搬到工具链 (prepareInputTask(evt, round) → TaskFrame.SecRound → executeToolCallOutcomeWithRound),并在 rtchild 里另补一条。 那是把**分层策略实现成跨层传参**:每多一条工具执行路径就要求调用方 多传一次句柄。profileActivator 的设计注释早就写明了正路: > 工具调用发生在 LLM 决定之后,此时插件已不在调用栈上。 > 唯一可靠的传递方式是**内核在输入进来时记住**,在该轮的工具判定里使用。 改为 security 自取: Runtime.ScanContentForAgent(agentID, isChild, ins, text) → ForAgent(agentID, isChild) // per-agent 视图,天然不串档 → EffectiveProfile() // session > longtime → 合并 全局词表 + 档位词表 + 工具词表 + DetectInjection core 只递交**事实**(我是谁 / 哪个工具 / 什么文本),不认识 Round。 与 CheckToolForAgent 同一套身份推断,不引入第二个入口语义。 TaskFrame.SecRound / executeToolCallOutcomeWithRound 全部撤除。 ## 三、目录名统一 third_party/homeagent-sdk → third_party/hologramagentsdk module 与远端仓早已是 hologramagentsdk,只剩物理目录是遗留名。 同步改了 go.mod replace、构建/打包/部署脚本、5 处测试里的分段路径。 注意:这里改的是**主仓 vendored 的 SDK 源码路径**,example/ 由 SDK 独立仓自己跟踪,主仓不涉及。 ## 判据 TestToolSensitiveWordsAppendOnlyForThatTool 词表只对目标工具生效 + 追加语义 TestToolSensitiveWordsUseAgentScope security 按 agent scope 自取生效 (变异验证:删掉 ForAgent 查询 → 红) ## 验证 go build ./... → 0 go vet ./... → 0 go test ./... -count=1 -race → 62 ok / 0 FAIL / 0 DATA RACE gofmt -l internal/ → 干净 | 3 天前 | |
feat(security): 插件按工具声明内容策略 + 目录名统一 ## 一、插件声明层(用户裁定) > 「敏感词策略也应该是插件声明啊,每个插件声明自己轮次以及自己工具的 > 敏感词策略」 SecurityProfile 新增两个字段: ToolSensitiveWords map[string][]string // key=工具名模式(尾 *),value=词表 DetectInjection bool // 开启内核内置注入迹象识别 语义是**追加不覆盖**:整轮 SensitiveWords 仍然生效。 取宽不取窄的理由:当前处置是 B 档(标记+警示,不阻断), 多标一条只是多一句警示,漏标则是安全缺口。 ## 二、修掉我自己造的「莲藕式分层」 前一版把 *security.Round 从 runInputTask 一路搬到工具链 (prepareInputTask(evt, round) → TaskFrame.SecRound → executeToolCallOutcomeWithRound),并在 rtchild 里另补一条。 那是把**分层策略实现成跨层传参**:每多一条工具执行路径就要求调用方 多传一次句柄。profileActivator 的设计注释早就写明了正路: > 工具调用发生在 LLM 决定之后,此时插件已不在调用栈上。 > 唯一可靠的传递方式是**内核在输入进来时记住**,在该轮的工具判定里使用。 改为 security 自取: Runtime.ScanContentForAgent(agentID, isChild, ins, text) → ForAgent(agentID, isChild) // per-agent 视图,天然不串档 → EffectiveProfile() // session > longtime → 合并 全局词表 + 档位词表 + 工具词表 + DetectInjection core 只递交**事实**(我是谁 / 哪个工具 / 什么文本),不认识 Round。 与 CheckToolForAgent 同一套身份推断,不引入第二个入口语义。 TaskFrame.SecRound / executeToolCallOutcomeWithRound 全部撤除。 ## 三、目录名统一 third_party/homeagent-sdk → third_party/hologramagentsdk module 与远端仓早已是 hologramagentsdk,只剩物理目录是遗留名。 同步改了 go.mod replace、构建/打包/部署脚本、5 处测试里的分段路径。 注意:这里改的是**主仓 vendored 的 SDK 源码路径**,example/ 由 SDK 独立仓自己跟踪,主仓不涉及。 ## 判据 TestToolSensitiveWordsAppendOnlyForThatTool 词表只对目标工具生效 + 追加语义 TestToolSensitiveWordsUseAgentScope security 按 agent scope 自取生效 (变异验证:删掉 ForAgent 查询 → 红) ## 验证 go build ./... → 0 go vet ./... → 0 go test ./... -count=1 -race → 62 ok / 0 FAIL / 0 DATA RACE gofmt -l internal/ → 干净 | 3 天前 | |
docs(en): 建立英文文档目录 —— 索引 + 教训全文 + 设计摘要 ## 问题 仓库是公开的,README / README_EN 已经双语,但 docs/ 只有 docs/zh/: 英文读者读完首页就撞墙。 ## 做法:摘要而非全译,并写明为什么 docs/zh/ 有 27 份文档、约 7000 行设计。**全译会烂得比审得还快**, 而一份陈旧的译文比没有更糟 —— 读者不知道它落后于原文多少。 所以按"内容变化速度"分级: | 类型 | 译法 | 理由 | | --- | --- | --- | | 教训 | ✅ 全文 | 复用价值最高,失败形态会反复出现 | | 索引 | ✅ 全文 | 没有它其余都不可发现 | | 设计文档 | ⚠️ 一页摘要 + 状态行 | 7000 行全译跟不上演进 | | 历史快照/证据 | ❌ 不译 | 时点产物,翻译无增益 | | 未实施计划 | ❌ 不译 | 会变;且按 §4.3 本就不该进仓 | 判据写在 docs/en/README.md 里: > 如果一份文档的内容变化速度快过译文能被审阅的速度,就译它的**摘要**而不是正文。 ## 三份文件 **docs/en/README.md** —— 索引。含分类表(哪些已译、哪些只有中文摘要、 哪些不译)、状态行约定、以及"本索引必须在同一提交里更新"这条硬规则 (滞后的索引比没有索引更糟:它教读者信任错的地图)。 **docs/en/engineering-lessons.md** —— §1(判据会骗人)+ §5(判据形态) 全文翻译,§2–§4 摘要。§5 是本轮新增的核心内容: 5.1 判据必须把被测模块当黑箱(含三个正当例外) 5.2 禁止读源码做断言(附"想保证什么 → 错误做法 → 正确做法"表) 5.2.1 判据不得以文档为依据 5.3 测试与业务代码分离 5.3.1 三种测试位置的实测边界 5.3.2 覆盖率必须用 -coverpkg=./... 5.4 CI 必须覆盖每个独立 module 5.5 CI 与本地必须跑同一套门 每个小节都带实测数据与变异验证结果,不是原则罗列。 **docs/en/design-summaries.md** —— 6 份核心设计的一页摘要: kernel-modularization 6 层 20 模块;依赖图在构建期算好并嵌进二进制; depgraph.bin 必须与二进制同目录,放数据目录会崩溃循环 secret-firewall security 是外围到核心之间的防火墙,自持数据结构、 只接受事实不接受策略;出入向都要洗 input-scheduler L1–L4 + 现场保存;工具执行是临界区不可抢占 resident-subagent 与 rtchild 语义相反;单数/复数共用 Cleanup toolcall-contract assistant(tool_calls) 后必须紧跟每个 id 的 tool 消息;批内不得插入任何东西 context-overflow-l4 下沉到冷上下文而非截断;InactivePages 是本次渲染 的临时标记(json:"-") 另有 13 份(插件相关 / 数据报告型)列为 "Chinese only" 并说明理由。 ## 中文索引加了英文入口 docs/zh/README.md 顶部增加指向 ../en/README.md 的一行,并明确: > 本目录是**中文原文,也是唯一权威**;英文侧只做摘要,与本文冲突时以本文为准。 这一条很重要 —— 否则两份文档会逐渐分叉,而读者不知道该信哪个。 ## 验证 go build ./... PASS go vet ./... PASS gofmt -l 干净 go test ./... 65 包全过,0 FAIL | 1 天前 | |
refactor(webui): 图谱判据迁到 test/ —— 导出 GraphDataForVisual 一层转换 graph_source_test.go → test/ handler_graph_pulse_test.go → test/ (7 条判据) webui/ 174 → 167 webui/test/ 28 → 35 合计 202 不变 ## 这一条必须动生产代码,理由要写清 判据想测的是「GraphData 的 raw map 被裁成可视化视图时,稠密向量必须被剥掉」。 两条路都不通: 走 HTTP 端点 /api/v1/memory/graph → 端点输入来自 h.memory.GraphData(),h.memory 是私有字段, 包外无法注入合成数据。测不了。 包内测试直接调 graphDataForVisual → 这正是要摆脱的白盒写法。 所以导出这一层转换 graphDataForVisual → GraphDataForVisual。它不是 为测试造出来的形状:函数的输入输出本来就是本包唯一的「raw map → visual map」语义,端点在 handler_memory.go:199 调它。导出的是真实边界, 不是内部细节。(判据 §5.3「不要为满足测试导出内部」指的是后者。) 顺带把 graphBlockView(私有 JSON 结构)在测试侧改成匿名结构体 —— 它只用于反序列化断言,没有必要引用包内类型。 ## 改走真实路由暴露的两个事实 ### ① newGraphHandler 直接设 &Handler{memory: m} 改成 NewHandler(sdk.New("webui", sdk.SDKConfig{Memory: m, ...})) —— Memory 确实能经 SDKConfig 注入(plugin.go:457 base.SetMemoryAPI(cfg.Memory))。 ### ② 503 暴露了鉴权要求 改走路由后立刻: status = 503, want 200 requireAPI 需要 api_key,而最小装配没配。补 seedWebUIConfig 后 200。 之前直接调 handler 的写法**从来不知道有这个要求** —— 路由层挡掉的东西, 绕过路由就看不见。 ## 判据有效性(变异验证) 在 GraphDataForVisual 里把裁剪短路掉(if blocks != nil { return out }), 实测红,且输出直接暴露泄漏内容: --- FAIL: TestHandleMemoryGraph_StripsVector response still contains vector field: {"data":{"memory_blocks":[{"id":"b_old",...,"vector":[0.1,0.2,0.3],... "success":true} 稠密向量确实会被下发到前端。恢复后全绿。 ## 验证 go build ./... PASS go vet ./... PASS gofmt -l 干净 go test ./... -count=1 67 包全过,0 FAIL | 1 天前 | |
refactor: 全面改名为 HologramAgent + 提示词技术内容下沉到 skill/工具描述 + 修 21 处乱码 ## ① 全面改名 HomeAgent → HologramAgent 仓库已改名,但代码与文档里仍有大量旧名。本次按"用户能看到的优先"全量替换: - 用户可见:登录页标题/页脚、默认人格第一句、CLI 状态输出、启动日志、 plugin_install 的工具描述、AI 图片插件的 User-Agent - 文档与脚本:README、assets/docs、docs/zh、deploy/*.sh、.github/workflows - 鸿蒙工程:cmd/ohos/HomeAgent → cmd/ohos/HologramAgent(git mv) 目录内的 module/app 描述同步;**证书路径原样保留** (default_HomeAgent_xxx.cer/p7b/p12 是签名材料的实际文件名,改了会找不到证书) - 插件元数据:plugin.json 的 author ★ 必须保持原样的两处(改了会坏): · legacySeededPromptSignature = "你是 HomeAgent 的看板娘「小宅」…" —— 那是识别历史播种人格卡的**原文锚点**,改了就识别不出旧数据 · 公开 SDK 的 ProxyAuthHomeAgent = "homeagent" —— 那是 SDK 契约里的值,属独立仓库,本仓不改 ## ② 修 21 处乱码(U+FFFD) 这些是历史编辑留下的编码破坏,全在注释里,例如: 「赌注重下���遍」→「赌注重下一遍」 「「不��滤」」 →「「不过滤」」 「**待��退场**」→「**待退场**」 ★ 保留 providers/chineseclip/tokenizer.go 里的 2 处:那是**故意**用 U+FFFD 演示"非法 UTF-8 字节被归一成 U+FFFD",不是乱码。 ## ③ 提示词技术内容下沉(用户:技术性内容应该收敛到 skill 与 -help 中) 实测系统提示词 2309 字,其中两段几乎全是**模型无法据以行动的实现细节**: 【工具执行顺序】1272 字(ParallelSafe 声明、批内降级、batchRunnable 判据…) 【三层记忆】 1069 字(L1/L2/L3 编号、消费式删除、向量空间、指纹…) 收敛后 **2309 → 1723 字(-25%)**,保留的全是"模型据此做什么不同的事": 工具顺序:默认并行 / 不要依赖先后 / 同通道保序 / 写类工具可能串行 三层记忆:时序里能看最近 / doc_query 把历史事件按原始时间戳插回时序 / 关系用 memory_recall / 成文知识用 knowledge_search 判断口径写进判据:新加一段提示词前先问"模型会据此做什么不同的事", 答不上来就该进 skill 或工具描述。 ## 判据 prompt_memory_test.go 新增 2 条: - TestSystemPromptHasNoImplementationJargon 黑名单:ParallelSafe / batchRunnable / 消费式 / DenseCosine / fingerprint / 向量空间 / 批内降级 / 自激 - TestSystemPromptStaysCompact:上限 2000 字(当前 1723) 变异:把 "ParallelSafe 声明、批内降级" 加回工具顺序段 ⇒ 第一条立刻红。 ## 验证 - go build ./... / go vet ./... 干净 - go test ./... -count=1:58 包 0 失败 - make check-client-versions:客户端与内核版本一致 | 14 天前 | |
style: gofmt 归位(CI 之外的漂移) | 10 天前 | |
feat(security): 插件按工具声明内容策略 + 目录名统一 ## 一、插件声明层(用户裁定) > 「敏感词策略也应该是插件声明啊,每个插件声明自己轮次以及自己工具的 > 敏感词策略」 SecurityProfile 新增两个字段: ToolSensitiveWords map[string][]string // key=工具名模式(尾 *),value=词表 DetectInjection bool // 开启内核内置注入迹象识别 语义是**追加不覆盖**:整轮 SensitiveWords 仍然生效。 取宽不取窄的理由:当前处置是 B 档(标记+警示,不阻断), 多标一条只是多一句警示,漏标则是安全缺口。 ## 二、修掉我自己造的「莲藕式分层」 前一版把 *security.Round 从 runInputTask 一路搬到工具链 (prepareInputTask(evt, round) → TaskFrame.SecRound → executeToolCallOutcomeWithRound),并在 rtchild 里另补一条。 那是把**分层策略实现成跨层传参**:每多一条工具执行路径就要求调用方 多传一次句柄。profileActivator 的设计注释早就写明了正路: > 工具调用发生在 LLM 决定之后,此时插件已不在调用栈上。 > 唯一可靠的传递方式是**内核在输入进来时记住**,在该轮的工具判定里使用。 改为 security 自取: Runtime.ScanContentForAgent(agentID, isChild, ins, text) → ForAgent(agentID, isChild) // per-agent 视图,天然不串档 → EffectiveProfile() // session > longtime → 合并 全局词表 + 档位词表 + 工具词表 + DetectInjection core 只递交**事实**(我是谁 / 哪个工具 / 什么文本),不认识 Round。 与 CheckToolForAgent 同一套身份推断,不引入第二个入口语义。 TaskFrame.SecRound / executeToolCallOutcomeWithRound 全部撤除。 ## 三、目录名统一 third_party/homeagent-sdk → third_party/hologramagentsdk module 与远端仓早已是 hologramagentsdk,只剩物理目录是遗留名。 同步改了 go.mod replace、构建/打包/部署脚本、5 处测试里的分段路径。 注意:这里改的是**主仓 vendored 的 SDK 源码路径**,example/ 由 SDK 独立仓自己跟踪,主仓不涉及。 ## 判据 TestToolSensitiveWordsAppendOnlyForThatTool 词表只对目标工具生效 + 追加语义 TestToolSensitiveWordsUseAgentScope security 按 agent scope 自取生效 (变异验证:删掉 ForAgent 查询 → 红) ## 验证 go build ./... → 0 go vet ./... → 0 go test ./... -count=1 -race → 62 ok / 0 FAIL / 0 DATA RACE gofmt -l internal/ → 干净 | 3 天前 | |
perf(starmap): WebUI/GUI 星图卡顿修复 + 抽出共享「布局数学」单一源 ## 症状与实测 WebUI 星图建图阻塞主线程 9339ms、draw calls 7514;GUI 建图 2466ms、 draw calls 3477。两者在空闲时仍在空转渲染。 ## WebUI(internal/plugins/webui/dashboard.js) - 力导向布局 O(n²) 两两互斥改**网格近邻**,并移入 **Web Worker** ⇒ 建图阻塞 **9339ms → 57ms** - 每节点光晕 3753 个 draw call → 1 个 InstancedMesh - 边 966 → 4 个 LineSegments - 空闲帧跳过 + 星空时间驱动 - 结果:draw calls **7514 → 3760**,空闲阻塞 **99% → 3%** ## GUI(cmd/gui/renderer/app.js) - draw calls **3477 → 1809**,建图 **2466ms → 943ms** - 首屏可见性守卫(GUI 默认 chat 视图,星图不该在后台初始化) - 空闲 12fps → 0.2fps 修 GUI 两个自引 bug: 1. 抄 WebUI 的 starmapMorphSet(GUI 实际用 state.starmapGrown)⇒ 每帧 ReferenceError、星图 0 帧; 2. camKey 读 starmapCam.position 无守卫(switchChatPanel 同步调 starmapAnimate 时 cam 为 nil)。**同一缺陷 WebUI 也存在,两处均已补守卫。** ## 共享源(方案 A:只共享「布局数学」这一层) 新增 shared/starmap/layout.js(3 个纯函数,零 DOM/全局引用)+ 生成器 tools/starmap-sync.mjs + 门禁 make check-starmap-shared(已进 make test)。三处(共享源 / dashboard.js / app.js)的标记块必须逐字节相同。 **为何只共享这一层**(实测依据):web 星图 54 个函数、gui 24 个,两者同名 函数逐字节相同数为 **0**。GUI 有设备桥/computer use/托盘/自有面板,WebUI 有 侧栏页签体系 —— 共享受众完全不同的 UI 代码是错的,能安全共享的只有布局数学。 **为何用生成器而非运行时加载**:GUI 用 file:// (main.js 的 loadFile),可能 离线;让其拉服务端 dashboard.js 会把「星图能否初始化」绑到网络与登录态上。 **为何 GUI 不用 Web Worker**:file:// 下外链与 Blob Worker 实测都能跑,但不把 「星图能否初始化」押在打包环境的 Worker 行为上。 ## 判据 - cmd/gui/starmap-perf.test.mjs(21 条,接入 npm test) - 扩充 internal/plugins/webui/starmap_perf_test.go(同源判据 + checkStarmapSharedBlock) - 变异 M1/M2/M3 全部报红 ## 附带 cmd/gui/renderer/app.js 同时含上一提交的 GUI system 事件消费(协议修复), 因同文件交织未再拆分,见提交 fix(clients)。 | 23 天前 | |
docs(site): 修正介绍站三处与代码不符的描述 ## 1. 「一轮里的多个 tool_call 默认并发」写反了 同一张卡的标题是「同轮工具默认串行,声明后才并发」,正文却写「默认并发」。 按 internal/agent/core/argvalidate.go:307 的 batchRunnable:批内**每一个** 工具都 ParallelSafe 才并发,有一个没声明就整批串行,Serial 优先级更高。 改:标题保留,正文按实现重写。 ## 2. 「最近 10 条恒受保护」已不成立 effectiveProtectedCount 现在返回 min(配置值, ContextTokens/单条平均token)。 固定 10 条在 50k 窗口、5800 token/事件时会占 58k,比 40000 预算还大 —— 这正是 召回率从 44% 掉到 22% 的那次实测。站点两处都还在写「恒受保护」,改成「受预算约束」。 ## 3. 第二层命名与描述 - 「低分下沉成文档,冷文档再蒸馏」→ 冷上下文 - ② 层正文「JSON + TF-IDF 索引的中间层」→ 实际是稠密 + TF-IDF 双路, 且与 L1 结构同构、取回走两阶段 - FAQ「上下文事件裁剪后归档成文档」同步改 全站 文档层|冷文档|归档成文档|Document 层 归零。 HTML 结构校验:div / section 配对差 0,<p> 开闭 62/62。 | 6 天前 | |
test: 判据改为面向接口输入输出 + 文档立规 + CI 补缺口 本提交是三件事的第一批,后续按计划分步推进。 ## 一、判据改造(两条实证) ### 1. turn_hint 判据原是自证循环 原测试在测试里**手动重放**了真实注入逻辑: f.Msgs = a.buildMessages(...) hint := stageHint(f.StageCtx, "turn_hint") f.Msgs = append(f.Msgs, Message{Role:"system", Content:"[Turn hints] "+hint}) // 然后断言"最后一条是 system、内容含 [Turn hints]" 它断言的是**它自己刚 append 的那一行** —— 把 task.go:656-661 抄进测试 再验证抄得对不对。真实代码那段删掉,它照样绿。 改为:把 turn_hint 放进 StageCtx,跑**真实的 stepPrepare** (newTaskFrame 起始就是 StepPrepare),检查它产出的 Msgs。 变异验证:删掉 task.go 里 if hint := stageHint(...) 那段 ⇒ 实测红。 ### 2. webui 文件下发判据原是白盒 原测试直接调未导出的 stageWebFile(src, isImage)、断言 webFilesDir 包级变量 —— 那都是**实现方式**。 改为从**输出通道 handler** 走(RegisterOutputChannel("webui", …), agent 发图/发文件时走的就是它): 输入:{payload: "<路径或URL>", type: "image"|"file"} 输出:{status:"ok", url:"/files/<name>.<ext>", size:N} 4 条判据(新版 handler_files_contract_test.go): · 图片 → 可下载 URL(前缀/扩展名/size) · 远端 URL 原样透传(不下载) · 危险扩展名被中和(.html 保留会在 webui 域执行脚本) · 空 payload 被拒 变异验证:丢扩展名 ⇒ 红;远端也拷贝 ⇒ 红。 ## 二、文档立规:engineering-lessons 新增第五节 原来只有「判据会骗人」,没有「判据本身形状错了」。新增 6 小节: 5.1 判据必须把被测模块当黑箱(含"如果重写内部实现、接口不变, 这条判据还会绿吗"这个可操作判据) 5.2 禁止读源码做断言(附正确替代表) 5.2.1 判据不得以文档为依据 5.3 测试文件与业务代码分离(现状:521 文件 / 11.2 万行, agent/core 一个包 196 文件里 144 个是测试) 5.4 CI 必须覆盖每个独立 module 5.5 CI 与本地必须跑同一套门 §5.2.1 的直接触发:interrupt_invariant_test.go 里有一条判据写着 「事实清单(来自探测,见 docs/zh/interrupt-subsystem-split-plan.md §1.5)」 —— 被引用方自己标着「状态:计划(未实施)」,而那条判据的实际断言 只是"入队后队列里 1 条",与声称的"反向依赖现状快照"毫无关系。 该判据已删除(入队行为已由 scheduler_preempt_test 的黑箱断言覆盖)。 ## 三、CI 补两个缺口 ### gofmt 门 提交纪律要求 gofmt -l 干净,但三个 workflow 里 gofmt 出现 **0 次** —— 纪律只靠人自觉。补上后未格式化的代码进不了主干。 只查本仓自管目录:SDK 是独立仓,格式由它自己的 CI 管。 ### SDK 判据步骤 third_party/hologramagentsdk/ 有自己独立的 go.mod: go list ./... | grep third_party → 0 个 主仓 go test ./... **永远不包含** SDK 任何测试。实测 SDK 有 30 个判据 文件,主仓 CI 一次都没跑过。 补的步骤只跑主仓 git 跟踪的文件(CI 上只有 22 个 vendored 文件, 工作机上是完整独立仓)—— 实测 git archive 出 22 个文件后 go test ./sdk/ ./meta/ 全绿。 ## 四、SDK 仓测试迁移(独立仓,已单独提交 3a38ef5) sdk/{plugin,proxy,stress}_test.go → sdk/test/,包名 sdk → test。 关键:原测试直接写 &PluginSDK{regStage: X, name: "test"} —— **私有字段**。 而 New(name, sett, regTool, regStage, regAPI, regOutput) 本来就把所有 registrar 作为参数,等价且是生产路径。共改 19 处,0 处剩余。 主仓同步更新了跟踪路径(这三个文件主仓也 vendor 了一份)。 ## 验证 go build ./... PASS go vet ./... PASS gofmt -l 干净 go test ./... -count=1 62 包全过,0 FAIL 干净检出(worktree) build + 改造后的判据全绿 ## 后续(按计划分步) 步骤 2 webui 29 个测试分类迁移(已实测出精确分类) 步骤 3 agent/core 739 处白盒引用先分类再动 步骤 4 SDK tools/hmapdev(package main,无法迁出,只加说明) 步骤 5 CI 补 coverage + staticcheck 步骤 6 文档双语(需定术语口径) | 1 天前 | |
perf(starmap): WebUI/GUI 星图卡顿修复 + 抽出共享「布局数学」单一源 ## 症状与实测 WebUI 星图建图阻塞主线程 9339ms、draw calls 7514;GUI 建图 2466ms、 draw calls 3477。两者在空闲时仍在空转渲染。 ## WebUI(internal/plugins/webui/dashboard.js) - 力导向布局 O(n²) 两两互斥改**网格近邻**,并移入 **Web Worker** ⇒ 建图阻塞 **9339ms → 57ms** - 每节点光晕 3753 个 draw call → 1 个 InstancedMesh - 边 966 → 4 个 LineSegments - 空闲帧跳过 + 星空时间驱动 - 结果:draw calls **7514 → 3760**,空闲阻塞 **99% → 3%** ## GUI(cmd/gui/renderer/app.js) - draw calls **3477 → 1809**,建图 **2466ms → 943ms** - 首屏可见性守卫(GUI 默认 chat 视图,星图不该在后台初始化) - 空闲 12fps → 0.2fps 修 GUI 两个自引 bug: 1. 抄 WebUI 的 starmapMorphSet(GUI 实际用 state.starmapGrown)⇒ 每帧 ReferenceError、星图 0 帧; 2. camKey 读 starmapCam.position 无守卫(switchChatPanel 同步调 starmapAnimate 时 cam 为 nil)。**同一缺陷 WebUI 也存在,两处均已补守卫。** ## 共享源(方案 A:只共享「布局数学」这一层) 新增 shared/starmap/layout.js(3 个纯函数,零 DOM/全局引用)+ 生成器 tools/starmap-sync.mjs + 门禁 make check-starmap-shared(已进 make test)。三处(共享源 / dashboard.js / app.js)的标记块必须逐字节相同。 **为何只共享这一层**(实测依据):web 星图 54 个函数、gui 24 个,两者同名 函数逐字节相同数为 **0**。GUI 有设备桥/computer use/托盘/自有面板,WebUI 有 侧栏页签体系 —— 共享受众完全不同的 UI 代码是错的,能安全共享的只有布局数学。 **为何用生成器而非运行时加载**:GUI 用 file:// (main.js 的 loadFile),可能 离线;让其拉服务端 dashboard.js 会把「星图能否初始化」绑到网络与登录态上。 **为何 GUI 不用 Web Worker**:file:// 下外链与 Blob Worker 实测都能跑,但不把 「星图能否初始化」押在打包环境的 Worker 行为上。 ## 判据 - cmd/gui/starmap-perf.test.mjs(21 条,接入 npm test) - 扩充 internal/plugins/webui/starmap_perf_test.go(同源判据 + checkStarmapSharedBlock) - 变异 M1/M2/M3 全部报红 ## 附带 cmd/gui/renderer/app.js 同时含上一提交的 GUI system 事件消费(协议修复), 因同文件交织未再拆分,见提交 fix(clients)。 | 23 天前 | |
chore: 规划书不进仓库(与 plan.md / kernelplan.md 同惯例) ## 为什么 规划/设计在交接前会反复改。入库后有两个实际问题: · 每改一次就是一条"改文档"提交,把真实代码提交埋掉 · 规划与实现必然漂移,读了仓库里的人会拿到过期结论 ## 既有惯例 本仓早就是这样处理的,不是新立的规矩: /plan.md 曾在 7901651「移除过程文档」被清出 /kernelplan.md 从未入库 两者都在 .gitignore 里,都只存在于工作区 ## 做法 · docs/zh/dshplgadapter-plan.md 从索引移除(文件本身留在工作区) · 挪到仓库根 dshplgadapter-plan.md,与 plan.md / kernelplan.md 同位置 · .gitignore 加 /dshplgadapter-plan.md,并写明理由 实现落地后,结论会进 docs/zh/ 下的正式设计文档 —— 那里才是该留档的地方。 ## 影响 内核仓历史里 e8d07cd、82d08eb 两个提交只含这份规划书,现已从工作树移除。 如需追查当时的结论,git show e8d07cd 仍可读到(历史不重写)。 | 1 天前 | |
chore(lint): 加 .golangci.yml(funlen/gocyclo/lll/dupl,warn-only)+ make lint-full main 上曾有 4 个 >=300 行函数、12 个 >=200 行函数;没有复杂度 linter 是它们 长期存活的直接原因。先以 warn-only(issues.exit-code:0)立阈值、只出清单, 历史债下降后再收成硬门禁。测试与 testdata/third_party 排除长度类规则。 | 1 个月前 | |
fix(mailmap): 归并目标改为 GitHub noreply,消除幽灵贡献者 ★ 根因是我把归并目标写成了 JianFeeeee@users.noreply.gitcode.com —— 那是**GitCode 的 noreply 域,GitHub 不认**。 而 459 个提交(早期历史的大头)用的正是它,GitHub 无法把它们关联到 任何本地账号,只能用启发式挂上一个**不相干的账号**当贡献者 (实测出现过 linu223xprob1e)。 ⇒ mailmap 把提交「归并到」某邮箱,等于告诉 GitHub「这些提交属于这个身份」; 归并到一个它不认识的域,反而**制造**幽灵贡献者。 ★ 教训:目标必须是**目标平台认的**标识,不是「看起来像匿名邮箱就行」。 归并目标改为标准格式 109188060+JianFeeeee@users.noreply.github.com (id + 登录名,从 GitHub API 实测得来,非猜测)。 本地实测(origin/main 全历史 765 提交): 归并前:459 gitcode noreply / 135 root@qq / 51 JianFeeeee@root@qq / 33 jianf… 归并后:745 JianFeeeee <109188060+JianFeeeee@users.noreply.github.com> 19 HomeAgent Agent(刻意不归并,区分自动化提交) 1 pi(自动化 agent) 1 jianf <â^@2198972886@qq.com> ← 坏字节,mailmap 治不了 最后 1 条需改写历史(邮箱前缀带 3 个坏字节,文本文件无法表达), 另开一次操作处理。 | 1 个月前 | |
docs: 重写 README,中英文对齐,历史移入 CHANGELOG ## 问题:README 像架构文档,不像 README 原文 420 行,开头第一句是「以核心域与应用域分离为设计原则的 Agent 框架」。 读者点进来想知道「这东西能给我干什么」,得到的是一句需要三分钟才能理解的定义。 量化一下 AI 味的几个典型指纹: | 指标 | 改前 | 改后 | |---|---|---| | 加粗标记 | **230** 处 | 56 处 | | 破折号插入语 | **17** 处 | 0 | | 行数 | 420 | 204 | | 「项目状态」占比 | 86 行(21%) | 移出 | 几乎每条 bullet 都是同一个模板:粗体术语开头 + 三行解释 + 收尾总结。 结构过度对称、每段都自我收尾,是 AI 写作最明显的痕迹。 ## 改法:先说人话,再讲设计 开头四句: > 一个能自己跑几个月、还记得住事的 Agent 内核。 > 说人话:你给它接上 QQ、WebUI、命令行、文件、浏览器,它就能在这些地方替你干活。 > 白天聊过的事,晚上还记得。三周前说过的偏好,不会因为上下文窗口滚动就丢了。 > 插件崩了它不倒,模型换了它不改。 然后把 ## 设计要点 改成 ## 它跟别的 Agent 框架哪里不一样 ——从「我们有什么设计」 改成「你和别人有什么不同」。每条都给了**为什么这么选**,不是**我们做得多好**: - 插件是独立进程 → 因为 .so 那套踩过 dlclose 空操作、cgo 超时杀不掉线程 - 内核不碰 IO → 好处不是干净,是能换掉整个交互层而不改内核 - 记忆会下沉 → 所以能跑几个月而不把历史全塞进 prompt ## 能力 不列 API,列「你能让它干什么」:说话的地方 / 看和听 / 动手 / 记住 / 调度。 ## 删掉 227 行重复与历史 - **## 架构图(142 行)**:ARCHITECTURE.md 里已有完整的「消息处理流程」「7 阶段 钩子」「三层记忆」,README 里这套是**逐字重复**。README 留一张 8 行流程图 + 三段话讲清机制,细节指向架构文档。 - **## 项目状态(85 行)**:这是更新日志,不是 README。移入新建的 CHANGELOG.md / CHANGELOG_EN.md。 ## 中英文同步 README_EN.md 之前停在旧版式(439 行,含 Architecture Diagrams / Project Status),与中文版结构完全不同 —— 不是这次才不同步,是**一直是两份不同的文档**。 本次按节 1:1 对齐(9 节完全一致),英文不是逐字直译,按英文习惯重写: ## 它跟别的 Agent 框架哪里不一样 → ## What makes it different ## 能力 → ## What it can do ## 怎么工作的 → ## How it works ## 快速体验 → ## Quick Start ## 代码结构 → ## Code Structure ... 补上英文版一直**缺失**的 ## Quick Start 整节(中文有、英文没有)。 ## 顺带修正的事实错误 - 「内置 13 个插件」→ 实际 18 个插件目录(webui/cli/files/cmd/mcp/kbtree/seq/skillmgr …) - 「参考 example/ 目录下的 Go 和 Lua 示例」→ 26 个插件已迁到 [github.com/HologramAgent](https://github.com/HologramAgent),example/ 只剩 2 个 ## 验证 - 两版死链检查:README.md / README_EN.md 内部链接全部有效 - 9 个二级标题逐一对齐 - 破折号插入语归零(英文剩余 4 处全是列表分隔符) | 6 天前 | |
docs: 重写 README,中英文对齐,历史移入 CHANGELOG ## 问题:README 像架构文档,不像 README 原文 420 行,开头第一句是「以核心域与应用域分离为设计原则的 Agent 框架」。 读者点进来想知道「这东西能给我干什么」,得到的是一句需要三分钟才能理解的定义。 量化一下 AI 味的几个典型指纹: | 指标 | 改前 | 改后 | |---|---|---| | 加粗标记 | **230** 处 | 56 处 | | 破折号插入语 | **17** 处 | 0 | | 行数 | 420 | 204 | | 「项目状态」占比 | 86 行(21%) | 移出 | 几乎每条 bullet 都是同一个模板:粗体术语开头 + 三行解释 + 收尾总结。 结构过度对称、每段都自我收尾,是 AI 写作最明显的痕迹。 ## 改法:先说人话,再讲设计 开头四句: > 一个能自己跑几个月、还记得住事的 Agent 内核。 > 说人话:你给它接上 QQ、WebUI、命令行、文件、浏览器,它就能在这些地方替你干活。 > 白天聊过的事,晚上还记得。三周前说过的偏好,不会因为上下文窗口滚动就丢了。 > 插件崩了它不倒,模型换了它不改。 然后把 ## 设计要点 改成 ## 它跟别的 Agent 框架哪里不一样 ——从「我们有什么设计」 改成「你和别人有什么不同」。每条都给了**为什么这么选**,不是**我们做得多好**: - 插件是独立进程 → 因为 .so 那套踩过 dlclose 空操作、cgo 超时杀不掉线程 - 内核不碰 IO → 好处不是干净,是能换掉整个交互层而不改内核 - 记忆会下沉 → 所以能跑几个月而不把历史全塞进 prompt ## 能力 不列 API,列「你能让它干什么」:说话的地方 / 看和听 / 动手 / 记住 / 调度。 ## 删掉 227 行重复与历史 - **## 架构图(142 行)**:ARCHITECTURE.md 里已有完整的「消息处理流程」「7 阶段 钩子」「三层记忆」,README 里这套是**逐字重复**。README 留一张 8 行流程图 + 三段话讲清机制,细节指向架构文档。 - **## 项目状态(85 行)**:这是更新日志,不是 README。移入新建的 CHANGELOG.md / CHANGELOG_EN.md。 ## 中英文同步 README_EN.md 之前停在旧版式(439 行,含 Architecture Diagrams / Project Status),与中文版结构完全不同 —— 不是这次才不同步,是**一直是两份不同的文档**。 本次按节 1:1 对齐(9 节完全一致),英文不是逐字直译,按英文习惯重写: ## 它跟别的 Agent 框架哪里不一样 → ## What makes it different ## 能力 → ## What it can do ## 怎么工作的 → ## How it works ## 快速体验 → ## Quick Start ## 代码结构 → ## Code Structure ... 补上英文版一直**缺失**的 ## Quick Start 整节(中文有、英文没有)。 ## 顺带修正的事实错误 - 「内置 13 个插件」→ 实际 18 个插件目录(webui/cli/files/cmd/mcp/kbtree/seq/skillmgr …) - 「参考 example/ 目录下的 Go 和 Lua 示例」→ 26 个插件已迁到 [github.com/HologramAgent](https://github.com/HologramAgent),example/ 只剩 2 个 ## 验证 - 两版死链检查:README.md / README_EN.md 内部链接全部有效 - 9 个二级标题逐一对齐 - 破折号插入语归零(英文剩余 4 处全是列表分隔符) | 6 天前 | |
docs(license): 核心仓采用 AGPL-3.0-only,并写进包内与安装器 本仓此前**没有任何许可文件**,README 里也没有许可声明,而 rpm 元数据里甚至写着 --license "Proprietary"(与我们实际的分发意图相反)。 ## 选择了什么 LICENSE:GNU Affero 通用公共许可证第 3 版官方全文(gnu.org 正本, 661 行 / 34523 字节, sha256 0d96a4ff68ad6d4b6f1f30f713b18d5184912ba8dd389f86aa7710db079abcb0)。 选 AGPL-3.0-only 的理由:GPL 家族里**传染性最强**的一档,并且不允许选后续版本。 它比 GPL-3.0 多出 §13(Remote Network Interaction)——通过网络提供服务时也要向 使用者提供源码。这正是「最严格」在 GPL 家族里的落点。 依赖许可已核对为全部宽松且兼容:go-sqlite3 / gojieba / gopher-lua / bubbletea / bubbles / lipgloss / yaml.v3(MIT)、golang.org/x/{sys,text}(BSD-3)、 Chinese-CLIP 产物(Apache-2.0,与 GPLv3+/AGPLv3 双向兼容)、ONNX Runtime(MIT)。 没有 GPL-2.0-only 这类与 AGPL 不兼容的依赖。 ## 落在哪些地方 - README.md / README_EN.md:新增「许可 / License」章节,写明 §13 的含义、 插件因**静态链接 SDK 源码**而成为衍生作品须同许可发布、以及随包第三方组件清单 - deploy/packaging/package-linux.sh: · 新增 stage_license(),**四个变体(full/server/client/tar)全带** /usr/share/doc/homeagent/{LICENSE,copyright}(copyright 为 DEP-5 机器可读格式, 含第三方条目) · fpm 的 --license "Proprietary" → "AGPL-3.0-only" - deploy/packaging/installer.nsi:新增 MUI 许可页 (..\..\LICENSE,NSIS 以 .nsi 所在目录解析相对路径) - third_party/homeagent-sdk/LICENSE:vendored SDK 的许可一并入库 —— 本仓 .gitignore 有意不镜像 SDK 的 README/tools/package/example,但依赖的许可 应当随依赖可见 ## 待办(下一步) README 的「项目状态」仍停在 v1.1.1,且写着已被 v1.2.0 **删除**的机制 (引用计数式 GC、[<mime> <digest>] <描述> 描述式索引)——单独一个提交修。 | 1 个月前 | |
fix(csrc): 用 -nostdinc 剃掉宿主头,修 clang 交叉的 stdint.h 冲突 ## 上一版为什么还红 装了 libc6-dev-arm64-cross(1.7MB 头包)后,CI 报的错变成了: In file included from csrc/src/ha_codec.c:23: In file included from /usr/lib/llvm-18/lib/clang/18/include/stdint.h:52: /usr/include/stdint.h:26:10: fatal error: 'bits/libc-header-start.h' file not found 注意路径:它找的是 **/usr/include/stdint.h(宿主 amd64 的)**,不是 aarch64 的那份。宿主 stdint.h 会 #include <bits/libc-header-start.h>, 那是 amd64 的布局,aarch64 目标下必然找不到。 **只加 -isystem 指向 aarch64 头不够** —— 默认搜索路径里的 /usr/include 仍然优先。 ## 修法:-nostdinc 剃掉全部默认路径,再按序加回 ARM64_INC="-nostdinc -isystem $(clang -print-resource-dir)/include" for d in /usr/aarch64-linux-gnu/include /usr/include/aarch64-linux-gnu; do [ -d "$d" ] && ARM64_INC="$ARM64_INC -isystem $d" done 顺序:**clang 内建头 → aarch64 目标头**。-nostdinc 保证宿主 /usr/include 不再参与,宿主头泄漏这条路径被彻底断掉。 只对 clang-target 模式生效;explicit / cross-gcc 模式用编译器自带搜索路径, 不传这些(它们本来就知道自己的 sysroot 在哪)。 ## 本地五条路径实测 | 路径 | 结果 | |---|---| | ① clang-target(默认) | ✅ 0 告警通过 | | ② 显式 cross-gcc | ✅ 0 告警通过 | | ③ 显式指向不存在的编译器 | ✅ FAIL(Error 1) | | ④ **真的写坏 C 源码** | ✅ FAIL(Error 1) | | ⑤ 恢复后 | ✅ 通过 | | make check-csrc 全门禁 | ✅ 全部通过,0 SKIP | ③④ 是防止"假通过"的回归判据:③ 验 command -v 校验 + PIPESTATUS, ④ 验编译器真实退出码没被 | head 吞掉。 | 14 天前 | |
docs: 架构节改成参考文档写法 上一版还是论证体:四个小标题里三个是否定式断言(「上下文**不是**截断+摘要」 「三层记忆是迁移,**不是**复制」「记忆按场面分,**不按**会话」), 每段都以一句评论收尾。段落字数 62–128,几乎等长 —— 这是最明显的机器痕迹。 改成参考文档: ## 小标题换成名词 上下文不是「截断 + 摘要」 → 上下文窗口 三层记忆是迁移,不是复制 → 三层记忆 记忆按场面分,不按会话 → 场面 标题说明这节讲什么,不下判断。判断该由正文的数据支撑,不该写在标题里。 ## 删掉与假想敌的对比 「常见做法是把超窗的对话裁掉或让模型自己摘要,两者的共同问题是丢什么由位置决定」 「普通 agent 通常一条会话一条历史,换个话题就失忆」 换成直接说我们怎么做的。读者不需要先被告知别人怎么错。 ## 段落长度拉开 之前 62–128 字(极差 66)。现在每节先给一行文件路径,正文长短不一: 32 字 internal/agent/core/context.go 127 字 这条约束来自一次实测。50k 窗口…… 51 字 N 默认 10,实际取值受预算约束:…… 129 字 场面由五个特征构成…… 极差 98 字。技术文档本来就是「一行路径 + 一段解释 + 一行数字」这种不均匀节奏。 ## 去掉两处破折号 「没有『取出即删除』的路径 —— 删除排在确认之后」 「不吸收整轮指纹 —— 吸收了会……」 改成句号。全文破折号归零。 ## 补上实现位置 原来只讲设计没给代码位置,改成每节标文件路径: internal/agent/core/context.go、internal/memory/coldcontext/、 internal/memory/scene_emerge.go、internal/agent/core/stages.go。 ## 核对 文中数字逐条对源码:preemptPromotionCap = 2、minSceneEvidence = 2、 FindColdContexts(72*time.Hour, 2) 均一致。两版 9 个标题对齐,无死链。 | 6 天前 | |
docs: 架构节改成参考文档写法 上一版还是论证体:四个小标题里三个是否定式断言(「上下文**不是**截断+摘要」 「三层记忆是迁移,**不是**复制」「记忆按场面分,**不按**会话」), 每段都以一句评论收尾。段落字数 62–128,几乎等长 —— 这是最明显的机器痕迹。 改成参考文档: ## 小标题换成名词 上下文不是「截断 + 摘要」 → 上下文窗口 三层记忆是迁移,不是复制 → 三层记忆 记忆按场面分,不按会话 → 场面 标题说明这节讲什么,不下判断。判断该由正文的数据支撑,不该写在标题里。 ## 删掉与假想敌的对比 「常见做法是把超窗的对话裁掉或让模型自己摘要,两者的共同问题是丢什么由位置决定」 「普通 agent 通常一条会话一条历史,换个话题就失忆」 换成直接说我们怎么做的。读者不需要先被告知别人怎么错。 ## 段落长度拉开 之前 62–128 字(极差 66)。现在每节先给一行文件路径,正文长短不一: 32 字 internal/agent/core/context.go 127 字 这条约束来自一次实测。50k 窗口…… 51 字 N 默认 10,实际取值受预算约束:…… 129 字 场面由五个特征构成…… 极差 98 字。技术文档本来就是「一行路径 + 一段解释 + 一行数字」这种不均匀节奏。 ## 去掉两处破折号 「没有『取出即删除』的路径 —— 删除排在确认之后」 「不吸收整轮指纹 —— 吸收了会……」 改成句号。全文破折号归零。 ## 补上实现位置 原来只讲设计没给代码位置,改成每节标文件路径: internal/agent/core/context.go、internal/memory/coldcontext/、 internal/memory/scene_emerge.go、internal/agent/core/stages.go。 ## 核对 文中数字逐条对源码:preemptPromotionCap = 2、minSceneEvidence = 2、 FindColdContexts(72*time.Hour, 2) 均一致。两版 9 个标题对齐,无死链。 | 6 天前 | |
feat(security): 插件按工具声明内容策略 + 目录名统一 ## 一、插件声明层(用户裁定) > 「敏感词策略也应该是插件声明啊,每个插件声明自己轮次以及自己工具的 > 敏感词策略」 SecurityProfile 新增两个字段: ToolSensitiveWords map[string][]string // key=工具名模式(尾 *),value=词表 DetectInjection bool // 开启内核内置注入迹象识别 语义是**追加不覆盖**:整轮 SensitiveWords 仍然生效。 取宽不取窄的理由:当前处置是 B 档(标记+警示,不阻断), 多标一条只是多一句警示,漏标则是安全缺口。 ## 二、修掉我自己造的「莲藕式分层」 前一版把 *security.Round 从 runInputTask 一路搬到工具链 (prepareInputTask(evt, round) → TaskFrame.SecRound → executeToolCallOutcomeWithRound),并在 rtchild 里另补一条。 那是把**分层策略实现成跨层传参**:每多一条工具执行路径就要求调用方 多传一次句柄。profileActivator 的设计注释早就写明了正路: > 工具调用发生在 LLM 决定之后,此时插件已不在调用栈上。 > 唯一可靠的传递方式是**内核在输入进来时记住**,在该轮的工具判定里使用。 改为 security 自取: Runtime.ScanContentForAgent(agentID, isChild, ins, text) → ForAgent(agentID, isChild) // per-agent 视图,天然不串档 → EffectiveProfile() // session > longtime → 合并 全局词表 + 档位词表 + 工具词表 + DetectInjection core 只递交**事实**(我是谁 / 哪个工具 / 什么文本),不认识 Round。 与 CheckToolForAgent 同一套身份推断,不引入第二个入口语义。 TaskFrame.SecRound / executeToolCallOutcomeWithRound 全部撤除。 ## 三、目录名统一 third_party/homeagent-sdk → third_party/hologramagentsdk module 与远端仓早已是 hologramagentsdk,只剩物理目录是遗留名。 同步改了 go.mod replace、构建/打包/部署脚本、5 处测试里的分段路径。 注意:这里改的是**主仓 vendored 的 SDK 源码路径**,example/ 由 SDK 独立仓自己跟踪,主仓不涉及。 ## 判据 TestToolSensitiveWordsAppendOnlyForThatTool 词表只对目标工具生效 + 追加语义 TestToolSensitiveWordsUseAgentScope security 按 agent scope 自取生效 (变异验证:删掉 ForAgent 查询 → 红) ## 验证 go build ./... → 0 go vet ./... → 0 go test ./... -count=1 -race → 62 ok / 0 FAIL / 0 DATA RACE gofmt -l internal/ → 干净 | 3 天前 | |
fix(media): 支持 webp 解码 + 向量迁移失败必须留下原因 ## 生产现状(2026-10-23 排查) 启动日志只有一行,没有任何细节: [media] 启动向量迁移: 4 个图像块 → fp=cd2a495cf990 dim=512 workers=4 [media] 向量迁移完成: 成功=0 失败=4 总计=4 反查 memory/graph.db 才找到根因: | mime | 块数 | fingerprint | |---|---|---| | image/jpeg | 153 | 当前 fp | | image/png | 394 | 当前 fp | | **image/webp** | **4** | **空**(从未嵌入成功) | 而 providers/chineseclip/image.go 只注册了 gif/jpeg/png: _ "image/gif" _ "image/jpeg" _ "image/png" ⇒ image.Decode 对 webp 返回 image: unknown format ⇒ 这 4 个块 **永远**拿不到向量 ⇒ 语义检索永远搜不到它们,且没有任何报错。 ## 两个独立缺陷 ### ① 功能缺口:webp 不可解码 加 _ "golang.org/x/image/webp"。这是纯 Go 实现、无 cgo,不改变二进制形态。 代价:x/image v0.25.0 硬性要求 x/text >= v0.23.0,因此 x/text 从 v0.3.8 升到 v0.23.0。已实测 providers/chineseclip 全绿(分词器用 x/text/unicode/norm 做 NFC 归一化,升级未改变行为)。 ### ② 可观测性缺口:失败被 continue 吞掉 原实现: if err := a.reembedOneBlock(b, fp); err != nil { atomic.AddInt64(&failed, 1) continue ← 原因就此消失 } 现在按 mime | error 聚合后打印(不逐条打,上千张图会刷屏): [media] 向量迁移完成: 成功=0 失败=0 不支持=4 总计=4 fp=cd2a495cf990 [media] 失败原因分类(按 mime | error 聚合): [media] 4 个: image/webp | chineseclip: 解码图像: image: unknown format 同时区分 ErrModalityUnsupported(**永久**无向量,重试无意义)与临时失败, 单独计为 不支持=N —— 混在"失败"里会让人以为重试就能恢复。 ## 判据(4 条,全部变异自证) | 判据 | 覆盖 | 变异 | |---|---|---| | TestWebpIsDecodable | webp 能被 image.Decode 识别 | 注释掉 webp 注册 ⇒ 红并指名「image.go 里缺少 ...」 | | TestWebpPreprocessProducesTensor | webp 能走完整预处理(缩放+归一化),不只解码 | 同上 ⇒ 红 | | TestPngStillDecodable | 注册表改动没弄坏 png | — | | TestReembedStaleMedia_LogsFailureReasons | 失败原因(mime + 错误原文 + 计数)必须进日志 | 删掉打印 ⇒ 红 | | TestReembedStaleMedia_SeparatesUnsupportedFromFailed | 不支持模态单独计数 | — | webp 夹具用 ffmpeg 生成的真实 2×2 webp(44 字节)。手写字节序列构造不出 合法容器——第一版手写就失败了,这是实测教训。 ## 验证 - providers/chineseclip / internal/agent/core / internal/memory/vector 全绿 - go build ./... / go vet ./... 干净 - 全量回归:见提交后 CI | 13 天前 |
⚠️ AI 辅助编程声明:本项目代码、文档及提交历史中,部分内容由 AI 辅助生成或修改。人工已审阅关键改动,但使用时请自行评估与验证。
HologramAgent
English: README_EN.md
自托管 Agent 内核。跑在自己的机器上,插件以独立进程接入。
make build build-cli
./build/homed -data /tmp/ha
API 密钥在 WebUI(http://localhost:8080)里填。需要 Linux。
跟常见 agent 框架的主要差别在上下文管理:不是超窗就截断或让模型摘要, 而是按相关度取舍,低分的下沉成冷上下文、再蒸馏进图数据库。另外两处不太一样的是 启动顺序在编译期定序,以及插件按设施就绪提前启动(详见下方架构)。
架构
homed(内核)
LLM 编排 · 记忆 · 检索 · 调度
│
┌──────────────┼──────────────┐
│ │ │
stdio RPC 共享内存 事件环
控制 大块数据 通知
│ │ │
└──────────────┼──────────────┘
│
插件(各自独立进程)
WebUI · QQ · 命令行 · 文件 · 浏览器
内核进程做编排、记忆、检索;HTTP、文件、设备都由插件负责(internal/plugin)。
插件崩溃后内核重启它,退避 1s / 2s / 3s;5 分钟内第 4 次崩溃停止自动重启,
等人工介入(cmd/homed/kmod_runtime.go)。
上下文窗口
internal/agent/core/context.go
事件进窗口时算两套向量:稠密向量与媒体、冷上下文共用同一空间;词向量用 TF-IDF 作退路。裁剪时用当前输入给每条历史事件打分,保留 topK,最近 N 条不参与打分直接保留。
N 默认 10,实际取值受预算约束:min(配置值, ContextTokens / 单条平均 token)。
这条约束来自一次实测。50k 窗口、单条事件平均 5800 token、预算 40000,固定保留 10 条 要占 58k,比预算本身还大。于是每轮裁剪都能达标却清不出空间,两轮后预算耗尽、任务终止, 召回率从 44% 降到 22%。现在小窗口会自动收紧,1M 窗口下用满配置值。
被裁掉的事件连同它持有的媒体块写入冷上下文层,块 ID 不变,只换持有层。
取回用 coldcontext_query:先 Peek 确认,再 Remove 源副本。
没有「取出即删除」的路径,删除排在确认之后。
三层记忆
internal/agent/core/context.go · internal/memory/coldcontext/ · internal/memory/graph.go
| 层 | 持有物 | 进模型的方式 | 冷化 |
|---|---|---|---|
| L1 上下文 | 事件 + 块 | 直接进 system prompt | 相关性低分下沉 |
| L2 冷上下文 | 事件 + 块(与 L1 同构) | 召回后插回 L1 时序 | ≤2 次访问且 72h 未读,蒸馏 |
| L3 稳定记忆 | 三元组,节点即块 | 召回后注入 | 不流出 |
一块在任一层只有一个持有者,没有引用计数。图片和音频是 L2/L3 里的块, 有自己的向量,与文本在同一空间检索。
场面
internal/memory/scene_emerge.go
场面由五个特征构成,权重各不相同:通道 1.0、对话对象 1.0、工具 0.8、话题 0.4、时段 0.2。
同一指纹出现两次才建场面(minSceneEvidence = 2),一次性的交互不建。
归属阈值 0.5,唤起阈值 0.35;重现时强度 +1,久不重现按半衰期衰减。
两个入口并行:插件在注入点声明(如 chan:qq/peer:group_1),或者指纹自行聚类。
声明场景只从键本身解析特征,不吸收整轮指纹。吸收之后它会以接近 1.0 的相似度
匹配后续所有同类轮次,自行聚类那条路就长不出更细的场面。
七个阶段
internal/agent/core/stages.go
| 阶段 | 时机 | 插件可做 |
|---|---|---|
on_input |
消息到达 | 黑名单、限流、短路回复 |
pre_action |
上下文就绪,调 LLM 前 | 注入外部数据 |
post_action |
LLM 返回文本与工具调用 | 敏感词过滤、强制改向 |
before_toolcall |
单个工具执行前 | 审计、拒绝、改参 |
after_toolcall |
单个工具执行完 | 脱敏、排序 |
before_output |
最终文本就绪 | 格式适配 |
after_output |
已发送 | 统计 |
内循环是 post_action → [before_toolcall → 执行 → after_toolcall] → post_action,
模型不再调工具、全部被拒、或达到上限时退出。
其余
- 通道:WebUI、命令行、QQ 群与私聊、A2A、IDE 里的 ACP
- 工具:文件、命令、代码、浏览器(真实 Chrome)、日程、备忘、RSS、音乐
- 模态:图片、视频、语音、截图、网页
28 个内置插件、89 个模型可调工具。外部插件一插件一仓,见 github.com/HologramAgent。
下载
Releases 提供三种变体:
| 变体 | 内容 | 适用 |
|---|---|---|
| full | homed + waiter + 桌面 GUI + systemd unit | 单机全功能 |
| server | homed + waiter + systemd unit | 服务器(无桌面环境) |
| client | waiter + 桌面 GUI | 连接远程 HologramAgent |
- Linux:
.deb(amd64/arm64)、.rpm(x86_64)、.tar.gz - Windows:
HologramAgent_v1.2.0_{Full,Server,Client}_win64.exe(NSIS 安装向导,含 AGPL 许可页)。自 v1.2.0 起因homed不再支持 Windows 原生(依赖 fd 继承与共享内存段内偏移解引用),安装器改为引导到 WSL2,并把 Linux 包送进发行版里按 Linux 方式安装。 - 免安装:
homeagent-bin-<os>_<arch>.tar.gz(含 homed/waiter/initconfig) - 校验:
SHA256SUMS
macOS 的 homed 需在原生 macOS 构建(CGO + sqlite3),发布包仅含 waiter/initconfig。
构建
依赖 Go 1.25+、CGo(go-sqlite3)、Linux。
make build build-cli # 守护进程 + CLI
make test # go test ./...
make install # 安装到系统
homed 只能在 Linux 上跑(依赖 fd 继承与共享内存段的段内偏移解引用)。
Windows 上构建 waiter.exe,homed 跑在 WSL2 里;macOS 要原生构建 homed。
许可
以 AGPL-3.0-only 发布,全文见 LICENSE。
随包的第三方组件保持各自许可,全文在 /usr/share/doc/hologramagent/licenses/。
随包分发的第三方组件
| 组件 | 许可 | 位置 |
|---|---|---|
| Chinese-CLIP ViT-B/16(ONNX 产物) | Apache-2.0 | /usr/lib/hologram/models/chinese-clip-vit-b16-onnx/ |
ONNX Runtime(libonnxruntime.so) |
MIT | /usr/lib/hologram/onnxruntime/ |
| jieba 词库(内嵌进二进制) | MIT | 源码 internal/memory/jiebadict/ |
| Go 依赖(go-sqlite3、gojieba、bubbletea 等) | MIT / BSD-3 / Apache-2.0 | 均为宽松许可,与 AGPL-3.0 兼容 |
发行包在 dep/rpm 元数据里声明本包许可为 AGPL-3.0-only。