feat: files built-in plugin, doc rewrite, architecture cleanup

- Add files plugin as built-in (internal/plugins/files/) with read/write/edit/ls tools,
  supporting overwrite/append/insert/create modes and offset/limit segmented reading
- Rewrite README.md with core domain separation and three-layer memory highlights
- Rewrite docs/OVERVIEW.md with per-subsystem file path references
- Rewrite docs/ARCHITECTURE.md (783→~300 lines), merge redundant sections
- Clean docs/PLUGIN_DEV.md: remove emoji, simplify SDK examples
- Fix provider Model pollution in LuaAdaptedProvider.Chat()
- Fix executeToolCall to return actual error vs quiet not-found
- Fix plugin.Open path caching with SHA256 temp-path workaround
- Add knowledge/homeagent_architecture demo entry
- Add config/personal/personal.md identity configuration
This commit is contained in:
root
2026-07-06 14:33:09 +08:00
parent 8cec92d947
commit df0abcd298
12 changed files with 1111 additions and 890 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,87 +1,72 @@
# HomeAgent — 你的 24/7 智能管家
# HomeAgent — 项目概览
## 这是什么的?
## 这是什么
HomeAgent 是一个**持续运行的个人智能管家**。它像一个随时在线的大脑,你可以通过聊天跟它交流,让它帮你记住事情、查询知识、设置提醒、执行任务
HomeAgent 是一个持续运行的个人智能 Agent 框架
## 核心目标
核心架构:一个长时间运行的内核进程(`homed`),通过插件系统接入各种 IO 通道QQ、Web、命令行等。内核负责 LLM 调用编排、记忆管理、知识检索;插件负责所有外部 IO——收发消息、执行文件操作、搜索网络等。
| 目标 | 说明 |
|------|------|
| **永远在线** | 启动后持续运行,不像普通聊天软件需要每次打开 |
| **真正记住你** | 它不会每次对话都"失忆"——它会积累对你的了解,记住你的喜好、关系网和重要信息 |
| **隐私可控** | 所有数据存储在你自己的设备上(本地数据库),你也可以选择使用自己的 API 密钥 |
| **能力可扩展** | 通过"插件"添加新能力——就像手机装 App 一样 |
### 核心创新
## 谁需要它?
**核心域与应用域分离** — 这是首个明确提出这一划分的 Agent 框架。内核(核心域)不做任何 IO所有 IO 能力归属插件(应用域)。边界通过 PluginSDK 明确定义:
- 插件向内核注册工具Tool供 LLM 调用
- 插件挂入处理管道Stage在各阶段拦截/改写消息流
- 插件订阅/发布事件Event松耦合通信
- 插件通过 IO API 排队或打断投递输入
- **想有个私人助理** — 帮你记待办、定时提醒、管理联系人
- **重视隐私的用户** — 数据全在本地,不经过第三方云服务
- **开发者和技术爱好者** — 可以自己编写插件来扩展功能
- **想探索 AI Agent 的人** — 一个真实可运行的 Agent 系统,不只是 API 调用
这一划分的意义:内核保持纯粹(零 IO只做编排和记忆插件保持灵活各司其职热加载互不污染。
## 它能做什么?
**三层记忆架构** — 解决 Agent 长期运行的记忆衰减:
- **Context 层**:内存中 TF-IDF 评分的事件窗口,实时维护最近上下文,低相关性事件自动下沉到下一层
- **Document 层**JSON 文件 + TF-IDF 向量索引的临时记忆,支持显式提交和隐式归档,冷数据蒸馏到 Graph
- **Graph 层**SQLite 图数据库持久化实体entities和关系relationsBFS 遍历召回,蒸馏管道从对话中提取三元组
### 🧠 记忆
- **记住你是谁** — 你的名字、喜好、重要日期
- **记住人际关系** — "张三是我同事,李四是我的朋友"
- **长期积累** — 聊得越多,它越了解你
三层递进:上下文 → 冷归档 → 长期图记忆,确保 Agent 长时间运行不退化。
### 📚 知识
- 你可以主动教它知识("公司的休假制度是……"
- 它会在需要时检索相关知识
## 它实际做了什么
### ⏰ 定时提醒
- "5分钟后提醒我喝水"
- 倒计时结束后它会主动通知你
代码位于 `/home/program/TrueAgent`Go 语言实现。
### 🔌 可扩展(插件)
- **Web 控制台** — 在浏览器中管理和配置7 标签页 SPA
- **命令行** — 通过终端快速交互
- **健康检查** — 自动检测系统各组件状态LLM 驱动故障排查
- **更多能力** — 开发者可以写插件接入任何服务
**内核** (`internal/agent/core/agent.go`)
- 维护一个消息循环(`eventLoop`),从 IO 层排队接收输入
- 每次输入走完整的处理管道:记忆召回 → 人格注入 → LLM 调用 → 工具执行 → 输出发送
- LLM 调用通过 Provider 接口抽象,支持 8 个 LLM 源自动降级
- 上下文管理(`context.go`)基于 TF-IDF 评分,自动剪枝低相关性事件
## 它是如何工作的?(简述)
**记忆系统** (`internal/memory/`)
- **GraphDB** (`graph.go`) — SQLiteentities + relations 表BFS 遍历
- **Document Store** (`document/doc.go`) — 临时记忆JSON 文件 + TF-IDF 向量索引,消费即删
- **Text Memory** (`text/text.go`) — 原始对话日志JSONL 文件轮转
- **Social Store** (`social/social.go`) — 人格特质 + 关系网,包装 GraphDB
- **Memory Indexer** (`indexer.go`) — 自动将 GraphDB 实体向量化,用户输入时召回注入 system prompt
```
你(通过聊天软件/终端/网页)
HomeAgent 内核 ←→ 插件(能力扩展)
本地存储(你的数据只在你这里)
```
**知识库** (`internal/knowledge/knowledge.go`)
- 文件系统目录 `knowledge/<name>/content.md`
- TF-IDF 向量搜索,独立于记忆系统的索引实例
- LLM 通过 `knowledge_search` / `knowledge_create` / `knowledge_list` 三个工具操作
- **内核** 是"大脑"——负责理解你说什么、调用什么能力、记住什么
- **插件** 是"手脚"——负责收发消息、设置定时器、连接外部服务等
- **所有数据存本地** — 你的对话、记忆、配置都保存在你自己的设备上
**插件系统** (`internal/plugin/`)
- 内置插件Go `init()` 自注册,编译进内核
- 外部插件Go `-buildmode=plugin` 编译为 `.so`,通过 `plugin.Open` 动态加载
- PluginSDK (`internal/plugin/sdk/`) 定义三通道RegisterTool / RegisterStage / Subscribe
- 阶段钩子 7 个on_input → pre_action → post_action → before_toolcall → after_toolcall → before_output → after_output
## 和普通 AI 聊天有什么区别?
**LLM Provider** (`internal/agent/api/provider.go`)
- Provider 接口Name / Chat / ChatStream
- 三种实现OpenAIProvider标准 OpenAI API、OllamaProvider本地、LuaAdaptedProviderLua 胶水适配)
- LuaAdapter 位于 `internal/lua/adapters/`,每个 LLM 源对应一个 `.lua` 脚本
- 内置 8 个适配器deepseek / openai / anthropic / gemini / mistral / groq / github / ollama
| | 普通 AI 聊天 | HomeAgent |
|---|---|---|
| 记忆 | 每次对话独立,不记得你 | 长期记忆,越来越了解你 |
| 持续运行 | 关掉就没了 | 7×24 在线 |
| 主动能力 | 只能回复问题 | 能设定时器、主动提醒 |
| 可扩展 | 固定能力 | 插件系统,可无限扩展 |
| 数据隐私 | 上传到云服务 | 本地存储,完全可控 |
## 快速体验
```bash
# 启动(需要 DeepSeek API 密钥)
DEEPSEEK_API_KEY="sk-xxx" ./homed -data /tmp/ha
# 在另一个终端聊天
echo "你好,请记住我喜欢喝咖啡" | ./waiter
```
**WebUI** (`internal/plugins/webui/`)
- 嵌入式 SPA 仪表盘(`dashboard.html` 通过 `//go:embed` 打包)
- REST API状态查询、配置管理、记忆操作、知识库管理、插件管理
- 兼容 OpenAI API 格式的 `/v1/chat/completions` 端点
- SSE 事件流 `/api/v1/chat/events`
## 项目状态
HomeAgent 正在积极开发中。核心功能已可运行插件系统和开发者 API 已就绪
核心功能已可运行插件系统和 SDK 已就绪,可独立开发外部插件
---
*想参与开发?查看 [PLUGIN_DEV.md](PLUGIN_DEV.md) 插件开发指南。*
*了解技术架构?查看 [ARCHITECTURE.md](ARCHITECTURE.md)。*
- 内置插件webui / cli / timer / cmd / mcp / agentcli / healthcheck / pluginmgr / openclaw / files
- 外部插件示例SDK 仓库 `example/`qq / files / web / memo
- 打包分发:`.hmap` 插件包格式,通过 WebUI 安装

View File

@ -2,9 +2,18 @@
## 概述
HomeAgent 的所有外部交互能力都来自插件。插件是独立运行的 Go 包,通过 `PluginSDK`Go API与内核交互。
HomeAgent 的所有外部交互能力都来自插件。插件通过 `PluginSDK`Go API与内核交互。
每个插件需要实现一个非常简单的接口:
**SDK 仓库**:插件开发工具、模板代码和示例插件统一托管在
**[gitcode.com/JianFeeeee/homeagent-sdk](https://gitcode.com/JianFeeeee/homeagent-sdk)**。
```bash
git clone https://gitcode.com/JianFeeeee/homeagent-sdk.git
cd homeagent-sdk
hack/plugin-dev/scaffold.sh myplugin ./plugins/myplugin
```
每个插件实现一个三方法接口:
```go
type Plugin interface {
@ -18,8 +27,8 @@ type Plugin interface {
| 方式 | 适用场景 | 复杂度 |
|------|---------|--------|
| **动态 .so 插件(推荐)** | 独立分发的第三方插件 | 中等,使用 [SDK 仓库](https://gitcode.com/JianFeeeee/homeagent-sdk) 脚手架生成 |
| **内置插件** | 随 HomeAgent 一起发布 | 简单,需合入主仓库 |
| **动态 .so 插件** | 独立分发的第三方插件 | 中等,需编译为 .so |
| **Lua 脚本插件** | 轻量快速原型 | 简单(预留功能) |
---
@ -170,41 +179,37 @@ func (p *Plugin) Stop() error {
### PluginSDK 核心 API
#### 📤 IO — 输入输出
#### IO — 输入输出
```go
// 排队通道投递输入(按序处理)
// 排队投递(按序处理)
sdk.InjectInput(source, channel string, payload map[string]interface{})
// 中断通道投递输入(可打断当前 LLM 处理)
// 中断投递(可打断当前 LLM 处理)
sdk.InjectInterrupt(source, channel string, payload map[string]interface{})
// 快捷方式:投递文本到排队通道
// 快捷方式:text → Input
sdk.InjectText(source, channel, text string)
// 快捷方式:投递文本到中断通道
sdk.InjectInterruptText(source, channel, text string)
// 同步请求-响应:发送文本并等待回复CLI 插件使用)
// 同步请求-响应CLI 插件使用)
sdk.InjectTextSync(source, channel, text string) *OutputEvent
// 注册一个输出通道LLM 通过 output_send 工具选择发送到通道)
// 注册/管理输出通道LLM 通过 output_send 选择发送到哪个通道)
sdk.RegisterChannel(name string, dev Device) error
sdk.UnregisterChannel(name string)
sdk.ListChannels() []ChannelInfo
```
#### 🛠️ 工具 — 让 LLM 可调用你的能力
#### 工具 — 让 LLM 可调用你的能力
```go
sdk.RegisterTool(name string, def ToolDef, handler ToolHandler) error
```
- `name`: 工具名称(LLM 通过此名称调用
- `def`: 工具定义(描述 + 参数 JSON Schema
- `handler`: 调用时执行函数
工具定义示例:
- `name`: LLM 通过此名称调用
- `def`: JSON Schema 描述+参数
- `handler`: 执行函数
```go
sdk.RegisterTool("weather_query", sdk.ToolDef{
@ -222,7 +227,6 @@ sdk.RegisterTool("weather_query", sdk.ToolDef{
},
}, func(args map[string]interface{}) (interface{}, error) {
city, _ := args["city"].(string)
// 查询天气并返回
return map[string]interface{}{
"city": city,
"temp": 25,
@ -231,50 +235,46 @@ sdk.RegisterTool("weather_query", sdk.ToolDef{
})
```
#### 🔌 阶段钩子 — 干预消息处理流
#### 阶段钩子 — 干预消息处理流
7 个阶段, 按执行顺序
7 个阶段:
| 阶段 | 时机 | 用途 |
|------|------|------|
| `on_input` | 消息刚到达 Agent | 黑名单、限流、短路回复 |
| `pre_action` | 即将调用 LLM | 注入额外上下文 |
| `post_action` | LLM 返回结果 | 修改 LLM 输出 |
| `on_input` | 消息刚到达 Agent | 黑名单、限流、短路 |
| `pre_action` | 即将调用 LLM | 注入上下文 |
| `post_action` | LLM 返回结果 | 修改输出/工具列表 |
| `before_toolcall` | 工具调用前 | 审计、拒绝、改参 |
| `after_toolcall` | 工具执行后 | 脱敏、改写结果 |
| `before_output` | 输出前 | 调整格式 |
| `after_output` | 输出后 | 统计、记录 |
| `before_output` | 输出前 | 格式适配 |
| `after_output` | 输出后 | 统计日志 |
```go
sdk.RegisterStage(sdk.StageOnInput, func(ctx *sdk.StageContext) error {
input := ctx.RawMessage
// 检查是否是黑名单用户
if ctx.UserID == "blocked_user" {
resp := "你已被限制使用"
ctx.Response = &resp // 设置 Response 会短路后续阶段
ctx.Response = &resp // 短路后续阶段
return nil
}
return nil
})
```
#### 📡 事件 — 订阅/发布系统事件
#### 事件 — 订阅/发布系统事件
```go
// 订阅事件
unsub := sdk.Subscribe(events.EventType("tool_call"), func(evt *events.Event) {
log.Printf("工具被调用: %v", evt.Payload)
})
defer unsub() // 插件 Stop 时取消订阅
defer unsub()
// 发布事件
sdk.Publish(&events.Event{
Type: "my_event",
Payload: map[string]interface{}{"key": "value"},
})
```
#### 🧠 能力访问
#### 能力访问
```go
// 记忆
@ -288,7 +288,7 @@ sdk.Knowledge().Search(query string) ([]string, error)
sdk.LLM().ListSources() []SourceInfo
sdk.LLM().SetSource(name string) error
// 配置(插件自身的配置表 config_<plugin_name>
// 配置(插件自身的 config_<plugin_name>
sdk.Settings().Get(key string) (interface{}, error)
sdk.Settings().Set(key string, value interface{}) error
sdk.Settings().List(prefix string) ([]string, error)
@ -296,20 +296,15 @@ sdk.Settings().List(prefix string) ([]string, error)
### 读取插件配置
插件有自己的配置`config_<插件名>`,例如 `config_mcp`
插件独立 SQLite `config_<name>`
```go
// 在 Start() 中
val, err := s.Settings().Get("api_key")
if err != nil {
// 未配置
}
```
用户通过 WebUI 或 CLI 设置:
```go
// 读取其他插件的配置
// 读取其他插件配置
s.Settings().GetPlugin("other_plugin", "some_key")
// 读取核心配置
@ -345,14 +340,28 @@ pluginReg.Load(plgDir) // 之后调用
## 四、动态 .so 插件
### 编译插件为 .so
动态插件是独立于 HomeAgent 内核编译的 Go 插件,使用外部的 [Plugin SDK](https://gitcode.com/JianFeeeee/homeagent-sdk)
而非内核内部的 SDK 包。
完整的外部插件示例在 SDK 仓库的 `example/` 目录下:`qq``files``memo``web`
### 快速开始
使用 SDK 仓库的脚手架生成项目:
```bash
git clone https://gitcode.com/JianFeeeee/homeagent-sdk.git
cd homeagent-sdk
hack/plugin-dev/scaffold.sh myplugin ./plugins/myplugin
```
生成的代码:
```go
// myplugin/plugin.go
package main
import (
sdk "gitcode.com/JianFeeeee/HomeAgent/internal/sdk"
"gitcode.com/JianFeeeee/homeagent-sdk/sdk"
)
func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) {
@ -371,13 +380,23 @@ func (p *myPlugin) Start(s *sdk.PluginSDK) error {
func (p *myPlugin) Stop() error { return nil }
```
编译
### 编译
```bash
go build -buildmode=plugin -o plugin.so ./myplugin/
cd <SDK_REPO_ROOT>
go build -buildmode=plugin -o plugins/myplugin/plugin.so plugins/myplugin/
```
或使用项目中的 Makefile
```bash
cd plugins/myplugin && make
```
### 部署
将插件目录(含 `plugin.json` + `plugin.so`)放入内核配置的插件目录:
```
<dataDir>/plugins/myplugin/
plugin.json — {"name": "myplugin", "version": "1.0", "description": "..."}
@ -386,21 +405,39 @@ go build -buildmode=plugin -o plugin.so ./myplugin/
内核扫描时会自动发现并加载。无需修改 `main.go``all.go`
### 打包分发
使用 SDK 仓库的打包工具生成 `.hmap` 分发包:
```bash
hack/plugin-dev/packager.sh plugins/myplugin
# 输出: dist/myplugin-0.1.0.hmap
```
通过 WebUI 插件管理页面上传安装,或使用 `plugin_install` 工具。
### 完整示例
SDK 仓库的 `example/qq/` 目录提供了一个完整的 QQ 集成插件示例(对接 NapCat 框架),
涵盖消息收发、群管理、好友管理、文件操作、OCR 等功能,可作为开发参考。
---
## 五、最佳实践
1. **Start() 非阻塞** — 长时间运行的任务用 goroutine 启动,不要 Start() 中阻塞
2. **Stop() 清理资源** — 关闭网络连接、停 goroutine、取消订阅
3. **工具 name 唯一** — 工具名不能与其他插件冲突,建议插件名前缀
4. **错误处理** — 工具 handler 返回 `error`LLM 会收到错误信息并可能重试
5. **中断 vs 排队** — 需要打断当前 LLM 处理的`InjectInterruptText`,普通`InjectText`
6. **配置优先** — 不要硬编码配置,`Settings().Get/Set` 读写插件配置
1. `Start()` 非阻塞 goroutine 启动长任务,不要阻塞 Start
2. `Stop()` 清理资源 — 关连接、停 goroutine、取消订阅
3. 工具名唯一 — 建议插件名前缀避免冲突
4. handler 返回 `error` LLM 会收到并可能重试
5. 打断`InjectInterruptText`,普通投递`InjectText`
6. 配置`Settings().Get/Set`,不要硬编码
---
## 六、现有插件参考
### 内置插件
| 插件 | 位置 | 特点 |
|------|------|------|
| Timer | `internal/plugins/timer/` | 最简单的完整示例,注册一个工具 + 中断反馈 |
@ -409,7 +446,15 @@ go build -buildmode=plugin -o plugin.so ./myplugin/
| WebUI | `internal/plugins/webui/` | HTTP 服务 + 依赖注入Configure 模式) |
| MCP | `internal/plugins/mcp/` | JSON-RPC over stdio/SSE连接 MCP 服务器 |
### 外部插件示例
| 插件 | 位置 | 特点 |
|------|------|------|
| QQ | `example/qq/` in [homeagent-sdk](https://gitcode.com/JianFeeeee/homeagent-sdk) | NapCat 框架对接14 个工具 |
| 你的插件 | `plugins/yourplugin/` | 使用 SDK 脚手架生成 |
---
*了解项目整体目标?查看 [OVERVIEW.md](OVERVIEW.md)。*
*了解技术架构?查看 [ARCHITECTURE.md](ARCHITECTURE.md)。*
*SDK 仓库与开发工具?查看 [homeagent-sdk](https://gitcode.com/JianFeeeee/homeagent-sdk)。*