From f33ee3e396ff7a87c16545a4648c3f9323056def Mon Sep 17 00:00:00 2001 From: root Date: Tue, 20 Oct 2026 09:41:07 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=8A=8A=E3=80=8C=E4=B8=9A=E5=8A=A1?= =?UTF-8?q?=E8=AF=AD=E4=B9=89=E5=BD=92=E6=8F=92=E4=BB=B6=E3=80=81=E8=81=94?= =?UTF-8?q?=E5=8A=A8=E8=B5=B0=E4=BA=8B=E4=BB=B6=E6=80=BB=E7=BA=BF=E3=80=8D?= =?UTF-8?q?=E8=A1=A5=E8=BF=9B=20OVERVIEW=20=E4=B8=8E=20PLUGIN=5FDEV?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 上一提交只在 ARCHITECTURE 里记了终端归属。本次把这条**通用原则**补到另外两份 文档,避免只读 OVERVIEW / 插件开发指南的人仍旧按旧直觉写代码。 ## OVERVIEW(中英) 设计要点新增一条「业务语义留在插件层」:内核不定义业务对象(终端、命令历史、 设备状态、媒体库都是插件域)。以终端为例说明:PTY/生命周期/输出缓冲归 agentcli,WebUI/CLI 订阅总线自攒视图,内核只提供事件总线与注册面 —— 新增一类 能力不需要改内核,也不会在核心里堆出第二套业务真相。 ## PLUGIN_DEV(中英) PluginSDK 章节新增「与其它插件联动 — 走事件总线,不要依赖内核内部组件」: - 代码示例:订阅 terminal_output + tool_call 自己攒视图 - 两条稳定事件表(谁发布、用途) - ★ 外部插件只能订阅、不能发布(Publish 属 withheld 能力),所以「能力所有者 需要广播状态」的插件必须内置(agentcli 即此例) - 指向 internal/pluginview 作为参考实现,并写明「视图不是权威、晚启动会缺早期 事件」 一个准确性修正:公开 SDK **没有** EventTerminalOutput 常量(属内核内部事件 类型),所以示例里外部插件用 sdk.EventType("terminal_output") 字符串订阅, 并注明内置插件可用 internal sdk 常量 —— 避免文档给出编不过的示例。 验证:四份文档代码围栏配对(74/74、0/0);仅文档改动,无代码影响。 --- assets/docs/en/OVERVIEW.md | 9 +++++++++ assets/docs/en/PLUGIN_DEV.md | 39 ++++++++++++++++++++++++++++++++++++ assets/docs/zh/OVERVIEW.md | 6 ++++++ assets/docs/zh/PLUGIN_DEV.md | 34 +++++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+) diff --git a/assets/docs/en/OVERVIEW.md b/assets/docs/en/OVERVIEW.md index 2ccfa145..1961699f 100644 --- a/assets/docs/en/OVERVIEW.md +++ b/assets/docs/en/OVERVIEW.md @@ -18,6 +18,15 @@ Core architecture: a long-running kernel process (`homed`) that connects to vari The significance lies in clear responsibility boundaries: the kernel focuses on orchestration and memory management, while plugins handle IO implementation — the two are not coupled. +**Business semantics stay in plugins** — the kernel does not define business +objects: terminals, command history, device state and media libraries all belong to +the plugin domain. Take terminals: the PTY, session lifecycle and output buffering +all live in `agentcli`; when WebUI or CLI needs a terminal panel it **subscribes to +the event bus** (`EventTerminalOutput` + `EventToolCall`) and builds its own view +instead of reading a state component inside the kernel. The kernel offers only the +event bus and the plugin registration surface — adding a new capability does not +require touching the kernel, and no second version of business truth accumulates there. + **Three-Layer Memory Architecture** — Manages information retention across long agent runtimes through a tiered storage strategy: - **Context Layer**: In-memory pretrained word embedding scored event window (StaticEmbedder word vectors → CosineSimilarity, TF-IDF fallback), maintains recent context in real-time, low-relevance events automatically sink to the next layer - **Document Layer**: JSON files + TF-IDF vector-indexed temporary memory, supports explicit submission and implicit archival, cold data distills to Graph diff --git a/assets/docs/en/PLUGIN_DEV.md b/assets/docs/en/PLUGIN_DEV.md index abfc5d43..399c3645 100644 --- a/assets/docs/en/PLUGIN_DEV.md +++ b/assets/docs/en/PLUGIN_DEV.md @@ -460,6 +460,45 @@ unsub := s.Events().Subscribe(sdk.EventToolCall, func(evt *sdk.Event) { defer unsub() ``` +#### Inter-plugin Integration — Use the Event Bus, Not Kernel Components + +The kernel carries no business-domain semantics (terminals, device state, media +libraries all belong to plugins). Plugins that need to coordinate should therefore +**subscribe to the event bus**, not read some state component inside the kernel — +the latter creates a second copy of the truth and pushes business logic back into +the kernel. + +```go +// Panel-style plugins (WebUI/CLI): subscribe to terminal events, build a view +// Note: terminal_output has no public constant yet (it is a kernel-internal event +// type), so external plugins subscribe by string; built-in plugins can use the +// internal SDK's EventTerminalOutput constant. +unsubTerm := s.Events().Subscribe(sdk.EventType("terminal_output"), func(evt *sdk.Event) { + // evt.Payload: terminal_id / command / output / running +}) +unsubTool := s.Events().Subscribe(sdk.EventToolCall, func(evt *sdk.Event) { + // evt.Payload: tool / args / status (cmd_run, terminal_create/close, ...) +}) +defer unsubTerm() +defer unsubTool() +``` + +The two stable events: + +| Event | Publisher | Purpose | +|---|---|---| +| `EventTerminalOutput` | agentcli | terminal output and lifecycle (create/close/timeout) | +| `EventToolCall` | kernel | tool invocations (incl. `cmd_run`, `terminal_create/close`) | + +★ **External plugins can subscribe but cannot publish**: `Publish` is a withheld +capability (`withheldCapabilities`), so a plugin that *owns* a capability and must +broadcast its state (like agentcli publishing terminal events) has to be built-in. +If your plugin is only a consumer, subscribing is enough. + +Reference implementation: `internal/pluginview` (the terminal view shared by WebUI +and CLI). It shows how a view is assembled from events, and the boundary that the +view is not authoritative — a late starter misses early events. + #### Capability Access ```go diff --git a/assets/docs/zh/OVERVIEW.md b/assets/docs/zh/OVERVIEW.md index 70b853c0..da3504a2 100644 --- a/assets/docs/zh/OVERVIEW.md +++ b/assets/docs/zh/OVERVIEW.md @@ -18,6 +18,12 @@ HomeAgent 是一个持续运行的个人智能 Agent 框架。 这一划分的意义在于职责边界清晰:内核专注于编排与记忆管理,插件负责具体 IO 实现,二者互不耦合。 +**业务语义留在插件层** — 内核不定义业务对象:终端、命令历史、设备状态、媒体库 +都属于插件域。以终端为例,PTY、会话生命周期与输出缓冲全归 `agentcli`;WebUI 与 +CLI 需要终端面板时,**订阅事件总线**(`EventTerminalOutput` + `EventToolCall`) +自己攒视图,而不是去读内核里的某个状态组件。内核只提供事件总线与插件注册面—— +新增一类能力不需要改内核,也不会在核心里堆出第二套业务真相。 + **三层记忆架构** — 通过分级存储策略管理 Agent 长周期运行中的信息留存: - **Context 层**:内存中预训练词嵌入评分的事件窗口(StaticEmbedder 词向量 → CosineSimilarity,TF-IDF 回退),实时维护最近上下文,低相关性事件自动下沉到下一层 - **Document 层**:JSON 文件 + TF-IDF 向量索引的临时记忆,支持显式提交和隐式归档,冷数据蒸馏到 Graph diff --git a/assets/docs/zh/PLUGIN_DEV.md b/assets/docs/zh/PLUGIN_DEV.md index b5657269..b5c64f0a 100644 --- a/assets/docs/zh/PLUGIN_DEV.md +++ b/assets/docs/zh/PLUGIN_DEV.md @@ -453,6 +453,40 @@ unsub := s.Events().Subscribe(sdk.EventToolCall, func(evt *sdk.Event) { defer unsub() ``` +#### 与其它插件联动 — 走事件总线,不要依赖内核内部组件 + +内核不承载业务域语义(终端、设备状态、媒体库都是插件的职责),因此插件之间的 +联动应当**订阅事件总线**,而不是读内核里的某个状态组件——后者会让同一件事出现 +第二份真相,也会把业务逻辑重新塞回内核。 + +```go +// 面板类插件(如 WebUI/CLI):订阅终端事件,自己攒视图 +// 注意:terminal_output 目前没有公开常量(属内核内部事件类型), +// 外部插件用字符串形式订阅;内置插件可用 internal sdk 的 EventTerminalOutput。 +unsubTerm := s.Events().Subscribe(sdk.EventType("terminal_output"), func(evt *sdk.Event) { + // evt.Payload: terminal_id / command / output / running +}) +unsubTool := s.Events().Subscribe(sdk.EventToolCall, func(evt *sdk.Event) { + // evt.Payload: tool / args / status(cmd_run、terminal_create/close 等) +}) +defer unsubTerm() +defer unsubTool() +``` + +稳定的两条事件: + +| 事件 | 发布者 | 用途 | +|---|---|---| +| `EventTerminalOutput` | agentcli | 终端输出与生命周期(创建/关闭/超时) | +| `EventToolCall` | 内核 | 工具调用(含 `cmd_run`、`terminal_create/close`) | + +★ **外部插件只能订阅,不能发布**:`Publish` 属于不对外授予的能力 +(`withheldCapabilities`),所以「某项能力的所有者需要向总线广播状态」这类插件 +(如 agentcli 发终端事件)必须内置。如果你的插件只是消费者,订阅即可。 + +参考实现:`internal/pluginview`(WebUI 与 CLI 共用的终端视图)。它演示了视图 +应当怎么从事件攒出来,以及「视图不是权威、晚启动会缺早期事件」的边界。 + #### 能力访问 ```go