"Agent-Reach"这个名字,我们团队内部叫了小半年。如果你乍一听觉得像某个海外SaaS产品,也不奇怪,因为当初立项的时候确实受了国外一些触达编排平台的启发。但落地到我们自己的业务土壤里,它早就不是当初那个"又一个消息推送系统"的雏形了。这篇文章我想完整复盘一下Agent-Reach从零到一的过程:为什么做、怎么设计、核心模块怎么实现、踩了哪些坑、以及下一步往哪儿走。它适合正在搭建统一触达平台、对消息调度和渠道治理感到头大的技术团队,也适合想在Agent应用里加一层"触达能力"的开发者。我会尽量把设计思路和实操细节都讲透,不藏私。
1. 项目缘起:为什么需要Agent-Reach
1.1 分散触达的痛点
在Agent-Reach出现之前,我们手上一共有五条业务线,每条业务线都有一套自己的触达逻辑。有做短信的、有做App推送的、有做站内信的、有做邮件的,还有几个部门自己接了企业微信群的机器人。每个系统对接渠道的方式都不一样,有的直接调服务商SDK,有的自己封装了一层HTTP接口,有的干脆在定时任务里拼参数拼到怀疑人生。
这种"百花齐放"的状态短期内看着热闹,时间一长全是坑。
第一是重复开发成本极高。新来一个渠道,比如要接入某个新的推送服务商,五条业务线各自都要改代码。改一遍还好,改五遍就是灾难。每个系统的参数模型还不一样,有的用userId,有的用phone,有的用mobile,光字段映射就够写一本字典。
第二是排查问题靠运气。线上告警说短信发送失败,你得顺着业务系统去翻它的日志,再找到它对接的渠道服务商,看返回的code是什么意思。有时候一个渠道抖音超时,五条业务线各报各的错,客服那边用户的投诉已经堆成山了,技术这边还在一个系统一个系统地定位。
第三是节奏完全失控。没有统一的限流和配额管理,营销活动一上线,所有系统同时往短信渠道灌消息,服务商直接限流拒发。更要命的是没控制夜间静默,用户凌晨三点收到推送,第二天投诉率直线上升。这些问题单靠"每个业务线自觉"根本解决不了,必须有一个统一的东西来管。
当时我们就意识到:我们缺的不是一个"发消息的工具",而是一个能把所有触达行为统一收口、统一调度、统一追踪的"触达中枢"。这就是Agent-Reach的起点。
1.2 我们要解决的四个核心问题
问题梳理清楚之后,我们在提案里把目标收敛成了四个核心命题:
- 统一接入:所有业务线不再直接对接渠道,只对接Agent-Reach一个平台,由平台负责和短信、推送、邮件、站内信等渠道打交道。
- 统一调度:消息该不该发、什么时候发、走哪个渠道、以什么优先级发,这些决策统一交给Agent-Reach的调度中心,业务方不用关心细节。
- 统一追踪:一条消息从提交、发送、送达、点击到失败,全链路状态在Agent-Reach一个地方看得到,日志、指标、回执全部集中。
- 统一治理:限流、配额、重试、退避、静默期、敏感词过滤、频率控制,这些治理策略在平台层统一配置,杜绝"谁都能发、发完就乱"的局面。
这四个目标写起来简单,落地时每一步都牵扯大量的设计取舍。后面我会把每一个模块的拆解思路、实现细节和当时踩过的坑都说清楚。
2. 架构设计与核心模块拆解
2.1 整体分层思路
Agent-Reach的整体架构,本质上就是一个"输入侧—处理侧—输出侧"三段式管线,再加一条贯穿全链路的监控追踪带。
输入侧是接入层,所有业务方统一通过一个HTTP API或者消息队列提交触达任务。我们当时定了一个原则:业务方只传"意图"和"必要参数",不传"渠道偏好"。什么意思?就是说业务方只需要告诉Agent-Reach"我要给这个用户发一个账户余额变更提醒,内容大概是xxxx",至于走短信还是App推送,由Agent-Reach根据用户状态、渠道成本、历史到达率来决策。这个设计在当时看来有点激进,因为它把渠道选择的权力从业务方手里拿走了,但事后证明这是让触达真正走向智能化的关键一步。
处理侧是核心引擎,包括调度中心、策略中心和渠道适配层三个子模块。调度中心管节奏,策略中心管规则,渠道适配层管具体执行。它们之间的关系是:一条消息进来,调度中心先判断它的优先级、是否在静默期、是否需要合并、用户是否订阅过这类通知,然后交给策略中心决定走哪个渠道,最后通过渠道适配层真正发出去。
输出侧就是各种渠道商。我们抽象了一套统一的渠道接口,不管对方是短信服务商、推送服务商、邮件厂商还是自建的站内信系统,接进Agent-Reach只需要实现一个send方法和一个queryStatus方法。
贯穿全程的是一条可观测带。所有消息的提交、决策、发送、回执、重试事件都打到统一的日志管道里,同时暴露核心指标。没有这条带子,后面做排查和优化就寸步难行。
这套分层设计最大的价值是让"变化"都被隔离在局部。业务方变了,只影响接入层的契约;渠道商变了,只影响适配层;调度策略变了,只影响策略中心。彼此不牵一发动全身。
2.2 渠道适配层的设计考量
渠道适配层是Agent-Reach最接地气的部分,也是最容易写成一坨屎的部分。我的经验是:这层一定要用适配器模式,并且每个渠道一个独立模块,不允许一个类里堆多个渠道的if-else。
我们先定义一个统一的渠道接口,核心方法就两个:
public interface ChannelAdapter { // 渠道唯一标识,如 sms、apns、fcm、email、inbox String channelName(); // 发送单条消息,返回渠道原始响应 SendResponse send(SendRequest request); // 主动查询某条消息的状态(用于补偿回执缺失) StatusResponse queryStatus(String messageId); }这里有个细节我想强调一下:send方法里一定不要自己写重试逻辑。重试是调度中心的事儿,渠道适配层只管"发一次"并如实地把渠道返回的结果带回来。为什么这么设计?因为如果每个渠道在适配层自己重试,全局的重试节奏就会失控——短信渠道重试了三次,推送渠道重试了两次,消息顺序完全乱套,渠道那边也会因为重复请求被限流。把重试收口到调度中心,才有一致性的保障。
另外每个渠道适配器内部都要做"参数转换"。比如短信渠道要的是手机号和签名,推送渠道要的是deviceToken和标题,站内信要的是userId和内容。这些转换逻辑全部封装在适配器内部,对外只暴露统一的SendRequest结构。这样业务方的代码就非常干净,永远不需要知道渠道长什么样。
我们当时顺手做了一个渠道健康状态登记:适配层每次收到渠道的超时或限流响应,都会往监控中心打一个事件,调度中心读了这个事件之后,会对该渠道做临时的降权处理。这个机制在渠道服务商出故障时救过我们好几次。
2.3 调度中心的决策逻辑
调度中心做的事情,表面上看就是"排队发消息",但真正要把节奏管好,至少要考虑四个维度:优先级、限流、静默期、去重。
优先级我们把消息分为三级:L0是安全类的,比如风控提醒、验证码,要求秒级送达;L1是交易通知类的,比如支付结果、发货通知,允许分钟级延迟;L2是营销运营类的,比如促销活动、内容推荐,允许延迟到低峰期发放。
实现上我们用了一个带优先级的分层队列,底层依赖Redis的有序集合。L0的消息不进队列,直接走快速通道同步发送;L1和L2进各自的分层队列,由调度器按权重消费。权重怎么配?当时我们定的是L1和L2的消费比是3:1,也就是说正常情况下每消费4条消息,3条是L1,1条是L2,保证高优先级消息不会被大量低优先级消息堵在后面。
限流这个维度,我们用的是令牌桶和滑动窗口结合的方案。令牌桶管"每秒最多发多少条",滑动窗口管"每分钟最多发多少条"。为什么两个都要?因为单用令牌桶会允许瞬间突发把一分钟的配额全打出去,渠道那边照样限流;单用滑动窗口则应对不了每秒尖峰。两个叠加在一起,既保住平均速率,又卡住峰值。
静默期是当时被用户投诉逼出来的。我们定义每天晚上22点到次日早上9点为静默时段,L1和L2的交易通知允许发,但营销类消息一律延后到第二天上午。这个功能看似简单,真正落地的时候发现业务方各种诉求:有人想对某个用户分组豁免静默期,有人想让某个类型的通知在静默期强制下发。后来我们干脆把静默期做成可配置的策略,默认全局开启,特殊场景按"用户标签+消息类型"双维度放行。
去重逻辑放在调度入口处。每条消息进来都带一个业务方生成的幂等键,我们以幂等键为唯一约束写入Redis,如果重复提交则直接返回"已存在"。这个设计防住了不少测试环境误触发和回调重推的情况。
3. 关键实现与参数细节
3.1 触达任务的建模
Agent-Reach里,一条被触达的消息不叫"消息",叫"触达任务",英文对应Task。为什么要叫Task?因为它不止是"把一句话发出去"这么简单,它代表一次完整的触达行为,其中包含触达内容、目标用户、渠道候选、优先级、时效要求、重试策略等。
任务的数据结构我们最终是这样定义的:
{ "task_id": "trx_20240601103000001", "biz_id": "order_paid_880123", "idempotent_key": "biz_order_paid_880123", "user_key": "uid_99812", "template_id": "tpl_payment_notify_v3", "template_params": { "amount": "99.00", "retailer": "极客便利店" }, "priority": "L1", "allow_channels": ["sms", "push", "inbox"], "policy": { "silent_period_skip": true, "expire_at": "2024-06-01T12:00:00Z", "max_retry": 3 } }有几个字段我想展开说说。
template_id和template_params是一对。业务方不能直接传一段完整的短信文案进来,而是传模板ID和参数,然后由Agent-Reach的模板引擎渲染出最终文案。这个设计一开始被业务方吐槽"多此一举",但后来大家发现它的好处:一是内容安全可控,不能在模板里塞敏感词;二是可以按渠道自适应文案长度,同一个模板渲染成短信版和推送版,字数规则完全不同;三是便于做A/B测试,换文案就是换模板,不用动代码。
allow_channels是候选渠道列表。业务方虽然不能指定"必须走短信",但可以声明"这个任务允许走哪些渠道"。比如验证码任务只允许走短信,因为时效性要求太高;营销活动允许走推送和站内信,但不允许走短信,因为成本太敏感。调度中心拿到候选列表之后,在里面选一个最优解。
policy里有个expire_at字段,这个字段太重要了。它表示任务的过期时间——比如一条"订单支付成功通知",如果消息在渠道队列里堵了30分钟才发出去,对用户来说已经没有意义了。所以我们在调度器消费任务时会先判断expire_at,过期任务直接标记为TIMEOUT,不再下发。这能有效避免"半夜补发昨天的消息"这种尴尬情况。
3.2 重试、退避与节流控制
重试是触达平台最容易翻车的地方。我们前前后后调过好几轮策略,最后的结论是:无脑重试不如有节奏重试,有节奏重试不如"先看清楚为什么失败再决定要不要重试"。
渠道返回的失败要分成两类。第一类是可重试失败,比如超时、限流、渠道5xx错误,这类值得重试;第二类是确定性失败,比如手机号格式错误、token失效、用户退订,这类重试一万次也没用,直接判FAILED并通知业务方。这个分类在渠道适配层就要做干净,否则调度中心拿到的错误是模糊的,重试策略就没法正确执行。
我们的重试退避策略用的是指数退避加随机抖动,参数是这么定的:
第一次重试:延迟 1 秒 第二次重试:延迟 4 秒 第三次重试:延迟 16 秒 之后不再重试,标记失败指数增长的倍数选4而不是2,是因为我们发现触达类任务的失败往往不是瞬时抖动,而是渠道端的问题持续几秒甚至几十秒。如果增长太慢,三次重试都在问题发作窗口内,等于白试。在每次延迟上还加了正负20%的随机抖动,这是为了避免大量任务同时重试形成"重试风暴"——想象一下所有消息都在第16秒准时重试,渠道那边就好像又经历了一次流量突刺,这个雪崩效应会直接打死渠道。
节流控制这块,我们同时管了两个方向。一个是"我方到渠道"的流量,用计数器按渠道限流,超过阈值的话新任务不直接发,而是放回队列等待;另一个是"渠道回调我方的回执",也做了限流,因为回执处理链路如果被打满,会导致回执堆积,进而产生"消息发了但回执一直没处理"的假象。
我的经验是:重试策略一定要可以按渠道独立配置。短信渠道的重试节奏和App推送渠道完全不一样。短信渠道稳定但费钱,所以宁可多等一会儿也要发成功;推送渠道快速但token失效率高,重试意义不大。我们最后把retry策略下沉到了渠道适配层配置文件里,每个渠道一份,互不干扰。
3.3 回执数据闭环与状态机
触达平台如果没有回执闭环,就是瞎子。Agent-Reach的状态机我们用得比较谨慎,没有搞得很复杂,一共六个状态:
PENDING -> SENT -> DELIVERED -> OPEN PENDING -> SENT -> FAILED PENDING -> TIMEOUT PENDING -> DUPLICATED PENDING -> BLOCKEDPENDING是任务刚提交,还没进调度;SENT是渠道已经受理,但还没确定送达;DELIVERED代表用户设备已经收到;OPEN一般只有推送和邮件有意义,代表用户打开查看了;FAILED是渠道明确告知失败;TIMEOUT是任务在调度队列里过期了;DUPLICATED是去重命中;BLOCKED是被策略拦截,比如触发了敏感词或静默期强制拦截。
回执来源有两个渠道。一个是被动的,渠道方异步回调我们的webhook接口;另一个是主动的,调度中心定期对那些"发了很久但一直没状态变化"的任务发起queryStatus。我们管这个机制叫"回执补偿",是应对渠道回调偶尔丢消息的关键手段。
补偿任务的扫描频率我们定的是每5分钟跑一次,扫描范围是"状态还在SENT且超过10分钟没更新"的任务。这里有个细节:补偿查询本身也会对渠道产生额外请求,所以频率不能太高,否则也是另一种形式的打扰。10分钟这个阈值是我们根据各渠道回执延迟的P95统计出来的,既能覆盖大部分正常延迟,又不会太频繁地主动查询。
回执数据最终落到一个专门的ClickHouse表里面,按天分区,按用户维度建索引。为什么用ClickHouse而不直接用MySQL?因为回执数据是典型的"写入频繁、按时间范围聚合查询"场景,ClickHouse在这一块舒服很多。我们在这个表上支撑了"用户最近30天在各渠道的触达明细"查询,运营和客服部门用得最多。
4. 实操过程:从零到一落地Agent-Reach
4.1 技术选型与基础环境
技术选型这块,我想先说一句可能会招黑的话:Agent-Reach没有用什么酷炫的技术栈,全部都是团队已经很熟悉的东西。基础设施选型的第一原则不是"最新最潮",而是"出了问题团队能最快解决"。
我们最终定的技术栈是这样的:
- 开发语言:Java 17 + Spring Boot 3.x。团队主语言是Java,而且Agent-Reach的核心链路对稳定性要求高,Java的生态和排查工具最成熟。
- 队列:RocketMQ。为什么不用Kafka?因为我们需要丰富而可靠的消息重试机制,RocketMQ自带重试队列和延迟消息,省了自己造轮子。调度中心本身就是吃消息的消费者,通过RocketMQ把任务投递给渠道适配层。
- 缓存和去重:Redis。幂等去重、分层队列、限流计数器全放Redis。
- 主数据库:MySQL。保存任务主记录、渠道配置、模板信息这类结构化数据。量大但不算爆炸,MySQL配合分表完全够用。
- 回执分析库:ClickHouse。专门承载回执明细和触达事件流水。
- 监控:Prometheus + Grafana + 自研告警规则。核心指标全部暴露成Prometheus指标。
部署形态上,Agent-Reach拆了四个微服务:接入服务、调度服务、适配服务、追踪服务。实际部署下来,调度服务和适配服务是压力最大的,这两个服务的Pod数会根据消息量动态伸缩。追踪服务出过一段时间的内存瓶颈,后来把实时聚合逻辑从内存搬到了ClickHouse的物化视图才解决。
这里有一个建议:Agent-Reach第一版尽量用单体或极简微服务跑起来,不要一上来就拆十几个服务。我们的教训是,前两个月大部分时间花在了服务之间消息传递的调试上,真正业务逻辑的推进反而被拖慢了。如果重新来一次,我会先做一个带清晰模块边界的单体应用,等流量模型验证清楚了再拆。
4.2 第一个触达任务的完整链路
我们接进Agent-Reach的第一个真实业务是"订单支付成功通知"。这个业务量虽然不大,但是链路非常典型,适合当验证场景。
业务方接入的代码长这样:
TouchTask task = TouchTask.builder() .idempotentKey("biz_order_paid_880123") .userKey("uid_99812") .templateId("tpl_payment_notify_v3") .priority(Priority.L1) .allowChannels(Arrays.asList("sms", "push", "inbox")) .build(); String taskId = agentReachClient.submit(task);业务方只需要传五个字段,剩下的全部交给平台。当时跟我们对接口的后端同学第一反应是"这么简单?模板参数呢?"我说参数得自己在模板里定义key,然后他回去翻了模板配置,把amount、retailer这两个参数填了进去。第一次联调,2个小时就跑通了全链路。
全链路走完大概是这样的:
- 业务方调用submit接口,接入服务先做参数校验和幂等检查,写入任务记录,状态PENDING。
- 消息投递到RocketMQ,调度服务消费后执行策略判断。此时是下午3点,不在静默期,任务也没有超时,判断结果放行。
- 调度中心结合渠道候选列表和用户画像选渠道。当时用户没装App,推送渠道不能选,站内信和短信都可用。策略中心选了站内信优先,把任务状态更新为SENT。
- 适配服务取到任务,渲染模板,得到最终文案,调用站内信渠道发送。站内信渠道瞬时OK,回执直接返回DELIVERED。
- 追踪服务收到DELIVERED回执,更新任务状态,写入ClickHouse回执明细。
整个过程从提交到送达,耗时不到300毫秒。虽然这个链路在后来看来很朴素,但当时跑通的那一刻,团队是真的有点兴奋的——大家第一次体会到,原来"给用户发一条通知"这件事可以做得这么干净。
4.3 灰度发布与监控指标
Agent-Reach本身作为一个平台型系统,它的发布策略如果搞成一刀切,一旦出问题就是全业务线一起遭殃。所以我们在上线第一个渠道之前,先搭了一套灰度发布机制。
灰度分为两个层面。第一是渠道灰度:新接入一个渠道适配器之后,先在测试环境用模拟流量跑三天,再把真实流量的5%切到新渠道上,观察回执率和报错率。等各项指标稳定了,再逐步扩大到20%、50%、100%。第二是策略灰度:比如新上一条静默期策略,不会全量生效,而是打上策略标签,先让特定用户群组命中,验证效果之后再全量放开。
监控指标我们当时定了三个必看的核心面板:
- 发送速率面板:每秒各渠道的发送量,Token桶水位,队列堆积量。这个面板红了,说明调度出问题了。
- 回执健康面板:各渠道的送达率、打开率、失败原因分布。这个面板红了,说明渠道或内容出问题了。
- 任务时效面板:任务从提交到送达的P50/P95/P99延迟,超时任务占比。这个面板红了,说明用户体验开始受影响了。
告警规则最有价值的一条是"渠道送达率低于历史基线5个百分点并持续5分钟"。这条规则后来真的帮我们在一次短信服务商故障中抢到了15分钟的响应时间。起初服务商的送达率从98%缓步跌到93%,肉眼根本看不出来,但监控面板上的持续下降趋势触发了告警,我们提前切了一部分流量到备用渠道,用户的感知被降到了最低。
5. 常见问题与排查实录
5.1 消息乱序问题
Agent-Reach上线第三周,客服那边接到反馈:有用户先收到"您的订单已退款成功"的通知,过了五分钟才收到"您的订单已支付成功"的通知。这明显是消息乱序了。
排查下来,罪魁祸首是多个调度服务实例并发消费同一个业务方的事件流。订单支付事件和退款事件都同一个业务方的不同消息,投递到RocketMQ时,两个事件落在不同的队列分区,消费者并行处理,谁先处理完谁先发,顺序就乱了。
解决方案是在接入层加了一个用户维度的顺序保障:同一个userKey的触达任务,写入Redis的同一个有序队列,由同一个消费者实例串行消费。代价是吞吐量下降了一点,但换来的是同一用户的触达顺序绝对可控。
这个改动上线之后,乱序投诉归零。我的心得是:触达系统的顺序保障不应该依赖消息队列的全局顺序,而应该依赖业务主键的分区顺序。RocketMQ并没有全局有序的概念,但按同一个userKey投递到同一个队列,就能保证这个用户的消息严格有序。
5.2 渠道限流与配额管理
渠道限流是我们遇到的最频繁的问题,尤其是短信渠道。短信渠道本身就是按条计费的,服务商为了保证通道质量,会对每个客户设置了每秒QPS上限和每日发送上限。
有段时间我们的营销活动总在晚上8点整并发触发,每次都能把短信渠道的QPS打满,然后大批任务进入重试。最初我们只是把重试间隔调大,但治标不治本——重试是在"渠道已经限流"之后的补救,不能靠重试解决限流。后来我们加了一层"配额预检":在调度中心发消息之前,先去配额中心查询当前渠道剩余配额,如果剩余量低于阈值,直接切到备用渠道或降级到站内信。
配额中心的数据来源是渠道服务商的用量API加上我们自己记录的发送计数,两边交叉验证。这里有个教训:渠道服务商的用量API有缓存延迟,自己计数又不一定准,中间会有偏差。后来我们统一以"自己计数为主、渠道计数校准"的策略,每天凌晨跑一次对账任务,不一致的以渠道侧为准修正。
5.3 回执掉单与状态悬挂
回执掉单是触达平台最隐蔽的坑,没有之一。短信发出去了,用户收到了,但因为渠道回调没到,我们任务状态一直挂在SENT,导致业务方误判"消息没送达"。
第一次遇到这个问题,我们以为是渠道服务商的webhook配置有问题,找对方排查了两天,对方信誓旦旦说回调都发了。后来我们自己查回调接收服务的日志,发现渠道回调根本不是"没发",而是大量回调集中在一个时间点打进来,接收服务的线程池被占满,部分请求直接超时丢弃了。
解决方案做了两件事。第一,回调接收服务单独拆分部署,和主业务隔离,避免互相影响;第二,加上了前面提到过的"回执补偿"机制,对于那些SENT超过10分钟还没更新的任务,主动向渠道queryStatus。这两个措施一起上,状态悬挂的问题基本解决了,从偶发变成了罕见的孤例。
我的建议是:任何依赖渠道回调的触达平台,都必须在设计之初就接受"回调可能丢"这个假设,主动补偿机制不是可选项,是必选项。
5.4 测试消息误发线上用户
这个事故我必须写下来,因为太典型了。有一天测试同事在Agent-Reach后台手动创建了一条测试任务,填了一个测试userId,但渠道适配层在解析用户映射的时候,没有做环境隔离,拉到了生产环境的用户数据,结果一条"【测试】欢迎参加周末活动"的站内信发到了线上真实用户那里。
事后复盘,根本原因是手动创建任务的后台接口和生产消息通道共用了一套配置,没有做环境标记。我们后来在测试环境的所有任务上都强制打了一个testOnly标签,渠道适配层看到这个标签,发消息前会做一次"先判环境、再判用户"的双重校验,测试用户和生产用户的映射表完全隔离。
这个事故给了我们一个铁律:触达平台的环境隔离必须做在渠道适配层,而不是依赖测试同事的自觉。测试环境的消息,无论如何都不允许走生产渠道网关。
6. Agent-Reach的后续演进
6.1 从规则调度走向智能路由
Agent-Reach第一版的所有调度决策都是基于人工配置的规则:优先级、限流阈值、静默期、渠道白名单。这些规则在业务规模不大时够用,但随着渠道增多、用户分群变细,规则会越来越臃肿,冲突也会越来越多。
我们正在做的下一步,是把调度中心从"规则驱动"升级为"目标驱动"。什么意思?业务方不再是简单地告诉平台"允许哪些渠道",而是告诉平台"这个任务的预期目标是什么"。比如运营说"我希望这个活动通知在推送渠道的打开率达到5%",Agent-Reach会把历史各渠道对该用户群的送达率、打开率、成本数据拉出来,算出一个最优渠道组合,并且在发送过程中根据实时回执动态调整。
这其实就是把Agent-Reach从"触达执行平台"推进成"触达决策平台"的关键一步。目前我们已经在用历史数据离线跑渠道推荐模型,准确率能做到七成左右,下一步就是把它部署成在线决策服务,让调度中心在发送瞬间完成一次轻量推理。
6.2 与Agent能力融合的思考
Agent-Reach这个名字里的"Agent",一直都是我个人的一个小心思。当时立项时大家觉得Reach是重点,Agent只是修饰,但我心里的蓝图是:Agent不只是一个执行触达的智能体,它更应该有"判断要不要触达、怎么触达、触达之后怎么跟进"的能力。
具体来说,我们希望未来Agent-Reach能接入一个"触达决策Agent"。当业务方提交一个触达任务时,Agent先做一轮判断:这个用户最近是不是已经收过太多消息了?当前这个内容是不是真的有必要现在发?有没有比直接推送更好的沟通方式,比如先发一条站内信,如果用户24小时内没看,再升级为短信?
这样Agent-Reach的定位就不只是一个底层消息管道,而是一个"懂用户、懂渠道、懂时机"的智能触达中枢。技术上,这个Agent会依赖回执闭环积累的海量数据,对用户触达偏好做画像建模;运营上,它需要和业务方重新定义触达协作关系——业务方负责表达"想要什么",Agent-Reach负责任判断"该怎么做"。
这条路还很长,但我们已经在走了。目前第一阶段是把历史回执数据清洗成标准特征集,接下来会做用户触达敏感度分群,再往后才是在线决策Agent的落地。
7. 写在最后的个人体会
复盘Agent-Reach这个项目,我最想说的是:这类平台的价值往往不是上线那一刻体现的,而是三个月、半年之后,当你发现一个新业务接入只需要调一个接口、一个新渠道接入只需要写一个适配器、一次线上事故排查只需要打开一个追踪面板的时候,你才真正感受到当初所有"看似麻烦"的设计都在为你省时间。
如果在这些经验里只能挑一条分享,我会选这一条:渠道适配层的抽象,是所有触达平台的地基。地基打的歪一点,上面盖什么都会歪。我们当时在适配层花了整整三周时间,反复讨论接口粒度、错误分类、重试边界,这些讨论在当时看很奢侈,但后来每一次新渠道接入的顺利,每一次故障排查的清晰,都是在为那次"奢侈"买单的回报。
Agent-Reach现在还在持续迭代,它远没有到"完美"的状态,还有很多问题没有完全解决,比如跨渠道的内容指纹去重、用户级日触达频控的精细化、以及真正的实时智能路由。但有一点是确定的——它已经把我们从"各业务线乱接渠道、触达行为黑盒、线上事故靠吼"的泥潭里拉了出来。这就是一个内部平台最朴素的成功。