Agent Relay投递机制深度剖析:持久化消息、Wake on Message与死信队列完整指南
【免费下载链接】relayReal time communication for agents. Wake on message, channels, DMs and actions. Useful for orchestrating agents.项目地址: https://gitcode.com/gh_mirrors/relay35/relay
Agent Relay(relay)是一款开源的 Agent 实时通信基础设施,为 AI 智能体提供频道、私信(DM)、动作调用等能力,常用于编排多个 Agent 协作。它的投递机制围绕三个核心设计展开:持久化消息(broker 重启不丢消息)、Wake on Message(用消息唤醒离线的 Agent)、以及死信队列(失败消息不静默丢弃)。本文带你完整理解这套机制是如何保证"消息必达"的。
为什么 AI Agent 的消息投递这么难?
人用 IM 时,消息发出去对方大概率还"在线"。但 Agent 世界完全不同:
- Agent 随时会死:进程崩溃、机器重启、会话超时,接收方可能在你发消息的瞬间就不存在了
- 接收方是"哑终端":很多 Agent(如 Claude Code、Codex)跑在 PTY 终端里,没有自己的事件循环,消息必须通过 stdin 注入
- 网络不可靠:跨节点部署时,broker 与控制面之间的 WebSocket 随时可能抖动
Agent Relay 的答案是三件套:先落盘再投递(持久化)、消息到了就把 Agent 拉起来(Wake on Message)、实在送不了就进死信队列并通知发送方(可观测的失败)。
持久化消息:broker 崩溃也不丢
消息状态机:从 queued 到 acked
每条消息在控制面(Relaycast)中遵循一个简单的状态机:
queued ──deliver(seq)──▶ delivered ──ack──▶ acked (≈已读) │ └── TTL 到期 ──▶ dead-letter- At-least-once + 按
msg_id去重:保证"至少送达一次",靠消息 ID 去重防止重复 - 单调递增的
seq:每个 Agent 位置有独立序列号,天然保证单 Agent 内的消息顺序 - 累积确认(cumulative ack):接收方回报"我确认到第 N 条",N 之前的全部标记为
acked
这套设计写在项目的设计文档里,值得精读:specs/fleet-delivery.md。
崩溃恢复:脏标记 + 原子写入
broker 本地维护一个PendingDeliveryStore(待投递消息表),核心源码在 crates/broker/src/runtime/delivery.rs:
- 脏标记(dirty tracking):任何对消息表的插入、删除、重试计数都会把 store 标记为"脏",事件循环在变更事件发生后立即把快照写到磁盘,而不是等下一个定时维护周期
- 关机时落盘:正常退出时,只要还有未投递完的消息,快照一定写回磁盘,下次启动继续投递(
persist_pending_on_shutdown) - 序列号基线校验:broker 只接受"恰好比已知游标大 1"的序列号,拒绝跳号,配合控制面保存的权威 ACK 游标(
relay:delivery-cursor-v1能力协商),重启后能从断点精确续传,不重复、不跳号
一句话:broker 的投递状态是内存态 + 磁盘快照,控制面才是唯一权威来源。broker 死了无所谓,重启后按游标重放即可。
Wake on Message:让消息唤醒沉睡的 Agent
这是 Agent Relay 最有意思的设计。普通消息队列在消费者离线时要么丢弃、要么无限堆积;Agent Relay 选择"有界持久化"(bounded-durable):
| 情况 | 处理方式 |
|---|---|
| 连接抖动,进程还活着 | 消息挂起(hold),重连后立即补投 |
| 进程已死,但 Agent可恢复会话(resumable) | 按 TTL 挂起,Agent 恢复后从最旧的消息开始冲刷 |
| 进程已死且不可恢复 | 立即进入死信队列,并通知发送方 |
默认的投递策略是lazy(懒加载):消息先进队列,等 Agent 通过重启策略或显式重新拉起时再消费。而Wake on Message 是它的"急进"(eager)模式——消息一到,broker 自动在节点上恢复该 Agent 的会话(resume = 定位到原节点的 spawn +session_ref),然后把邮箱里的消息按序注入。
关键约束是身份连续性依赖会话连续性:只有支持恢复会话的 harness(如 Claude Code、Codex 这类会保存session_ref的运行时,见 crates/broker/src/runtime/relaycast_events.rs)才能"带上下文醒来";没有会话可恢复的 Agent,唤醒它等于把一堆旧消息砸给一个失忆的进程——那还不如直接死信。
死信队列:失败必须可观测、可重试
什么消息会变成"死信"?
两条路径:
- 重试耗尽:broker 向 worker 注入消息连续失败(PTY 写失败等),达到重试上限
- 接收方已死:消息的目标 Agent 进程消失且无法恢复会话,TTL 到期
死信的实现见 crates/broker/src/runtime/dead_letter.rs,每个DeadLetterEntry保留:
- 完整的原始消息体(
RelayDelivery)——这是关键,意味着死信可以原样重新入队,走正常的投递路径再试一次 - 尝试次数、失败原因、入队时间、最终失败时间——运维排查所需的一切上下文
有界队列:500 条上限与最旧优先淘汰
死信队列不是无限黑洞,上限是 500 条(MAX_DEAD_LETTERS)。队列满时:
- 新死信进来,淘汰最旧的一条,并通过
tracing::warn打日志留痕 - 从磁盘恢复快照时如果超限,同样执行裁剪并立即回写,保证上限是持久化的不变量
这个取舍很务实:死信队列的职责是"保留足够近期的失败样本供重放和排查",而不是当数据库用。
失败绝不静默
设计文档里有一句话说得很直白:"静默丢弃是错误的默认值"。死信触发后,系统会向发送方发出delivery_failed/ expired 事件。同理,邮箱溢出时采用reject-new(拒绝新消息并反馈给发送方),而不是悄悄丢掉最旧消息——发送方应当知道自己的 Agent 积压了。
投递最后一公里:回显验证与自适应节流
消息"写进了 PTY"不等于"Agent 真的看到了"。Agent Relay 在 crates/broker/src/broker/delivery_verification.rs 中做了两道精细的活:
1. 回显验证(echo verification)
broker 向 PTY 注入消息后,会剥掉 ANSI 转义序列,在终端输出里查找预期回显字符串,5 秒窗口内匹配到才算"确认送达"(Success);匹配不到则走超时兜底,标记为Unverified。特别注意MAX_VERIFICATION_ATTEMPTS = 1——验证失败绝不重复注入,因为重复注入会让 Agent 处理同一消息多次,成倍放大 API 调用甚至触发限流。
2. 自适应节流(throttle)
- 连续 3 次成功确认 → 注入延迟减半(最快 100ms)
- 连续失败 → 延迟阶梯式退避:100ms → 200ms → 500ms → 1s → 2s → 5s 封顶
Unverified(超时兜底)不算成功也不算失败,只是打断成功连击——未经验证的投递永远不能驱动延迟下降
核心设计一览
| 机制 | 解决的问题 | 关键取舍 |
|---|---|---|
| 消息持久化 | broker 崩溃丢消息 | 磁盘快照 + 游标重放,at-least-once + 去重 |
| Wake on Message | Agent 离线收不到消息 | 有界持久化:可恢复会话才唤醒,否则死信 |
| 死信队列 | 失败静默丢失 | 500 条有界 + 通知发送方 + 可原样重放 |
| 回显验证 | "写进去"≠"送到了" | 只注入一次,失败不重试防重复消费 |
| 累积 ACK 游标 | 重启后重复/跳号 | 权威游标在控制面,broker 无状态续传 |
源码地图:从哪开始读
想深入源码,建议按这个顺序:
- 设计全景(强烈建议先读):specs/fleet-delivery.md ——投递模型、持久化策略、节点生命周期全部讲清楚了
- 待投递消息与崩溃恢复:crates/broker/src/runtime/delivery.rs
- 死信队列存储:crates/broker/src/runtime/dead_letter.rs
- 回显验证与节流:crates/broker/src/broker/delivery_verification.rs
- 消息注入格式:crates/broker/src/broker/injection_format.rs
- 会话恢复(resume)处理:crates/broker/src/runtime/relaycast_events.rs
- 消息路由:crates/broker/src/routing.rs
总结:Agent Relay 的投递机制本质上是把企业级消息队列的思想(至少一次投递、ACK 游标、死信队列、退避重试)适配到了"接收方是一个随时会死的 LLM 进程"这个极端场景。持久化保证消息不丢,Wake on Message 保证 Agent 该醒就醒,死信队列保证失败可见可救——三者配合,让多 Agent 编排终于有了可靠的通信底座。
【免费下载链接】relayReal time communication for agents. Wake on message, channels, DMs and actions. Useful for orchestrating agents.项目地址: https://gitcode.com/gh_mirrors/relay35/relay
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考