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