|
|
8372f5bd8f
|
fix(scheduler)!: 撤掉“优先级=可配置策略表”的错误设计,回归内核内部属性
用户指正:**优先级是内核内部属性**,不是配置项,更不该由插件声明。
我此前把它建模成“策略表 + 字符串解析”,甚至准备接配置中心
(core.agent.priority.<channel>)——方向性错误,故整体撤销。
撤销:
- 删除 ParseLevel(字符串解析只服务于“外部可配”这个错误前提)
- 删除 AgentConfig.PriorityLookup / Agent.priorityLookup 及 taskLevel 中的查表分支;
taskLevel 回归为纯内核内部规则(cli/webui/http→L3,system/_consolidation_→L1,
其余 L1),注释明确“不对外暴露、不做运维可调项”
- 设计稿 §3.2 改写为“内核内部属性,不做成配置项”,并删除 §15 里
“ChannelDef.Priority / InjectOptions.Priority 进公开 SDK”这一方向(同属外化)
- 未触碰配置中心(registry.go/main.go 的优先级配置一行未加)
同时落地 D6(与本撤销无关、此前遗漏的承诺):
- AgentConfig.MaxToolTurns + runTaskSteps 在发起新一轮 LLM 前按 f.Turn 收尾;
0 = 不限;cmd/homed/main.go 从既有 core.agent.max_tool_turns 取值
- 新增 task_test.go 2 项:上限 3 时恰好跑 3 批工具/3 次 LLM 并收尾;
0 = 不限(跑完脚本)
验收:agent 全量 + -race;全仓 build/vet 通过
|
2026-09-13 06:17:49 +08:00 |
|
|
|
98d67559d1
|
fix(scheduler): 补齐 M3 与设计稿的两处语义偏离(发现即修)
两处都不是风格差异,而是真的偏离设计语义(其一为回归),
已按“先写判据确认失败、再修”的方式处理,判据保留为回归测试。
1. 空闲时到达的中断永远不会被处理(设计 §5.1 ③ 未落地)
调度器空闲时只阻塞在 select{InputChan, selfInputCh, ctx.Done},
而 pendingInterrupts 不是 channel——interceptLoop 把中断入队后
没有任何东西唤醒调度器,中断要等“下一条输入”才被看到。
修复:调度器加 wake channel,enqueueInterrupt 非阻塞 signalWake,
空闲分支增加 wake 分支。
2. 临界区内只“不让位”却仍被“取消”(设计 §4.3/§5.2)
requestPreempt 不判临界区,interceptLoop 照常 cancelLLM,
于是正在流式的记忆整理被中断,stepLLM 以 error 提前结束——
整理任务被砍掉一半,而设计要求的是“请求排队等它结束”。
修复:scheduler 增加原子 critical 标志(帧仍只由调度器读写),
runInputTask 在 prepare 后设置、结束(含挂起)时清除;
requestPreempt 在临界区内不 arm、不取消,中断只入队。
- 新增 scheduler_regression_test.go 2 项(先失败后通过)
- 验收:agent 全量 + -race;全仓 vet 通过
|
2026-09-13 06:10:44 +08:00 |
|
|
|
6bd3313a22
|
docs(scheduler): 回写 M1–M7 实现状态与 5 处实现期偏差
- 里程碑表补提交号与验收结果
- 记下与设计稿的偏差:M3 拆分、a.mu 移除、interceptCh 删除、
M0 伪时钟未做(用阻塞 provider 替代)、工具执行临界区由结构保证
|
2026-09-13 00:48:20 +08:00 |
|
|
|
f11de37bf2
|
feat(scheduler): M7 可观测性 + 压力测试 + 端到端测试
设计依据 docs/zh/input-scheduler-design.md §11.5(O1/O2)、§11.6(E1/E2)。
- 可观测性:KernelStatus 新增 Scheduler 段(running/三集合深度/计数/
深度上限),由 GetKernelStatus 从 DumpScheduler 原子快照填充;
新增 events.EventScheduler,挂起/恢复各发一条(action/task/level)
- TaskKind.String() 便于日志与状态输出
- 新增 scheduler_e2e_test.go 3 项:
· 压力:200 排队输入 + 50 中断全部经真实 loop 执行,结束时三集合排空、
LLM 调用数精确等于输入数、无 Rejected
· 可观测性:挂起/恢复事件齐备,状态快照计数一致
· 端到端:完整启动 schedulerLoop+interceptLoop,经真实 channel 投递
L1 任务与 L4 中断,验证「LLM 流式中断 → 挂起 → 中断先完成 → 原任务恢复」
整条链路(LLM 调用数 = 丢弃1+中断1+恢复1+常规1)
- 验收:agent 全量 + -race;全仓 build/vet 通过
|
2026-09-13 00:45:28 +08:00 |
|
|
|
4e4e0ad656
|
feat(scheduler): M6 任务级回执 + 断链点统一为终态事件
设计依据 docs/zh/input-scheduler-design.md §7(I5)、§11.3(X1/X2/X4)。
- 新增 emitSkippedReply:跳过路径(去重命中、空输入)给同步调用方一个
skipped 终态,但**不发 agent_output 事件**(避免 WebUI 聊天记录凭空多出
空消息)。修复前 cli/clawhubadapter 这类无超时的同步注入在去重命中时永久挂起
- 回执按任务归属:中断任务的回执只写自己的 ResponseCh,绝不误投给被挂起的等待者;
被挂起任务恢复并结束后才拿到自己的回执
- 新增 task_terminal_test.go 3 项:X1 回执不误投、X2 空输入有 skipped 终态、
X4 无超时同步调用方 1s 内拿到终态(回归判据)
- 更新 M3a 去重用例:由「不得回执」改为「必须有 skipped 终态」
- 验收:agent 全量 + -race;全仓 build/vet 通过
|
2026-09-13 00:39:56 +08:00 |
|
|
|
a971fc8877
|
feat(scheduler): M5 饥饿防护(抢占计数提升有效级 + 抢占冷却)
设计依据 docs/zh/input-scheduler-design.md §9、§11.5(G1/G2)。
- Task 增加 PreemptCount / LastPreemptAt;effectiveLevel(t) =
min(L4, Level + min(PreemptCount, 2)):被抢占越多越“值钱”,
逐步追上抢占它的流,但封顶 L4 因而抢不过真正的紧急输入
- 选择函数 taskBefore 改用有效级;requestPreempt 与 preemptGrantedFor
同样以有效级比较
- 抢占冷却 preemptCooldown=2s:刚被抢占的任务期内不再被抢占,
避免同一任务被反复打断到永不完结
- 新增 scheduler_starvation_test.go 4 项:提升与封顶、冷却期内不得再抢占、
提升后同级不得抢占而更高可、选择函数确实用有效级
- 验收:agent 全量 + -race;全仓 build/vet 通过
|
2026-09-13 00:38:30 +08:00 |
|
|
|
7565248f61
|
feat(scheduler): M4 临界区显式化 + 清掉被 pendingInterrupts 取代的 interceptCh
设计依据 docs/zh/input-scheduler-design.md §4.3、§11.1(P5/P6)。
- 临界区语义显式化:让位检查**只在 step 之间**做,执行中的 step
(工具 RPC / ONNX / CAS 落盘)天然不可抢占;_consolidation_ 整任务
经 inCriticalSection() 判为不可抢占(它直接改图库)
- 删除 interceptCh 与 drainInterrupts:M3b 起中断一律走 pendingInterrupts,
旧的「同行注入 + 三处 drain + 批次放弃」已无写入者,属死代码
- 新增 scheduler_critical_test.go 3 项:
P5/P6 工具执行中 arm 了让位信号也不得挂起、必须等工具返回后的安全点;
_consolidation_ 判为临界区;无抢占时同批工具必须全部执行(新语义回归)
- 验收:agent 全量 + -race;全仓 build/vet 通过
|
2026-09-13 00:34:39 +08:00 |
|
|
|
c69a1f11af
|
feat(scheduler): M3a+M3b 任务生命周期重构 + 四级优先级抢占
设计依据 docs/zh/input-scheduler-design.md §3–§8、§14。
M3a(行为等价的所有权重构):
- processInput 拆为 prepareInputTask / runTaskSteps / finishInputTask,
帧覆盖 prepare→step…→finish;提交与回执只在 finish 段发生一次,
为安全点挂起做准备(挂起不重复提交)
- process() 不再持 a.mu(挂起不能持锁),a.mu 字段随之移除
- TaskFrame 增加任务层现场(Evt/CleanInput/IsInterrupt/StartedAt/Terminal/
Level/SeedMsgs)与 taskTerminal / outcomeSuspended
- 新增 task_lifecycle_test.go 5 项:正常恰好一次终态、去重 skipped、
on_input 短路、错误终态、consolidation 路由
M3b(优先级与抢占):
- interceptLoop 重写:只做「收中断 → 定级 → requestPreempt → 必要时取消
LLM」,绝不触碰帧(不变量 I2);三条降级路径与 interceptCh 兜底退场,
改为统一的 pendingInterrupts
- scheduler:pendingInterrupts / suspendPool / 让位信号,nextRef 在三集合上
按统一排序键取值;深度上限 4(canSuspend 在安全点拦下)
- 抢占判据 incoming.level > running.level;相等与更低只入队
- 安全点只在 step 之间;执行中的 step(工具 RPC/ONNX/CAS)天然不可抢占;
_consolidation_ 整任务视为临界区
- 恢复走 resumeTask:从 frame.Step 继续,不重跑 prepare
- D1=A:suspend 把被打断任务的只读前缀交给抢占比它的中断任务(SeedMsgs)
- 新增 scheduler_preempt_test.go 5 项:抢占-挂起-恢复(含 R1/R5)、同级更低
不抢占、深度上限、空闲中断不丢、seed 路径不污染标志位
验收:agent 全量 + -race 通过;全仓 build/vet 通过
|
2026-09-13 00:25:52 +08:00 |
|
|
|
716ee46471
|
docs(scheduler): M3 拆为 M3a(所有权重构)/M3b(抢占语义)
真正的中途挂起要求帧跨越 prepare→step…→finish 全生命周期;若只让
process() 可挂起,processInput 会在挂起返回后继续 context.Append 与
emitResponse,造成重复提交。故 M3 分为两步:M3a 行为等价的所有权重构
(含移除 process() 整轮持有的 a.mu),M3b 再引入优先级与抢占。
|
2026-09-12 23:51:13 +08:00 |
|
|
|
7082a50365
|
feat(scheduler): M2 调度器骨架(就绪队列 + 选择函数 + 快照 + 任务级 panic 隔离)
设计依据 docs/zh/input-scheduler-design.md §14 M2。
- 新增 scheduler.go:四级 Level 常量(默认 L1,显式才是特权)、
Task/TaskKind、scheduler(有界队列/running/统计)、纯函数 pickTaskIndex
(排序键 -Level → EnqueuedAt → ID)、schedulerLoop/pumpInbox/executeTask、
DumpScheduler 原子快照
- eventLoop 退场,职责由 schedulerLoop 承担;M2 全部任务为 L1,
因而行为等价于原先的 channel FIFO
- 每任务 panic 隔离(不变量 I6):panic 只失败该任务,调度器存活,
取代原先「重启整个循环」
- Start() 改起 schedulerLoop;interceptLoop 暂不动(M3 重写)
- 新增 scheduler_test.go:Q1 排序 4 组、Q4 背压、生命周期、K1 panic 隔离、
O1 快照一致性、Level 取值契约
- 验收:agent 全量 + -race 通过
|
2026-09-12 23:49:55 +08:00 |
|
|
|
9a588780b9
|
refactor(scheduler): M1 把 process() 拆成 step 状态机 + TaskFrame(行为等价)
设计依据 docs/zh/input-scheduler-design.md §14 M1。
- 新增 task.go:Step 游标、TaskFrame,以及 stepPrepare/stepLLM/
stepToolBegin/stepToolExec/stepToolAfter/stepTurnEnd 六个 step;
原函数内嵌的 provider 回退/重试抽为 resolveProviders + callLLMWithFallback
- process() 改为驱动状态机的薄壳:签名不变,调用方(processInput/
processConsolidation/测试)零改动
- StepToolExec 显式标注为临界区:工具副作用不可回滚,执行中不是安全点
- 步数上限护栏:转移缺失时以错误退出而非死循环
- 新增 task_test.go:R3(工具往返结果与配对正确)、X3(多轮必然终止)、
批中途中断必须放弃剩余工具、未知 step 必须失败退出
- 验收:既有 agent 全量测试通过;-race 通过(TMPDIR 指向真实磁盘,
/tmp tmpfs 已 98% 满会导致链接失败,与代码无关)
|
2026-09-12 23:47:12 +08:00 |
|
|
|
06edff1af2
|
docs(scheduler): 输入调度器设计稿(四级优先级/可抢占/现场保存 + 测试点与里程碑)
背景:现状排队与中断两条语义建立在串行 eventLoop 上,存在队头阻塞、
中断三条隐式降级路径、回执无任务归属、断链点静默、背压策略分裂、
假取消、不可观测七个已确认问题。
设计:四级内核预定义优先级(严格大于才抢占)、显式临界区、
单调度线程 + 单中断线程、step 化任务帧与安全点、suspendPool/pendingInterrupts/
readyQueue 三集合统一选择函数、任务级 responseCh。
含 11 组测试点(优先级/恢复/回执/队列/深度饥饿并发/端到端),
测试方式与预期结果逐条写明;M0–M7 里程碑逐步实现。
公开 SDK 在 v1 保持冻结(diff 必须为 0)。
|
2026-09-12 23:39:10 +08:00 |
|
|
|
163c5f70b3
|
chore(sdk-mirror): 同步 vendored SDK 到 SDK main(browser_search 现代 Bing 版式解析修复)
镜像追平 SDK main(`ebd700e`):
- browser 示例:browser_search 三处根因修复(www.bing.com 302 → cn.bing.com;
标题取 h2 > a 而非块内第一个 <a>;摘要兼容 p.b_lineclamp*)并解开 /ck/a 跳转包装,
解析不出结果时显式报错;附真实响应夹具与 5 项单测;版本 2.4.0 → 2.4.1。
注:SDK 仓新增的 example/deepsearch(联网检索 + SearXNG 生命周期托管)与
example/vikunja(任务管理)按本仓策略(.gitignore 忽略 example/ 下未跟踪文件)不入镜像。
|
2026-09-12 23:39:10 +08:00 |
|
|
|
ca5e62f775
|
docs(plan): 记忆与多模态目标纠正(媒体为一等节点、同一指纹空间、音频 unsupported)
把计划里过时的描述式索引路线改正:媒体/媒体块是 L3 一等节点与原生边,
multimodal doc/context 的向量与裁剪须与 text/媒体块在同一指纹空间共同参与;
音频在该模型下明确 unsupported(不做文本描述式索引)。
|
2026-09-12 20:21:01 +08:00 |
|
|
|
07e08352b9
|
feat(waiter,devicebridge): 设备桥连接带授权态,命令处理器类型统一
- `runDeviceBridgeLoop`/`connectDeviceBridge` 增加 `authorized` 入参(未授权不再尝试建立桥连);
- `Bridge.OnCmd` 的处理器类型改为具名 `BridgeCmdHandler`,与 waiter 侧签名对齐。
|
2026-09-12 20:21:01 +08:00 |
|
|
|
8f40b91dea
|
feat(ohos): 设备桥会话管理、状态/设备信息载荷与能力路由完善
- 新增 `DeviceBridgeSession.ets`:会话生命周期(创建/复用/回收)与 TTS 会话显式 shutdown;
- `BridgeCaps`/`BridgeRouter` 与内核实际支持的本机命令保持一一对应,补齐 status/deviceinfo;
- 页面与状态存储调整(DevicePage/Index/SettingsPage/StatusStore/SubPage)、新增 ScreensuePage;
- module.json5 与 string.json 同步(新增页面与文案)。
|
2026-09-12 20:21:01 +08:00 |
|
|
|
d1662e77cf
|
chore(sdk-mirror): 同步 vendored SDK 到 SDK main(qq 插件重写、browser 会话超时必填、路牌 1.3.0)
核心仓 `third_party/homeagent-sdk` 是 SDK 仓的 vendored 副本,内容以 SDK 仓 main 为准
(本仓 `.gitignore` 忽略 example/ 下的未跟踪文件,已跟踪的镜像文件用 `git add -u` 更新)。
本次把镜像追平到 SDK main(`8c10b7e`):
- qq 插件重写(+498 行):`output_send` 回声/自激路径的根因修复与去重;
- browser 示例:交互式会话 `timeout` 由默认 10m 改为**必填**(附参数校验与测试);
- `meta.Version` 1.2.0 → **1.3.0**(SDK v1.2.0 已定版,SDK main 按纪律推进到下一个中版本)。
|
2026-09-12 20:21:01 +08:00 |
|
|
|
dc4982464d
|
fix(packaging): SHA256SUMS 只列本批产物,且用平铺名(此前会带上历史版本、且与附件名不符)
v1.2.2 出包时发现:dist/ 跨多次构建累积,而清单用 `find $DIST_DIR` 全目录扫,
于是 SHA256SUMS 里混进了 1.2.0/1.2.1 的包名——用户从发布页下载这份清单后
`sha256sum -c` 必然报「文件缺失」(那些包并不在本页)。
两处一起修:
- 按本批 `$PKG_VERSION` 过滤(只列这次真正打出来的产物);
- 名字用 basename(平铺名),与发布页附件名一致;哈希取真实路径(此前若直接
对 basename 求哈希会找不到文件——就在 `deb/`、`tar/` 子目录里)。
验证:对含 1.2.0/1.2.1/1.2.2 的 dist/ 跑新逻辑 → 4 条(旧逻辑 12 条);
并拿发布页真下载的 SHA256SUMS 逐条核对四个产物的实际哈希 → 全部一致。
|
2026-09-12 19:18:26 +08:00 |
|
|
|
fe883d5362
|
test(plugins): 真实插件产物先校验平台再 exec,错误平台给可读提示而不是 exec format error
排查知识库改动是否引入回归时,internal/plugins 6 个测试全红、报
"fork/exec .../plugin.bin: exec format error"。查了半小时才发现与代码无关:
早前验证跨平台示例构建时,最后一次构建(darwin/arm64)把
`example/*/build/plugin.bin` 覆盖成了 Mach-O arm64,而 realPluginBinary
只按文件名找候选、**不校验平台**,于是拿 macOS 产物在本机 exec。
两个陷阱一起堵:
- 校验魔数(ELF / Mach-O / PE),平台不符则 **SKIP 并给出可直接粘贴的重建命令**
(`hmapdev build --target <host> --no-bundle`),不再用误导性的 exec format error;
- 明确写出「产物缺失即 skip」的语义,避免"全绿其实什么都没验"
(本次对照实验里,改动前的 worktree 因无产物而全绿,看着像通过)。
反向验证:把 weather 产物换成 PE 假头 → 相关测试转为 SKIP 且提示平台与重建命令 ✓。
|
2026-09-12 18:38:24 +08:00 |
|
|
|
5fbd6514c2
|
fix(knowledge): 知识库检索改为「稠密 + 词法」两路融合(真实 KB 自检索 MRR 0.271→0.376)
追「实例看起来没更新」时发现知识库检索本身也不可信,先把病因查清再动手:
- **两段式召回不是瓶颈**:Store 的结果与全量暴力 cosine 完全一致;
- **真因是向量没有区分度**:词向量取平均后各向异性明显,真实 KB(33 条)上自检索
top-1 只有 15%、前两名平均只差 0.013,排序基本是噪声;
- 且全为停用词的查询会得到**空向量**("最近更新"),直接搜不出任何东西。
先在真实数据上把候选方案量了一遍(用自检索 top-1 / MRR)再动手:IDF 维度加权零收益、
去均值反而更差,**都不做**;唯一有收益的是与词法路(TF-IDF)融合。
改动:
- `Store` 增设词法路索引,`Search` 融合两路:各自按**查询内最大值**归一化后加权。
权重 0.5 由权重扫描定:1.0(旧行为)MRR 0.271 / 0.8→0.354 / 0.7→0.358 / **0.5→0.376** /
0.3→0.336 / 0.0→0.307;语义查询也从"全是 openharmony 噪声"变成命中正确条目
(「首启人格门禁」→changelog_v1.2.1、「插件怎么开发和部署」→plugin_dev_build);
- `vector.Store` 的候选中选阈值改为**可设**(默认 0.05 保持既有行为):TF-IDF 余弦量级
只有 0.0~0.2,沿用 0.05 会把词法路有效候选**静默砍掉**——这一条正是 0.376→0.197 的
差距来源,且当时没有任何报错;
- Add/Remove/scanAll/ReindexWithVectorizer 同步维护两路;分数相同时按名字定序(结果可重复)。
**顺带修一个真实毛病**:Add/Remove 原先用**无追踪的 goroutine** 写索引(因为
writeIndex→BuildTree 会 RLock,而调用方持写锁,同步调用会死锁)→ 失败只打日志,
且与调用方竞态(测试的临时目录清理就撞上了)。改为持锁就地 flush
(buildTreeLocked / writeIndexLocked)。
判据(不依赖人工标注问答对):新增 `internal/knowledge/rankdiag_test.go`,用**自检索
top-1 / MRR** 量区分度,`KB_DIAG=1` 跑、`KB_DIAG_ASSERT=1` 断言(MRR ≥ 0.34)。
另有不依赖真实数据的单测 6 条(空稠密向量靠词法路救回、稠密并列时词法路定序、
词法路阈值接线、Add/Remove 双路一致、并列时确定性、空库不 panic)。
**反向验证**(证明判据真能发现缺陷):权重退回 1.0、词法路阈值改回 0.05、
把阈值写死回 0.05 —— 对应测试逐条变红。另:我第一版夹具余弦 0.365/0.273 远高于阈值,
注入缺陷也不报错(等于没验),故加了「夹具前提」断言并改成两层判据
(语义层由 vector 包测试证明、接线层由知识库测试钉住)。
顺带纳入上一轮漏提交的 `TestAddOverwriteReplacesVector`(同名覆盖必须摘掉旧向量,
生产改动当时已提交,测试一直未入库)。
|
2026-09-12 18:14:22 +08:00 |
|
|
|
5aaae93367
|
feat(healthcheck): 内核状态快照报出内核版本号与 ONNX 模型启用状态
healthcheck_kernel 此前没有任何「ONNX 模型是否在用」的信息,只报「向量可用/不可用」,
分不清「统一多模态空间已加载」与「退回到词嵌入/TF-IDF 路径」;人格卡要求
「版本以运行时快照为准」,也缺一个可查字段(build.version 早就在,但没人知道)。
- KernelStatus 新增 onnx 段:enabled / provider / dim / fingerprint / modalities / reason。
判据取 Loaded()(provider 真正打开且元数据合法),**不是**「配置里写了 provider」
—— 后者在模型缺失 / 运行时缺失时也为真,报出去就是假绿。
- 未启用时 reason 给**具体原因**:未配置(说明会走回退路径)/ 打开失败的具体错误。
homed 把「配置的 provider 名」与「打开失败原因」透传给 Agent,仅供状态报告。
- ProviderAdapter 新增 Modalities()(可选能力,按接口断言取用,不改公开契约)。
- healthcheck_kernel 的工具描述同步说明它回答这两件事。
**顺带修一个真实 panic**:collectKernelStatus 的 knowledge 是**接口**参数,
(*knowledge.Store)(nil) 塞进接口后 `ks != nil` 仍为真 → 调 List() 直接 panic,
而 healthcheck_kernel 正是走这条路径(panic 发生在工具 goroutine 里)。
GetKernelStatus 改为先按具体指针判空、再赋给接口;并加刻画测试钉住这个成因
(一旦不再 panic 说明参数形状已变,守卫与该测试应同步删除)。
验证:单测 4 例(已启用 / 打开失败 / 未配置 / 未加载)+ 刻画测试;
隔离实例 E2E 7/7:正例 provider=chineseclip → enabled=true、dim=512、模态 2;
反例 provider=nonexistent → enabled=false 且 reason 含具体错误与 provider 名,
真实对话仍通。
|
2026-09-12 14:56:25 +08:00 |
|
|
|
46e014a8ca
|
feat(persona): 首启人格门禁跨通道化 + 内核 persona_set 工具
WebUI 首启向导只覆盖 WebUI 这一条通道,而「人格该问一次」是所有通道的事:
走 QQ / CLI / ACP / 邮件来的人永远见不到那个向导,人格就永远是没确认过。
- 门禁移到 buildSystemPrompt(每轮重建 → WebUI/QQ/CLI/ACP/邮件全覆盖),
以 core.internal.persona_initialized 为准:未确认时要求模型主动询问用户
(默认 / 自定义 / 以后再说),确认后该段消失;personaStore 为 nil 时静默关闭。
- 新增内核内置工具 persona_set(mode=default|custom|later[, content]),
落库逻辑与 WebUI 向导**共用 internal/config**(一个实现 + 两个薄入口:
ConfigRegistry 直连 / 插件侧 SettingsAPI),避免两套语义各自漂移。
- AgentConfig 增加 PersonaStore 接口,cmd/homed 用 RegistryPersonaStore 实现。
- 非法输入(未知 mode / custom 空内容)在打标记**之前**拒绝:否则标记置位、
向导被跳过,用户再没机会设。
E2E(隔离实例 + 真实 LLM 往返走 /v1/chat/completions,/var/tmp/persona/e2e.sh)9/9 PASS:
未确认时模型主动询问 → 用户答「用默认的」→ 模型调用 persona_set 落库并置位标记
→ 之后不再追问;反向对照(清标记 + 清会话上下文 + 重启)重新开始询问,
排除了「同一段对话里已问过」这一混淆。
|
2026-09-12 13:57:06 +08:00 |
|
|
|
ea21803acb
|
docs(branching): 明确开发者文档的发布归属——以 rel 分支的形态为准,再合入 main
用户裁定:开发者文档应当在每个 rel 分支被修正为对应 rel 的形式,随后合入 main。
新增 §二.7,写清:
- 规则与做法(release 上按本版口径改 → cherry-pick 到 main,遵守 §三 只 pick 不 merge)
- 为什么不能直接改 main:main 语义是「下一个未发布版本」;assets/docs 会随发行包
分发并在 WebUI 被阅读,服务的是「这一版」;版本号/工具名/机制有无都随版变动
- main 上描述「下一版才有」的行为必须显式标注(如「(下一版)」)
- 反例表(本仓真实踩过):人格卡写死 v0.9.0 + 已删除的 C ABI、架构文档把已移除的
描述式索引/引用计数写成现行、README 停在旧版本
- 配套硬约束:任何会被当作事实的文本不得写死版本号,须插值或读运行时快照并加测试
|
2026-09-12 13:05:24 +08:00 |
|
|
|
10367b384e
|
feat(webui): 首启人格向导(默认 / 自定义 / 稍后)+ 一次性标记
接续人格配置项化(597f07c):现在人格是 core.agent.personal_prompt,
本次加上「首启问一次」的界面,之后不再打扰。
后端(GET/POST /api/v1/persona):
- GET → {initialized, current_prompt, file_override}
未设置时 current_prompt 回落到内置默认模板;存在 personal/personal.md
时报告 file_override(它会覆盖配置项,向导据此提示用户)
- POST → {"mode":"default"|"custom"|"later","content":"…"}
写配置 + 打一次性标记 core.internal.persona_initialized;
custom 返回 restart_required=true(人格在启动时载入);
「稍后」= 保留当前默认 + 打标记,**绝不阻塞任何流程**
- 空内容的 custom 与未知 mode 一律 400,且**不打标记**(否则向导会被跳过)
前端(dashboard.html):
- 首启拉一次 /api/v1/persona,未初始化则弹向导(复用一直没人用的 .confirm-* 样式)
- 「自定义…」第一次点击展开文本域并预填当前人格,再次点击才提交(避免误提交)
- 中英双语走既有 __() 机制;保存失败/空内容用 toast 提示
测试:TestPersonaWizardFlow(首启状态、later 打标记不改人格、custom 写入 + 需重启、
空内容与未知 mode 被拒且不打标记)、TestPersonaWizardReportsFileOverride。
E2E(真实实例):首启 initialized=false → POST later → initialized=true,
config 中标记=1、人格键为默认模板;前端页面含向导函数。
|
2026-09-12 13:02:18 +08:00 |
|
|
|
ce8bc27db8
|
docs(architecture): 记忆流转图对齐统一多模态空间
流程图里 Context/Prune/DocStore 三行仍只写 StaticEmbedder 与 TF-IDF,
读起来像"向量化只有词嵌入一条路",与 1.2.0 实际(多模态统一空间为主,
带 fingerprint;词嵌入/TF-IDF 是降级层)不符。
- Context Append:补三层向量层级说明
- Context Prune:改为 DenseCosine(仅同指纹比较)→ StaticEmbedder 回退
- DocStore:改为稠密向量 + dense_fp 同指纹要求(不符即重算)
- 中英双版同步
|
2026-09-12 12:46:21 +08:00 |
|
|
|
7318a10828
|
feat(persona): 人格设定配置项化 + 默认模板契约测试 + 腐坏告警
起因(v1.2.0 压测):线上实例内核日志/接口都报 1.2.0,agent 被问版本时却按人格卡
自述 v0.9.0 + C ABI v2(该机制 v1.0.0 已删除)。根因是人格只有「文件」一个来源且无人
维护——写死的版本号必然随发版腐坏。
改动:
1. 新增配置项 core.agent.personal_prompt(多行文本),默认值为内置模板
config.DefaultPersonaPrompt,随其它默认值同批播种(老安装不注入,语义不变)
2. 默认模板**不含任何版本号字面量**,并显式要求「被问到版本/构建信息时以运行时快照
(healthcheck_kernel)为准」——从根上消掉这类腐坏
3. 人格来源优先级:personal/personal.md(存在且非空)> 配置项 > 无
启动日志明确打印来源;文件含腐坏内容(版本号字面量 / 已删除机制的说法)时告警并
建议迁移到配置项
4. internal/agent.PersonaStaleHints:腐坏检测(版本号正则 + 已删除机制词表)
契约测试(防复发):
- TestDefaultPersonaPromptHasNoVersionLiterals:默认模板不得含 v?\d+\.\d+\.\d+,
且必须含「运行时快照」要求
- TestPersonaPromptRegisteredWithDefault:注册存在、默认值一致、播种真的写入
- TestPersonaStaleHints:线上人格卡原文必须被识别(v0.9.0 / C ABI v2),干净文本不误报
验证:go build ./cmd/homed ok;go vet 三个包 ok;go test ./internal/config ./internal/agent ok;
端到端两场景(无文件→来源=配置项 1307 字节;有旧文件→来源=文件 + 告警列出 v0.9.0 与 C ABI v2)。
|
2026-09-12 12:42:43 +08:00 |
|
|
|
b15d0bd114
|
refactor(plugin)!: 重编提示与模板路径改用 hmapdev;文档全面对齐 1.2.0
工具链在 SDK 1.2.0 更名为 hmapdev(原 plugindev)。核心侧三处功能耦合同步:
1. 用户可见报错:旧 C ABI 产物 / 协议版本不匹配 / 共享段版本不匹配
三处「请用配套 plugindev 重编」→ hmapdev(对应两条测试断言同步)
2. e2e_template_test 的模板路径改为 tools/hmapdev/templates,
并保留旧路径回退(旧 SDK 检出仍能跑测试)
3. 注释与文档同步
文档更新(用户可见面):
- assets/docs/{zh,en}/PLUGIN_DEV.md:工具链章节整体改为 hmapdev,
补改名说明与 SDK 存储目录迁移;命令示例全部更新
- assets/docs/{zh,en}/ARCHITECTURE.md:**流程图与章节对齐 v1.2.0** ——
· 向量化章节改为三层降级:统一多模态空间(主)→ 词嵌入 → TF-IDF(回退),
写明「同指纹且同维度才参与融合」
· 媒体记忆章节重写:媒体是一等记忆块(无独立 GC / 无引用计数 / 无描述式索引 /
正文不再写 media marker),并写明 reembedStaleMedia 的跨空间迁移与写回
- README{,_EN}.md、docs/zh/plugin-interface-matrix.md:工具名与模板路径同步
(历史条目标注「当时名为 plugindev」)
验证:go test ./internal/plugin/ ./internal/plugin/proc/ ok,
含 4 条真实模板 E2E(模板路径切换后仍通过)。
|
2026-09-12 12:42:43 +08:00 |
|
|
|
34628720f2
|
fix(memory): 修 beta.2 压测发现的三个向量/文档缺陷
来源:v1.2.0-beta.2 全方位压测(报告 /var/tmp/stress/REPORT.md)
1. 文档向量迁移结果不落盘(生产已复现)
- BuildDenseIndex 改完内存不置 dirty;docStore.Stop() 全仓无调用者 → flush 成死代码
- 后果:每次启动重算同一批文档(线上 496 篇约 17s),磁盘 dense_fp 永不收敛
- 修:迁移当场落盘(抽出 flushLocked 以免重入锁)+ main.go 关停链 defer docStore.Stop()
- 生产证据:线上 488 篇文档仅 4 篇含 dense_fp,且这 4 篇均为运行期 Insert 的新文档
2. 块指纹对但维度错时污染文档向量(健壮性缺口)
- denseFor 只校验 b.Fingerprint,不校验长度;FuseVectors 取最大维度并跳过长度不符者
→ 512 维文本 + 2048 维块 = 2048 维且打上当前指纹
→ 该文档在检索侧被长度守卫永久跳过,且每次启动重算(不收敛)
- 修:denseFor 要求 len(b.Vector) == 空间维度
- 可达性:内核两个块产出点均成对取自同一行(it.Vec ↔ it.VecModel),故属防御性修复
3. Insert 与 loadAll 的 ID 约定不对称(低)
- Insert 落盘任意 <id>.json,loadAll 只加载 doc_ 前缀 → 自定义 ID 文档重启后静默消失
- 修:loadAll 只要求 .json(空 ID 仍跳过)
回归测试 4 条:迁移跨重启落盘 + 已对齐 0 重算(用向量空间调用计数判定,
不靠日志)、坏块不参与融合且同维度正常块仍参与、自定义 ID 可加载、Stop 落盘。
反向验证(纪律要求):临时回退本次修复后,前 3 条均变红且报错正是缺陷签名
(dim=999/space-OLD-999 永不收敛、文档向量 2048 维、文件重启后消失);恢复后全绿。
验证:go build ./cmd/homed ok;go vet ./internal/memory/document ./cmd/homed ok;
go test ./internal/memory/... 7 包全绿。
|
2026-09-12 11:05:11 +08:00 |
|
|
|
85223fc1c9
|
docs(matrix): 记录 v1.2.x 的接口扩展,以及本次审计抓出的两处漂移
§九 原本只写到 v1.1.x。补上 1.2.0 的接口增量(注入侧的 InjectOptions 与六个
*Opts 变体、ContextPolicy 取值、ChannelDef.ContextPolicy 与它的 JSON tag),
并明确一件容易被误读的事:**「接口纯追加」不等于「无需重编」**——同版把插件运行
协议升到了 2(fd3 布局改变),协议不匹配会在握手时被明确拒绝,这两件事必须分开说。
同时把这次扩展自己抓出来的两处漂移入档(都属于本节第 3 条要防的类型):
1. 模板接线守卫 `TestProcTemplate_CoversAllCoreMethods` 红了:模板不再发
io.injectTextNoMem(改走 io.injectText + NoMemory),而内核保留该 id 是刻意的
向后兼容面。修的是判据(显式 deprecated 表 + 反向保护)。
2. mocksdk 与公共 SDK 机械求差,差集为旧的三参数 InjectInputSync(通道类插件闭环
要调的方法);git log -S 证实从来就缺,已补齐。
验证表按**本机实跑结果**填写:示例 vet 17/17、plugindev 测试全绿、
sdk -race -count=5 通过、mocksdk 差集为空。
|
2026-09-12 09:41:39 +08:00 |
|
|
|
dc80855540
|
feat(status): 暴露构建源码地址,WebUI 状态页给出 AGPL §13 的源码入口
选了 AGPL-3.0-only 之后,§13(Remote Network Interaction)就不只是声明问题:
把修改过的版本作为网络服务提供出去时,必须给使用者取得 Corresponding Source 的机会。
只在仓库里放 LICENSE 并不自动满足这一条——**使用者拿到的是服务,不是仓库**。
所以把它做进产品里,而不是写进文档就算完:
- `internal/meta.SourceURL`:本次构建对应的源码地址,默认指向本仓库。
注释里写明「修改后对外部署的分支必须改指向自己的仓库」,并给出 -ldflags 覆盖方式
(`-X .../internal/meta.SourceURL=<你的仓库>`),不需要改源码。
- `BuildStatus.SourceURL`(`json:"source_url,omitempty"`)+ 内核状态填充:
走已有的 `/api/v1/kernel` 构建身份链路,不新增端点。
- WebUI 状态页在「版本」行下渲染「源码 / Source」链接(URL 经 escHtml 转义后进属性;
`target="_blank" rel="noopener noreferrer"`)。
- 用 `omitempty`:未注入该值的旧构建不会在 JSON 里多出一个空字段。
验证:
- `internal/plugins/webui/dashboard.html` 的两个内联 script 块 `node --check` 均通过;
- 全仓库 `go build ./...` 通过,`internal/meta/meta.go` gofmt 干净
(`internal/agent/core/status.go` 有**既有**的 gofmt 差异,与本改动无关,按纪律未整体重排);
- 端到端:本地构建 homed → 冷启动 → `GET /api/v1/kernel` 的 `build.source_url`
实测为 `https://gitcode.com/JianFeeeee/HomeAgent`。
|
2026-09-12 09:41:39 +08:00 |
|
|
|
0b1c201b5c
|
docs: README 更新到 v1.2.0,并纠正已在 v1.2.0 删除的机制
README 的「项目状态」停在 **v1.1.1**,而且其中两条描述与当前实现**相反**:
「媒体以 `[<mime> <短digest>] <描述>` 标记存在于纯文本记忆中,描述是可检索的
语义记忆」与「引用计数式 GC(有引用者绝不删)」——这两套机制正是 v1.2.0 拆掉的。
本次不重写历史条目(它们记录了演进),而是:
1. 「项目状态」顶部新增 **v1.2.0** 条目:统一多模态向量空间(模型中立 SPI +
Chinese-CLIP 默认,含「纯文本语义弱于 MLLM 型嵌入器」这一已知代价)、
媒体升为图记忆一等节点(并写明具体删掉了什么)、数据面全量走共享内存
(协议 2、不支持滚动升级)、注入标志位、发行包默认启用 ONNX 且随包模型、
homed 改走 WSL2、以及三个安装链静默失败的修复。
2. 在 v1.2.0 条目后加一条显式提示:下方历史条目中的「描述式索引」与
「媒体引用计数式 GC」**已在 v1.2.0 移除**——避免读者按旧文档理解现行行为。
3. 「下载」:Windows 安装器的版本号改到 v1.2.0,并写明自 v1.2.0 起因 homed
不再支持 Windows 原生,安装器改为引导到 WSL2 并在其中按 Linux 方式安装;
同时注明安装器含 AGPL 许可页。
中英双份同步。SDK 仓 README 另在 SDK 仓提交(d893bfa)。
|
2026-09-12 09:27:28 +08:00 |
|
|
|
5e10f012cb
|
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>] <描述>` 描述式索引)——单独一个提交修。
|
2026-09-12 09:17:03 +08:00 |
|
|
|
727768e083
|
docs(git): 修正上一提交的错字(但声两仓 → 但两仓)
|
2026-09-12 09:01:59 +08:00 |
|
|
|
0247206dfe
|
docs(git): 写清「两仓 main 同步推进」的前提,修正 SDK 路牌
§七.4 原来只说「在 1.1.x 线发布期间,两仓 main 上的值都是 1.2.0」,容易被读成
「两仓 main 永远同值」,我正是据此把 SDK main 也推到了 1.3.0(已回退为 1.2.0)。
补写前提:推进以**该中版本已正式发布**为条件。
- 核心切出 release/v1.2.x 后 1.2.0 归发布线所有 → main 立即到 1.3.0(beta 也算占号)
- SDK 因 §七.2(beta 不发 SDK)要等核心正式 tag 才定版 → 在那之前 main 停在 1.2.0
并明确:**此阶段核心 main(1.3.0) 与 SDK main(1.2.0) 故意不对称**,
不是遗漏同步。§三 的 SDK 表行同步修正。
|
2026-09-12 09:01:34 +08:00 |
|
|
|
fbd3adabea
|
fix(sdk-mirror): vendored SDK 路牌回到 1.2.0(SDK 不跟 beta 发版)
SDK 仓 44bd915 把 main 的 meta.Version 推到 1.3.0 是错的(12cabcb 已改回 1.2.0)。
按 §七.2,beta 不伴随 SDK 发版:SDK 1.2.0 要等核心的**正式** tag 才定版打 tag
(§七.3),在那之前 1.2.0 仍是 SDK 尚未发布的中版本,路牌不得越过它。
核心 main 的 1.3.0 不受影响(1.2.0 已归 release/v1.2.x 所有),两仓在此阶段
故意不对称——这一点已写进 SDK meta.go 的注释,避免再被「对齐」回去。
本提交只同步 core 里跟踪的镜像:sdk/ 目录与 SDK 仓仍然逐字节一致。
|
2026-09-12 09:00:41 +08:00 |
|
|
|
f778c613f1
|
docs(git): 1.2.x tag 历史补上 v1.2.0-beta.1
release/v1.2.x 已按 §2.4 走出 beta 通道:tag v1.2.0-beta.1(提交 215804c),
注释里写明通道、不兼容点与验收证据;beta 阶段不发 SDK(§七.2)。
正式 tag 待试运行无回退问题后再打,届时同步 SDK 仓。
同时把 tag 名资产整理记录在案:历史大写 tag V0.8.0 / V0.7.1 已按用户确认删除
(提交本身未受影响),现在主仓 tag 全部符合 SemVer 小写 v 约定。
|
2026-09-12 08:54:09 +08:00 |
|
|
|
7a418346a4
|
build(packaging): 统一 -buildvcs=false,版本/提交只认 ldflags 注入
发布分支的产物上出现了 `vcs.revision=1715b5c`——一个本机任何仓库都不存在的提交。
原因:VCS 信息**不进 build cache key**(Go 文档明确说明 VCS 变化不会触发重建),
命中缓存时会把上一次的 revision 一并带回来。
而 build.sh 本来就用 ldflags 注入 meta.Version / meta.Commit(权威来源),
所以这个额外信号既不可靠又会误导溯源:拿 `go version -m` 去查源码提交,会指向
一个幽灵提交——正是本项目一直在治的「静默不一致」。
处置:三处 go build 统一 `-buildvcs=false`,并在 LDFLAGS 旁写明溯源方法
(`strings homed | grep -x '<短 hash>'`,meta.Commit 是字符串常量)。
验证:重新构建 homed linux/amd64 →
· `go version -m` 中 vcs.revision 0 处(此前 1 处且是错误值)
· strings 中恰好 1 处等于当前 HEAD 短 hash
· `-tags=onnxruntime` 仍在
|
2026-09-12 08:42:18 +08:00 |
|
|
|
a070cb563d
|
docs(git): 分支对齐更新到 2026-09-12(1.2.x 线开启)
- main 路牌调到 1.3.0;release/v1.2.x 承载 1.2.0(vendored SDK 定版 1.2.0)
- release/v1.1.x 按 §2.6 退役(保留供追溯);两个历史 feature 分支
(memory-media / plugin-proc-migration)已从远端删除,旧表待删项清掉
- 新增「1.2.x 发布线 tag 历史」表(当前尚无 tag),并写明它与存量插件
**不兼容**(RPC 协议 2、fd3 布局改变、不支持滚动升级)以及
**不满足 §2.4 跳级条件**的理由(改动面大,非单点修复)
- SDK 仓的 release/v1.2.x 尚未创建:按 §七.3 随核心**正式** tag 一起做
|
2026-09-12 08:39:05 +08:00 |
|
|
|
685ed7e4a0
|
fix(knowledge): 覆盖同名条目时摘掉旧向量
从一次真实的知识库更新里发现:在线实例更新一个已有条目之后,
knowledge_count=32 而 vector_count=33——多出来的那一条是上一版的副本。
成因:vector.Store.Insert 是**追加**语义(s.docs = append + index.Add),不按 id 去重;
而 Store.Add 走的是「写 content.md + 覆盖 items[id] + Insert 向量」。
文件与内存条目都被正确替换了,只有向量索引多留了一份。
危害不在于多占内存:**检索可能命中已被替换掉的旧内容**,而且完全静默——
条目数看起来是对的,只有向量数比条目数多。
修法:Insert 之前先 s.vec.Remove(id)(Remove 已按 id 过滤 docs 与倒排索引)。
回归测试 TestAddOverwriteReplacesVector 钉住 knowledge_count / vector_count /
content.md 三者都必须只剩新版。
注:该文件在 origin/main 上本就有 32 行 gofmt 差异(结构体字段注释对齐),
不属本次改动,按纪律不做整体重排。
|
2026-09-12 08:30:22 +08:00 |
|
|
|
dd99105d66
|
fix(build): GUI 输出目录用 --config.directories.output,-o 是 --mac 的别名
electron-builder 的 `-o` 是 `--mac`/`--macos` 的短别名(见 --help 的
Building 段),不是 output。于是 `-o "$BUILD_DIR"` 被当成 macOS 的 target
列表,报:
⨯ Unknown target: /home/program/trueagent/build
路径被 lowercase 后去匹配 target 名表,所以错误信息里的路径是全小写的
——这也是它看起来像「路径错」而实际是「参数位置错」的原因,v1.0.1 与
v1.0.3 两次发布都因此手工组装过 GUI。
改用 --config.directories.output=<dir>,已实测确认产物落在指定目录。
同时把 GUI 构建失败降级为警告:homed/waiter/initconfig 是发布主体,
而 GUI 依赖 electron 运行时下载(离线机器、arm64 缺缓存都会失败)。
set -euo pipefail 下不接住的话,一个可选组件会让整轮跨平台构建全废——
v1.0.3 就是这样只产出了 linux/amd64 三个二进制、arm64 与 windows
压根没跑到。
|
2026-09-12 08:26:12 +08:00 |
|
|
|
1fb5700662
|
chore(meta): main 的版本路牌推到 1.3.0
按 docs/git-branching.md §2.1,main 的 meta.Version 始终是**下一个未发布中版本**。
1.2.x 线已开(release/v1.2.x 承载 1.2.0),所以 main 指向 1.3.0。
它标记「main 正在积攒 1.3 的东西」,不表示 1.3.0 已经存在——1.3.0 没有任何 tag。
1.2.0 已由 release/v1.2.x 承载;main 不能再挂着 1.2.0,否则 main 的版本号
就与某一次发布的版本号相同,违反 §2.1「main 的版本号不是任何一次发布的版本号」。
两仓同步:SDK 仓 main 也在同一时刻推到 1.3.0(commit 44bd915)——SDK 版本跟随
核心中版本(§七.1),且两仓 main 都表示下一个未发布中版本(§七.4)。
本 commit 同时把 vendored SDK 镜像(third_party/homeagent-sdk/meta/meta.go)
更新到与 SDK 仓一致。
SDKCompatibleVersion 保持 1.2.0:那是本内核**实际实现并兼容的最高 SDK 接口版本**,
与「下一个未发布中版本」是两件事(参见 e4be966 时 main 的 1.2.0 / SDK 兼容 1.1.0)。
**此 commit 不 cherry-pick 到发布分支**(§五:版本号 bump 不跨分支搬)。
|
2026-09-12 08:25:30 +08:00 |
|
|
|
f1091676f8
|
style: gofmt 两处对齐(graphmedia_test.go 注释、tfidf.go 结构体字段)
这两处是本次特性分支带入的格式回归(origin/main 上不脏)。
纯空白/对齐调整,无语义变化;不整体重排文件。
|
2026-09-12 08:23:30 +08:00 |
|
|
|
eb4762a2b8
|
merge: 多模态统一向量空间与子进程数据面收敛(feature/multimodal-embedding)
合入 43 个提交,主线内容:
- 统一多模态向量空间:公共 provider SPI(pkg/embedding)+ 注册表,
chineseclip 为 text+image 默认空间(512 维,Apache-2.0),qwen3vl 保留
- 共享内存接管全部数据面:工具调用帧、Cleaner、输入/输出 lane、媒体块、
文档/知识正文;RPC 只传偏移描述符(协议版本 2)
- 图记忆原生媒体节点:媒体是一等节点与边,删除 media_refs 与描述式索引
- 注入行为可声明 no_memory / context_policy(默认不裁剪)
- SDK 1.2.0:注入标志位纯追加 + 示例 hmap 随发版
- 发行包默认启用 ONNX 向量空间;本批起模型与运行库随 server/full 包发布
- homed 放弃 Windows 原生支持,改走 WSL2
|
2026-09-12 08:21:34 +08:00 |
|
|
|
4d2028fbef
|
chore(sdk): 同步 vendored SDK 镜像到 1.2.0
core 仓里跟踪的 SDK 镜像落后于 third_party/homeagent-sdk(那是个独立仓库):
已提交的 sdk/memory.go 仍带 MediaAttachment.Description,而 SDK 仓 b2eafdf 已把它
删掉(媒体不再以文本描述参与索引)。磁盘上的文件其实就是 SDK 仓当前的版本,
只是 core 的索引没有跟上——于是**干净检出 main 拿到的是一个与它构建所用 SDK
不一致的镜像**。
clean worktree 能编译(内核已不再引用 Description),所以这个不一致不会被构建
发现,只能靠比对发现——属于本项目一直在治的「静默不一致」。
同步内容:sdk/memory.go 与 SDK 仓 HEAD 逐字节一致。
|
2026-09-12 08:21:21 +08:00 |
|
|
|
d4c5e808c7
|
feat(packaging): 模型与 ONNX Runtime 随 server/full 包发布
模型与运行库是发行版能力的一部分,不做成「装完再自己下载」:
- package-linux.sh:新增 stage_multimodal_assets(),打 server/full 前校验产物
SHA256SUMS、逐文件非空、运行库架构与目标一致,缺一即失败;client 包不含。
顺带修掉三个让打包在最后一步才炸的既有缺陷:
· 版本串直接取 git describe(v1.0.0-68-gxxx-dirty)不是合法包版本——deb 要求
数字开头、rpm 不允许 '-'。以前只有显式 VERSION=1.0.3 才打得出来;默认路径
从来没通过过。现在归一化,非数字开头时显式报错。
· 三处 mktemp -d 落在 /tmp(本机 9.8GB tmpfs),而 staging 要复制 719MB 模型,
中途 ENOSPC;报错文本指向某个 .onnx 文件,看着像资产坏了。改为落在与构建产物
同盘的 build/.stage-tmp。
· 开工前删掉旧的 SHA256SUMS:失败时脚本直接退出、不重算,留着像在为残缺产物背书。
- setup.sh:把包内 /usr/lib/homeagent/models/chinese-clip-vit-b16-onnx 软链到
<dataDir>/models/…(不复制 754MB、保持 dataDir 可迁移、已有自定义目录不覆盖)
- homeagent.service:ExecStart 改 /usr/bin/homed(deb 装在那里,此前写 /usr/local/bin,
装了也不会被 unit 用上)、加 ONNXRUNTIME_DIR 与 StateDirectory、MemoryMax 2G→8G
(实测常驻约 4.5GB,2G 会在首次全量建索引时被 cgroup OOM)
- control-{full,server}:补 libstdc++6 / libgcc-s1(libonnxruntime.so 需要)
- providers/{chineseclip,qwen3vl}:findOnnxLib 支持 ONNXRUNTIME_DIR / ONNX_ML_DIR
与包内 /usr/lib/homeagent/onnxruntime,随包的运行库才真的会被用上
- postinst:修掉两个让「装完即用」失效的点——它检查 /lib/systemd/system/ 下的 unit
而 deb 装到 /etc/systemd/system/,于是 daemon-reload/enable **从未执行**;以及
setup.sh 的失败被 `|| true` 吞掉(正是 initconfig 静默缺陷被藏住的原因)。现在
三个候选路径都查、失败可见并给出补救命令、首装 start / 升级 restart。
- docs/zh/multimodal-space.md:新增「随包分发」一节,并修正播种判据的说明
验收(从真实 deb 走一遍,不是读脚本):
- 包内 initconfig 已是动态链接,凭据真的写进 config.db
- 解包 → 按 postinst 顺序跑 setup.sh → 包内 homed 冷启动:
multimodal space active: provider=chineseclip dim=512 fp=cd2a495cf990
modalities=[text image]
- 全新安装的默认值确实被播种(core.plugin.dir / provider / model_dir 都在)
- 真实对话拿到回复(3.4s,回复中含唯一标记)
- 包内模型 SHA256SUMS 5/5 通过;包内 ORT 与源同 sha256;server 包 722MB
(旧版 17MB,差额即模型与运行库);full 包同样含全部资产;client 包不含
|
2026-09-12 08:10:44 +08:00 |
|
|
|
5299e18cd8
|
fix(config): 全新安装的默认值播种判据改为显式标记
播种判据曾经是「config 表为空」。而发行包的 postinst 先跑 setup.sh →
initconfig,后者会写一行 webui.listen_addr,于是**全新安装**被判定为
"已有配置"并整体跳过播种:没有 core.plugin.dir(装完 0 个插件)、没有
core.memory.* 路径、也没有随包模型对应的多模态 provider——754MB 产物与
24MB 运行库全成死重量。同样的机制此前已在协议 2 迁移演练里被观察到。
也不能改成"每次都补缺键":老安装升级时被注进新默认值,会让它突然去加载
一个 1.8GB 的模型,那是刻意要避免的静默变重。
故改为显式标记 core.internal.seed_version:
有标记 → 已播种,返回
无标记但有 core.daemon.data_dir → 老安装,只补标记、不播种
两者都没有 → 全新安装,播种并打标记
测试:TestSeedDefaultsAfterInitconfigPrepopulate(精确复现 initconfig 的
那一行写入)、TestSeedDefaultsDoesNotInjectIntoLegacyInstall。
|
2026-09-12 08:10:34 +08:00 |
|
|
|
6a3439a49c
|
fix(initconfig): 必须带 cgo 构建,且失败不再静默
cmd/initconfig 通过 database/sql 使用 mattn/go-sqlite3,而 build.sh 一直用
CGO_ENABLED=0 构建它:该库在非 cgo 下退化成 static_mock.go 里的桩,sql.Open
是惰性的所以不报错、第一次 Exec 才失败,而 main.go 丢掉了所有返回值。合起来
是一个完全静默的空操作——打印凭据、退出码 0、config.db 里一个字节都没写。
安装脚本把这份凭据写进 credentials.txt,用户照着登录必然失败,全程无报错。
- build.sh: initconfig 改 CGO_ENABLED=1,并写明为何不能图省事去掉 cgo
- main.go: 每个 Exec 都检查;写完**回读比对**(不看返回码,看真实落盘内容),
不一致即非零退出
反向验证:仍用 CGO_ENABLED=0 构建时,现在 stderr 报
"Binary was compiled with 'CGO_ENABLED=0', go-sqlite3 requires cgo to work"
且 exit 1(此前是 exit 0 并把凭据照打印出来)。
|
2026-09-12 08:10:26 +08:00 |
|
|
|
ba0b5a1fe0
|
feat(release): 发行版默认启用本地向量空间(onnxruntime 标签 + chineseclip 默认)
用户要求:后续发行版默认带 ONNX 模型能力。这条要求落到两处,并顺带修掉一个
被它**暴露出来**的真缺陷。
## 1. 构建默认带 onnxruntime(deploy/packaging/build.sh)
`HOMED_TAGS` 默认 `onnxruntime`,需要极简构建时显式 `HOMED_TAGS=` 关闭。
不带标签时 provider 仍注册、但打开即报「requires build tag」并优雅降级——
不静默假装成功。运行期还需要 `libonnxruntime.so`(provider 按
/opt/onnxruntime、/usr/local/lib、/usr/lib 顺序查找),缺失时同样是
「日志里明确错误 + 降级」。
## 2. 新装默认选 chineseclip(internal/config/registry.go)
`SeedDefaults` 写入:
core.memory.multimodal_space.provider = chineseclip
core.memory.multimodal_space.options.model_dir = <dataDir>/models/chinese-clip-vit-b16-onnx
选它而不是 qwen3vl:后者实测常驻 9.4GB,多数机器装不下;chineseclip 是
1.99GB(实测,见下)。同时更新两个 ConfigDef 的默认值与描述(WebUI 显示用)。
**老安装不会自动拿到这两个默认值**,这是有意的:`seedDBValues` 对非空配置库
直接返回,`GetString` 缺键时回落到调用方默认值(main.go 传的是空串)。
升级就静默加载 ~1.8GB 模型不是无副作用的事,应由部署显式开启。已写进文档。
## 3. 修掉 ORT 环境被重复初始化 + 误销毁(internal/nlp/onnx.go)
这是「默认带标签」才暴露的缺陷:此前不带标签时进程内不会有多个 ORT 消费者。
- `NewONNXParser` 无条件 `InitializeEnvironment()` → 若多模态 provider 先初始化,
这里报「The onnxruntime has already been initialized」并**降级**(实测日志:
`ONNX parser init: init onnx env: ... using fallback`)。
- 更严重的是失败路径与 `Close()` 里的 `DestroyEnvironment()`:它会把别人
(多模态 provider)正在用的进程级环境一起拆掉,让对方的会话失效。
改为:初始化前先 `IsInitialized()`;**任何消费者都不销毁环境**(随进程存活),
只销毁自己的会话。providers/chineseclip 与 providers/qwen3vl 本来就是这个约定,
现在三处一致。
## 验证(实测)
- 全新数据目录启动:配置库出现上述两个默认值。
- 模型未安装:`multimodal space active` 不出现,代之以明确错误
(点名缺失的 embed_config.json 路径 + 已注册 provider 列表)+ 降级,不静默。
- 模型就位:`multimodal space active: provider=chineseclip dim=512 fp=cd2a495cf990
modalities=[text image]`。
- 内存:同一份 homed,启用时 RSS **1.99GB**(峰值 2.09GB),不启用 **0.17GB**。
- NLP 修复:日志由 `ONNX parser init: ... using fallback` 变为 `dep parser initialized`。
- 构建矩阵:`go build/vet ./...` 与 `-tags onnxruntime` 两种都过;
`bash -n deploy/packaging/build.sh` 通过。
## 未做(明确记录)
- `libonnxruntime.so`(24MB)与 Chinese-CLIP 产物(754MB)目前都需自行安装/导出,
发行版尚未打包它们。若要让「默认启用」在干净机器上真正开箱可用,需要决定
是随包分发、安装时下载、还是保持文档指引。
|
2026-09-12 00:10:32 +08:00 |
|
|
|
bfdb395731
|
feat(memory): 新增 chineseclip provider —— text+image 的小体积可商用向量空间
## 为什么
用户决定「本轮不覆盖 video,先支持 text+image」。这一刀正好解锁了此前
「小 + 可商用 + 覆盖视频」三者不可兼得的僵局:不要求视频后,唯一同时满足
**小、可商用、中文原生** 的选项是 Chinese-CLIP ViT-B/16。
实测对比(同机、真实跑出来的数字):
| | Chinese-CLIP | jina-v5-omni-nano | Qwen3-VL-Emb-2B |
|---|---|---|---|
| 参数量 | 188M | 1.04B | 2B |
| 产物 / 常驻内存 | 754MB / **1.15GB** | ~2GB / 2.23GB | 8GB / 9.4GB |
| 维度 | 512 | 768 | 2048 |
| 许可 | **Apache-2.0** | CC BY-NC(不可商用) | Apache-2.0 |
| 视频 | 无 | 有 | 有 |
本机可用内存只有 5.3GB,Qwen 的 9.4GB 无法进程内使用;而 ORT format + mmap
那条路被证实当前不通(转换器对三段图段错误;走通还需同时升 ORT 运行时与
Go 绑定,v1.36 要求 API 29 而本机只有 28)。1.15GB 则可以直接进程内跑。
**代价已写进包注释与文档**:CLIP 是双塔对比学习,text↔image 是强项,但纯文本
语义明显弱于 MLLM 型嵌入器;文本检索仍由既有词向量/TF-IDF 路径兜底。
需要更强文本语义或视频时切回 qwen3vl。
## 内容
- `providers/chineseclip/`:按公共 SPI 实现的 provider(注册名 `chineseclip`),
含 BERT WordPiece 分词器、图像预处理、ONNX 双塔推理、无标签 stub。
- `scripts/export_chineseclip_onnx.py`:从官方权重导出规范产物 + 冻结参考,
自带逐用例 PyTorch 对比与覆盖度断言(计划集合≠执行集合即非零退出)。
- `cmd/homed/main.go`:空白导入两个 provider,由配置选其一。
- `go.mod`:`golang.org/x/text` 由间接依赖转为直接依赖(删音标需要 NFD)。
## 实现要点
- **分词器逐 token 对齐官方**。第一版探针自己拼 BertTokenizer(只给 vocab.txt、
没删音标、中文没逐字切),中文被整体切成 [UNK],三个不同句子产出几乎相同的
向量(余弦 0.98)——差点把「模型坏了」当成结论。官方配置是 do_lower_case=true
+ 删音标生效 + 中文逐字切分;`TestTokenizerMatchesOfficialReference` 钉住
逐 token 一致。
- **图像缩放自写 bicubic**(复刻 PIL 的 precompute_coeffs + a=-0.5 核),不引
golang.org/x/image:它未进本机模块缓存,且最新版要求把整个工具链升到 Go 1.26,
为一个缩放函数动工具链不划算。
- **归一化在 provider 侧**(两个塔的图里都没归一化),检索按余弦。
- **指纹覆盖全部影响语义的产物**:两个 ONNX 图 + vocab.txt + embed_config.json,
读不到就写 MISSING(跳过等于对缺件不敏感)。
- 会话 Run 用 runMu 串行化(ORT 会话不保证并发安全),创建/销毁用 mu。
## 模态范围
只声明 `text` 与 `image`;`audio`/`video` 明确返回 `ErrUnsupportedModality`,
绝不用别的模型向量冒充(这是「音频明确 unsupported」纪律的落地)。
## 验证(实测)
导出侧:10 个用例(5 文本 + 5 图像)ONNX vs 官方 PyTorch 全部
`cos = 1.000000000`,覆盖度断言 10/10 通过。
Go 侧(`CHINESECLIP_MODEL_DIR=... go test -tags onnxruntime ./providers/chineseclip/ -v`):
11/11 通过,其中
- 文本 5 用例 `cos = 1.000000000000`(逐位一致)
- 图像 4 纯色用例 `cos = 1.000000`(与官方预处理在 6 位小数内一致)
- 跨模态判别:红图对「红色」文本高于「蓝色」文本
- 模态拒绝 / 空输入 / 指纹稳定 / 产物缺失报错
顺带修掉测试自身的一个假通过:参考向量是**未归一化**的原始输出(模长 10~36),
原先「点积当余弦 + 单侧下界」会让 13.6 也判过,已改为真余弦 + 双侧容差。
构建矩阵:`go build/vet ./...` 与 `-tags onnxruntime` 两种都过;
`providers/... pkg/... internal/config/... internal/memory/vector/...` 回归通过
(qwen3vl 的 TestVideoModelInputMRope 需要 QWEN_ONNX_MODEL_DIR 指向含视频档的
v3 目录,缺该环境变量时用的是只有文本+图像的目录,与本改动无关)。
## 未做(明确记录)
- 发行版默认 provider 与构建标签变更:留下一提交(涉及打包与模型分发策略)。
- 模型产物(754MB)不进仓库,由导出脚本生成。
|
2026-09-11 23:58:53 +08:00 |
|
|
|
d1959cbe80
|
feat(core): 注入行为的记忆/裁剪标志位落地 + jieba 词库内嵌 + Windows 改走 WSL
配套 SDK 提交:homeagent-sdk ba49dfd(公开 API 纯追加,无签名变更)。
本仓第三方的库镜像同步至该版本,以保证全新 clone 能编译。
## 1. 注入标志位(内核侧)
- 7 条注入路径(排队/中断/同步 × 纯文本/带媒体 + 旧 NoMem 变体)解析并转发
no_memory / context_policy / cleaner_name;策略在入口**校验**,
非法值报错而不是静默降级成 none(降级会让调用方以为自己声明的裁剪在生效)。
- 新增 validateContextPolicy(与 tool.register 同一套规则)与 pubSdkInjectOpts。
- input.register 不再手写字段白名单重建 ChannelDef,改为整体传递 + 补 ContextPolicy。
- io 层:applyInjectOpts 把标志位写进事件 payload,仅非零时写
(零值与旧 payload 逐字节一致,事件订阅方与旧内核都不受影响)。
- ioAdapter / procCore / internal-sdk 别名补齐六个 *Opts 实现。
## 2. 修掉「输入无条件裁剪」这个真缺陷
eventloop 此前对**每条非中断输入**都调 `context.Prune(...)`:破坏性(低相关事件被
归档移出上下文)且无法从调用点看出是谁触发的。改为 pruneOnInput/pruneDeclared:
优先级:注入点声明(payload.context_policy)> 通道声明(ChannelDef.ContextPolicy)
> 默认**不裁剪**
查询向量仍取清洗后的内容;新增 cleanInputFor 解析清洗文本,优先级为
注入点声明的 cleaner(cleaner_name)> 按 source 查到的通道 cleaner > 原文,
名字查不到时**记日志再回退**(注入是 fire-and-forget,插件看不到错误,
至少要在内核日志留下「你声明的清洗没生效」的痕迹)。
## 3. jieba 词库内嵌(修「猜 GOMODCACHE → 静默失效」)
原 jiebaDictDir() 去猜 GOMODCACHE/GOPATH/~/go/pkg/mod,部署机上通常没有 Go 模块
缓存 → GetJieba() 返回 nil → 分词/关键词提取/NLP 依存解析(进而 doc→graph 三元组
抽取)/静态词向量 tokenizer **一律静默返回空列表**,只有一行日志。本机看起来正常
只因开发机与生产机重合、恰好有那份缓存。
现在词库随二进制分发:internal/memory/jiebadict/ 5 文件约 11.6MB + go:embed,
按**内容哈希**命名缓存目录落盘(词库升级不复用旧文件),已齐全则跳过写入。
模块缓存降为兜底。homed 体积 32MB。
顺带确认(并有测试佐证):gojieba 的 Tag() 不需要 pos_dict/ 目录——
cppjieba 的 PosTagger 从主词典每行的词性列取 tag。
## 4. homed 放弃 Windows 原生,改走 WSL2
插件体系依赖「继承的 fd」+「统一共享内存区的段内偏移解引用」,Windows 既无 fd
继承语义,其句柄模型也无法表达后者;强行适配等于再维护一套平台专属 ABI
(C ABI 时代三套 ABI 并存曾导致改写型插件在某平台静默失效)。
- cmd/homed/platform_{windows,other}.go:原生 Windows 启动即拒绝并打印 WSL2 指引。
- internal/plugin/proc/shmalloc_windows.go:allocShm 直接返回「请用 WSL2」,
**不返回半可用的段**(与 shmalloc_other.go 同风格:未支持平台显式报错);
procEnvForShm 返回 nil。顺手修掉两处长期编译错误
(cryptorand→rand、h.evData→h.unified.evtData),使 GOOS=windows 至少能编译。
注:homed 本就编不出 Windows——internal/memory 依赖 cgo-only 的 gojieba。
- deploy/packaging/installer.nsi:不再安装 homed.exe/initconfig.exe,改为携带
**linux payload** 并调用新的 install-via-wsl.ps1;退出码 20/21 表示
「需先装 WSL/发行版」,走指引而非报错。
- deploy/packaging/windows/install-via-wsl.ps1(新):检测 WSL → 引导安装 →
确保 WSL2 → 送包进发行版 → 在 WSL 内按 Linux 方式安装。**复用 Linux 包与
linux/setup.sh**,不另写一套安装逻辑;落点与 deb 布局统一
(/usr/bin/homed + /usr/lib/homeagent/setup.sh)。
- deploy/packaging/linux/setup.sh:API Key 允许 HOMEAGENT_API_KEY 覆盖
(否则安装器界面显示一份、config.db 里另一份 → 登录不上)。
- deploy/packaging/build.sh:windows 目标只构建 waiter + gui,并新增
stage_linux_payload 把 Linux 包暂存给安装器;homed/initconfig 在 windows
目标下明确拒绝。
## 5. 插件调用点统一写明意图
- webui 的 OpenAI 兼容端点(固定提示词模板)→ InjectTextSyncNoMemory。
- agentcli 的 5 处纯状态通知(已启动/超时/执行结束/进程退出/读取结束)→ NoMemory;
**带输出**的 2 处(定时反馈、有新输出)刻意保留记忆并注明理由。
- timer 的定时提醒 → NoMemory(中断本来也隐含 NoMemory,这里是写明意图)。
## 6. 版本
meta.Version 仍为 1.2.0(main 是下一个未发布中版本);
SDKCompatibleVersion 1.1.0 → **1.2.0**(本内核已实现 SDK 1.2.0 全部新增方法)。
## 测试
- core:默认不裁剪(无声明/none/空)、通道 opt-in、注入点双向覆盖通道、
nil context/io 安全、cleaner 优先级与未知名回退。
- io:零值 opts 与历史 payload 逐键相同;text/中断/媒体三类注入标志位都落到
payload;旧方法仍生效。
- proc:validateContextPolicy 只接受 ""/none/prune,报错含位置与实际值;
**跨进程** e2e——testdata 插件经 io.injectText 送出三个标志位,断言它们穿过 RPC
到达内核。
- memory:模块缓存不可见时内嵌词库仍可用(分词与 POS 内容词均非空)、
落盘幂等、内容哈希稳定。
验证:go build ./... / go vet ./... / go vet -tags onnxruntime ./...
go test -short ./internal/memory/... ./internal/nlp/... ./internal/plugin/...
./internal/agent/{core,io}/... ./pkg/...
|
2026-09-11 20:31:50 +08:00 |
|