【免费下载链接】overstory
Multi-agent orchestration for AI coding agents — pluggable runtime adapters for Claude Code, Pi, and more
Overstory 是一个面向 AI 编程智能体的多智能体编排工具,它让多个 Claude Code 等编码智能体在隔离的 git worktree 中并行工作。当几十上百个智能体同时跑代码时,智能体间通信就成了生死线——Overstory 的答案是一套自研的、基于 SQLite 的高速邮件系统:所有消息落盘到单个.overstory/mail.db数据库,单次查询延迟仅约 1~5 毫秒,配合 WAL 日志模式轻松支撑多进程并发读写。本文将带你拆解这套邮箱的存储设计、消息协议、投递通道和可靠性机制,看懂多智能体协作是如何"发邮件"完成协作的。
一、为什么多智能体系统需要一套"邮件"?
单兵作战的 AI 编码助手很好用,但 Overstory 把一次编码任务拆成一个"智能体团队":scout 侦察代码、builder 写代码、reviewer 审代码、merger 合并分支、watchdog 监控健康……
这些智能体各跑各的进程、各有各的工作目录,它们之间必须有一种通信方式。Overstory 选择了最朴素的类比——邮件(Mail):
- 📮异步:发件人不阻塞,收件人下次"看信"时处理,天然适配智能体逐轮(turn-based)执行模型
- 💾持久化:消息写入 SQLite,智能体崩溃重启后信依然还在,不会丢
- 🔍可审计:所有通信记录都在数据库里,随时可以用命令或 Web UI 回看整条协作时间线
二、架构总览:三层结构
邮件系统分为三层,职责清晰:
| 层级 | 文件 | 职责 |
|---|---|---|
| 存储层 | src/mail/store.ts | SQLite 增删改查、建表建索引、WAL 配置 |
| 客户端层 | src/mail/client.ts | send / check / reply / list 等高级操作 |
| 命令与投递层 | src/commands/mail.ts、src/agents/headless-mail-injector.ts | ov mailCLI、hook 注入、邮件投递循环 |
整个邮箱数据库位于项目根目录的.overstory/mail.db。一个贴心细节:智能体往往运行在 git worktree 里,命令层会通过 resolveProjectRoot 向上解析到主项目根目录,保证 worktree 中的智能体写的信、主项目里收的信,进的是同一个库。
三、存储层设计:为什么是 SQLite,而且是 WAL 模式
存储层用bun:sqlite实现零依赖的同步数据库访问。打开数据库时,它会先执行三行关键配置(见 src/mail/store.ts):
PRAGMA journal_mode = WAL; -- 写前日志:多读者 + 单写者并发 PRAGMA synchronous = NORMAL; -- WAL 下平衡安全性与性能 PRAGMA busy_timeout = 5000; -- 锁竞争时最多重试 5 秒这三行正是"高速"二字的来源:WAL(Write-Ahead Logging)模式允许"读不阻塞写、写不阻塞读"。当 orchestrator 正在发消息、builder 正在查收件箱、watchdog 正在写健康检查信时,谁也不用等谁。
表结构同样为查询性能做了设计:
CREATE INDEX idx_inbox ON messages(to_agent, read); -- 收件箱查询走索引 CREATE INDEX idx_thread ON messages(thread_id); -- 会话串查询走索引最高频的操作是"查某智能体的未读消息",idx_inbox索引恰好覆盖这个查询路径(见 src/mail/store.ts)。此外,存储层还内置了无感 schema 迁移:老库缺payload列就ALTER TABLE补上,缺约束则事务性地重建表并回填数据,升级版本不会丢信。
四、消息协议:不止是文本,还有 14 种"语义类型"
一封邮件长什么样?看 src/types.ts 中的MailMessage定义:
| 字段 | 说明 |
|---|---|
id | msg-+ 12 位随机字符,全局唯一 |
from/to | 发件人 / 收件人(智能体名) |
subject/body | 主题与正文 |
type | 14 种消息类型之一(见下) |
priority | low/normal/high/urgent四档 |
threadId | 会话串 ID,回复自动归入同一串 |
payload | JSON 结构化数据,协议消息专用 |
read/createdAt | 已读标记与时间戳 |
类型分两大类:
- 🗨️语义类型(人类可读):
status、question、result、error—— 日常沟通 - 🤖协议类型(结构化协作信号):
worker_done(完工)、worker_died(进程死亡)、merge_ready(分支待合并)、merged/merge_failed(合并结果)、escalation(升级上报)、health_check(健康探活)、dispatch/assign(任务分派)、decision_gate(等待人工决策)
协议消息的威力在于payload 字段:例如worker_done消息携带的结构化数据包含taskId、branch、exitCode、filesModified(定义见 src/types.ts),上级智能体拿到的是可直接程序化处理的字段,而不是一段需要"读懂"的自然语言。数据库层还用CHECK约束保证type只能是这 14 种合法值——脏数据在入库那一刻就被拦下。
五、邮件如何送达智能体?三条投递通道
信写进数据库只是第一步,Overstory 按智能体的运行模式设计了三条投递通道(核心实现在 src/agents/headless-mail-injector.ts 与 src/agents/hooks-deployer.ts):
1️⃣ Hook 注入通道(tmux 常驻智能体)
部署 hooks 时,Overstory 会在智能体的UserPromptSubmit钩子里注册一条命令:每次智能体提交新提示词之前,先自动执行ov mail check --inject --agent <name>。未读邮件被格式化成人类可读的文本块,直接塞进智能体的上下文,效果就像有人往它耳边递了张纸条。PostToolUse钩子还挂了一条带**防抖(debounce)**的查信命令,避免智能体每次工具调用都查一次信箱。
2️⃣ Spawn-per-Turn 轮询通道(无头工作智能体)
builder 这类短生命周期智能体每个 turn 都是新起进程,没有常驻会话。ov serve会为每个智能体起一个投递循环:每 2 秒轮询一次数据库,发现未读邮件就把整批信打包成一个 user turn,拉起一个新的 claude 进程(--resume恢复会话)消费这批邮件。关键细节是——只有本轮干净结束(exit code 0)才标记已读,失败则保持未读,下一个 tick 自动重试,实现"至少投递一次"。
3️⃣ 持久 stdin 通道(coordinator / monitor 等长驻智能体)
常驻智能体是一个长期存活的 claude 进程。投递循环读取未读邮件后,通过已注册的连接直接写入该进程的 stdin 管道,智能体在当前回合结束后即可消费。数据即使暂时没被读取,也安全地躺在管道缓冲区里。
💡 三条通道殊途同归:数据库是唯一事实来源,通道只负责"搬运"。这就是为什么任何进程重启都不会造成通信黑洞。
六、广播与群组寻址:@all 与 @builders
给单个智能体写信很简单,给"所有 builder"写信呢?src/mail/broadcast.ts 提供了群组地址解析:
@all—— 所有活跃智能体(自动排除发件人自己)@builders/@builder—— 所有 builder 能力智能体- 同理支持
@scouts、@reviewers、@leads、@mergers等
发送时做扇出(fan-out):把一条群发解析成 N 条独立消息逐一落库,每条都有独立 ID,可单独回执、单独追踪。若群组解析结果为空(比如当前没有活跃的 builder),会直接报错而非静默丢弃。
七、可靠性机制:让重要消息绝不"石沉大海"
通信系统最怕两件事:消息丢了、收信人没反应。Overstory 各有对策:
⚡ 自动 Nudge(催办)发送urgent/high优先级或worker_done、merge_ready等关键协议消息时,系统会在.overstory/pending-nudges/下写一个标记文件(而非直接发送 tmux 按键——那会污染智能体的 I/O 导致进程被杀)。收信方下一次查信时,标记被读出并渲染成醒目的PRIORITY: ...横幅置顶展示(见 src/commands/mail.ts)。
🛡️ 防轮询背板智能体被明令禁止用 Bash 写until ov mail check; do sleep 1; done这类死等轮询。为防止提示词被绕过,src/agents/mail-poll-detect.ts 会在运行时识别"循环 + 查信 + sleep"的三重特征并告警——这是典型的纵深防御:提示词约束是防线一,运行时检测是防线二。
🧹 到期清理ov mail purge支持按天数(--days)、按智能体(--agent)或清空(--all)批量删除历史消息,防止数据库无限膨胀。
🚫 拒绝寄往坟场给已处于completed/zombie状态的智能体发消息会被直接拒绝并报错——因为那条信永远不会有投递循环来消费它,宁可当下报错也不让它烂尾在库中。
八、动手体验:常用邮件命令速查
| 命令 | 作用 |
|---|---|
ov mail send --to builder-1 --subject "开始任务" --body "..." | 发送一封消息 |
ov mail check --agent orchestrator | 查自己的收件箱(并标记已读) |
ov mail list --unread --from lead | 全局视角过滤未读来信 |
ov mail reply <id> --body "收到" | 同串回复,自动带上Re:与前文的threadId |
ov mail read <id> | 手动标记已读 |
ov nudge <agent> "请尽快汇报进度" | 给卡住智能体发催办 |
回复功能(src/mail/client.ts)有个巧妙设计:系统会自动推断回复对象——你是原发件人,信就回到原收件人;你是原收件人,信就回到原发件人,并且自动延续同一threadId,整段对话天然成串。
九、延伸阅读:相关文件导航
- 存储层(建表、WAL、迁移):src/mail/store.ts
- 客户端层(send/check/reply/协议消息):src/mail/client.ts
- 群组广播解析:src/mail/broadcast.ts
- CLI 命令与 nudge 机制:src/commands/mail.ts
- 三条投递通道实现:src/agents/headless-mail-injector.ts
- Hooks 部署(注入查信命令):src/agents/hooks-deployer.ts
- 消息类型与 payload 定义:src/types.ts
- 轮询检测背板:src/agents/mail-poll-detect.ts
十、总结:小而美的工程取舍
回看这套邮件系统,没有引入消息队列、没有 RPC 框架,就是一个 SQLite 文件 + 三个 PRAGMA + 两张索引。但正是这种克制让它成为多智能体编排的地基:
- 零依赖、单文件——
mail.db拷走即带走全部通信档案 - WAL 并发模型——多进程读写互不阻塞,实测单次查询 1~5 毫秒
- 协议化消息——自然语言与结构化 payload 并存,机器可读、人类可懂
- 投递与存储解耦——数据库是事实来源,投递失败自动重试,消息永不丢失
对于想理解"多智能体系统怎么通信"的开发者来说,这套约千行代码的邮件系统,是一个值得精读的极简范本。🌳
【免费下载链接】overstory
Multi-agent orchestration for AI coding agents — pluggable runtime adapters for Claude Code, Pi, and more
相关推荐
终极微信读书助手wereader:轻松打造你的智能数字书房
终极微信读书助手wereader:轻松打造你的智能数字书房 还在为微信读书中的海量笔记整理而烦恼吗?wereader是一款功能全面的微信读书笔记助手,让你可以轻
终极指南:如何通过Pydantic-AI的MCP实现智能体高效协作
终极指南:如何通过Pydantic AI的MCP实现智能体高效协作 Pydantic AI是一个强大的Agent框架,它通过MCP(Multi Agent Co
人工智能大模型AI Agent工具调用MCP ClientsOS.js文件系统揭秘:虚拟文件系统(VFS)的工作原理与实践
OS.js文件系统揭秘:虚拟文件系统 VFS 的工作原理与实践 OS.js是一个功能强大的JavaScript Web桌面平台,其核心特性之一就是 虚拟文件系统
前端后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考