工作原理

定时跟进

智能体如何把工作排到之后再做,并把结果送回它来的那个渠道——持久化任务、原子领取与故障恢复。

有些事在一轮对话里答不完:一次要跑几分钟的核查、一个下周二的提醒、一场六周之后才该开启的续约谈话。智能体可以把这类工作登记下来,等结果就绪了再送达。

机制

1. 登记

对话进行中,智能体针对本轮内完不成的工作调用 schedule_followup:要跑几分钟的核查、下周二的提醒、六周后的续约谈话。

2. 落库

任务是 Postgres 里的一行,不是消息队列里的一条消息。待执行的任务是已提交的数据,重新部署或进程崩溃都不会把它弄丢——持久性直接来自本就在用的存储,不额外花钱。

3. 轮询

轮询器每隔几秒醒一次,找出到期的任务。代价是精度:任务在下一次轮询时触发,而不是精确到毫秒。对于以分钟到周为单位的跟进,这点损失没有意义。

4. 领取

到期任务用 FOR UPDATE SKIP LOCKED 领取。单个轮询器本来用不着这么写——现在就写对,意味着日后跑第二个实例也不会重复发送,而一条跟进发两遍,赔进去的是客户对发件方的信任。

5. 生成

生成结果。如果领走任务的进程随后挂掉,会有一趟恢复流程把它退回待执行状态,而不是让它永远卡在执行中。

6. 送达

送达时回到请求发起的那个渠道——网页挂件里的一个气泡,或者 Telegram、WhatsApp 里的一条消息。从 WhatsApp 问的,就在 WhatsApp 回。

工具立即返回——客户当下就拿到回复,排期的结果稍后再来。

这个工具不会阻塞当前这一轮。它写一行数据就返回,所以客户当下就拿到回复,排期的结果稍后再来。

为什么队列就是一张数据库表

这里没有 Redis,也没有消息中间件。任务就是 Postgres 里的行,这是一个刻意的取舍:

  • 重启不会丢活儿。 待执行的任务是一行已提交的数据。进程可以在半途重新部署,轮询器回来的路上就把任务接着捡起来——持久性随着本就在用的数据存储白拿,而不是靠再引入一套有自己故障模式的系统。
  • 少运维一样东西。 单进程部署的场景,一个轮询循环就够了。

代价是延迟精度:任务在下一次轮询时触发,而不是精确到毫秒。对于以分钟到周为单位的跟进,这点损失没有意义。

领取是原子的

到期任务用 FOR UPDATE SKIP LOCKED 领取。单个轮询器不可能需要这个——但现在就写对,意味着日后跑第二个实例不会重复发送。一条跟进发两遍不是无伤大雅的小毛病:客户会收到两遍「您的续约到期了」,然后不再信任发件的这一方。

SKIP LOCKED 还意味着一个跑得慢的任务不会把后面的队列堵住,其他工作进程会跨过被锁住的那行,去拿下一个。

故障恢复

任务被某个进程领走、而这个进程随后挂掉,它本会永远卡在执行中。一趟恢复流程会把卡住的任务退回待执行,让它们被重试,而不是无声无息地丢掉。

送达跟着来路走

结果会被推回请求发起的那个渠道,而不是平台随便挑的一个渠道。从 WhatsApp 问的,就在 WhatsApp 回。租户自己发起的定时触达——续约提醒、期限催办——也是同样的机制,按你配置的节奏执行。

这在实际使用中意味着什么

  • 智能体说得出「这个我回头答复你」,而且真的做得到。
  • 提醒能扛过一次发布、一次重启和一次崩溃。
  • 一条跟进只送达一次,并且送到这个人当初跟你说话的地方。

「跟进不该依赖谁记性好」这件事的商业逻辑,见把时间还给你;排期是内置工具之一,详见工具与 MCP