site logo

Marico's space

设计单用途代理,而非大型自动化脚本

算法解析 2026-06-24 14:49:28 9

今年"Agent(智能代理)"这个词算是彻底泛滥了,到处都在用,但谁也说不清到底指什么。不过在它变成热词之前,我已经在自己的Homelab里跑了一堆这东西了。不是赶时髦,而是每次试图写一个大一统脚本的时候,都会撞上同一堵墙——功能越堆越多,一个地方的bug能拖垮整个系统。

所以现在的方案是:与其搞一个大脚本,不如部署一堆小型单用途的自动化代理。每个代理只干一件事,输出格式固定,调度相互独立。整个系统之所以还能维护,靠的就是三条规矩:代理之间通过JSON文件通信,走同一个通知渠道,最后汇到一个仪表盘上。

说白了,Homelab里每个重复性的活儿都写成独立的小程序,按自己的节奏跑。真的坐下来数一数,发现这种东西比我预想的多多了。

给不熟悉背景的同学: 这个项目的代码库叫 hogwarts,每个代理都按霍格沃茨的人物来命名。一旦开始用巫师给服务起名,就得给每个服务写一份符合角色设定的"岗位说明书",不管它自己需不需要。

值班层:四个观察者。 四个监控代理每五分钟轮询一次,把结果报到关联器那里。这一层的存在意义就是让我在问题变成凌晨三点告警之前就知道,而不是之后才知道:

  • 阿格斯·费尔奇(Argus Filch) 监控正在运行的Docker容器,发现重启循环、健康检查失败、或者容器悄无声息消失的情况就报警。
  • 天文塔(Astronomy Tower) 轮询Prometheus,抓取正在触发的告警、挂掉的抓取目标、以及那些悄无声息停止工作的录制规则。
  • 活点地图(Marauder's Map) 扫描UniFi网络,发现设备离线、WAN主备切换、防火墙规则莫名开放等情况。
  • 疯眼汉的警戒(Mad-Eye's Watch) 跟踪所有配置好的端点的TLS证书到期时间。保持警惕:提前30天警告,7天报严重。
  • 邓校(The Headmaster) 是这个列表里唯一一个设计上就不是单用途的角色。它的全部工作就是读取另外四个代理觉得值得报告的内容,关联分析后合并成一条状态,只有真的值得当作事件时才上报。

日常和周期任务: 这些按各自的cron时间表运行,彼此之间不直接通信:

  • 莫莉的碗柜(Molly's Cupboard) 每周检查一次Home Assistant的实体列表:不可用的实体、缺失或重复的名字、被禁用的自动化。(韦斯莱夫人:维持家庭运转,用慈爱的眼光嫌弃你的乱堆乱放。)
  • 丽塔的办公桌(Rita's Desk) 是RSS早间摘要:订阅源进,昨天的文章出,按我手动打分的标签权重排序。设计上就是确定性的,不走LLM(大型语言模型)那套。(什么样的标题都敢登,但至少都标了来源。)
  • 克利切的厨房(Kreacher's Kitchen) 从我的菜谱库和几个靠得住的烹饪网站规划一周的菜单。(全程碎碎念,但饭还是能端上桌。)
  • 图书馆(The Library) 每晚选一个技术话题,收集资料,写成5分钟的摘要和15到20分钟的深度分析。(包名是 research-digest,但它每天晚上都泡在禁书区里,所以就叫图书馆了。)
  • 平斯夫人的目录(Madam Pince's Catalogue) 列出所有正在运行的容器,与代码库里infra仓库的服务目录交叉比对,标记出没有任何对应文档的容器。(一个很较真的图书管理员:每本书都要登记,不然就给你没收了。)
  • 多比的巡场(Dobby's Rounds) 是Homelab里的"家养小精灵":每周做一次大扫除,清理过期的快照、报告和状态文件,防止它们堆积成山。
  • O.W.L.考试(O.W.L.s) 是每日基础设施审计:配置漂移、开放端口、合规检查。只读操作,故意设计得很保守。(普通巫师等级考试:全面、耗精力、不听你的借口。)
  • 傲罗办公室(Auror Office) 是每日跨域安全摘要,把O.W.L.的发现和认证日志、Docker安全姿态、上面的网络监控关联成一份报告。(没徽章,但确实在到处抓黑巫师。这部分和O.W.L.的协作方式我在别处写过详细的分析。)
  • ……还有其他的,包括媒体管理、推荐系统,以及更多同风格的代理。数量已经多到把每个都列出来能单独写一篇文章。

十三个以上的名字,差不多十三个活儿。除了专门设计成要了解其他所有人的两个关联角色——邓校和傲罗办公室——其他没有一个需要知道其他代理的存在。

让这套系统运转的三个约定

这些代理单独拎出来,没有一个技术含量多高。让整个集群保持可管理的真正原因,是它们都遵守同样三条小小的契约:

一、统一的产物格式。 每个代理把结果写成JSON(通常还有一个配套的Markdown说明),输出到自己专属的 outbox/latest/ 路径。格式固定:一份"最新"指针,加一份带时间戳的归档。没有任何代理直接读取其他代理的outbox。如果需要跨代理交叉引用,那是另一个专门做关联的代理的工作,而不是在代理之间埋隐式依赖。

二、统一的通知渠道。 所有需要通知我的代理都走同一个ntfy主题规范,通知里带上跳转到详情的深层链接。我不需要维护五套不同的告警集成,只需要维护一套,每个代理都只是这个渠道的轻薄客户端。

三、统一的汇聚点。 一个仪表盘读取所有人的 outbox/latest/ 并渲染展示。它自己不采集任何数据,没有Docker访问权限,没有Home Assistant凭证,没有任何API密钥。它只是读取别人生成的JSON文件,然后展示出来。整个系统里,只有这个地方被允许知道所有代理的存在。

整个集成就这么三板斧。加上第六个代理,明天就能加,不用动前五个一根毫毛。

为什么分解优于整合

最常见的质疑:五个小东西难道不比一个大东西更难维护?我的经验是,不。跟公司里一組小型服务通常比单体架构强,是一个道理。

Peeves的Trakt分页出了bug,不会影响到莫莉的Home Assistant检查,因为它们不共享进程、不共享部署、不共享调度。我可以用fixture文件完整隔离地测试每一个代理,不需要配真实凭证。我可以把任意一个代理的目录交给贡献者——不管是人还是AI编码代理——他们拿到所有需要的信息就能理解和使用它,不用先把其他四个的逻辑先塞进脑子里。当我要下线某一个的时候(Peeves对我有意义是因为我还有媒体服务器,这不会永远成立),删掉的是一个目录,而不是一个需要解耦的共享模块。

这跟任何规模合理的工程组织的服务边界和团队拓扑 lesson是一样的:组件之间的接口应该小、明确、无趣,设计的大部分精力都应该花在保持这个状态上,而不是让某个组件本身变得多精巧。精巧应该藏在单个代理的窄墙之内,那里它漏不出来。

无聊的管道才是关键

上面说的五个代理没有一个在做什么技术上很难的事。RSS解析、REST API客户端、cron任务——这些东西我们任何人都能一个下午写完。真正的设计工作在于是不是一开始就想清楚了:"outbox JSON加一个通知渠道加一个仪表盘"就是它们全部的契约,然后坚持不让任何一个代理绕过这套机制。

只有一两个代理的时候,这种纪律几乎不花什么代价。但它是唯一能防止五个(甚至十五个)代理重新退化回那个一开始就想躲开的大一统脚本的东西。