常驻型个人 Agent 框架

一个不会忘记
也不会被一条长任务卡死的管家

HomeAgent 是内核 + 插件的双层 Agent 框架。内核只管 LLM 编排、记忆与调度;所有外部能力(消息、文件、网络、设备) 都是可独立安装、崩溃不牵连内核的插件。

看板娘 小宅

四个不那么常见的设计决定

大多数框架先写功能再补边界。HomeAgent 反过来:先把 边界和调度定死,再往上加能力。

◇

内核零 IO

内核不直接读写任何外部世界。收发消息、文件操作、网络请求、硬件交互 全部由插件经 PluginSDK 实现。于是「内核有多可信」与「某个插件有多安全」 成为两个可独立评估的问题。

◈

插件是独立进程

外部插件编译成普通 Go 二进制,由内核 spawn 为子进程,经 stdio JSON-RPC(控制)+ 共享内存段(数据)+ 事件环(通知)通信。 插件崩溃不影响内核,换掉 plugin.bin 即真热重载。

◉

输入是有级别的

输入先进调度器,分「排队」与「中断」两类,中断再按“有多不能等”分 L1–L4。 高优先级可抢占并保存现场,同级不抢占。L4 只归内核 —— 所以“停止”按钮真的能立刻停下。

◆

忙时有人顶班

主 agent 执行长任务时,后来的消息不会干等十几分钟:内核把积压交给 临时分诊助手 —— 简单的直接办完,需要主 agent 的立刻回 「忙碌中,请稍候」。

三层记忆:从「刚才说了什么」到「三年前说过什么」

不是一个向量库包打天下,而是三层各司其职、逐层衰减与整合。 品牌的蓝 / 青 / 金三色,对应的就是这三层。

① Context工作窗口

内存中的事件窗口,按与当前输入的相关性评分排序(预训练词向量 → 余弦相似度, TF-IDF 回退),并保护最近若干条。低分事件自动下沉。

② Document文件记忆

JSON + TF-IDF 索引的中间层。支持显式提交与隐式归档; 冷数据按时间下沉,蒸馏成三元组进入图谱。

③ Graph图数据库

SQLite 持久化实体与关系,BFS 遍历召回。蒸馏管道从对话里提取三元组, 越用越准;这是“几个月前的事还记得”的落点。

媒体也是一等节点

图片/音频不是附件,而是三层里的一类节点:内容寻址存储(相同字节只存一份)、 引用计数 GC(有引用绝不删)。

描述才是持久语义

视觉模型生成的描述以 [mime digest] 描述 写进纯文本记忆并参与检索; blob 只是可被淘汰的缓存。所以几个月后“那张三色图”仍能搜到。

统一多模态空间

文本与图像落在同一向量空间(默认 Chinese-CLIP), 因此可以用一句话搜图,也可以用图找相关记忆。

一条消息进来之后

输入先过调度器,再进处理管道;管道有 7 个阶段钩子,插件可在此改写或短路。

  外部输入(QQ / WebUI / CLI / 邮件 / 设备 …)
        │
        ▼
  输入调度器  两类别 · 四级中断 L1–L4
        │   ├─ 同级不抢占 → 入队列
        │   ├─ 更高级     → 抢占(现场压入中断栈,稍后恢复)
        │   └─ 忙太久     → 转投 临时分诊助手
        ▼
  处理管道  on_input → pre_action → post_action → before/after_toolcall → before/after_output
        │   记忆召回 + 人格注入 + LLM 调用 + 工具循环
        ▼
  三层记忆  Context → Document → Graph
        │
        ▼
  输出通道  可寻址到具体 agent(父 ↔ 驻留子)

核心组件

homed常驻内核进程:LLM 编排、记忆管理、输入调度、插件生命周期
PluginSDK四通道:注册工具 / 挂阶段钩子 / 订阅事件 / 声明输出通道
inputch最基本的输入路由单位,可划给某个 agent(含驻留子)
Stage7 个处理阶段钩子,插件可改写或短路消息流
resident驻留子 agent:独立调度器 + temp 图记忆,父可查看/收发/压缩/回收
hmapdev插件工具链:生成工程、构建 .hmap、安装
36
外部插件(实测在跑)
340
可用工具
110k
Go 代码行
10
LLM 协议适配器

跑起来

依赖 Go 1.25+ 与 CGo(sqlite3)。内核需 Linux —— Windows 上 homed 跑在 WSL2 里,只构建 waiter.exe。

# 构建守护进程 + CLI make build build-cli # 启动内核(数据目录自定) ./build/homed -data /tmp/ha # 另开一个终端:交互模式,或单条消息 ./build/waiter echo "你好,记住我喜欢喝咖啡" | ./build/waiter

WebUI 与 API 密钥在 http://localhost:8080 配置,持久化在 SQLite。

开箱可用的插件

外部插件经 hmapdev 构建为独立二进制,可单独安装、卸载、热重载。

qq wechat browser files memo calendar rss editdoc ocr music bili ai_image weather vikunja vanblog deepsearch mcp a2a acp mc remotedevice recoverydiag …共 36 个

常见问题

这些是设计上最容易引起误解的地方。

「内核零 IO」是不是意味着内核很简单?

恰恰相反——零 IO 是为了让内核能专注做难的部分:输入调度(四级中断、 抢占与现场保存)、上下文预算、三层记忆的蒸馏与召回。 把 IO 挪出去之后,这些才有清晰的所有权。

插件崩了会不会把 Agent 带崩?

不会。从 v1.0.0 起外部插件是独立子进程,与内核只通过 stdio JSON-RPC、共享内存段、事件环三个面通信。插件崩溃后内核摘除其 注册面(工具/阶段/通道)并按退避重启它,其余插件与内核不受影响。

为什么消息会「排队」而不是立即处理?

因为同一时刻只应该有一个任务在改上下文。同级中断不抢占同级任务—— 抢了会让两边都做一半。需要立刻插队的场景请用更高级别的中断 (如 L4:内核与内核级插件),而不是让所有消息都变成抢占者。

长任务会不会让用户等很久?

这正是「分诊助手」要解决的。主 agent 忙超过阈值(默认 5 分钟)且积压够多时, 内核会拉起一个临时驻留子接手积压:能直接办的办完并回复, 需要主 agent 的立刻回一句「忙碌中,请稍候」。

记忆会不会无限膨胀?

不会。三层是迁移关系而不是复制:上下文事件被裁剪后归档成文档, 文档冷掉后蒸馏成图谱三元组。图谱有去重与 UNIQUE 约束, 媒体则靠引用计数 GC 回收无主内容。

能换 LLM 吗?

可以。内核不绑定任何厂商:每个 LLM 源对应一个 Lua 脚本负责请求/响应转换, 仓库内置 10 个适配器(OpenAI、Anthropic、Gemini、DeepSeek、Groq、Ollama 等), 也可以自己写一个接任意兼容 API。