mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-01 06:47:29 +00:00
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 处全是列表分隔符)
This commit is contained in:
89
CHANGELOG.md
Normal file
89
CHANGELOG.md
Normal file
@ -0,0 +1,89 @@
|
||||
# 更新记录
|
||||
|
||||
本文件记录各版本的实质变化。README 只留"现在能干什么",历史演进在这里。
|
||||
|
||||
入口文档:[README](README.md) · [架构](assets/docs/zh/ARCHITECTURE.md) · [未来计划](docs/zh/future-plan.md)
|
||||
|
||||
|
||||
**v1.4.x 线**(进行中,`main`)—— **声明式并发安全** + **序列编排(seq)** + **工具结果诚实化**。
|
||||
|
||||
- **同轮工具并行执行**:同一轮里的多个 `tool_call` 默认并发执行,**但以「安全」为前置**——
|
||||
工具必须在自己的 `ToolDef` 里显式声明 `ParallelSafe`(内置工具用 `toolDefOptions`),
|
||||
未声明的一律串行;`Serial` 优先级更高。**批内只要有一个不安全,整批降级为串行**。
|
||||
并发声明写在**工具自身**,内核不做硬编码工具名安全表。同通道输出**严格保序**,
|
||||
结果与文本分离(`toolOutcome.Text` / `Raw`)。
|
||||
- **`seq` 序列编排插件**:AST 解析 + 持久化、具名 group、变量槽、**组内并行 + 组间串行**、
|
||||
条件调用(`seq_when_call`)、跨序列调用图与环检测、错误契约。
|
||||
- **工具结果只统计不裁剪**:结果超出预算时**报告**统计量,不静默截断。
|
||||
- **结构化工具错误契约**:参数按 schema **预校验**(分派前拦下)、
|
||||
「工具不存在」与「执行失败」区分、`Success` 不再恒真。
|
||||
- **设备命令白名单可配置**(waiter `device_cmd_allowlist`)、
|
||||
**站点漂移巡检**、**GUI 增量渲染**(保留全部历史,按 `data-msgkey` 复用节点,
|
||||
50/200/400 条实测 8.2/23.5/38.2ms)。
|
||||
|
||||
**v1.3.x 线**(v1.3.1–v1.3.13)—— **驻留式子 agent** + **输入调度器重做**。
|
||||
|
||||
- **驻留式子 agent**:内核可派驻轻量内核的子 agent(自己的调度器、自己的 temp 稳定记忆、
|
||||
共享通道登记表)。父经 `resident_agents`(list/create/send/inspect/compress/reclaim/destroy)
|
||||
派活与收活;inputch 可划给子,输入**在进内核之前**就已路由到子。
|
||||
- **输出通道可寻址到具体 agent**:`AllowedOutputs` 授权集合(三处过滤点一致),
|
||||
父/子之间可互相投递;设备能力也 outputch 化(每设备一个 `device/<id>` 通道)。
|
||||
- **输入调度器**:排队/中断两类别 + 四级中断(L1~L4)+ 抢占/挂起/恢复/中断栈;
|
||||
同级不抢占、有饥饿防护与抢占冷却;L4 只归内核与内核级插件(WebUI 终止按钮)。
|
||||
- **轻量内核 profile**:子的记忆面收窄为「传统上下文 + 稳定记忆(stablemem)」(窄接口,
|
||||
主库以 query_only 受限句柄打开,写走自己的 temp 实例)。
|
||||
- **积压及时反馈**(后续线):主 agent 长时间忙时,内核把排队输入交给临时**分诊助手** ——
|
||||
简单的直接处理并回复,需要主 agent 的立刻回「忙碌中,请稍候」,用户不再干等十几分钟。
|
||||
- 修掉一批真实缺陷:销毁驻留子时入站 inputch(`child/<id>`)注册残留、
|
||||
子的轮次永远显示 0(`info()` 根本没填)、子侧 childIO 空壳(未继承父的输出通道)、
|
||||
设备心跳 pong 忘了 Flush(每 60 秒掉线)、Lua 插件桥与 SDK 1.3.0 对齐。
|
||||
|
||||
**v1.2.0** — 统一多模态向量空间 + 媒体升为稳定记忆一等节点 + 数据面全量迁到共享内存。
|
||||
|
||||
- **模型中立的统一向量空间**:内核不再适配任何具体模型,只提供公共 provider SPI
|
||||
(`pkg/embedding`:`Modality` / `Input{Data,MIME}` / `Info{Dimension,Fingerprint,Modalities}`
|
||||
- 名字注册表),实现在 `providers/*`。默认 **Chinese-CLIP ViT-B/16** —— text 与 image
|
||||
落在**同一空间**(512 维、指纹 `cd2a495cf990`、Apache-2.0;实测加载峰值 1.59GB、静置回收后稳态约 0.89GB);
|
||||
`qwen3vl` 保留(2048 维、常驻约 9.4GB,供内存充足或将来要视频的机器切回)。
|
||||
文本检索仍由既有词向量 / TF-IDF 兜底:CLIP 双塔的**纯文本语义弱于 MLLM 型嵌入器**,
|
||||
这是已知并写进文档的代价。
|
||||
- **媒体是图数据库的一等节点与边**:**彻底删除**「用文本描述式索引图片」这套将就机制,
|
||||
以及 `media_refs` 与媒体引用计数。记忆块遵循单层不变量——Context → Document → Graph
|
||||
是块的**迁移**,不是复制、也不靠引用保活。
|
||||
- **数据面全部走共享内存**(工具调用帧 / Cleaner / 输入输出通道 / 媒体块 / 文档与知识正文),
|
||||
RPC 只传偏移描述符;**RPC 协议升到 2**,fd3 布局改变,**不支持滚动升级**——
|
||||
内核与全部插件必须同批重建、同批安装,存量插件须用新版 `hmapdev` 重编。
|
||||
- 注入可声明 `InjectOptions{NoMemory, ContextPolicy}`(**默认仍记入记忆、默认不裁剪**);
|
||||
裁剪必须显式声明,且先经插件注册的 `Cleaner`。SDK 1.2.0 相对 1.1.0 **纯追加**。
|
||||
- **发行包默认启用** ONNX 向量空间,并把模型(754MB)与 ONNX Runtime(24MB)随
|
||||
server/full 包发布;`homed` 放弃 Windows 原生支持改走 WSL2;jieba 词库内嵌进二进制。
|
||||
- 修掉三个**安装链静默失败**:`initconfig` 因 `CGO_ENABLED=0` 是空操作(打印凭据却一个字节
|
||||
没写)、全新安装被误判「已有配置」而整体跳过默认值播种(装完 0 插件)、deb 的 `postinst`
|
||||
查错 unit 路径导致 `enable` 从未执行。
|
||||
- 自本版起以 **AGPL-3.0-only** 发布(含网络条款;插件静态链接 SDK 故须同许可,见「许可」)。
|
||||
|
||||
> 以下历史条目保留原文以呈现演进,其中两条机制**已在 v1.2.0 移除**:
|
||||
> 「媒体以 `[<mime> <短digest>] <描述>` 标记参与检索」(描述式索引)与「媒体引用计数式 GC」。
|
||||
>
|
||||
> 另有**一处许可口径已变更**(2026-09-24):上面 v1.2.0 条目里的「插件静态链接 SDK 故须同许可」
|
||||
> **自今日起不成立**。SDK 改以 **MIT** 发布(宽松许可,不继承内核的 AGPL),故外部插件**不是**
|
||||
> 本项目的衍生作品,作者可自行选择许可(含闭源、商业、私有),亦不受 §13 网络条款约束。
|
||||
> 原文保留以呈现当时的口径。
|
||||
>
|
||||
> 另有**两项性能断言的量纲需要更正**(2026-09-20 实测):
|
||||
> 「崩溃到恢复 <1s」不成立 —— 崩溃后是**线性退避重启**,即 1s / 2s / 3s(`procRestartBackoff=1s × 第 n 次`),
|
||||
> 首次重启就要等 1s。且 5 分钟窗口内第 **4** 次崩溃即停止自动重启待人工介入(`procMaxRestarts=3`,判定为 `n > 3`)。
|
||||
> 该断言写下时(v1.0.0)退避值已是 1s,故从未成立。
|
||||
> 「RPC 往返 p50 24.1µs」与当前实测同量级但不吻合:本机 `BenchmarkToolInvoke` 实测
|
||||
> inline/small **30.4µs**、frame/small 51.5µs、inline/large 767µs、frame/large 398µs。
|
||||
> 保留原文不修改,以免伪造历史。
|
||||
|
||||
**v1.1.1** — 多模态贯通**插件边界**。v1.1.0 让记忆系统支持了二进制多媒体节点,但那条链路只对内核自己开放;本版打通到插件与模型。公开 SDK 新增媒体字段与三个媒体注入接口(配套 [SDK v1.1.0](https://github.com/JianFeeeee/hologramagentsdk/tree/v1.1.0),整条 1.1.x 线共用),内核实现对应四个 RPC。桥接层此前在**静默裁字段**:插件交进来的 `Confidence`/类型/`SentenceText` 全被丢弃、`Doc` 只留三个字段、`Remove` 不解引用(媒体永久算「被引用」,GC 收不掉)。`processTextInput`/`processMediaInput` 归一成一条 `processInput`,媒体路径由此获得它一直缺的去重、`no_memory`、通道 `Cleaner`、中断语义、`EventRawInput`。修掉三处真实缺陷:**用户发的图从来没出现在 WebUI 聊天记录里**(媒体路径发布 map 而订阅方断言 string)、**`memory_commit` 的 `sentence_text` 从未暴露给模型**(注:该工具现名 `stablemem_commit`,本条保留 1.1.1 发布时的原名)(而它是媒体绑定链的必经环节)、**`PluginSDK` 两处并发竞态**(`-race` 实测 11 处,插件重载瞬间偶发 nil 解引用崩溃)。
|
||||
|
||||
**v1.1.0** — 记忆系统支持**二进制多媒体节点**。内容寻址媒体存储(CAS + SQLite 元数据 + 磁盘 blob,`Get` always 重校 digest),贯通 L0(上下文事件)/L2(文档)/L3(图谱句子)三层,引用计数式 GC(有引用者绝不删)。视觉模型生成的描述文本是持久语义记忆,blob 只是可被容量 GC 淘汰的缓存。
|
||||
|
||||
**v1.0.0** — 外部插件从 C ABI 动态库迁移到**子进程 + 共享内存**。首个不再加载 `.so`/`.dll` 的版本,与 0.9.x 不兼容(存量插件须用新版工具链重编;该工具链当时名为 `plugindev`,**现名 `hmapdev`**)。外部插件需重编为 `plugin.bin`,**业务代码零改动**)。消除 6 类此前在生产造成故障的缺陷:热重载失效(`DF_1_NODELETE` 让 `dlclose` 成 no-op)、崩溃隔离缺失(插件 panic 带崩 homed)、stage lost update(副本模型丢失 35.8~36.8%)、cgo 超时不可中断(线程线性泄漏)、`output_send` 假成功(模型收到「已发送」而消息未送达)、Windows 能力断层(只见 3 个 stage 字段且无法写回)。三面通信:stdio JSON-RPC(控制)+ 共享内存段(数据)+ 事件环(通知);权限梯度显式化为三道闸。RPC 往返 p50 24.1µs,崩溃到恢复 <1s。
|
||||
|
||||
**v0.9.0** — C ABI v2:外部插件 Stage 回调支持写回(`invoke_stage` 增加 result 输出,插件可在 OnInput/AfterToolcall/PostAction 修改 RawMessage/LLMText/ToolResults 等并同步回内核),ABI 版本随内核 minor 对齐(v0.9.x → ABIVersion=2,`version_min=1` 向后兼容旧插件)。同步修复工具循环 zen 兼容补位误伤首轮 system 上下文的问题。配套 SDK 提供增强版 sanitizer 示例(坏 UTF-8/U+FFFD/ANSI 转义全链路清洗)。**该 ABI 已随 v1.0.0 退场。**
|
||||
|
||||
**v0.8.0** — 核心可用,插件系统增强。内置 20+ 插件,外部插件开发见 [hologramagentsdk](https://github.com/JianFeeeee/hologramagentsdk) 仓库。新增输入通道 `NoMemory`/`Cleaner`、`ChannelDef`、插件禁用/启用系统(CLI + WebUI),`plugindev` 工具链完成 C ABI `ChannelDef` 传递。
|
||||
129
CHANGELOG_EN.md
Normal file
129
CHANGELOG_EN.md
Normal file
@ -0,0 +1,129 @@
|
||||
# Changelog
|
||||
|
||||
What actually changed in each release. The README only says what it does today; the
|
||||
history lives here.
|
||||
|
||||
Entry points: [README](README_EN.md) · [Architecture](assets/docs/en/ARCHITECTURE.md) · [Future plan](docs/zh/future-plan.md)
|
||||
|
||||
|
||||
**v1.4.x line** (in progress, `main`) — **declarative concurrency safety** + **sequence orchestration (seq)** + **honest tool results**.
|
||||
|
||||
- **Same-round parallel tool execution**: multiple `tool_call`s in one round run concurrently,
|
||||
**but safety gates it** — a tool must explicitly declare `ParallelSafe` in its own `ToolDef`
|
||||
(built-ins use `toolDefOptions`); anything undeclared runs serially, and `Serial` wins.
|
||||
**A single unsafe tool degrades the whole batch to serial.** Declarations live **in the tool
|
||||
itself**; the kernel keeps no hardcoded name table. Same-channel output stays **strictly ordered**,
|
||||
and results are separated from text (`toolOutcome.Text` / `Raw`).
|
||||
- **`seq` orchestration plugin**: AST parsing + persistence, named groups, variable slots,
|
||||
**parallel within a group / serial between groups**, conditional calls (`seq_when_call`),
|
||||
cross-sequence call graph with cycle detection, error contract.
|
||||
- **Tool results are reported, never silently truncated** — over budget emits statistics.
|
||||
- **Structured tool error contract**: arguments pre-validated against the schema (rejected before
|
||||
dispatch), "tool not found" distinguished from "execution failed", `Success` no longer always true.
|
||||
- **Configurable device command allowlist** (waiter `device_cmd_allowlist`),
|
||||
**site drift watcher**, **incremental GUI rendering** (keeps all history, reuses nodes by
|
||||
`data-msgkey`; 50/200/400 messages measured at 8.2/23.5/38.2ms).
|
||||
|
||||
**v1.3.x line** (v1.3.1–v1.3.13) — **resident sub-agents** + **input scheduler rework**.
|
||||
|
||||
- **Resident sub-agents**: the kernel can station lightweight-kernel child agents (their own
|
||||
scheduler, their own temp stable memory, sharing the channel registry). The parent dispatches
|
||||
and collects work via `resident_agents` (list/create/send/inspect/compress/reclaim/destroy).
|
||||
An inputch can be assigned to a child, so input is routed to it **before entering the kernel**.
|
||||
- **Output channels addressable to a specific agent**: the `AllowedOutputs` grant set
|
||||
(consistent across all three filter points) lets parent/child deliver to each other;
|
||||
device capabilities became output channels too (one `device/<id>` per device).
|
||||
- **Input scheduler**: two task classes (queued/interrupt) + four interrupt levels (L1–L4)
|
||||
- preempt/suspend/resume/interrupt-stack; same level never preempts same level, with a
|
||||
starvation guard and preemption cooldown. L4 belongs only to the kernel and kernel-level
|
||||
plugins (e.g. the WebUI stop button).
|
||||
- **Lightweight kernel profile**: a child's memory surface narrows to "conventional context
|
||||
- stable memory" (narrow interface; the main graph opens as a query_only handle, writes go
|
||||
to its own temp instance).
|
||||
- **Backlog timely feedback** (later in the line): when the main agent is busy for a long time,
|
||||
the kernel hands queued input to a temporary **triage assistant** — simple items are handled
|
||||
directly, items needing the main agent get an immediate "busy, please wait", so users no
|
||||
longer wait 10+ minutes in silence.
|
||||
- Fixed a batch of real defects: inbound inputch (`child/<id>`) registration leak on resident
|
||||
destruction, a child's round count always showing 0 (`info()` never filled it), an empty
|
||||
child-side childIO (output channels not inherited), device heartbeat pong missing Flush
|
||||
(dropping every 60s), and Lua plugin bridge alignment with SDK 1.3.0.
|
||||
|
||||
**v1.2.0** — unified multimodal vector space, media promoted to first-class stable-memory nodes, and the whole data plane moved into shared memory.
|
||||
|
||||
- **Model-neutral unified embedding space**: the kernel no longer adapts to any specific model.
|
||||
It exposes only a public provider SPI (`pkg/embedding`: `Modality` / `Input{Data,MIME}` /
|
||||
`Info{Dimension,Fingerprint,Modalities}` + a name registry), with implementations under
|
||||
`providers/*`. Default: **Chinese-CLIP ViT-B/16** — text and image land in the **same space**
|
||||
(512-dim, fingerprint `cd2a495cf990`, Apache-2.0; measured ~1.59GB peak on load, settling to ~0.89GB steady-state);
|
||||
`qwen3vl` is kept (2048-dim, ~9.4GB) for machines with headroom or future video. Text search
|
||||
still falls back to the existing word-vector / TF-IDF path — a CLIP dual tower's pure-text
|
||||
semantics are **weaker than an MLLM-style embedder**, a cost documented rather than hidden.
|
||||
- **Media are first-class nodes and edges in the graph DB**: the "index images via generated
|
||||
text descriptions" stopgap, `media_refs` and media reference counting are **removed**.
|
||||
Memory blocks follow a single-layer invariant — Context → Document → Graph is a **migration**,
|
||||
not a copy, and not kept alive by references.
|
||||
- **The entire data plane goes through shared memory** (tool-call frames, Cleaners, input/output
|
||||
lanes, media blocks, document and knowledge bodies); RPC carries only offset descriptors.
|
||||
**RPC protocol is now 2**: the fd3 layout changed and there is **no rolling upgrade** —
|
||||
kernel and all plugins must be rebuilt and installed together.
|
||||
- Injections can declare `InjectOptions{NoMemory, ContextPolicy}` (**defaults: still recorded,
|
||||
not pruned**); pruning must be requested explicitly and goes through the plugin's registered
|
||||
`Cleaner`. SDK 1.2.0 is **purely additive** over 1.1.0.
|
||||
- **Release packages enable the ONNX space by default** and bundle the model (754MB) plus
|
||||
ONNX Runtime (24MB) in the server/full packages; `homed` drops native Windows support in
|
||||
favour of WSL2; the jieba dictionary is embedded in the binary.
|
||||
- Fixed three **silent install-chain failures**: `initconfig` was a no-op (`CGO_ENABLED=0` stub)
|
||||
that printed credentials without writing any, fresh installs were misdetected as "already
|
||||
configured" so default seeding was skipped entirely (0 plugins installed), and the deb
|
||||
`postinst` looked for the unit in the wrong path so `enable` never ran.
|
||||
- Licensed **AGPL-3.0-only** from this version on (network clause included; statically linked
|
||||
plugins must match — see License).
|
||||
|
||||
> The historical entries below are kept verbatim to show the evolution; two mechanisms in them
|
||||
> were **removed in v1.2.0**: text-description-based media indexing, and reference-counted media GC.
|
||||
>
|
||||
> **One licensing statement has also changed** (2026-09-24): "statically linked plugins must match"
|
||||
> in the v1.2.0 entry below **no longer holds**. The SDK is now released under **MIT** (a permissive
|
||||
> license that does not inherit the kernel's AGPL), so external plugins are **not** derivative works
|
||||
> of this project: authors choose their own license (closed-source, commercial or private included)
|
||||
> and are not bound by §13. The original text is kept to show the position at the time.
|
||||
>
|
||||
> **Two performance claims also need correcting** (measured 2026-09-20):
|
||||
> "crash-to-recovery under 1s" does not hold — restarts are **linearly backed off**, i.e.
|
||||
> 1s / 2s / 3s (`procRestartBackoff=1s × nth crash`). Even the *first* restart waits 1s.
|
||||
> And the **4th** crash within a 5-minute window stops automatic restarts pending human
|
||||
> intervention (`procMaxRestarts=3`, tested as `n > 3`).
|
||||
> The backoff was already 1s when this claim was written (v1.0.0), so it never held.
|
||||
> "RPC round-trip p50 24.1µs" is the right order of magnitude but does not match current
|
||||
> measurements: `BenchmarkToolInvoke` on this machine gives inline/small **30.4µs**,
|
||||
> frame/small 51.5µs, inline/large 767µs, frame/large 398µs.
|
||||
> The original text is left unedited rather than rewritten, so the history isn't falsified.
|
||||
|
||||
**v1.1.1** — Multimodal reaches the **plugin boundary**. v1.1.0 gave the memory system binary
|
||||
multimedia nodes, but that path was open only to the kernel itself; this release opens it to
|
||||
plugins and the model. The public SDK gains media fields and three media injection methods
|
||||
(paired with [SDK v1.1.0](https://github.com/JianFeeeee/hologramagentsdk/tree/v1.1.0),
|
||||
shared by the whole 1.1.x line), and the kernel implements the four matching RPCs. The bridge
|
||||
layer had been **silently dropping fields**: `Confidence`/types/`SentenceText` handed in by a
|
||||
plugin were discarded, `Doc` kept only three fields, and `Remove` never released references
|
||||
(media stayed "referenced" forever, so GC could never reclaim it). `processTextInput` and
|
||||
`processMediaInput` were unified into a single `processInput`, which finally gives the media
|
||||
path the dedup, `no_memory`, channel `Cleaner`, interrupt semantics and correct `EventRawInput`
|
||||
it had always lacked. Three real defects fixed: **user-sent images never appeared in the WebUI
|
||||
chat log** (the media path published a map while the subscriber asserted a string),
|
||||
**`memory_commit`'s `sentence_text` had never been exposed to the model** (note: this tool is now named `stablemem_commit`; the original name is kept for the v1.1.1 release note) (though it is the
|
||||
mandatory link in the media binding chain), and **two data races in `PluginSDK`** (11 reported
|
||||
by `-race`; in production this showed up as sporadic nil-dereference crashes during plugin reload).
|
||||
|
||||
**v1.1.0** — Memory system supports **binary multimedia nodes**. Content-addressed media store
|
||||
(CAS + SQLite metadata + on-disk blobs, `Get` always re-verifies the digest) wired through L0
|
||||
(context events) / L2 (documents) / L3 (graph sentences), with reference-counted GC (referenced
|
||||
items are never deleted). The description text produced by the vision model is the durable
|
||||
semantic memory; the blob is only a cache that capacity GC may evict.
|
||||
|
||||
**v1.0.0** — External plugins moved from C ABI shared libraries to **subprocess + shared memory**. The first release that no longer loads `.so`/`.dll`, and it is incompatible with 0.9.x (existing plugins must be rebuilt into `plugin.bin` with the new toolchain — called `plugindev` back then, **now `hmapdev`** — though **business code needs zero changes**). Eliminates 6 classes of defects that had caused production incidents: hot-reload silently failing (`DF_1_NODELETE` making `dlclose` a no-op), no crash isolation (a plugin panic took down homed), stage lost updates (35.8~36.8% loss under the copy model), uncancellable cgo timeouts (linear OS-thread leaks), `output_send` reporting false success (the model was told "sent" while the message never went out), and Windows capability degradation (only 3 stage fields visible, no write-back). Three communication planes: stdio JSON-RPC (control) + shared memory segment (data) + event ring (notification); the privilege gradient is now enforced by three explicit gates. RPC round-trip p50 24.1µs; crash-to-recovery under 1s.
|
||||
|
||||
**v0.9.0** — C ABI v2: external plugin Stage callbacks can now write back (`invoke_stage` gained a result out-param; plugins may mutate RawMessage/LLMText/ToolResults etc. in OnInput/AfterToolcall/PostAction and have them synced to the core). ABI version now tracks core minor releases (v0.9.x → ABIVersion=2, `version_min=1` keeps old plugins loadable). Also fixes the tool-loop zen-compat placeholder that wrongly fired on first-turn system context tail. The SDK ships an enhanced sanitizer example (bad-UTF-8 / U+FFFD / ANSI-escape scrub across the whole pipeline). **This ABI retired with v1.0.0.**
|
||||
|
||||
**v0.8.0** — Core is functional, plugin system enhanced. 20+ built-in plugins. External plugin development via [hologramagentsdk](https://github.com/JianFeeeee/hologramagentsdk) repo. Added input channel `NoMemory`/`Cleaner`, `ChannelDef`, plugin disable/enable system (CLI + WebUI), `plugindev` toolchain C ABI `ChannelDef` support.
|
||||
344
README.md
344
README.md
@ -4,202 +4,70 @@
|
||||
|
||||
> **English**: [README_EN.md](./README_EN.md)
|
||||
|
||||
以**核心域与应用域分离**为设计原则的 Agent 框架。内核执行零 IO 策略——所有外部交互(WebUI、QQ、命令行、文件操作、网络搜索、备忘等)均由插件层承载,内核不直接处理任何 IO 操作。
|
||||
一个能自己跑几个月、还记得住事的 Agent 内核。
|
||||
|
||||
配合**三层记忆架构**(Context → Document → Graph),通过分级存储与自动归档机制维持单会话长周期运行的上下文连贯性。
|
||||
说人话:你给它接上 QQ、WebUI、命令行、文件、浏览器,它就能在这些地方替你干活。
|
||||
白天聊过的事,晚上还记得。三周前说过的偏好,不会因为上下文窗口滚动就丢了。
|
||||
插件崩了它不倒,模型换了它不改。
|
||||
|
||||
```bash
|
||||
./build/homed -data /tmp/ha # 起来
|
||||
./build/waiter # 跟它说话
|
||||
```
|
||||
|
||||
硬件只需要一台 Linux。API 密钥在 WebUI 里填,存进本地 SQLite。
|
||||
|
||||
---
|
||||
|
||||
## 它跟别的 Agent 框架哪里不一样
|
||||
|
||||
**插件是独立进程,不是动态库。** 插件 panic 带不崩内核,还能自动重启。
|
||||
换个 `plugin.bin` 就生效,不用重装、不用重启。这不是架构洁癖:`.so` 那套踩过的坑够写一页
|
||||
(`dlclose` 被 `DF_1_NODELETE` 变成空操作、cgo 超时杀不掉线程、插件一崩整个 homed 陪葬),
|
||||
所以才改成子进程 + 共享内存。
|
||||
|
||||
**内核自己不碰 IO。** 不发 HTTP、不读文件、不连数据库之外的世界。
|
||||
WebUI、QQ、命令执行、网络搜索全是插件。好处不是"干净",是你能在不改内核的前提下换掉整个交互层。
|
||||
|
||||
**记忆是会下沉的,不是一直堆在上下文里。** 最近的留在窗口,旧的沉到文档层,
|
||||
再旧蒸馏进图数据库。所以它能连续跑几个月而不会把历史全塞进 prompt。
|
||||
|
||||
**它记得的是"场面",不是"会话"。** 你把话题从工作切到生活,它按通道、对话对象、
|
||||
用到的工具、时段自动分场。同一个场面再次出现,上次的结论会被想起来,包括它自己说错过的那些。
|
||||
|
||||
**安全是一堵墙,不是每个工具自己随手扫一下。** 内核流出的请求、插件流回的数据都要先过它。
|
||||
密钥不进 prompt(执行前才回填)、工具黑白名单、敏感词预警、按主体授权
|
||||
(同一个 QQ 插件,群主和路人的权限可以不同)。
|
||||
|
||||
## 能力
|
||||
|
||||
不列 API,列你能让它干什么:
|
||||
|
||||
- **说话的地方**:WebUI、CLI/TUI、QQ 群与私聊、其他 Agent(A2A)、IDE 里的 ACP
|
||||
- **看和听**:图片、视频、语音、屏幕截图、网页
|
||||
- **动手**:读写文件、跑命令、写代码、浏览器操作(含真实 Chrome)、日程、备忘、RSS、音乐
|
||||
- **记住**:三层记忆 + 图记忆 + 知识库,支持按场面召回
|
||||
- **调度**:一边跟人聊,一边把长任务派给子 agent 并行做;用户催了就插队,被打断的工作能接回去
|
||||
|
||||
28 个内置插件、89 个模型可调工具。外部插件一插件一仓,
|
||||
见 [github.com/HologramAgent](https://github.com/HologramAgent)。
|
||||
|
||||
## 怎么工作的
|
||||
|
||||
```go
|
||||
homed(内核零 IO) ← PluginSDK → 插件(所有 IO 能力)
|
||||
homed(内核:LLM 编排 + 记忆 + 检索)
|
||||
↑ PluginSDK
|
||||
插件(WebUI / QQ / CLI / 文件 / 网络 / 一切 IO)
|
||||
```
|
||||
|
||||
**v1.1.1 起媒体贯通插件边界**:插件与模型都能读写记忆里的图片/音频(`InsertWithMedia`、`InjectInputMedia`),媒体以 `[<mime> <短digest>] <描述>` 标记存在于纯文本记忆中——描述是可检索的语义记忆,digest 是回到字节的钥匙。
|
||||
内核和插件之间三条通道:stdio JSON-RPC 管控制、共享内存管大块数据、事件环管通知。
|
||||
大对象不序列化、不拷两遍。
|
||||
|
||||
**v1.0.0 起外部插件是独立子进程**:经 stdio JSON-RPC(控制面)+ 共享内存段(数据面)+ 事件环(通知面)与内核通信。插件崩溃不影响内核且自动重启,换 `plugin.bin` 即生效的真热重载。
|
||||
消息进来先过**调度器**,不直接喂给模型。排队的和插队的分开,插队分四级
|
||||
("这有多不能等"),高级能抢占低级并保存现场。同级的排队等,不会互相踩。
|
||||
|
||||
## 设计要点
|
||||
|
||||
**核心域与应用域分离** — 内核职责限定为 LLM 编排、记忆管理与知识检索;所有 IO 能力(消息收发、文件读写、网络请求、硬件交互等)由插件实现。这种划分在 Agent 框架层面进行领域边界界定,内核与插件各有其责任范围。
|
||||
|
||||
**三层记忆架构** — 通过分级存储策略管理 Agent 长期运行中的信息留存:
|
||||
|
||||
- **Context 层**:预训练词嵌入 / TF-IDF 回退的相关性评分事件窗口,保护最近 10 条,维护 topK 条上下文
|
||||
- **Document 层**:临时记忆,冷数据自动下沉,也支持用户主动提交
|
||||
- **Graph 层**:SQLite 图数据库,持久化实体关系和语义记忆,支持蒸馏管道从原始对话中提取三元组
|
||||
|
||||
**场面识别:声明 + 涌现** — 记忆不按「会话」切,而按**可观察的场所指纹**切:
|
||||
通道(`chan:*`)与对话对象最强(权重 1.0),工具 0.8、话题 0.4、时段最弱 0.2。
|
||||
两条路同时走——**声明**(注入点/工具声明「这是哪个场面」)与**涌现**
|
||||
(指纹同类重复 ≥2 次就自己长出场景,`origin=emergent`,无需标注、无需模型配合)。
|
||||
相似度 ≥0.5 判归属、≥0.35 判唤起(**唤起比归属宽松**:想不起来是损失,多想起一条只是多几行上下文)。
|
||||
场景每次重现强度 +1,久不重现按半衰期衰减。
|
||||
|
||||
> 这条机制有一个值得注意的副作用:**agent 自己的回复也写回记忆**
|
||||
> (`task.go` 的 `context.Append(Source: "agent")`),所以它在同一场面上会**读到自己先前的结论**。
|
||||
> 当输入已过期时,它会据此认出「这事上一轮已经办过」并**主动纠正自己先前的错误判断**——
|
||||
> 表现为自我复盘。这是记忆召回的自然结果,内核里**没有**任何名为「反思」的机制。
|
||||
|
||||
**安全防火墙(独立内核模块)** — 安全不是"某个工具顺手扫一下",而是**横在 Core 与
|
||||
全部外部模块之间的一堵墙**:Core 流出的请求、外部流回的响应都先过 security 审查。
|
||||
它管四件事:凭据闸门(明文 ⇄ `@{别名}`,执行前才回填)、工具黑白名单与沙箱
|
||||
(读写根/网络/`..` 逃逸,`deny` 永远优先)、敏感词与提示词注入预警(命中记录不带原文)、
|
||||
按主体授权与**插件命名限式**(`qq.owner` / `qq.guest`,未声明的档位会报错而不是静默回落)。
|
||||
落地形态是**显式代理**,不是 monkey patch。
|
||||
|
||||
**device 数据面(独立内核模块)** — 工具注册与路由、inputch、outputch、设备登记
|
||||
统一归 `internal/kernelmod/device`;内置工具路由表在 `internal/toolroute`(避免
|
||||
core ↔ kernelboot 成环),device 保留对外入口。
|
||||
|
||||
**输入调度:两类别 + 四级中断** — 输入不直接进 LLM,先进调度器。
|
||||
排队(待办工作)与中断(按"有多不能等"分 L1~L4)两类;高级可抢占低级并保存现场
|
||||
(中断栈),同级不抢占。L4 只归内核与内核级插件(如 WebUI 终止按钮)。
|
||||
见 [`assets/docs/zh/ARCHITECTURE.md`](assets/docs/zh/ARCHITECTURE.md) 的「输入调度器与中断机制」。
|
||||
|
||||
**驻留式子 agent** — 内核可派驻轻量内核的子 agent(自己的调度器与 temp 稳定记忆,
|
||||
共享通道登记表),把长任务/积压交出去并行做。主 agent 忙久了,内核还会把排队输入
|
||||
交给临时**分诊助手**:简单的直接处理,需要主 agent 的立刻回「忙碌中,请稍候」,
|
||||
用户不再干等。见 [`docs/zh/resident-subagent-design.md`](docs/zh/resident-subagent-design.md)。
|
||||
|
||||
## 架构图
|
||||
|
||||
### 一、消息处理时序
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant U as 用户/插件
|
||||
participant IO as IOManager
|
||||
participant SCH as 输入调度器
|
||||
participant EV as eventLoop
|
||||
participant CTX as RelevanceContext
|
||||
participant LLM as LLM+工具循环
|
||||
participant ST as StageHost
|
||||
participant MEM as 三层记忆
|
||||
|
||||
U->>IO: InjectInput(type, payload)
|
||||
IO->>SCH: inputCh
|
||||
rect lavender
|
||||
Note over SCH: 两类别 + 四级中断(L1~L4)
|
||||
SCH->>SCH: 同级不抢占 → 入就绪队列/中断队列
|
||||
SCH->>SCH: 更高级 → 抢占(现场压中断栈,稍后可恢复)
|
||||
SCH->>SCH: 转投(主 agent 忙久了 → 交给临时分诊助手)
|
||||
end
|
||||
SCH->>EV: 选中一个任务开始跑
|
||||
rect lavender
|
||||
Note over EV: processTextInput
|
||||
EV->>ST: StageOnInput 插件可改写/短路
|
||||
EV->>CTX: Prune(input,topK) StaticEmbedder/TF-IDF余弦相似度裁剪
|
||||
CTX->>MEM: 低分事件归档 Document (原始时间戳)
|
||||
EV->>CTX: Append(input) CleanTemplateText→三分支向量→5s写盘
|
||||
end
|
||||
rect lightgreen
|
||||
Note over EV,LLM: process()
|
||||
EV->>MEM: buildMemoryContext Indexer召回Graph(向量+jieba→BFS depth=2)
|
||||
EV->>MEM: buildSystemPrompt DocQuery摘要+Graph记忆索引+人格+技能
|
||||
EV->>ST: StagePreAction 插件可预拦截
|
||||
loop 工具循环
|
||||
LLM->>LLM: 安全点:中断求值/让位
|
||||
LLM->>LLM: LLM Chat
|
||||
LLM->>ST: StagePostAction 插件可修改/短路
|
||||
alt 无 tool call
|
||||
LLM-->>EV: 返回response
|
||||
else 一批 N 个 tool_call
|
||||
Note over EV: batchRunnable 判据:批内 >1 且全部 ParallelSafe<br/>且无同通道重复发送(output_send__「通道」)
|
||||
alt 可并发(三条全满足)
|
||||
par fan-out 并发执行
|
||||
ST->>ST: StageBeforeToolcall ×N 插件可拒绝
|
||||
LLM->>LLM: executeToolCall ×N
|
||||
end
|
||||
else 整批降级串行(任一个未声明 ParallelSafe)
|
||||
loop 每个 tool 依次
|
||||
ST->>ST: StageBeforeToolcall 插件可拒绝
|
||||
LLM->>LLM: executeToolCall
|
||||
end
|
||||
end
|
||||
Note over EV: 顺序收尾:按**声明序** StageAfterToolcall → 落 tool 消息<br/>同通道输出严格保序
|
||||
end
|
||||
end
|
||||
end
|
||||
rect lightpink
|
||||
Note over EV: emitResponse
|
||||
CTX->>CTX: Append(response)
|
||||
ST->>ST: StageBeforeOutput 插件可改写
|
||||
EV-->>U: ResponseCh CLI同步
|
||||
EV-->>EV: 事件总线 WebUI SSE
|
||||
ST->>ST: StageAfterOutput 只读
|
||||
EV->>MEM: emitMemoryCandidate
|
||||
end
|
||||
```
|
||||
|
||||
### 二、Stage 管道
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
S1[① on_input] --> S2[② pre_action]
|
||||
S2 --> S3[③ post_action]
|
||||
S3 --> Q{有 tool_call?}
|
||||
Q -->|是,一批 N 个| PB{batchRunnable?<br/>全声明 ParallelSafe<br/>且无同通道重复发送}
|
||||
PB -->|可并发| S45P[④⑤ 并发 ×N<br/>before_toolcall×N → 执行×N<br/>→ 按声明序 after_toolcall]
|
||||
PB -->|整批降级| S45S[④⑤ 依次 ×N<br/>before_toolcall → 执行<br/>→ after_toolcall]
|
||||
S45P --> S3
|
||||
S45S --> S3
|
||||
Q -->|否| S6[⑥ before_output]
|
||||
S6 --> S7[⑦ after_output]
|
||||
style S1 fill:#e1f5fe
|
||||
style S3 fill:#fff3e0
|
||||
style S6 fill:#e8f5e9
|
||||
style S45P fill:#fce4ec
|
||||
style S45S fill:#f5f5f5
|
||||
```
|
||||
|
||||
> ①–⑦ 七个 Stage 均由 `internal/sdk/plugin.go` 公开(`StageOnInput` … `StageAfterOutput`),
|
||||
> 插件可注册挂钩。⚠️ 并行只影响 **④⑤ 的执行时序**:`post_action` 仍在本批工具
|
||||
> 全部收尾后由下一轮触发,`after_toolcall` 也仍按**声明序**回调——
|
||||
> 并发的是 IO 等待,不是插件契约的可见顺序。
|
||||
|
||||
### 三、三层记忆
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph C[① Context 工作窗口]
|
||||
RC[RelevanceContext]
|
||||
A[Append] -->|CleanTemplateText→三分支向量| RC
|
||||
P[Prune StaticEmbedder/TF-IDF Cosine] -->|低分原始时间戳| D
|
||||
P -->|保留| TL[timeline→按时间排序→system prompt]
|
||||
end
|
||||
subgraph D[② Document 文件记忆]
|
||||
DS[DocStore JSON+TF-IDF]
|
||||
Q1[QueryScored+crossModalMarkdown] -->|【跨模态相关记忆】| SP
|
||||
Q2[coldcontext_query 冷上下文召回] -->|Consume+删除源| DS
|
||||
Q2 -->|原始时间戳写入上下文| RC
|
||||
CD[FindColdDocs 72h] -->|docToTriples| G
|
||||
end
|
||||
subgraph G[③ Graph 图数据库]
|
||||
DB[(SQLite)]
|
||||
IDX[Indexer 向量+jieba→BFS depth=2] -->|【记忆索引】| SP
|
||||
MEM[stablemem_*/coldcontext_*]
|
||||
SOC[person_query/set_trait]
|
||||
subgraph SC[场面识别:声明 + 涌现]
|
||||
FE[① 指纹 chan/peer/tool/topic/part<br/>权重 1.0/1.0/0.8/0.4/0.2]
|
||||
EN{② EnterSceneWithHint<br/>相似度 ≥0.5 归属<br/>≥0.35 唤起}
|
||||
FE --> EN
|
||||
EN -->|插件已声明| DEC[origin=declared]
|
||||
EN -->|重现 ≥2 次| EM[origin=emergent<br/>自动长出场景]
|
||||
EM -->|每次重现 strength+1| STR[③ 用进废退<br/>久不重现按半衰期衰减]
|
||||
DEC --> STR
|
||||
end
|
||||
STR -->|Primary| CARRY[本轮命中的场景<br/>挂载的记忆自动唤起]
|
||||
CARRY -->|【场景记忆】| SP
|
||||
end
|
||||
subgraph H[④ 心跳蒸馏]
|
||||
REORG -->|Step3 冷文档| CD
|
||||
REORG -->|Step4 Bigram Jaccard| CONS[consolidation]
|
||||
PIPE[Pipeline 正则] -->|姓名/住址/喜好/年龄/职业| DB
|
||||
end
|
||||
SP[System Prompt] -->|顺序组装| LLM
|
||||
LLM[LLM] -->|coldcontext_query| Q2
|
||||
LLM -->|stablemem_recall| MEM
|
||||
```
|
||||
|
||||
详细说明见 [`assets/docs/zh/ARCHITECTURE.md`](assets/docs/zh/ARCHITECTURE.md)。
|
||||
然后进七阶段管道,模型可以连续调工具。同轮多个工具默认**串行**,
|
||||
要让它们并行,得由工具自己声明安全。批里有一个没声明,整批退回串行。
|
||||
|
||||
## 快速体验
|
||||
|
||||
@ -246,7 +114,7 @@ internal/
|
||||
├── memory/ 三层记忆:Graph(SQLite) / Document(JSON+TF-IDF) / Text(JSONL) + StaticEmbedder(预训练词嵌入/TF-IDF回退) + CleanTemplateText(去模版)
|
||||
├── knowledge/ 知识库(文件系统 + TF-IDF)
|
||||
├── plugin/ 插件注册表 + 子进程加载器(stdio RPC + 共享内存段 + 事件环)
|
||||
├── plugins/ 内置 13 个插件(webui/cli/agentcli/cfgmgr/healthcheck/kbtree/multimodal/pluginmgr/clawhubadapter/remotedevice/skillmgr/seemedia/seq)—— 判定标准见 ARCHITECTURE「内置插件的判定标准」
|
||||
├── plugins/ 内置插件(webui / cli / files / cmd / mcp / kbtree / seq / skillmgr …)
|
||||
├── sdk/ PluginSDK(Tool/Stage/Event 三通道)
|
||||
├── config/ SQLite 配置中心
|
||||
├── events/ 事件总线
|
||||
@ -254,98 +122,13 @@ internal/
|
||||
外部插件开发见 [hologramagentsdk](https://github.com/JianFeeeee/hologramagentsdk) 仓库,使用 `hmapdev` 工具链开发,参考 `example/` 目录下的 Go 和 Lua 示例
|
||||
```
|
||||
|
||||
## 项目状态
|
||||
|
||||
**v1.4.x 线**(进行中,`main`)—— **声明式并发安全** + **序列编排(seq)** + **工具结果诚实化**。
|
||||
|
||||
- **同轮工具并行执行**:同一轮里的多个 `tool_call` 默认并发执行,**但以「安全」为前置**——
|
||||
工具必须在自己的 `ToolDef` 里显式声明 `ParallelSafe`(内置工具用 `toolDefOptions`),
|
||||
未声明的一律串行;`Serial` 优先级更高。**批内只要有一个不安全,整批降级为串行**。
|
||||
并发声明写在**工具自身**,内核不做硬编码工具名安全表。同通道输出**严格保序**,
|
||||
结果与文本分离(`toolOutcome.Text` / `Raw`)。
|
||||
- **`seq` 序列编排插件**:AST 解析 + 持久化、具名 group、变量槽、**组内并行 + 组间串行**、
|
||||
条件调用(`seq_when_call`)、跨序列调用图与环检测、错误契约。
|
||||
- **工具结果只统计不裁剪**:结果超出预算时**报告**统计量,不静默截断。
|
||||
- **结构化工具错误契约**:参数按 schema **预校验**(分派前拦下)、
|
||||
「工具不存在」与「执行失败」区分、`Success` 不再恒真。
|
||||
- **设备命令白名单可配置**(waiter `device_cmd_allowlist`)、
|
||||
**站点漂移巡检**、**GUI 增量渲染**(保留全部历史,按 `data-msgkey` 复用节点,
|
||||
50/200/400 条实测 8.2/23.5/38.2ms)。
|
||||
|
||||
**v1.3.x 线**(v1.3.1–v1.3.13)—— **驻留式子 agent** + **输入调度器重做**。
|
||||
|
||||
- **驻留式子 agent**:内核可派驻轻量内核的子 agent(自己的调度器、自己的 temp 稳定记忆、
|
||||
共享通道登记表)。父经 `resident_agents`(list/create/send/inspect/compress/reclaim/destroy)
|
||||
派活与收活;inputch 可划给子,输入**在进内核之前**就已路由到子。
|
||||
- **输出通道可寻址到具体 agent**:`AllowedOutputs` 授权集合(三处过滤点一致),
|
||||
父/子之间可互相投递;设备能力也 outputch 化(每设备一个 `device/<id>` 通道)。
|
||||
- **输入调度器**:排队/中断两类别 + 四级中断(L1~L4)+ 抢占/挂起/恢复/中断栈;
|
||||
同级不抢占、有饥饿防护与抢占冷却;L4 只归内核与内核级插件(WebUI 终止按钮)。
|
||||
- **轻量内核 profile**:子的记忆面收窄为「传统上下文 + 稳定记忆(stablemem)」(窄接口,
|
||||
主库以 query_only 受限句柄打开,写走自己的 temp 实例)。
|
||||
- **积压及时反馈**(后续线):主 agent 长时间忙时,内核把排队输入交给临时**分诊助手** ——
|
||||
简单的直接处理并回复,需要主 agent 的立刻回「忙碌中,请稍候」,用户不再干等十几分钟。
|
||||
- 修掉一批真实缺陷:销毁驻留子时入站 inputch(`child/<id>`)注册残留、
|
||||
子的轮次永远显示 0(`info()` 根本没填)、子侧 childIO 空壳(未继承父的输出通道)、
|
||||
设备心跳 pong 忘了 Flush(每 60 秒掉线)、Lua 插件桥与 SDK 1.3.0 对齐。
|
||||
|
||||
**v1.2.0** — 统一多模态向量空间 + 媒体升为稳定记忆一等节点 + 数据面全量迁到共享内存。
|
||||
|
||||
- **模型中立的统一向量空间**:内核不再适配任何具体模型,只提供公共 provider SPI
|
||||
(`pkg/embedding`:`Modality` / `Input{Data,MIME}` / `Info{Dimension,Fingerprint,Modalities}`
|
||||
- 名字注册表),实现在 `providers/*`。默认 **Chinese-CLIP ViT-B/16** —— text 与 image
|
||||
落在**同一空间**(512 维、指纹 `cd2a495cf990`、Apache-2.0;实测加载峰值 1.59GB、静置回收后稳态约 0.89GB);
|
||||
`qwen3vl` 保留(2048 维、常驻约 9.4GB,供内存充足或将来要视频的机器切回)。
|
||||
文本检索仍由既有词向量 / TF-IDF 兜底:CLIP 双塔的**纯文本语义弱于 MLLM 型嵌入器**,
|
||||
这是已知并写进文档的代价。
|
||||
- **媒体是图数据库的一等节点与边**:**彻底删除**「用文本描述式索引图片」这套将就机制,
|
||||
以及 `media_refs` 与媒体引用计数。记忆块遵循单层不变量——Context → Document → Graph
|
||||
是块的**迁移**,不是复制、也不靠引用保活。
|
||||
- **数据面全部走共享内存**(工具调用帧 / Cleaner / 输入输出通道 / 媒体块 / 文档与知识正文),
|
||||
RPC 只传偏移描述符;**RPC 协议升到 2**,fd3 布局改变,**不支持滚动升级**——
|
||||
内核与全部插件必须同批重建、同批安装,存量插件须用新版 `hmapdev` 重编。
|
||||
- 注入可声明 `InjectOptions{NoMemory, ContextPolicy}`(**默认仍记入记忆、默认不裁剪**);
|
||||
裁剪必须显式声明,且先经插件注册的 `Cleaner`。SDK 1.2.0 相对 1.1.0 **纯追加**。
|
||||
- **发行包默认启用** ONNX 向量空间,并把模型(754MB)与 ONNX Runtime(24MB)随
|
||||
server/full 包发布;`homed` 放弃 Windows 原生支持改走 WSL2;jieba 词库内嵌进二进制。
|
||||
- 修掉三个**安装链静默失败**:`initconfig` 因 `CGO_ENABLED=0` 是空操作(打印凭据却一个字节
|
||||
没写)、全新安装被误判「已有配置」而整体跳过默认值播种(装完 0 插件)、deb 的 `postinst`
|
||||
查错 unit 路径导致 `enable` 从未执行。
|
||||
- 自本版起以 **AGPL-3.0-only** 发布(含网络条款;插件静态链接 SDK 故须同许可,见「许可」)。
|
||||
|
||||
> 以下历史条目保留原文以呈现演进,其中两条机制**已在 v1.2.0 移除**:
|
||||
> 「媒体以 `[<mime> <短digest>] <描述>` 标记参与检索」(描述式索引)与「媒体引用计数式 GC」。
|
||||
>
|
||||
> 另有**一处许可口径已变更**(2026-09-24):上面 v1.2.0 条目里的「插件静态链接 SDK 故须同许可」
|
||||
> **自今日起不成立**。SDK 改以 **MIT** 发布(宽松许可,不继承内核的 AGPL),故外部插件**不是**
|
||||
> 本项目的衍生作品,作者可自行选择许可(含闭源、商业、私有),亦不受 §13 网络条款约束。
|
||||
> 原文保留以呈现当时的口径。
|
||||
>
|
||||
> 另有**两项性能断言的量纲需要更正**(2026-09-20 实测):
|
||||
> 「崩溃到恢复 <1s」不成立 —— 崩溃后是**线性退避重启**,即 1s / 2s / 3s(`procRestartBackoff=1s × 第 n 次`),
|
||||
> 首次重启就要等 1s。且 5 分钟窗口内第 **4** 次崩溃即停止自动重启待人工介入(`procMaxRestarts=3`,判定为 `n > 3`)。
|
||||
> 该断言写下时(v1.0.0)退避值已是 1s,故从未成立。
|
||||
> 「RPC 往返 p50 24.1µs」与当前实测同量级但不吻合:本机 `BenchmarkToolInvoke` 实测
|
||||
> inline/small **30.4µs**、frame/small 51.5µs、inline/large 767µs、frame/large 398µs。
|
||||
> 保留原文不修改,以免伪造历史。
|
||||
|
||||
**v1.1.1** — 多模态贯通**插件边界**。v1.1.0 让记忆系统支持了二进制多媒体节点,但那条链路只对内核自己开放;本版打通到插件与模型。公开 SDK 新增媒体字段与三个媒体注入接口(配套 [SDK v1.1.0](https://github.com/JianFeeeee/hologramagentsdk/tree/v1.1.0),整条 1.1.x 线共用),内核实现对应四个 RPC。桥接层此前在**静默裁字段**:插件交进来的 `Confidence`/类型/`SentenceText` 全被丢弃、`Doc` 只留三个字段、`Remove` 不解引用(媒体永久算「被引用」,GC 收不掉)。`processTextInput`/`processMediaInput` 归一成一条 `processInput`,媒体路径由此获得它一直缺的去重、`no_memory`、通道 `Cleaner`、中断语义、`EventRawInput`。修掉三处真实缺陷:**用户发的图从来没出现在 WebUI 聊天记录里**(媒体路径发布 map 而订阅方断言 string)、**`memory_commit` 的 `sentence_text` 从未暴露给模型**(注:该工具现名 `stablemem_commit`,本条保留 1.1.1 发布时的原名)(而它是媒体绑定链的必经环节)、**`PluginSDK` 两处并发竞态**(`-race` 实测 11 处,插件重载瞬间偶发 nil 解引用崩溃)。
|
||||
|
||||
**v1.1.0** — 记忆系统支持**二进制多媒体节点**。内容寻址媒体存储(CAS + SQLite 元数据 + 磁盘 blob,`Get` always 重校 digest),贯通 L0(上下文事件)/L2(文档)/L3(图谱句子)三层,引用计数式 GC(有引用者绝不删)。视觉模型生成的描述文本是持久语义记忆,blob 只是可被容量 GC 淘汰的缓存。
|
||||
|
||||
**v1.0.0** — 外部插件从 C ABI 动态库迁移到**子进程 + 共享内存**。首个不再加载 `.so`/`.dll` 的版本,与 0.9.x 不兼容(存量插件须用新版工具链重编;该工具链当时名为 `plugindev`,**现名 `hmapdev`**)。外部插件需重编为 `plugin.bin`,**业务代码零改动**)。消除 6 类此前在生产造成故障的缺陷:热重载失效(`DF_1_NODELETE` 让 `dlclose` 成 no-op)、崩溃隔离缺失(插件 panic 带崩 homed)、stage lost update(副本模型丢失 35.8~36.8%)、cgo 超时不可中断(线程线性泄漏)、`output_send` 假成功(模型收到「已发送」而消息未送达)、Windows 能力断层(只见 3 个 stage 字段且无法写回)。三面通信:stdio JSON-RPC(控制)+ 共享内存段(数据)+ 事件环(通知);权限梯度显式化为三道闸。RPC 往返 p50 24.1µs,崩溃到恢复 <1s。
|
||||
|
||||
**v0.9.0** — C ABI v2:外部插件 Stage 回调支持写回(`invoke_stage` 增加 result 输出,插件可在 OnInput/AfterToolcall/PostAction 修改 RawMessage/LLMText/ToolResults 等并同步回内核),ABI 版本随内核 minor 对齐(v0.9.x → ABIVersion=2,`version_min=1` 向后兼容旧插件)。同步修复工具循环 zen 兼容补位误伤首轮 system 上下文的问题。配套 SDK 提供增强版 sanitizer 示例(坏 UTF-8/U+FFFD/ANSI 转义全链路清洗)。**该 ABI 已随 v1.0.0 退场。**
|
||||
|
||||
**v0.8.0** — 核心可用,插件系统增强。内置 20+ 插件,外部插件开发见 [hologramagentsdk](https://github.com/JianFeeeee/hologramagentsdk) 仓库。新增输入通道 `NoMemory`/`Cleaner`、`ChannelDef`、插件禁用/启用系统(CLI + WebUI),`plugindev` 工具链完成 C ABI `ChannelDef` 传递。
|
||||
|
||||
## 文档
|
||||
|
||||
**在线文档**:
|
||||
|
||||
- 介绍站(项目总览):<https://introduce.homeagent.jianfgit.xyz/>
|
||||
- 插件 SDK 文档(快速开始 / API 参考 / 指南 / 示例):<https://sdk.homeagent.jianfgit.xyz/>
|
||||
—— 面向 agent 的纯文本入口:[`llms.txt`](https://sdk.homeagent.jianfgit.xyz/llms.txt) /
|
||||
给 agent 直接读的纯文本入口:[`llms.txt`](https://sdk.homeagent.jianfgit.xyz/llms.txt) /
|
||||
[`llms-full.txt`](https://sdk.homeagent.jianfgit.xyz/llms-full.txt)
|
||||
|
||||
**仓内文档**(随代码版本走):
|
||||
@ -355,8 +138,9 @@ internal/
|
||||
- [插件开发指南](assets/docs/zh/PLUGIN_DEV.md) | [English](assets/docs/en/PLUGIN_DEV.md)
|
||||
- [Lua Adapter](assets/docs/zh/ADAPTER.md) | [English](assets/docs/en/ADAPTER.md)
|
||||
- [知识库演示](assets/knowledge/homeagent_architecture/content.md)
|
||||
- [设计文档索引](docs/zh/README.md) — 32 篇设计/计划文档的**状态分类与可信度规则**
|
||||
- [未来计划](docs/zh/future-plan.md) — 尚未实现 / 已知缺口的**唯一登记处**
|
||||
- [设计文档索引](docs/zh/README.md) — 32 篇设计文档的状态分类与可信度规则
|
||||
- [更新记录](CHANGELOG.md) — 每个版本实际改了什么
|
||||
- [未来计划](docs/zh/future-plan.md) — 没做的东西都记在这
|
||||
|
||||
## 下载
|
||||
|
||||
@ -399,13 +183,13 @@ make install # 安装到系统
|
||||
即:任何人把改过的 HologramAgent 对外提供网络服务,都必须让该服务的使用者拿到改动后的源码。
|
||||
|
||||
插件与本项目通过公开 [hologramagentsdk](https://github.com/JianFeeeee/hologramagentsdk) 静态链接
|
||||
(SDK 源码会进入插件二进制),但那个仓**以 MIT 发布**——MIT 是宽松许可,拿到授权的代码不继承
|
||||
(SDK 源码会进入插件二进制),但那个仓以 **MIT** 发布。MIT 是宽松许可,拿到授权的代码不继承
|
||||
本项目的 AGPL。因此**外部插件不是本项目的衍生作品**,作者可自行选择许可(含闭源、商业、私有),
|
||||
既不必同许可、也不受 §13 网络条款约束。第三方插件生态的安全与活跃正建立在这条之上。
|
||||
|
||||
边界很清楚:**AGPL 覆盖内核与随包内置插件**(`homed`、`internal/`、`internal/plugins/` 下 13 个内置插件);
|
||||
**MIT 覆盖公开 SDK**(`sdk/`,`go.mod` 零外部依赖、只依赖 Go 标准库,不引用内核任何代码)。
|
||||
子进程隔离在这里不重要了——决定许可的是被链接的 SDK 代码本身,而它是 MIT。
|
||||
子进程隔离在这里不重要了。决定许可的是被链接的 SDK 代码本身,而它是 MIT。
|
||||
|
||||
### 随包分发的第三方组件
|
||||
|
||||
|
||||
415
README_EN.md
415
README_EN.md
@ -4,193 +4,80 @@
|
||||
|
||||
> **中文**: [README.md](./README.md)
|
||||
|
||||
An Agent framework designed around **separation of core domain and application domain**. The kernel enforces a zero-IO policy — all external interaction (WebUI, QQ, CLI, file operations, web search, memos, etc.) is handled by the plugin layer; the kernel performs no direct IO operations.
|
||||
An agent kernel that runs for months and still remembers.
|
||||
|
||||
Combined with a **three-layer memory architecture** (Context → Document → Graph), it maintains contextual coherence across long-running single-conversation sessions through tiered storage and automated archival.
|
||||
In plain terms: connect it to QQ, a web UI, your shell, your files, a browser, and it gets
|
||||
work done in all of them. What you discussed this morning, it still knows tonight. A
|
||||
preference you mentioned three weeks ago doesn't vanish when the context window scrolls.
|
||||
A plugin crashes and it keeps running. Swap the model and nothing else changes.
|
||||
|
||||
```bash
|
||||
./build/homed -data /tmp/ha # start the kernel
|
||||
./build/waiter # talk to it
|
||||
```
|
||||
|
||||
All you need is a Linux box. API keys go in through the web UI and live in local SQLite.
|
||||
|
||||
---
|
||||
|
||||
## What makes it different
|
||||
|
||||
**Plugins are separate processes, not shared libraries.** A plugin panic can't take the
|
||||
kernel down, and it restarts on its own. Drop in a new `plugin.bin` and it's live. No
|
||||
rebuild, no restart. This isn't architectural purity for its own sake: the `.so` approach
|
||||
burned us badly enough to justify the rewrite (`dlclose` silently no-ops under
|
||||
`DF_1_NODELETE`, cgo timeouts leave threads alive, one plugin panic dragged the whole
|
||||
`homed` process down with it).
|
||||
|
||||
**The kernel touches no IO.** No HTTP calls, no file reads, no reach into the world outside
|
||||
its database. Web UI, QQ, command execution, web search are all plugins. The payoff isn't
|
||||
tidiness; it's that you can replace the entire interaction layer without touching the kernel.
|
||||
|
||||
**Memory sinks as it ages; it doesn't pile up in the context window.** Recent turns stay in
|
||||
the window, older ones settle into a document layer, oldest get distilled into a graph.
|
||||
That's why it can run for months without stuffing the whole history into every prompt.
|
||||
|
||||
**It remembers scenes, not sessions.** Move from work talk to personal talk and it splits
|
||||
the context by channel, conversation partner, tools used, and time of day. When a scene
|
||||
recurs, last time's conclusions come back, including the ones it got wrong.
|
||||
|
||||
**Security is a wall, not a line each tool remembers to check.** Everything leaving the
|
||||
kernel and everything flowing back from plugins passes through it. Secrets never enter the
|
||||
prompt (they're substituted at execution time), tool allow/deny lists, sensitive-word
|
||||
alerts, per-principal authorization (the same QQ plugin can grant an owner and a random
|
||||
group member different powers).
|
||||
|
||||
## What it can do
|
||||
|
||||
No API listings. What you can point it at:
|
||||
|
||||
- **Where it talks**: web UI, CLI/TUI, QQ groups and DMs, other agents (A2A), ACP in your IDE
|
||||
- **What it sees**: images, video, voice, screenshots, web pages
|
||||
- **What it does**: read and write files, run commands, write code, drive a real Chrome, calendar, notes, RSS, music
|
||||
- **What it remembers**: three-layer memory plus graph memory plus a knowledge base, recallable by scene
|
||||
- **How it schedules**: chat with a human while long jobs run in parallel sub-agents; jump the queue when the user pushes back; resume interrupted work where it stopped
|
||||
|
||||
28 built-in plugins, 89 model-callable tools. External plugins live one-per-repo at
|
||||
[github.com/HologramAgent](https://github.com/HologramAgent).
|
||||
|
||||
## How it works
|
||||
|
||||
```go
|
||||
homed (kernel, zero IO) ← PluginSDK → plugins (all IO capabilities)
|
||||
homed (kernel: LLM orchestration + memory + retrieval)
|
||||
↑ PluginSDK
|
||||
plugins (web UI / QQ / CLI / files / network / all IO)
|
||||
```
|
||||
|
||||
**Since v1.1.1 media reaches the plugin boundary**: plugins and the model can both read and
|
||||
write images/audio in memory (`InsertWithMedia`, `InjectInputMedia`). Media lives in plain-text
|
||||
memory as a `[<mime> <short digest>] <description>` marker — the description is the searchable
|
||||
semantic memory, the digest is the key back to the bytes.
|
||||
Three channels between kernel and plugins: stdio JSON-RPC for control, shared memory for
|
||||
bulk data, an event ring for notifications. Large objects aren't serialized and copied twice.
|
||||
|
||||
**Since v1.0.0 external plugins are independent subprocesses**, communicating with the kernel over
|
||||
stdio JSON-RPC (control plane) + a shared memory segment (data plane) + an event ring (notification
|
||||
plane). A plugin crash cannot take down the kernel and it restarts automatically; swapping
|
||||
`plugin.bin` gives true hot-reload.
|
||||
Incoming messages hit a **scheduler** first, not the model. Queued work and interruptions
|
||||
are separate classes; interruptions come in four levels ("how badly can this wait"), and a
|
||||
higher level preempts a lower one while saving its state. Same level waits its turn.
|
||||
|
||||
## Design Principles
|
||||
|
||||
**Separation of Core Domain and Application Domain** — The kernel's responsibilities are limited to LLM orchestration, memory management, and knowledge retrieval; all IO capabilities (message send/receive, file read/write, network requests, hardware interaction, etc.) are implemented by plugins. This separation defines domain boundaries at the Agent framework level, with distinct responsibility scopes for the kernel and plugins.
|
||||
|
||||
**Three-Layer Memory Architecture** — Manages information retention in long-running agents through a tiered storage strategy:
|
||||
|
||||
- **Context Layer (L1, hot)**: Pretrained word embedding / TF-IDF fallback relevance-scored event window, protects last 10 entries, maintains topK context entries
|
||||
- **Cold-context Layer (L2, not fed directly)**: Temporary memory with automatic cold data sinking, also supports user-initiated submissions
|
||||
- **Stablemem Layer (L3, persistent)**: SQLite graph database, persists entity relationships and semantic memory, supports distillation pipelines to extract triples from conversations
|
||||
|
||||
**Security firewall (standalone kernel module)** — Security is not "a scan tucked into some tool"; it is a wall between Core and every external module: requests leaving Core and responses coming back both pass inspection. It owns four things: the credential gate (plaintext ⇄ `@{alias}`, real values filled only right before execution), tool allow/deny plus sandbox (read/write roots, network, `..` escapes; **deny always wins**), sensitive-word and prompt-injection warnings (findings never carry the matched text), and per-principal grants with **plugin-named profiles** (`qq.owner` / `qq.guest`; an undeclared profile is an error, never a silent fallback). Implemented as an **explicit proxy**, not monkey patching.
|
||||
|
||||
**Device data plane (standalone kernel module)** — Tool registration and routing, inputch, outputch and the device registry all belong to `internal/kernelmod/device`; the built-in routing table lives in `internal/toolroute` (to avoid a core ↔ kernelboot cycle) while device keeps the public entry point.
|
||||
|
||||
**Input Scheduling: 2 classes + 4 interrupt levels** — Input does not go straight to the LLM;
|
||||
it first enters the scheduler. Two classes (queued = pending work, interrupt = ranked L1–L4 by
|
||||
"how urgent") with preemption and frame saving (interrupt stack); same level never preempts
|
||||
same level. L4 belongs only to the kernel and kernel-level plugins (e.g. the WebUI stop button).
|
||||
See "Input Scheduler & Interrupt Mechanism" in [`assets/docs/en/ARCHITECTURE.md`](assets/docs/en/ARCHITECTURE.md).
|
||||
|
||||
**Resident sub-agents** — The kernel can station lightweight-kernel child agents (their own
|
||||
scheduler and temp stable memory, sharing the channel registry) to run long or backlogged work
|
||||
in parallel. When the main agent stays busy, the kernel hands queued input to a temporary
|
||||
**triage assistant**: simple items are handled directly, items needing the main agent get an
|
||||
immediate "busy, please wait" — users no longer wait in silence.
|
||||
See [`docs/zh/resident-subagent-design.md`](docs/zh/resident-subagent-design.md).
|
||||
|
||||
## Architecture Diagrams
|
||||
|
||||
### 1. Message Processing Sequence
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant U as User/Plugin
|
||||
participant IO as IOManager
|
||||
participant SCH as Input Scheduler
|
||||
participant EV as eventLoop
|
||||
participant CTX as RelevanceContext
|
||||
participant LLM as LLM+Tool Loop
|
||||
participant ST as StageHost
|
||||
participant MEM as Three-Layer Memory
|
||||
|
||||
U->>IO: InjectInput(type, payload)
|
||||
IO->>SCH: inputCh
|
||||
rect lavender
|
||||
Note over SCH: 2 task classes + 4 interrupt levels (L1-L4)
|
||||
SCH->>SCH: same level never preempts -> ready/interrupt queue
|
||||
SCH->>SCH: higher level -> preempt (frame pushed to interrupt stack)
|
||||
SCH->>SCH: offload (main agent busy too long -> temporary triage assistant)
|
||||
end
|
||||
SCH->>EV: pick one task and run it
|
||||
rect lavender
|
||||
Note over EV: processTextInput
|
||||
EV->>ST: StageOnInput Plugin can rewrite/short-circuit
|
||||
EV->>CTX: Prune(input,topK) StaticEmbedder/TF-IDF cosine pruning
|
||||
CTX->>MEM: Low-score events archived to Document (original timestamp)
|
||||
EV->>CTX: Append(input) CleanTemplateText→three-branch vector→5s write
|
||||
end
|
||||
rect lightgreen
|
||||
Note over EV,LLM: process()
|
||||
EV->>MEM: buildMemoryContext Indexer recalls from Graph (vector+jieba→BFS depth=2)
|
||||
EV->>MEM: buildSystemPrompt DocQuery summary+Graph memory index+Persona+Skills
|
||||
EV->>ST: StagePreAction Plugin can pre-intercept
|
||||
loop Tool loop
|
||||
LLM->>LLM: safe point: interrupt eval / yield
|
||||
LLM->>LLM: LLM Chat
|
||||
LLM->>ST: StagePostAction Plugin can modify/short-circuit
|
||||
alt No tool call
|
||||
LLM-->>EV: Returns response
|
||||
else a batch of N tool_calls
|
||||
Note over EV: batchRunnable criteria: batch >1 AND all declare ParallelSafe<br/>and no same-channel duplicate send (output_send__«channel»)
|
||||
alt parallel (all three satisfied)
|
||||
par fan-out concurrent execution
|
||||
ST->>ST: StageBeforeToolcall ×N Plugin can reject
|
||||
LLM->>LLM: executeToolCall ×N
|
||||
end
|
||||
else whole batch degrades to serial (any undeclared)
|
||||
loop Each tool in turn
|
||||
ST->>ST: StageBeforeToolcall Plugin can reject
|
||||
LLM->>LLM: executeToolCall
|
||||
end
|
||||
end
|
||||
Note over EV: ordered tail: StageAfterToolcall in **declaration order** → append tool msgs<br/>same-channel output strictly ordered
|
||||
end
|
||||
end
|
||||
end
|
||||
rect lightpink
|
||||
Note over EV: emitResponse
|
||||
CTX->>CTX: Append(response)
|
||||
ST->>ST: StageBeforeOutput Plugin can rewrite
|
||||
EV-->>U: ResponseCh CLI sync
|
||||
EV-->>EV: Event bus WebUI SSE
|
||||
ST->>ST: StageAfterOutput Read-only
|
||||
EV->>MEM: emitMemoryCandidate
|
||||
end
|
||||
```
|
||||
|
||||
### 2. Stage Pipeline
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
S1[① on_input] --> S2[② pre_action]
|
||||
S2 --> S3[③ post_action]
|
||||
S3 --> Q{Any tool_call?}
|
||||
Q -->|Yes, batch of N| PB{batchRunnable?<br/>all declare ParallelSafe<br/>and no same-channel duplicate send}
|
||||
PB -->|parallel| S45P[④⑤ concurrent ×N<br/>before_toolcall×N → execute×N<br/>→ after_toolcall in declaration order]
|
||||
PB -->|degrade to serial| S45S[④⑤ sequential ×N<br/>before_toolcall → execute<br/>→ after_toolcall]
|
||||
S45P --> S3
|
||||
S45S --> S3
|
||||
Q -->|No| S6[⑥ before_output]
|
||||
S6 --> S7[⑦ after_output]
|
||||
style S1 fill:#e1f5fe
|
||||
style S3 fill:#fff3e0
|
||||
style S6 fill:#e8f5e9
|
||||
style S45P fill:#fce4ec
|
||||
style S45S fill:#f5f5f5
|
||||
```
|
||||
|
||||
> All seven stages ①–⑦ are published by `internal/sdk/plugin.go` (`StageOnInput` …
|
||||
> `StageAfterOutput`); plugins may register hooks. ⚠️ Parallelism affects only the
|
||||
> **execution timing of ④⑤**: `post_action` still fires on the next round once the whole
|
||||
> batch is collected, and `after_toolcall` still runs in **declaration order** —
|
||||
> what runs concurrently is IO waiting, not the plugin-visible contract order.
|
||||
|
||||
### 3. Three-Layer Memory
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph C[① Context Working Window]
|
||||
RC[RelevanceContext]
|
||||
A[Append] -->|CleanTemplateText→three-branch vector| RC
|
||||
P[Prune StaticEmbedder/TF-IDF Cosine] -->|Low score original timestamp| D
|
||||
P -->|Keep| TL[timeline→chronological→system prompt]
|
||||
end
|
||||
subgraph D[② Document File Memory]
|
||||
DS[DocStore JSON+TF-IDF]
|
||||
Q1[Query summary auto-inject] -->|Related Memory Docs| SP
|
||||
Q2[coldcontext_query cold-context recall] -->|Consume+delete source| DS
|
||||
Q2 -->|Original timestamp write to context| RC
|
||||
CD[FindColdDocs 72h] -->|docToTriples| G
|
||||
end
|
||||
subgraph G[③ Graph Database]
|
||||
DB[(SQLite)]
|
||||
IDX[Indexer vector+jieba→BFS depth=2] -->|Memory Index| SP
|
||||
MEM[stablemem_*/coldcontext_*]
|
||||
SOC[person_query/set_trait]
|
||||
subgraph SC[Scene recognition: declared + emergent]
|
||||
FE[① fingerprint chan/peer/tool/topic/part<br/>weights 1.0/1.0/0.8/0.4/0.2]
|
||||
EN{② EnterSceneWithHint<br/>similarity ≥0.5 join<br/>≥0.35 recall}
|
||||
FE --> EN
|
||||
EN -->|plugin declared| DEC[origin=declared]
|
||||
EN -->|seen ≥2 times| EM[origin=emergent<br/>the scene grows itself]
|
||||
EM -->|each recurrence strength+1| STR[③ use-it-or-lose-it<br/>decay by half-life when unused]
|
||||
DEC --> STR
|
||||
end
|
||||
STR -->|Primary| CARRY[scene hit this round<br/>its memories auto-recalled]
|
||||
CARRY -->|Scene Memory| SP
|
||||
end
|
||||
subgraph H[④ Heartbeat Distillation]
|
||||
REORG -->|Step3 Cold docs| CD
|
||||
REORG -->|Step4 Bigram Jaccard| CONS[consolidation]
|
||||
PIPE[Pipeline regex] -->|Name/Address/Likes/Age/Job| DB
|
||||
end
|
||||
SP[System Prompt] -->|Sequential assembly| LLM
|
||||
LLM[LLM] -->|coldcontext_query| Q2
|
||||
LLM -->|stablemem_recall| MEM
|
||||
```
|
||||
|
||||
See [`assets/docs/en/ARCHITECTURE.md`](assets/docs/en/ARCHITECTURE.md) for details.
|
||||
Then seven pipeline stages, and the model can call tools in a loop. Multiple tool calls in
|
||||
one turn are **serial by default**; to run them in parallel a tool must declare itself safe.
|
||||
One undeclared tool in a batch drops the whole batch back to serial.
|
||||
|
||||
## Quick Start
|
||||
|
||||
@ -203,11 +90,32 @@ make build build-cli
|
||||
# Interactive mode
|
||||
./build/waiter
|
||||
|
||||
# Or single message
|
||||
echo "Hello, remember that I like coffee" | ./build/waiter
|
||||
# Or a single message
|
||||
echo "Hi, remember I like coffee" | ./build/waiter
|
||||
```
|
||||
|
||||
API keys are configured via WebUI `http://localhost:8080` settings page, persisted in SQLite.
|
||||
### Daemon mode
|
||||
|
||||
`waiter` can also run resident, holding a persistent connection to `homed` and waiting for a
|
||||
TUI instance to attach. Useful when the agent needs to reach you proactively or keep a device
|
||||
bridge alive.
|
||||
|
||||
```bash
|
||||
# Resident (connects to ~/.homeagent/cli.sock by default)
|
||||
./build/waiter --daemon
|
||||
|
||||
# Custom socket
|
||||
./build/waiter --socket /path/to/cli.sock --daemon
|
||||
|
||||
# Any TUI or one-shot instance then attaches to the running daemon instead of homed directly
|
||||
./build/waiter
|
||||
```
|
||||
|
||||
The daemon listens on `~/.homeagent/waiter.sock`, replays the last 256 lines of conversation to
|
||||
new clients, survives disconnects, reconnects to `homed` on its own, and keeps the device bridge
|
||||
alive when `device_gateway` / `device_token` are configured.
|
||||
|
||||
Set API keys in the web UI at `http://localhost:8080`; they persist in SQLite.
|
||||
|
||||
## Code Structure
|
||||
|
||||
@ -220,145 +128,23 @@ internal/
|
||||
├── memory/ Three-layer memory: Graph(SQLite) / Document(JSON+TF-IDF) / Text(JSONL) + StaticEmbedder(pretrained word embedding/TF-IDF fallback) + CleanTemplateText(de-template)
|
||||
├── knowledge/ Knowledge base (filesystem + TF-IDF)
|
||||
├── plugin/ Plugin registry + subprocess loader (stdio RPC + shared memory segment + event ring)
|
||||
├── plugins/ 13 built-in plugins (webui/cli/agentcli/cfgmgr/healthcheck/kbtree/multimodal/pluginmgr/clawhubadapter/remotedevice/skillmgr/seemedia/seq) — see ARCHITECTURE "Which Plugins Stay Built-in"
|
||||
├── plugins/ built-in plugins (webui / cli / files / cmd / mcp / kbtree / seq / skillmgr …)
|
||||
├── sdk/ PluginSDK (Tool/Stage/Event three channels)
|
||||
├── config/ SQLite config center
|
||||
├── events/ Event bus
|
||||
└── internal/lua/adapters/ 10 LLM protocol adapter scripts
|
||||
External plugin development: see [hologramagentsdk](https://github.com/JianFeeeee/hologramagentsdk) repo, use `hmapdev` toolchain, refer to Go and Lua examples in `example/`
|
||||
External plugin development: see the [hologramagentsdk](https://github.com/JianFeeeee/hologramagentsdk)
|
||||
repo with the `hmapdev` toolchain. Each plugin has its own repo under
|
||||
[github.com/HologramAgent](https://github.com/HologramAgent).
|
||||
```
|
||||
|
||||
## Project Status
|
||||
|
||||
**v1.4.x line** (in progress, `main`) — **declarative concurrency safety** + **sequence orchestration (seq)** + **honest tool results**.
|
||||
|
||||
- **Same-round parallel tool execution**: multiple `tool_call`s in one round run concurrently,
|
||||
**but safety gates it** — a tool must explicitly declare `ParallelSafe` in its own `ToolDef`
|
||||
(built-ins use `toolDefOptions`); anything undeclared runs serially, and `Serial` wins.
|
||||
**A single unsafe tool degrades the whole batch to serial.** Declarations live **in the tool
|
||||
itself**; the kernel keeps no hardcoded name table. Same-channel output stays **strictly ordered**,
|
||||
and results are separated from text (`toolOutcome.Text` / `Raw`).
|
||||
- **`seq` orchestration plugin**: AST parsing + persistence, named groups, variable slots,
|
||||
**parallel within a group / serial between groups**, conditional calls (`seq_when_call`),
|
||||
cross-sequence call graph with cycle detection, error contract.
|
||||
- **Tool results are reported, never silently truncated** — over budget emits statistics.
|
||||
- **Structured tool error contract**: arguments pre-validated against the schema (rejected before
|
||||
dispatch), "tool not found" distinguished from "execution failed", `Success` no longer always true.
|
||||
- **Configurable device command allowlist** (waiter `device_cmd_allowlist`),
|
||||
**site drift watcher**, **incremental GUI rendering** (keeps all history, reuses nodes by
|
||||
`data-msgkey`; 50/200/400 messages measured at 8.2/23.5/38.2ms).
|
||||
|
||||
**v1.3.x line** (v1.3.1–v1.3.13) — **resident sub-agents** + **input scheduler rework**.
|
||||
|
||||
- **Resident sub-agents**: the kernel can station lightweight-kernel child agents (their own
|
||||
scheduler, their own temp stable memory, sharing the channel registry). The parent dispatches
|
||||
and collects work via `resident_agents` (list/create/send/inspect/compress/reclaim/destroy).
|
||||
An inputch can be assigned to a child, so input is routed to it **before entering the kernel**.
|
||||
- **Output channels addressable to a specific agent**: the `AllowedOutputs` grant set
|
||||
(consistent across all three filter points) lets parent/child deliver to each other;
|
||||
device capabilities became output channels too (one `device/<id>` per device).
|
||||
- **Input scheduler**: two task classes (queued/interrupt) + four interrupt levels (L1–L4)
|
||||
- preempt/suspend/resume/interrupt-stack; same level never preempts same level, with a
|
||||
starvation guard and preemption cooldown. L4 belongs only to the kernel and kernel-level
|
||||
plugins (e.g. the WebUI stop button).
|
||||
- **Lightweight kernel profile**: a child's memory surface narrows to "conventional context
|
||||
- stable memory" (narrow interface; the main graph opens as a query_only handle, writes go
|
||||
to its own temp instance).
|
||||
- **Backlog timely feedback** (later in the line): when the main agent is busy for a long time,
|
||||
the kernel hands queued input to a temporary **triage assistant** — simple items are handled
|
||||
directly, items needing the main agent get an immediate "busy, please wait", so users no
|
||||
longer wait 10+ minutes in silence.
|
||||
- Fixed a batch of real defects: inbound inputch (`child/<id>`) registration leak on resident
|
||||
destruction, a child's round count always showing 0 (`info()` never filled it), an empty
|
||||
child-side childIO (output channels not inherited), device heartbeat pong missing Flush
|
||||
(dropping every 60s), and Lua plugin bridge alignment with SDK 1.3.0.
|
||||
|
||||
**v1.2.0** — unified multimodal vector space, media promoted to first-class stable-memory nodes, and the whole data plane moved into shared memory.
|
||||
|
||||
- **Model-neutral unified embedding space**: the kernel no longer adapts to any specific model.
|
||||
It exposes only a public provider SPI (`pkg/embedding`: `Modality` / `Input{Data,MIME}` /
|
||||
`Info{Dimension,Fingerprint,Modalities}` + a name registry), with implementations under
|
||||
`providers/*`. Default: **Chinese-CLIP ViT-B/16** — text and image land in the **same space**
|
||||
(512-dim, fingerprint `cd2a495cf990`, Apache-2.0; measured ~1.59GB peak on load, settling to ~0.89GB steady-state);
|
||||
`qwen3vl` is kept (2048-dim, ~9.4GB) for machines with headroom or future video. Text search
|
||||
still falls back to the existing word-vector / TF-IDF path — a CLIP dual tower's pure-text
|
||||
semantics are **weaker than an MLLM-style embedder**, a cost documented rather than hidden.
|
||||
- **Media are first-class nodes and edges in the graph DB**: the "index images via generated
|
||||
text descriptions" stopgap, `media_refs` and media reference counting are **removed**.
|
||||
Memory blocks follow a single-layer invariant — Context → Document → Graph is a **migration**,
|
||||
not a copy, and not kept alive by references.
|
||||
- **The entire data plane goes through shared memory** (tool-call frames, Cleaners, input/output
|
||||
lanes, media blocks, document and knowledge bodies); RPC carries only offset descriptors.
|
||||
**RPC protocol is now 2**: the fd3 layout changed and there is **no rolling upgrade** —
|
||||
kernel and all plugins must be rebuilt and installed together.
|
||||
- Injections can declare `InjectOptions{NoMemory, ContextPolicy}` (**defaults: still recorded,
|
||||
not pruned**); pruning must be requested explicitly and goes through the plugin's registered
|
||||
`Cleaner`. SDK 1.2.0 is **purely additive** over 1.1.0.
|
||||
- **Release packages enable the ONNX space by default** and bundle the model (754MB) plus
|
||||
ONNX Runtime (24MB) in the server/full packages; `homed` drops native Windows support in
|
||||
favour of WSL2; the jieba dictionary is embedded in the binary.
|
||||
- Fixed three **silent install-chain failures**: `initconfig` was a no-op (`CGO_ENABLED=0` stub)
|
||||
that printed credentials without writing any, fresh installs were misdetected as "already
|
||||
configured" so default seeding was skipped entirely (0 plugins installed), and the deb
|
||||
`postinst` looked for the unit in the wrong path so `enable` never ran.
|
||||
- Licensed **AGPL-3.0-only** from this version on (network clause included; statically linked
|
||||
plugins must match — see License).
|
||||
|
||||
> The historical entries below are kept verbatim to show the evolution; two mechanisms in them
|
||||
> were **removed in v1.2.0**: text-description-based media indexing, and reference-counted media GC.
|
||||
>
|
||||
> **One licensing statement has also changed** (2026-09-24): "statically linked plugins must match"
|
||||
> in the v1.2.0 entry below **no longer holds**. The SDK is now released under **MIT** (a permissive
|
||||
> license that does not inherit the kernel's AGPL), so external plugins are **not** derivative works
|
||||
> of this project: authors choose their own license (closed-source, commercial or private included)
|
||||
> and are not bound by §13. The original text is kept to show the position at the time.
|
||||
>
|
||||
> **Two performance claims also need correcting** (measured 2026-09-20):
|
||||
> "crash-to-recovery under 1s" does not hold — restarts are **linearly backed off**, i.e.
|
||||
> 1s / 2s / 3s (`procRestartBackoff=1s × nth crash`). Even the *first* restart waits 1s.
|
||||
> And the **4th** crash within a 5-minute window stops automatic restarts pending human
|
||||
> intervention (`procMaxRestarts=3`, tested as `n > 3`).
|
||||
> The backoff was already 1s when this claim was written (v1.0.0), so it never held.
|
||||
> "RPC round-trip p50 24.1µs" is the right order of magnitude but does not match current
|
||||
> measurements: `BenchmarkToolInvoke` on this machine gives inline/small **30.4µs**,
|
||||
> frame/small 51.5µs, inline/large 767µs, frame/large 398µs.
|
||||
> The original text is left unedited rather than rewritten, so the history isn't falsified.
|
||||
|
||||
**v1.1.1** — Multimodal reaches the **plugin boundary**. v1.1.0 gave the memory system binary
|
||||
multimedia nodes, but that path was open only to the kernel itself; this release opens it to
|
||||
plugins and the model. The public SDK gains media fields and three media injection methods
|
||||
(paired with [SDK v1.1.0](https://github.com/JianFeeeee/hologramagentsdk/tree/v1.1.0),
|
||||
shared by the whole 1.1.x line), and the kernel implements the four matching RPCs. The bridge
|
||||
layer had been **silently dropping fields**: `Confidence`/types/`SentenceText` handed in by a
|
||||
plugin were discarded, `Doc` kept only three fields, and `Remove` never released references
|
||||
(media stayed "referenced" forever, so GC could never reclaim it). `processTextInput` and
|
||||
`processMediaInput` were unified into a single `processInput`, which finally gives the media
|
||||
path the dedup, `no_memory`, channel `Cleaner`, interrupt semantics and correct `EventRawInput`
|
||||
it had always lacked. Three real defects fixed: **user-sent images never appeared in the WebUI
|
||||
chat log** (the media path published a map while the subscriber asserted a string),
|
||||
**`memory_commit`'s `sentence_text` had never been exposed to the model** (note: this tool is now named `stablemem_commit`; the original name is kept for the v1.1.1 release note) (though it is the
|
||||
mandatory link in the media binding chain), and **two data races in `PluginSDK`** (11 reported
|
||||
by `-race`; in production this showed up as sporadic nil-dereference crashes during plugin reload).
|
||||
|
||||
**v1.1.0** — Memory system supports **binary multimedia nodes**. Content-addressed media store
|
||||
(CAS + SQLite metadata + on-disk blobs, `Get` always re-verifies the digest) wired through L0
|
||||
(context events) / L2 (documents) / L3 (graph sentences), with reference-counted GC (referenced
|
||||
items are never deleted). The description text produced by the vision model is the durable
|
||||
semantic memory; the blob is only a cache that capacity GC may evict.
|
||||
|
||||
**v1.0.0** — External plugins moved from C ABI shared libraries to **subprocess + shared memory**. The first release that no longer loads `.so`/`.dll`, and it is incompatible with 0.9.x (existing plugins must be rebuilt into `plugin.bin` with the new toolchain — called `plugindev` back then, **now `hmapdev`** — though **business code needs zero changes**). Eliminates 6 classes of defects that had caused production incidents: hot-reload silently failing (`DF_1_NODELETE` making `dlclose` a no-op), no crash isolation (a plugin panic took down homed), stage lost updates (35.8~36.8% loss under the copy model), uncancellable cgo timeouts (linear OS-thread leaks), `output_send` reporting false success (the model was told "sent" while the message never went out), and Windows capability degradation (only 3 stage fields visible, no write-back). Three communication planes: stdio JSON-RPC (control) + shared memory segment (data) + event ring (notification); the privilege gradient is now enforced by three explicit gates. RPC round-trip p50 24.1µs; crash-to-recovery under 1s.
|
||||
|
||||
**v0.9.0** — C ABI v2: external plugin Stage callbacks can now write back (`invoke_stage` gained a result out-param; plugins may mutate RawMessage/LLMText/ToolResults etc. in OnInput/AfterToolcall/PostAction and have them synced to the core). ABI version now tracks core minor releases (v0.9.x → ABIVersion=2, `version_min=1` keeps old plugins loadable). Also fixes the tool-loop zen-compat placeholder that wrongly fired on first-turn system context tail. The SDK ships an enhanced sanitizer example (bad-UTF-8 / U+FFFD / ANSI-escape scrub across the whole pipeline). **This ABI retired with v1.0.0.**
|
||||
|
||||
**v0.8.0** — Core is functional, plugin system enhanced. 20+ built-in plugins. External plugin development via [hologramagentsdk](https://github.com/JianFeeeee/hologramagentsdk) repo. Added input channel `NoMemory`/`Cleaner`, `ChannelDef`, plugin disable/enable system (CLI + WebUI), `plugindev` toolchain C ABI `ChannelDef` support.
|
||||
|
||||
## Documentation
|
||||
|
||||
**Online docs**:
|
||||
|
||||
- Introduction site (project overview): <https://introduce.homeagent.jianfgit.xyz/>
|
||||
- Plugin SDK docs (getting started / API reference / guides / examples): <https://sdk.homeagent.jianfgit.xyz/>
|
||||
— agent-friendly plain text: [`llms.txt`](https://sdk.homeagent.jianfgit.xyz/llms.txt) /
|
||||
plain-text entry for agents: [`llms.txt`](https://sdk.homeagent.jianfgit.xyz/llms.txt) /
|
||||
[`llms-full.txt`](https://sdk.homeagent.jianfgit.xyz/llms-full.txt)
|
||||
|
||||
**In-repo docs** (versioned with the code):
|
||||
@ -368,6 +154,9 @@ semantic memory; the blob is only a cache that capacity GC may evict.
|
||||
- [Plugin Development Guide](assets/docs/en/PLUGIN_DEV.md) | [中文](assets/docs/zh/PLUGIN_DEV.md)
|
||||
- [Lua Adapter](assets/docs/en/ADAPTER.md) | [中文](assets/docs/zh/ADAPTER.md)
|
||||
- [Knowledge Base Demo](assets/knowledge/homeagent_architecture/content.md)
|
||||
- [Design doc index](docs/zh/README.md) — status classification and trust rules for 32 design docs
|
||||
- [Changelog](CHANGELOG_EN.md) — what actually changed in each release
|
||||
- [Future plan](docs/zh/future-plan.md) — everything not yet built
|
||||
|
||||
## Downloads
|
||||
|
||||
@ -413,7 +202,7 @@ as a network service therefore has to make the modified source available to that
|
||||
|
||||
Plugins are **statically linked** against this project through the public
|
||||
[hologramagentsdk](https://github.com/JianFeeeee/hologramagentsdk) (the SDK source ends up inside the
|
||||
plugin binary), but that repository is released under **MIT** — a permissive license, so code
|
||||
plugin binary), but that repository is released under **MIT**. That is a permissive license, so code
|
||||
received under it does **not** inherit this project's AGPL. External plugins are therefore **not
|
||||
derivative works of this project**: authors pick their own license (closed-source, commercial or
|
||||
private included), with no same-license obligation and no §13 network clause. The safety and
|
||||
@ -421,8 +210,8 @@ vitality of the third-party plugin ecosystem rest on this.
|
||||
|
||||
The boundary is clean: **AGPL covers the kernel and the bundled plugins** (`homed`, `internal/`,
|
||||
the 13 built-in plugins under `internal/plugins/`); **MIT covers the public SDK** (`sdk/`, whose
|
||||
`go.mod` has zero external dependencies and imports only the Go standard library — it never
|
||||
references any kernel code). Process isolation is beside the point here — what decides the
|
||||
`go.mod` has zero external dependencies and imports only the Go standard library, never
|
||||
any kernel code). Process isolation is beside the point here. What decides the
|
||||
license is the linked SDK code itself, and that code is MIT.
|
||||
|
||||
### Third-party components shipped with the packages
|
||||
|
||||
Reference in New Issue
Block a user