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:
JianFeeeee
2026-10-31 11:08:12 +08:00
parent acaf0887c7
commit 7f43a6bfc7
4 changed files with 384 additions and 593 deletions

View File

@ -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