- 人工智能
- AI 应用
- 交互助手
- AI Agent
【免费下载链接】ironclaw
IronClaw is an Agent OS focused on privacy, security and extensibility
IronClaw将"谁发起的通信"(ingress identity)、"以什么权限执行"(execution authority)与"最终投递到哪里"(communication destination)作为三个相互独立的概念。本契约communication-delivery-resolution定义了 Reborn 架构中用户可见通信事件的投递目标解析规则:由ironclaw_outbound::OutboundPolicyService完成候选目标选择、目标再校验与投递尝试记录,最终把已通过校验的目标交给产品外发路径。读完本文,你将掌握候选解析的输入信封(resolution envelope)、OutboundPushKind投递种类语义、通知目标存储模型、确定性解析规则以及"候选不等于授权"的校验边界,并能直接对照 delivery_resolution.rs 与 resolution_engine.rs 的源码验证每一处实现细节。
1. 契约目的:把"投递到哪里"从"谁发起的"中彻底解耦
通信投递解析(communication delivery resolution)要回答的问题非常具体:当 Reborn 已经决定"应当尝试投递"某个用户可见的通信事件之后,应当优先尝试哪个外发目标(outbound target)。
候选选择(candidate selection)是ironclaw_outbound::OutboundPolicyService契约的一部分。选择步骤只返回一个候选(candidate only);同一个外发服务边界随后会校验该目标、记录一次投递尝试,并把经过校验的目标交给产品外发路径。传输(transport)流量完全位于选择步骤之外。
本契约刻意保持三个概念的分离:
- ingress identity(入口身份):一条消息、触发器或事件是如何进入系统的;
- execution authority(执行权限):当前正在运行的是哪个 tenant/user/thread 作用域;
- communication destination(通信目的地):最终回复、进度更新、审批提示、认证提示或投递状态通知应当尝试投递到哪里。
一个容易被忽视但关键的边界是:触发器事件本身的执行不依赖投递解析。触发器轮询、可信入口、turn 提交与运行持久化可以在没有外发目标的情况下照常进行;只有当系统打算把外部触发器结果或另一条运行通知投递到某个产品界面(product surface)时,才需要投递解析。
2. 所有权边界:谁拥有什么
契约以一张所有权表划清了组件职责,防止政策语言与产品行为被注入到解析器内部:
| 组件 | 拥有 | 不拥有 |
|---|---|---|
ironclaw_outbound::OutboundPolicyService | 候选选择、目标再校验、投递尝试记录、发送前门控(pre-send gating) | 触发器执行、认证/审批状态、传输发送 |
ironclaw_outbound::OutboundResolutionEngine | OutboundPolicyService内部候选选择的可选辅助引擎 | 公共调用方边界、目标校验权威、投递尝试记录 |
ironclaw_conversations | 入口身份、来源路由绑定、回复目标绑定语义 | 外发政策选择、产品专属回复行为 |
ironclaw_event_projections/ironclaw_event_streams | 持久化事件事实、投影重建、通知/扇出界面 | 最终外发目标选择、传输发送、发送权威 |
ChannelAdapter实现与传输胶水 | 外发政策批准候选后的渲染、宿主提供的传输执行 | 通信政策选择、持久化投递状态 |
契约强调:解析器必须保持宿主所有(host-owned)且确定性(deterministic)。Channel adapter 可以描述自身能力(capabilities),但无权定义解析器的政策语言,也无权把产品专属行为注入契约。这一点在源码中体现为 resolution_engine.rs 的OutboundResolutionEngine被声明为pub(crate),仅作为OutboundPolicyService的内部辅助存在。
3. 契约不变量(Contract Invariants)
契约定义了 8 条不可违背的不变量,是理解整个解析模型安全语义的基石:
- 外发候选选择只返回一个
CommunicationDeliveryCandidate; - 候选不等于授权(candidate is not authority):任何发送之前,候选仍必须通过
OutboundPolicyService的校验; - 解析器绝不能把入口身份、执行权限、通信目的地合并成单个字段或字符串;
- 触发器事件执行不依赖投递解析,只有触发器结果的外部投递才使用本契约;
- 解析器不得编码产品专属行为(例如"Web UI 可以显示审批卡片""Telegram 不能做门控提示");能力由外发政策边界在稍后阶段评估;
- §6 的解析规则固定且确定(fixed and deterministic);
- 若选中目标不可用、被撤销、未授权或以其他方式失效,系统失败关闭(fail closed),绝不静默回退到另一个渠道;
- 隐式回退不是解析规则的一部分;未来的回退必须建模为显式的有序政策规则并配套测试。
第 3 条不变量在 delivery_resolution.rs 中直接体现:解析请求携带scope: TurnScope、actor: TurnActor、modality: CommunicationModality、intent: CommunicationDeliveryIntent四个独立字段,目标只存在于intent分支的ReplyTargetBindingRef内,没有任何"一个字符串同时表达身份与目的地"的通道。
4. 解析输入:单一类型化信封(Resolution Envelope)
4.1 请求结构
外发服务使用一个类型化解析信封,这样调用方无法把无关的认证、审批或传输字段塞进请求,同时实现仍保持单一公共外发 API 表面。请求结构如下(源码见 delivery_resolution.rs):
pub struct CommunicationDeliveryResolutionRequest { scope: TurnScope, actor: TurnActor, modality: CommunicationModality, intent: CommunicationDeliveryIntent, }其中scope是(tenant_id, agent_id, project_id, thread_id)的 turn 作用域,actor是当前用户,modality是通信模态,intent是本次解析的意图分支。
4.2 投递种类:一个贯穿全链路的规范枚举
OutboundPushKind是从解析到持久化投递尝试全链路唯一规范的投递种类枚举(见 types.rs):
pub enum OutboundPushKind { FinalReply, Progress, DeliveryStatus, GateRequired, AuthPrompt, ModelDelivery, }该枚举在序列化时使用snake_case,且保留了私有 serde 别名:Progress仍接受历史拼写progress_update,GateRequired仍接受approval_prompt——这保证旧持久化记录(写的是progress/gate_required)与新代码兼容;而新写入者始终使用既有的持久化值。曾经存在过的"仅用于解析的镜像枚举"已被删除。
4.3 意图分支:显式请求 vs. 运行通知
CommunicationDeliveryIntent只有两个分支:
pub enum CommunicationDeliveryIntent { RequestedOutbound(RequestedOutboundContext), RunNotification(RunNotificationContext), } pub struct RequestedOutboundContext { requested_target: ReplyTargetBindingRef, requested_kind: RequestedOutboundKind, } pub enum RequestedOutboundKind { ProductMessage, DeliveryStatus, }- RequestedOutbound是显式命令意图(explicit command intent):调用方明确点名一个目标;
- RunNotification是生命周期政策(lifecycle policy),覆盖最终回复、进度更新、审批提示、认证提示与投递状态通知。
关键语义:
RequestedOutboundContext.requested_target是类型化的回复目标绑定候选(ReplyTargetBindingRef),不是裸渠道名、adapter 字符串、产品专属 conversation id 或传输地址;RequestedOutboundKind刻意比运行通知的投递种类更窄,排除审批/认证提示载荷;- 请求式外发目标仍然只是候选,任何发送前都必须在当前 scope、actor、派生的投递种类与模态下通过
OutboundPolicyService校验; CommunicationDeliveryResolutionRequest的投递种类由intent派生(delivery_kind()),调用方不得再提供一个可能与请求分支矛盾的顶层种类字段。源码中RequestedOutboundContext::delivery_kind()将ProductMessage → FinalReply、DeliveryStatus → DeliveryStatus(delivery_resolution.rs)。
4.4 运行通知上下文:事件种类与起源
pub struct RunNotificationContext { event_kind: RunNotificationEventKind, origin: RunNotificationOrigin, } pub enum RunNotificationEventKind { FinalReplyReady, ProgressUpdate, ApprovalNeeded, AuthRequired, RunBlocked, DeliveryStatus, /// 显式模型发起的投递(`builtin.outbound_deliver`)。 /// 自带 `OutboundPushKind::ModelDelivery`,使投递尝试 /// 在持久化审计轨迹与按运行核算中保持可区分。 ModelDelivery, } pub enum RunNotificationOrigin { LiveSourceRoute { source_route: SourceRouteContext }, RunScopedTarget { target: ReplyTargetBindingRef }, SystemEventTarget { reason: SystemEventReasonCode, target: ReplyTargetBindingRef }, SystemEvent { reason: SystemEventReasonCode }, }事件种类到投递种类的映射在源码中是一一对应且被测试全覆盖的(delivery_resolution.rs):
| 事件种类 | 派生的OutboundPushKind |
|---|---|
FinalReplyReady | FinalReply |
ProgressUpdate | Progress |
ApprovalNeeded或RunBlocked | GateRequired |
AuthRequired | AuthPrompt |
DeliveryStatus | DeliveryStatus |
ModelDelivery | ModelDelivery |
RunNotificationOrigin四个变体的语义:
LiveSourceRoute:携带已验证的回复目标(SourceRouteContext.reply_target_binding_ref),用于从真实入站产品消息衍生、且该事件支持"回复到消息来源处"的活跃会话(活跃会话的最终回复、进度更新、门控提示或认证提示);RunScopedTarget:本次运行唯一存活的按目标起源(per-target origin)。它逐字解析(verbatim),完全不触发偏好查询(§5、§6)。每个后台运行通知与每个显式模型投递都经过这一分支;SystemEventTarget:宿主起源事件 + 一个 owner 作用域内的密封目标,例如提交前就永久失败的触发器触发。它没有 run,但可投递;其目标仍由OutboundPolicyService再校验,稳定的事件投影引用配合turn_run_id = None让重试幂等,无需捏造运行状态;SystemEvent:纯元数据,解析为无投递候选,原因码仅作为投递元数据记录。
契约特别澄清了历史上曾存在的"偏好槽位(preference slots)"模型:没有专门的触发器通信上下文、没有按目的区分的偏好目标字段、也没有它们之间的优先级链。构造RunNotificationContext的调用方(后台运行通知器,或builtin.outbound_deliver工具处理器)已经决定了本次通知使用的唯一目标;解析器的工作只是把它原样读回作为候选。
ModelDelivery的特殊约束:它永远不能携带LiveSourceRoute——该起源的含义是"回复到入站消息来源处",即运行自身会话;而显式投递指向运行自身会话会在解析运行前就被拒绝(工具的 same-origin 检查)。ModelDelivery总是携带模型选定的RunScopedTarget。
SystemEventReasonCode是稳定、脱敏的枚举/码(Generic、Trigger、Tool、Operator,见 delivery_resolution.rs)。人可读的后端细节、原始工具输入、提示材料、OAuth 状态、审批载荷与传输错误不会进入解析请求;若产品界面需要显示文本,则在目标选定并校验之后接收独立的脱敏显示载荷。
4.5 解析结果
pub enum CommunicationDeliveryResolution { Candidate { candidate: CommunicationDeliveryCandidate }, NoDelivery { reason: SystemEventReasonCode }, } pub struct CommunicationDeliveryCandidate { pub target: ReplyTargetBindingRef, pub kind: OutboundPushKind, }大多数意图产生具体投递候选;宿主/系统事件在 P0 阶段仅元数据(除非调用方显式请求外发投递),因此解析为NoDelivery而非非法输入。
5. 通知目标:用户通信默认值存储模型
注:本节原名"偏好字段(Preference Fields)"。原先描述的四个按目的区分的偏好槽——
final_reply_target、progress_target、approval_prompt_target、auth_prompt_target——已全部退役。现在没有任何代码再写入按目的目标:投递是模型调用的工具,绝不再是存储的隐式路由。
用户通信默认值由ironclaw_outbound::CommunicationPreferenceRepository拥有,由专门的类型化 tenant/user 数据库表(CommunicationPreferenceRecord)支撑。它们不存储在通用 JSON 设置存储中,也不是 profile/tone 偏好。
5.1 记录结构
记录以 scope 为键(DeliveryDefaultScope,即(tenant_id, user_id),也支持SharedAgent变体),携带(源码见 communication_preferences.rs):
notification_targets: Vec<OutboundDeliveryTargetId>:显式的、用户配置的0..8 个目录目标集合(上限常量NOTIFICATION_TARGETS_CAP = 8),接收后台/例行运行(无活跃来源路由时)的门控提示、认证提示与失败通知。空集合 = "无外部通知路由"——运行的回复仍落在自己的线程中,且没有专门的应用内伪目标可配置。需要说明的是:web-app目录目标(浏览器推送到用户已注册设备)是真实的、provider 支撑的外部目标,可像任何渠道目标一样选入该集合,并未重新引入已退役的应用内伪目标;legacy_notification_target: Option<ReplyTargetBindingRef>:仅作为读取迁移输入(read-migration),序列化时保留历史线名final_reply_target以便迁移前的行仍能反序列化;没有任何代码再写它。CommunicationPreferenceRecord::effective_notification_target_ids仅在notification_targets为空时才把该旧槽折叠进通知集合(见 communication_preferences.rs);default_modality: Option<CommunicationModality>。
契约明确:progress_target、approval_prompt_target、auth_prompt_target字段已不存在,它们之间也没有优先级链;OutboundResolutionEngine(§6)根本不读取任何存储偏好。一次运行的最终回复、进度更新、审批提示、认证提示与投递状态通知,分别从活跃来源路由或调用方已选定的显式RunScopedTarget解析——绝不来自存储的按目的槽位。
存储的通知目标同样只是候选。外发服务在记录投递尝试前,必须再校验 tenant 所有权、目标能力、投递种类与模态。
5.2 版本化写入与上限强制
CommunicationPreferenceRepository使用**乐观锁(CAS)**语义:
CommunicationPreferenceVersion是不透明比较令牌,读取返回、写入必须携带(expected_version: None仅在行不存在时创建);- 上限
NOTIFICATION_TARGETS_CAP = 8在仓库写入缝(write seam)强制,而非Deserialize——过大的历史行必须能完整加载以便修正(见 communication_preferences.rs 及测试communication_preference_record_deserializes_scoped_and_legacy_payloads)。
5.3 产品读写面
产品面的读写均为 descriptor 支撑的 ProductSurface 能力:
get_notification_channels视图:投影调用方生效的通知目标集合(按上述规则折叠旧槽);- 两个唯一的写入面:
builtin.notification_channels_set(模型可调用、需审批、全量替换 CAS 写)与 WebUI 通知渠道面板的产品命令——两者都进入同一个经过校验的set_notification_channels服务路径(owner 作用域内 id 校验、去重、上限); get_outbound_preferences/set_outbound_preferencesfacade 已随偏好槽一起删除;outbound_delivery_targets仍是 descriptor 支撑的、按调用方作用域裁剪的目标清单视图。写入保持副作用性,必须走能力路径。
6. 解析规则:直接读取,而非搜索
候选是对调用方提供的意图的直接读取(direct read),绝不是对存储回退项的搜索。OutboundResolutionEngine::resolve是对CommunicationDeliveryIntent的单一 match(源码见 resolution_engine.rs):
RequestedOutbound逐字返回调用方显式目标:候选目标即requested_target,种类由requested_kind派生(ProductMessage → FinalReply、DeliveryStatus → DeliveryStatus)。不咨询任何其他规则;RunNotification直接从origin读出目标:LiveSourceRoute→ 来源路由的回复目标。只要运行源自真实入站产品消息、且该事件支持"回复到来源处"(活跃会话的最终回复、进度更新、门控提示或认证提示)即使用;RunScopedTarget→ 对每种事件种类都逐字使用密封目标。后台运行的门控/认证/失败通知以及每次显式ModelDelivery都走此路——调用方在请求解析器之前已经解析出确切目标(通知渠道集合中的一项,或模型选定的目录目标);SystemEvent→ 无候选,原因码仅作为投递元数据记录,不尝试外部发送。
没有隐式回退链,没有按目的偏好查询:上述每条规则都是直接的字段读取而非搜索。如果构造请求的调用方对某事件拿不出目标,它应构造SystemEvent,而不是让解析器发明一个。
解析器读出目标后不会继续搜索。如果返回的候选随后在校验中失败(不可用、被撤销、未授权),结果是失败,而不是自动跳到其他渠道——由调用方决定是否以及如何用不同目标重试。这一设计与不变量 7、8 完全一致。
resolve的实际实现非常直白(resolution_engine.rs):candidate_from_requested_outbound直接克隆requested_target;resolve_run_notification_context对四种origin变体做 match,其中SystemEvent返回NoDelivery,其余三种返回CommunicationDeliveryCandidate { target, kind }。
7. 校验边界:候选如何变成可发送目标
校验与投递尝试记录仍位于ironclaw_outbound。完整流程:
OutboundPolicyService candidate-selection step -> returns CommunicationDeliveryCandidate OutboundPolicyService -> validates target and capability scope -> records delivery attempt -> returns validated target or rejection Channel adapter / host transport -> renders through adapter and sends through host-owned transport only after validation在服务层,OutboundPolicyService::prepare_communication_delivery_attempt先调用OutboundResolutionEngine::resolve得到CommunicationDeliveryResolution,再通过lower_communication_delivery_resolution把候选降级为PrepareOutboundDeliveryRequest(见 service.rs)。降级时每个候选都被强制打上requires_reply_target_revalidation: true——解析只选候选,任何被降级的候选都必须先通过回复目标校验才能被渲染或发送。若解析结果是NoDelivery,则返回Ok(None),不产生任何投递尝试。
真正的校验在prepare_delivery_attempt中执行:
validate_delivery_scope_candidate:强制候选的tenant_id、agent_id、project_id、thread_id与请求 scope 完全一致(validation.rs);ReplyTargetBindingValidator::validate_reply_target:验证候选的回复目标绑定对当前 scope 仍被授权;返回的ReplyTargetBindingClaim是不受信任的,服务只有在确认 claim 的目标与原始候选一致(无目标替换)后才铸造密封的ValidatedReplyTargetBinding(见 service.rs);- 校验通过 → 记录
OutboundDeliveryAttempt(状态Prepared,任何供应商出口之前持久化)并返回Authorized;AccessDenied→ 记录Failed+AuthorizationRevoked并返回Rejected;瞬态校验器错误 → 记录Failed+TransientValidatorError并返回Rejected(service.rs)。
校验器对"候选是否仍属于当前 tenant/user/scope、目标是否支持所请求的模态与通知种类"拥有最终裁决权。这也意味着:prepare_delivery_attempt天然支持幂等重放——已存在的Prepared行表示"记录与发送声明之间崩溃,供应商出口尚未发生,可安全重试";而Sending及之后的状态以存储行为权威。
8. 触发器投递边界:触发不阻塞、结果走通信
触发器循环不被外发投递解析阻塞。触发器可以触发、执行并持久化自己的 run,即使尚无外部投递路径可用。
当触发器结果必须外部投递时,解析器把它当作通信事件处理,而不是触发器权威(trigger authority)。触发器身份留在触发器域;外发目的地选择留在ironclaw_outbound。SystemEventTarget(reason: Trigger)正是"提交前永久失败的触发器触发"这类无 run 生命周期事实的受控出口——它携带密封目标、仍走再校验,并以turn_run_id = None保证重试幂等。
9. 非目标(Non-Goals)
本契约不定义以下内容,它们分别属于各自的契约与服务:
- 传输专属渲染(transport-specific rendering);
- 产品 UI 行为;
- 订阅扇出政策(subscription fan-out policy);
- 认证流程创建或回调处理(参见 auth-product.md);
- 审批解析或租约语义(参见 approvals.md);
- 触发器调度、轮询或执行编排(参见 triggers.md)。
10. 关联契约与延伸阅读
本契约依赖以下兄弟契约,阅读时建议按依赖顺序展开:
- events-projections.md:持久化事件事实与投影重建,是通知/扇出界面的数据底座;
- conversation-binding.md:入口身份、来源路由绑定与回复目标绑定语义;
- approvals.md:审批解析与租约语义(本契约明确不拥有);
- auth-product.md:认证流程创建与回调处理;
- run-state.md:运行状态、turn run 持久化与生命周期。
实现与测试证据集中在以下路径:
- delivery_resolution.rs:信封、意图、起源与候选的全部类型定义及 JSON 往返/映射测试;
- resolution_engine.rs:
OutboundResolutionEngine::resolve的实现与逐分支解析测试; - communication_preferences.rs:
CommunicationPreferenceRecord、NOTIFICATION_TARGETS_CAP = 8、版本化 CAS 写入与旧行迁移测试; - service.rs:
prepare_communication_delivery_attempt的解析→降级→校验→记录完整调用链; - validation.rs:作用域匹配与偏好记录校验;
- outbound_deliver.rs:
builtin.outbound_deliver工具实现(ModelDelivery的模型发起入口,含空内容拒绝与 same-origin 拒绝约束)。
结语
IronClaw 的通信投递解析契约用"单一类型化信封 + 确定性直接读取 + 候选/授权分离"三件套,解决了多渠道产品下最容易出错的投递选路问题:解析器永远不猜、不搜、不回退,只忠实地把调用方意图读成候选;权威校验、投递尝试记录与持久化全部收敛在ironclaw_outbound一个边界内;入口身份、执行权限与通信目的地自始至终是三个独立的类型。对实现者而言,这意味着投递逻辑可测试、可审计、可幂等重放;对架构演进而言,"未来的回退必须建模为显式有序政策规则并配测试"这条不变量,为多目标容灾留出了清晰且受控的扩展路径。
- 人工智能
- AI 应用
- 交互助手
- AI Agent
【免费下载链接】ironclaw
IronClaw is an Agent OS focused on privacy, security and extensibility
相关推荐
三步完成电子课本下载:tchMaterial-parser 批量下载 PDF 完整指南
三步完成电子课本下载:tchMaterial parser 批量下载 PDF 完整指南 tchMaterial parser 是一款面向国家中小学智慧教育平台的
网页爬虫教育IronClaw 权威词汇契约:深入解读 `ironclaw_host_api` 的架构边界与工作规则
IronClaw 权威词汇契约:深入解读 ironclaw_host_api 的架构边界与工作规则 导读 ironclaw_host_api 是 IronCla
人工智能AI 应用交互助手AI AgentIronClaw 技能系统深度解析:SKILL.md 解析、确定性选择、学习与管理全链路
IronClaw 技能系统深度解析:SKILL.md 解析、确定性选择、学习与管理全链路 IronClaw 是一个以隐私、安全和可扩展性为核心的 Agent O
人工智能AI 应用交互助手AI Agent
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考