mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-21 09:28:14 +00:00
- 所有内置插件 init() 自注册 (plugin.RegisterFactory), 移除 main.go 硬编码 - 新增 .so 动态加载器 (internal/plugin/dynamic.go), 插件可编译为 plugin.so - 新增 plugin.json 元数据 (internal/plugin/manifest.go) - 新增 interceptLoop 独立 goroutine: (a) cancelLLM() 取消进行中的 HTTP 请求 (b) interceptCh → drainInterrupt() 注入 [打断消息] 到 LLM 上下文 (c) InjectInput 空闲时触发新处理循环 - 新增 internal/plugins/all.go 空白导入触发所有内置插件 init() - internal/sdk/ 作为 PluginSDK 正式 Go API - internal/api/ → internal/plugins/webui/ 迁移 - 删除旧 cmd/cli/, 使用 cmd/waiter/ 替代 - 更新 PLAN.md / ARCHITECTURE.md / README.md 文档
13 KiB
13 KiB
HomeAgent 架构与实施
架构概览
┌─────────────────────────────────────────────────────────────────────┐
│ homed (内核) │
│ ┌──────────┐ ┌──────────┐ ┌────────────┐ ┌───────────────────┐ │
│ │LLM源管理 │ │Agent编排 │ │记忆管理 │ │知识管理 (TF-IDF) │ │
│ │Lua适配器 │ │主agent │ │图/文本/ │ │ │ │
│ │(协议转换) │ │子agent │ │文档三层 │ │ │ │
│ └──────────┘ └──────────┘ └────────────┘ └───────────────────┘ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ IO通道管理器 (IOManager) │ │
│ │ 排队通道 (Queue) + 中断通道 (Interrupt) │ │
│ │ 核心回环 _consolidation_ (记忆消歧/系统维护) │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ 阶段管道 (StageHost 7 阶段, 并行执行) + 事件总线 (EventBus) │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ PluginSDK — 内核对插件的完整 Go API │ │
│ │ IO/工具/阶段/事件/记忆/知识/LLM/配置 │ │
│ └──────────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────┤
│ 内置插件 (编译入内核, init() 自注册) │
│ internal/plugins/all.go (空白导入触发 init) │
│ ┌──────────┐ ┌──────────┐ ┌────────────┐ ┌──────────────┐ │
│ │ WebUI │ │ CLI │ │ OpenClaw │ │ Timer │ │
│ │ HTTP 服务│ │ Unix socket│ │SKILL.md→SDK│ │ timer_set 工具│ │
│ └──────────┘ └──────────┘ └────────────┘ └──────────────┘ │
├─────────────────────────────────────────────────────────────────────┤
│ 动态插件 (<data>/plugins/<name>/ 按需加载) │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ plugin.json 元数据 + plugin.so (Go -buildmode=plugin) │ │
│ │ 或 main.lua (Lua 脚本, 预留) │ │
│ │ Registry.Load() 自动扫描, 无 factory → tryLoadSO → tryLoadLua │ │
│ └──────────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────┤
│ waiter (通用客户端) │
│ waiter -say "你好" → Unix socket → CLI 插件 │
│ waiter (交互模式) → 同上 │
└─────────────────────────────────────────────────────────────────────┘
核心原则
- 核心零 IO — homed 不监听端口, 不启动 HTTP 服务, 不读 stdin
- 一切外界交互都是插件 — 通过 PluginSDK 与核心交互
- 插件自注册 — 每个插件的
init()调用plugin.RegisterFactory(), 内核无需硬编码 - 动态 .so 加载 — 第三方插件编译为
.so, 放入plugins/<name>/plugin.so - PluginSDK 是内核"系统调用" — 插件只能通过 SDK 访问核心能力
- Stage 并行执行 — 同阶段所有 handler goroutine 并发, StageContext 内嵌 RWMutex
- 中断最高优先级 — 独立
interceptLoop可随时打断进行中的 LLM 请求 - 子 agent 是主 agent 的工具 —
spawn_child/child_result, 不是 SDK 部分
插件注册体系
两种注册路径
| 路径 | 场景 | 实现 |
|---|---|---|
| 自注册 (init) | 内置插件 (timer/cli/openclaw/webui) | 包 init() 调 plugin.RegisterFactory(name, factory) |
| 动态加载 | 第三方插件 | 扫描 <data>/plugins/<name>/, 读 manifest + .so |
自注册流程
internal/plugins/timer/plugin.go
func init() {
plugin.RegisterFactory("timer", func(name string, cfg map[string]interface{}) (sdk.Plugin, error) {
return New(name), nil
})
}
internal/plugins/all.go
package plugins
import ( _ "timer" _ "cli" _ "openclaw" _ "webui" )
// 空白导入触发所有 init() → RegisterFactory
cmd/homed/main.go
cli.DefaultSocket = *cliSocket // 注入运行时变量
openclaw.SkillsDir = filepath.Join(...)
webui.Configure(httpAddr, sup, mem, sk, ...)
pluginReg.Load(plgDir) // 自动创建目录 + 加载
动态 .so 加载
插件目录结构:
<data>/plugins/myplugin/
plugin.json — 元数据 {name, version, description, author, entry}
plugin.so — Go -buildmode=plugin 编译, 导出 NewPlugin(name, config)
动态加载器 internal/plugin/dynamic.go 扫描 .so:
func tryLoadSO(dir, name string, config map[string]interface{}) (sdk.Plugin, error) {
p, _ := plugin.Open(filepath.Join(dir, "plugin.so"))
sym, _ := p.Lookup("NewPlugin")
fn := sym.(func(string, map[string]interface{}) (sdk.Plugin, error))
return fn(name, config), nil
}
内置插件也可剥离为 .so, 当前保持 init 自注册。
输入双通道 + 中断打断
通道结构
IOManager
├── inputCh (chan *InputEvent, 256) — 排队通道, 按序处理
├── interruptCh (chan *InputEvent, 64) — 中断通道, 可打断 LLM
└── outputCh (chan *OutputEvent, 256) — 输出通道
中断打断机制
timer 插件 interceptLoop (独立 goroutine)
│ │
├─ s.InjectInterruptText(...) ─────────┤
│ │
│ ┌──────────┴──────────┐
│ │ (a) cancelLLM() │ → 取消进行中的 HTTP 请求
│ │ (b) interceptCh <- │ → process() 非阻塞读取
│ │ (c) InjectInput(...) │ → 空闲时 eventLoop 消费
│ └─────────────────────┘
│ │
▼ ▼
interruptCh process() 工具循环
│
每个 turn 开始前:
drainInterrupt() → 注入 [打断消息] system msg
三种投递路径 (interceptLoop):
- (a)
cancelLLM()— 直接取消当前 Provider HTTP 请求, 捕获context.Canceled - (b)
interceptCh <- text—process()每轮 LLM 调用前drainInterrupt(), 注入[打断消息]到上下文 - (c)
InjectInput("interrupt", "text", ...)—eventLoop在空闲时收到新输入, 启动新处理循环
阶段管道 (Stage Pipeline)
7 个阶段, 并行执行:
on_input → pre_action → post_action ↔ before_toolcall/after_toolcall → before_output → after_output
| 阶段 | 时机 | 插件能力 |
|---|---|---|
on_input |
消息到 Agent | 可短路回复 |
pre_action |
LLM 调用前 | 注入 system 消息 |
post_action |
LLM 返回后 | 审查/修改文本和工具调用 |
before_toolcall |
工具执行前 | 拒绝/改参 |
after_toolcall |
工具执行后 | 修改结果 |
before_output |
输出前 | 改写最终文本 |
after_output |
输出后 | 只读统计 |
并行规则: 所有 handler 用 goroutine 并发, StageContext 内嵌 sync.RWMutex, handler 通过 Lock()/RLock()/IsResponded() 协防。
配置体系 (ConfigRegistry)
全部配置持久化在 SQLite:
| 表 | 用途 | 访问 |
|---|---|---|
config |
核心配置 (LLM/daemon/agent) | SettingsAPI.GetCore/SetCore |
config_<plugin> |
插件独立配置 | SettingsAPI.Get/Set/List |
| 跨插件读写 | GetPlugin/SetPlugin/ListPlugin/Dump |
内核入口 (cmd/homed/main.go)
初始化顺序:
1. 基础设施 → 记忆/技能/Lua/监督/追踪/IO/事件
2. 配置中心 (SQLite) + LLM Provider
3. 阶段管道 StageHost + 插件注册表 Registry
4. 注入内置插件依赖 (cli.DefaultSocket / openclaw.SkillsDir / webui.Configure)
5. Registry.Load(plgDir) → 自注册 + 动态加载
6. Agent 启动 (eventLoop + interceptLoop + distillLoop)
7. 等待信号 → 关机
目录结构
cmd/
homed/main.go — 内核入口 (零 IO)
waiter/main.go — CLI 客户端 (Unix socket)
internal/
sdk/ ★ PluginSDK (Go API)
plugin.go — Plugin 接口 + PluginSDK 结构体
memory.go / knowledge.go — 记忆/知识包装
settings.go / llm.go — 配置/LLM 源
agent/
core/
agent.go — Agent: eventLoop/interceptLoop/process
context.go — RelevanceContext (TF-IDF)
stages.go — StageHost (并行阶段管道)
api/provider.go — Provider 接口 + LuaAdaptedProvider
io/channel.go — IOManager (Queue/Interrupt/Output)
plugin/
registry.go — 注册表: 生命周期, Load, RegisterFactory
manifest.go — PluginManifest (plugin.json)
dynamic.go — .so 动态加载器
plugin.go — SKILL 插件解析
plugins/
all.go — 空白导入触发所有内置插件 init()
timer/ cli/ openclaw/ webui/ — 内置插件
events/bus.go — 系统事件总线
memory/ — 三层记忆 (Context→Document→Graph)
knowledge/ — 知识库
config/registry.go — 配置中心
tracker/ — overlayfs 变更追踪
supervisor/ — 守护管理
skill/ — 技能管理
lua/vm.go — Lua VM (LLM 协议适配)
pkg/types/ — 类型定义
docs/ARCHITECTURE.md — 完整架构文档
与旧架构关键区别
| 维度 | 之前 | 现在 |
|---|---|---|
| 插件注册 | main.go 硬编码 RegisterNative | init() 自注册 + .so 动态加载 |
| 内核入口 | 逐个 import 插件包 | 仅 import all.go (空白导入) |
| 中断处理 | 无消费者, 消息丢失 | interceptLoop + cancelLLM + drainInterrupt |
| 插件目录 | 手动硬编码创建 | Load() 自动为每个注册工厂创建 |
| 依赖注入 | 闭包绑定在 RegisterNative | 包级变量 (cli.DefaultSocket 等) |
| 阶段执行 | 顺序 | 并行 (goroutine + WaitGroup) |
构建与验证
make build # 编译 homed + waiter
./build/homed -data /tmp/ha # 启动
./build/waiter -say "你好" # 发送消息
要求: Go 1.19+, CGo (go-sqlite3), Linux (Unix socket + overlayfs).