Channel 运行时生命周期
Channels 位于 ZeroClaw 的边缘。它们与聊天平台、webhook、编辑器和事件源通信,然后将标准化后的工作交给 agent 运行时。
当更改涉及 channel listeners、gateway webhooks、message dispatch、reply intent、streaming drafts、per-channel reload、health/backoff 行为,或平台特定适配器与运行时拥有的 turn processing 之间的边界时,请使用此页。
目标边界和当前过渡
目标边界很简单:
- 通道适配器负责各平台特定的 I/O。
- 运行时拥有的代码负责管理代理轮次生命周期。
- Gateway webhook 处理器负责处理通用的 HTTP 传输细节,然后进入与长运行监听器相同的通道轮转生命周期。
当前代码仍处于过渡阶段。zeroclaw-channels 包含一个很大的 orchestrator 模块,其中有 ChannelRuntimeContext、run_message_dispatch_loop 和 process_channel_message。这段代码目前执行的是运行时规模的工作:消息路由、钩子、自环保护、被动上下文、媒体/链接增强、自动保存、记忆召回、回复意图、工具循环调用、草稿更新、取消、回执、成本跟踪以及最终投递。
那是可运行的代码,但不能成为阻止每次通道变更的理由。审查规则更为狭窄:新的通道、webhook 或流式处理工作应在可能时复用这个共享生命周期,并且不应再添加另一个本地的小型编排器。
谁拥有什么
| Surface | 所有者 | 审查规则 |
|---|---|---|
| 平台监听器或通道入站适配器 | 通道模块或通道插件 | 将签名检查、有效负载解码、平台重试、provider 验证、挑战处理以及 ChannelMessage 构造保留在传输适配器内部。 |
| Gateway webhook 路由 | 网关处理程序 | 将路由托管、代理、超时行为、快速确认以及通用 HTTP 响应策略保留在网关本地。除文档中说明的迁移债务外,不要在此处增加新的平台特定解析。 |
| 规范化传入消息 | ChannelMessage 来自 zeroclaw-api | 保留发送者、回复目标、频道、别名、线程、附件、主题、被动上下文和会话范围。添加结构化元数据,而不是将路由信号隐藏在用户可见文本中。 |
| 通道别名的代理所有权 | start_channels / AgentRouter 和活动通道绑定 | 从已配置的绑定中解析所属 agent。不要在某个 channel 未被拥有或已禁用时,静默回退到无关的 agent。 |
| 消息分发和取消 | 共享通道调度循环 | 复用进行中跟踪、/stop、发送者/线程取消、最大进行中限制和 worker 并发。 |
| 开启处理 | 共享运行时/通道生命周期 | Hooks、自循环保护、被动上下文、媒体/链接增强、运行时命令、模型路由、自动保存、记忆召回、回复意图、工具执行、回执、成本和投递应统一放在一条路径中。 |
| Gateway webhook 确认 | 网关处理程序 | Fast-ack 传输可能会在模型完成前返回 HTTP 200,但后台工作仍应进入共享通道生命周期。 |
| 通道健康状态和重新连接 | 监听器主管 | 可重试的监听器失败使用有界指数退避和感知取消的关闭。不可重试的失败应停止或清晰暴露。 |
| 运行时重新加载 | 守护进程重载和通道重启路径 | 仅保存配置还不够。长时间运行的监听器只有在守护进程重新加载或进程重启时才会采用 channel/provider/scheduler 的更改。 |
入站形状
长连接通道和由 webhook 支持的通道有不同的传输入口点,但它们应当收敛为相同的消息形状:
flowchart LR
A["Platform event"] --> B["Transport adapter"]
B --> C["ChannelMessage"]
C --> D["Channel dispatch loop"]
D --> E["Agent turn lifecycle"]
E --> F["Channel send / draft / reply"]
适配器应保留只有平台才能理解的工作:
- 路由和别名解析;
- 正文大小限制和解码;
- 签名或令牌验证;
- 特定于平台的解析规则;
- 配对、allowlist 或发送方身份提取;
- 提供程序挑战或验证端点;
- 立即确认策略。
之后,传递一个规范化的 ChannelMessage。除非该例外是范围狭窄、已文档化且经过测试,否则不要将生命周期的其余部分复制到适配器中。
运行时轮次职责
共享生命周期应拥有必须在各个通道之间保持一致的行为:
- 诸如 message-received 和 message-sent 之类的 hooks;
- 通过
Channel::self_handle()和drop_self_messages的自环保护; - 无模型/提供方副作用的被动上下文记录;
- 早期确认反应和无回复清理;
- 在调用提供方之前进行媒体和链接预处理;
- 运行时命令,例如
/new、/model、/models、/config和/stop; - 自动保存和会话历史键;
- 记忆回顾和历史修剪;
- 群组和 ambient 频道的回复意图分类;
- 流式草稿更新和多消息行为;
- 工具批准、执行、收据、观察者事件和成本跟踪;
- 取消、超时、回滚和最终回复传递。
如果一个 PR 只针对一个 channel 更改了其中一项职责,审阅者应询问它是属于共享生命周期,还是属于类型化的 channel 能力元数据。
Gateway webhooks
Gateway webhooks 有一个合理的特殊要求:即使 agent 的轮次很慢,HTTP 请求也可能需要快速返回。Nextcloud Talk 是最明确的例子,因为较慢的本地模型可能会超过提供方的 webhook 超时。
快速确认要求不应让网关拥有独立的代理生命周期。当前由网关支持的处理程序仍使用网关专用的验证后分发路径。应将其视为迁移债务和过渡背景,而不是新 webhook 支持的渠道工作的目标模式。webhook 处理程序遵循固定顺序:
- 验证请求;
- 解码负载;
- 解析一个或多个
ChannelMessage值; - 选择同步或后台派发;
- 返回适合该传输方式的 HTTP 响应。
对于消息分发渠道的 webhook,第 1 步和第 4 步属于结构要求,而非约定。网关的 webhook_ingress 模块负责一个经过身份验证的入口契约:
- 每个消息分发 webhook 适配器都在一个注册表(
MESSAGE_DISPATCHING_WEBHOOKS)中声明其身份验证模式,而漂移防护测试会将网关路由表与该注册表进行核对; authenticate强制执行故障关闭凭据策略。缺失、空白或无法解析的必需密钥会在解析任何负载字节之前拒绝请求并返回401。提供商特定的签名算法和标头格式仍由传输处理程序负责,并封装在仅在凭据解析成功后运行的闭包中;- 成功的检查会生成一个携带已验证字节的
VerifiedWebhookIngress证明。使用parse_messages会将这些完全相同的字节提供给解析器,并返回一个私有的VerifiedWebhookMessages值。这两种证明都无法在其他位置构造或克隆; dispatch_verified_webhook是当前入站日志、会话密钥、自动保存、代理调度、快速入门回退、回复/错误传递以及同步或快速确认执行所共用的网关 webhook 辅助函数。它会使用已解析的证明,因此 webhook 内容无法在缺少与同一请求绑定的验证结果和解析步骤的情况下进入代理调度。
该辅助函数消除了重复的网关 parse -> autosave -> chat -> send 链,并为认证入口提供了一个统一且强制执行的关口。不过,它仍会调用网关聊天路径,因此并不是上文所述的共享通道轮次生命周期。网关 Webhook 仍需汇聚到该生命周期,以支持钩子、自循环控制、被动上下文、媒体/链接处理、运行时命令、取消、回复意图、回执和成本跟踪。未来的这种汇聚必须保留认证入口的安全保证,以及各传输方式的同步或快速确认响应行为。
注册表中的每个消息分发 webhook 适配器都会声明一个必需的按别名区分的凭据;当该凭据缺失、为空白或无法解析时,会在解析前被拒绝。注册表没有可选验证模式:静默放行不是身份验证模式。因此,没有入站凭据机制的适配器目前无法注册为消息分发适配器;要支持这种适配器,需要扩展注册表,添加明确的“仅拒绝”策略,而不是放宽验证。
在审查 webhook 变更时,请分别比较同步处理程序和快速确认处理程序:
- 同步处理器必须保留现有的状态码、无效签名行为、自动保存键以及回复传递;
- fast-ack 处理器必须证明 HTTP 确认在模型调用可能阻塞提供方超时之前发生;
- 在保留网关特定路径的同时,两种形态都必须通过
dispatch_verified_webhook进入 dispatch,而不是再添加一条parse -> autosave -> chat -> send链路。固定的调用点清单会在处理程序绕过认证流程时使构建失败。这一要求并不意味着该辅助函数就是目标通道的生命周期。
重载和监听器生命周期
Channel 配置可以在运行中的 listener 看到之前保存。daemon 持有长生命周期的 subsystem graph,因此当 daemon 重新加载或重启相关 subsystem 时,channel listener 的更改就会生效。Standalone gateway 的启动可能需要重启进程才能使 channel listener 的更改生效。
通过检查以下内容审查对重新加载敏感的更改:
- 更改后的值是否已保存到
config.toml; - 运行中的 channel 上下文是立即读取新值,还是在重新加载时读取,或者仅在重启后才读取;
- 监听器任务是否通过取消来停止,而不是遗留旧连接;
- 活动通道绑定是否仍然是决定哪个 agent 拥有哪个通道别名的事实来源。
流式传输、草稿和取消
流式传输是一个能力边界。一个通道可能支持草稿编辑、多消息流式传输、输入中指示,或者只支持最终发送行为。共享生命周期决定这些能力在一次轮次中如何使用。
通过提问来审查流式更改:
- 通道是否声明了该能力,而不是在回合循环中硬编码行为?
- 草稿消息在每个成功、无回复、失败和取消路径上都会被最终确定、取消,还是替换?
/stop会取消正确的发送者/线程范围吗?- 中断的轮次会避免将部分 assistant 响应持久化为已完成的吗?
- 用户可见的行为在直接消息、群聊和线程回复中是否保持一致?
健康与退避
长期运行的监听器必须以运维人员能够理解的方式失败。可重试的平台故障应采用退避并重试;不可重试的配置或身份验证故障应清晰地显现,而不是无限循环。
对于监听器更改,请证明相关路径:
- 取消时正常关闭监听器;
- 可重试的 API 失败会退避并恢复;
- 不可重试的失败会停止或报告一个持久性的配置问题;
- 一个阻塞的 channel 不会卡住同级监听器或观察者的投递。
审阅者清单
对于 channel、webhook 或 channel-runtime 的更改,在审阅者签署批准前,请先回答以下问题:
- 适配器中还剩下哪些特定于传输层的工作,以及为什么?
- 代码最初是在什么地方创建或接收
ChannelMessage? - 哪个 agent 拥有此消息的 channel alias?
- 此更改是否复用了共享的调度和轮次生命周期?
- 如果它添加了特定于 channel 的生命周期行为,那么考虑过哪些共享 hook 或能力,为什么它还不够?
- 自环、被指向性、被动上下文和回复意图是如何传递的?
- 在进入提供方可见的上下文之前,媒体、链接、附件和工具输出是否被限制了范围?
- 快速确认(如果有)是否仍会保留与同步分发相同的后台轮换行为?
- 重新加载、监听器取消、提供程序超时、
/stop、无回复以及发送失败时会发生什么? - 哪个聚焦测试或手动冒烟验证了发生变化的边界?
源指针
规范文档:
关键代码入口点:
- Channel trait and message shape:
crates/zeroclaw-api/src/channel.rs - Ingress 上下文 ABI:
crates/zeroclaw-api/src/ingress.rs - 通道分发和轮次生命周期:
crates/zeroclaw-channels/src/orchestrator/mod.rs - 运行时轮转循环:
crates/zeroclaw-runtime/src/agent/turn/ - 运行时泛型进程入口点:
crates/zeroclaw-runtime/src/agent/loop_.rs - Gateway webhook/chat 路径:
crates/zeroclaw-gateway/src/lib.rs