mobile wallpaper 1
mobile wallpaper 2
mobile wallpaper 3
mobile wallpaper 4
2769 字
7 分钟
Personal Assistant 架构全解:pi headless + 五插件

记录一下「会成长的个人助手」这套系统的 MVP 架构:它不是一个聊天窗口,而是一台服务器上常驻、能自己收邮件、写博客、记资料,并且会把纠正记下来的 agent。

缘起#

目标很直接:做一个持续为你工作的个人助手,具备五项基础能力——

  • 个人数据库:索引、检索资料,随资料变化更新索引,回答时能定位到原文
  • 持续关联 QQ 邮箱:获取新邮件、关联历史往来、拟回复草稿,你编辑确认后再发送,且重复检查不重复处理
  • 编辑维护博客:按需编辑发布文章
  • 手机端联动:从手机发起任务、看进度、编辑草稿、确认操作
  • 能力组合:以上功能共享上下文,完成跨工具任务

此外还有一个更长期的要求:它得会成长——沟通方式、具体功能都能积累。成功流程和纠正要沉淀成可读可改的脚本、技能或记忆,并用版本控制管理。

本文介绍这套系统在 MVP 阶段定稿的架构。

一句话架构#

以 pi agent 为核心的单进程插件化系统,服务器常驻:

core = pi(headless)跑 agent 循环 + 五个可插拔功能插件(extension 管代码逻辑 + skill 管知识流程),手机 Web 页和 cron 定时任务都通过网关入口触发 agent。

关键词有三个:单进程、插件化、数据与代码分离。

总体形态#

[手机 Web 页] [cron 定时器] [本机终端 pi]
\ | /
\ | /
┌───────────────▼───────────────┐
│ 云服务器 │
│ ~/assistant/ (Git 仓库) │
│ ┌─────────────────────────┐ │
│ │ pi headless 进程 │ │
│ │ ├─ agent 循环 │ │
│ │ ├─ extension: gateway │ │
│ │ ├─ extension: memory-db │ │
│ │ ├─ extension: email │ │
│ │ ├─ extension: blog │ │
│ │ └─ extension: flows │ │
│ └───────────┬─────────────┘ │
│ ~/assistant-data/memory/ │
│ (真实数据, 不进 Git) │
└───────────────────────────────┘
  • 代码与数据分离:仓库只放代码、规则、技能;真实记忆数据放在 ~/assistant-data/,不进 Git。服务器换个实例,数据目录照搬即可。
  • 单进程:没有一个插件是独立服务,全部挂在同一个 pi 长会话里。
  • 所有入口最终都是一次 agent 循环:不管任务从手机、cron 还是终端来,都是往同一台 agent 投一段文本。

三种触发方式#

入口机制例子
定时cron 调网关的 HTTP 端点(带 token 鉴权)邮箱每 5 分钟轮询
消息手机 Web 页把任务交给网关「帮我拟一封回复」
本地SSH 到服务器手动跑 pi 命令开发调试

agent 循环#

整个系统的行为都收敛成一个循环:

触发 → 判断执行内容 → 调用插件工具 → 完成实现 → 总结流程并沉淀

最后一步是「会成长」的关键:一次跨工具任务做完,助手会把成功步骤写进流程文档、把纠正写进规则,下次换个场景也能复用。

插件体系:extension 与 skill 分工#

所有功能插件化,不写独立服务。 载体分两类,分工很清晰:

载体内容位置
extension(TypeScript)工具、定时逻辑、HTTP 端点仓库 extensions/<name>/
skill(.md 文档)知识与流程:怎么写草稿、怎么发博客仓库 skills/<name>/SKILL.md,装机脚本安装到服务器

两条开发契约值得强调:

  • 插件之间只通过工具接口通信,严禁触碰彼此实现。接口先定,代码后写。
  • extension 不在工厂函数里起定时器 / 网络服务,从会话生命周期钩子启动——这样未配置的机器也能加载插件,只有真正调用工具时才失败。

五个插件#

gateway:网关与任务状态机#

单进程内的 HTTP 入口,是手机页与 cron 的统一通道。它同时管着「任务」这件事:$ASSISTANT_DATA/tasks/*.json 每个文件就是一台任务状态机。

状态长这样:

pending → delivered → awaiting_confirm → confirmed → executing → done
↘ ↘ ↘
└──────────────── failed ←────────┘
  • 状态写入只有一个入口:迁移要走 applyEvent,非法迁移直接报错,写不进去。
  • 确认按「轮次」计:每次 request_confirm 生成新的 confirmId;手机页确认时必须回传同一个 id,对不上直接 409。多轮外发(同一任务发第二封)因此不会绑错轮次。
  • 迁移保守:旧任务在读路径惰性归一化;历史上「已确认但未执行」的轮次永不自动投递,必须由用户重新确认一次,防不知情重发。

模型能主动调用的工具只有两个:

task_update(id, status: "delivered" | "done" | "failed", note?: string, draft?: string)
request_confirm(id, what: string)

其余是给手机页和 cron 的 HTTP 端点:提交任务、列任务、确认、取消、重试、触发。

memory-db:个人数据库#

Markdown 目录树 + grep 检索,数据根由环境变量 ASSISTANT_DATA 指定。目录约定本身就承载了「记忆怎么组织」:

$ASSISTANT_DATA/memory/
profile/ # 个人资料(固定信息)
notes/ # 资料、笔记(按主题建文件)
rules/ # 纠正规则,每条一节,含触发场景
flows/ # 成功流程沉淀
index.md # 目录索引(插件自动维护)

对外工具就三个:memory_search / memory_save / memory_read。契约只有两条但很硬:

  • 检索必须返回原文位置(路径 + 行号),回答时能定位到原文。
  • 资料变化时同步重建 index.md(文件名 → 主题 → 摘要)。

用 Markdown 而不是向量库,是刻意的:文件可以直接读、直接改,也更容易观察 agent 到底读了什么、写了什么、怎么组织记忆。

email:持续关联邮箱#

QQ 邮箱经 IMAP + SMTP 接入(授权码登录)。三个工具:

email_check_new() // 拉取新邮件并归档,跳过已处理 ID
email_draft(id) // 取存档邮件全文(只读)
email_send(id, body) // 收件人 / 主题由存档邮件推导,只传正文

几个设计点:

  • 去重:靠一份邮件 ID 清单,重复轮询不会重复处理或重复发送同一封邮件。
  • 拟稿是被动模式:新邮件只归档、以任务条目呈现在手机页;用户主动说「帮我回这封」才结合记忆拟草稿。
  • 收件人不由模型编造:email_send 的收件人优先取 Reply-To,主题固定为「Re: 原主题」。这是个约束也是安全边界——但代价是发不了「新收件人 + 自定义主题」的新邮件,只能回复已有来信。这条限制后来被老老实实记进了 notes。

blog:编辑维护博客#

博客是 Astro 静态站(Mizuki 主题)。两个工具形成一条单向链:

blog_publish(title, content, description?, tags?) // 写入 posts/<slug>.md,draft: true,不上线
blog_deploy(path) // 确认后:draft 改 false → 构建 → 部署
  • 未确认的文章绝不上线:draft: true 直接挡住渲染。
  • 构建失败兜底:文章留在目录里但标回 draft: true,线上仍是上一版,任务报失败原因。
  • 编辑已有文章 = 同名覆盖,不单独提供编辑工具,确认链不变。
  • slug 有讲究:标题里带英文 / 数字 / 日期才有可读 URL;纯中文标题会回退成哈希名。这条也是踩出来的。

phone 与 flows#

手机页就是网关的 GET /,覆盖三件事:发任务、看进度、编辑并确认草稿——MVP 不堆功能,按实际痛点迭代。

跨工具工作流不写代码,靠流程文档 + skill 驱动:成功流程沉淀到 memory/flows/<name>.md,格式固定为「触发场景 → 步骤 → 验收」。某个流程被验证为高频后,才考虑代码化。

一条贯穿所有外发动作的确认链#

这是整个系统最重要的安全设计:发送邮件、发布博客这类对外可见的动作,必须先经用户在手机页确认;未收到确认前,绝不调用 email_send / blog_deploy。

流程是固定的一套:

  1. 拟好草稿,交到任务上(task_update 的 draft 字段)
  2. request_confirm 请求确认
  3. 手机页展示草稿,用户可以直接编辑
  4. 用户确认时回传该轮次的 confirmId 与最终正文
  5. 仅当确认后,才用用户提交的最终版本执行外发
  6. 结果写回任务,收尾

工具本身不做确认检查——所以「绝不提前调用」这条纪律由 agent 侧守住。改稿后要重新 request_confirm,会换发新的 confirmId,旧轮次立即失效,保证发出去的一定是用户最后看到的那版。

成长机制#

「会成长的助手」由三个互补机制构成:

  • 纠正规则(硬约束) → memory/rules/ 自由文本 Markdown。daemon 启动时把 rules 全部内容注入会话系统提示,每次任务都可见——所以像「总结时先列发件人和主题」「不要把报名截止时间当成活动开始时间」这类纠正,会在新会话、新场景里自动生效。
  • 操作知识(怎么做某类事) → pi 原生 skills,skills/<name>/SKILL.md。extension 管代码逻辑,skill 管「怎么做」。首批包括邮件回复、博客发布、资料入库、流程沉淀。
  • 成功流程(跨工具任务沉淀) → memory/flows/<name>.md,不强制,自然生长。

再加一层版本管理:memory 是独立 git 仓,agent 改完规则 / 流程后自动 commit + push 到服务器上的裸仓;daemon 启动跑一次同步脚本,长会话期间由 timer 周期调用。改动原因由任务系统留痕。这样「记忆」也能 review、能回滚。

设计与取舍#

几个值得记下来的判断:

  • 单进程 > 微服务:规模和负载都不需要拆分,单进程让上下文天然共享,跨工具任务才串得起来。
  • Markdown > 数据库:可读、可 diff、可 grep、可手改,透明度优先。
  • 被动拟稿 > 自动回复:发件人 no-reply、系统自动发送类邮件实际无法送达,自动回复风险大于收益。
  • 确认链 > 权限系统:与其做复杂的授权模型,不如把所有外发动作都压到「人看一眼再点确认」这一个动作上。

当前进度#

  • 阶段 1:架构文档 + memory-db 插件跑通 ✅
  • 阶段 2:gateway + 服务器部署 ✅
  • 阶段 3:email(拉取 → 归档去重 → 按需拟稿 → 确认发送)✅
  • 阶段 4:blog(写文 → 确认 → 构建部署)✅
  • 阶段 5:成长机制 + 博客二期(进行中)

结语#

这套架构没有多复杂的技术,真正的设计重心在「边界」上:哪些动作必须停下来问人、记忆怎么组织才可读、流程怎么沉淀才能复用。

助手会成长的前提,是它每次做完事都能把「这次为什么这么做」写下来——规则、技能、流程各管一层,再用 git 把整个过程管住。

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Personal Assistant 架构全解:pi headless + 五插件
https://hajim1.art/posts/personal-assistant-pi-headless/
作者
Takamatsu Tomori
发布于
2026-10-02
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

目录