
IronClaw 循环层契约解析ironclaw_loop_contracts 如何用端口与 DTO 隔离可替换的 Agent 循环【免费下载链接】ironclawIronClaw is an Agent OS focused on privacy, security and extensibility项目地址: https://gitcode.com/gh_mirrors/iro/ironclaw本文深入剖析 IronClaw以隐私、安全与可扩展性为核心的 Agent OS中负责循环层契约的 crate——ironclaw_loop_contracts。它定义了任何 loop、hook 框架与宿主适配器在不导入 turn 内核的前提下与内核对话所需的全部词汇十一个Loop*Port端口、AgentLoopDriver驱动契约、run-profile 解析词汇以及LoopExit退出手势。读完本文你将理解该 crate 的边界设计动机、源码级实现细节、配套架构测试如何锁定这些不变量以及如何在保证不越界的前提下为其新增端口与字段。一、为什么需要一个纯契约crate在 IronClaw 的六层架构contracts substrates runtimes kernel loops products app中ironclaw_loop_contracts位于最底层的contracts族crates/contracts/AGENTS.md 定义了该族的准入测试与边界。它存在的唯一目的正如 AGENTS.md 开篇所言ironclaw_agent_loop—— 唯一一个允许被整体替换、且替换时不触碰任何特权代码的构件 —— 必须以零层矩阵异常满足其仅依赖 contracts 层的规则。也就是说ironclaw_agent_loop是整个系统中被设计为可热替换的用户态循环实现为了让它不依赖内核必须有一个专门承载循环层契约的 crate 供它编译。同时turn 内核ironclaw_turns可以自由演进其内部状态机制而无需触碰循环侧契约。方向永远反转The direction inverts, always这是该 crate 最核心的一条不变量任何代码都不得反向依赖ironclaw_turns。turn 内核是这些契约的实现者与校验者而契约 crate 永远是被依赖的一方。一旦出现反向依赖整个 crate 就失去了存在的意义。这条规则由reborn_dependency_boundaries.rs中的ironclaw_loop_contracts白名单§11.2.3锁定——白名单采用允许列表而非黑名单它直接禁止所有其他工作区 crate而不是逐一列举今天的违规者从而让未来可能混入的内核或领域依赖无法侥幸通过。二、边界规则什么能进、什么永远不能进2.1 内部依赖白名单crate 的 Cargo.toml 声明了[package.metadata.ironclaw] layer contracts实际持有的工作区内部依赖仅两个依赖用途ironclaw_host_api引入 turn 词汇TurnId/RunProfileVersion等与权威 ID 类型ironclaw_extension_contractsLoopRuntimeContext携带OptionChannelPresentationrender_presentation_hint需要读取它§8.2 contracts 行的授权而架构测试中强制执行的loop_contracts_allowed白名单reborn_dependency_boundaries.rs还额外允许ironclaw_common与ironclaw_prompt_envelope——这两个 crate 当前并未被使用白名单只是为未来预留了空间。文档特别提醒早期指导文件把持有集列成了host_api/common/prompt_envelope但manifest 与门禁才是事实它们与这份旧清单在正反两个方向都不一致。2.2 框架、驱动与运行时客户端一概禁止不允许出现 axum、reqwest、wasmtime、libsql、deadpool、tokio-postgres、sqlx 等任何框架或驱动。唯一的例外是tokio且只启用rtfeature——为的是CommunicationContextFetch::Spawned中那唯一一个JoinHandle。在 runtime_context.rs 的源码中可以看到CommunicationContextFetch的Spawned变体持有tokio::task::JoinHandleOptionCommunicationRuntimeContext其Drop实现会在未 resolve 时abort()底层任务防止 run 启动热路径上浪费后端工作。该 carve-out 在 manifest 注释与架构测试reborn_contracts_crates_hold_no_framework_dependencies拒绝清单含 axum/deadpool/hyper/libsql/reqwest/rusqlite/sqlx/tokio-postgres/tonic/tower/wasmtime 等中双重记录想要放宽必须是有意的显式编辑。2.3 不实现任何在此声明的端口LoopExit的校验、退出应用器exit applier、协调器coordinator与状态存储全部属于 turn 内核的权威范围。Loop*Port的实现必须落在循环托管层ironclaw_loop_host、ironclaw_turn_runner、ironclaw_hooks三个 crate 中。它们构成文档 docs/internal/reborn/target-architecture/families/loop.md 声明的唯一一条装饰器链ironclaw_loop_host实现基于内核服务的基座适配器ironclaw_turn_runner将其组合为交给每次 run 的具体宿主ironclaw_hooks在最外层包裹使每次端口调用都先经过策略检查与审计。工作区内不允许出现任何平行、未声明的端口实现。2.4 DTO 内容纪律公开/线缆 DTO 只能携带引用refs、有界的安全摘要safe summaries、类型化 ID、版本、游标与脱敏后的错误。临时性的 prompt 构造类型可以携带有界、经宿主批准的模型可见内容但绝不能成为 serde 线缆/公开 DTO。原始 prompt 文本、原始助手内容、工具输入 JSON、密钥、宿主路径与后端错误全部必须留在宿主实现之后。三、Turn 词汇不属于本 crate一个类型一条导入路径TurnId/TurnRunId/TurnScope/TurnActor、gate/message/result 引用、TurnStatus、RunProfileId/RunProfileVersion/RunProfileRequest、ProductTurnContext、GateResumeDisposition等 turn 词汇全部定义在ironclaw_host_api::turn。该 crate刻意不做 re-exportone type, one import path一个类型一条导入路径。例如 driver.rs 中直接use ironclaw_host_api::turn::{RunProfileVersion, TurnCheckpointId, TurnId, TurnRunId};。这样做避免了同一类型存在两条导入路径、导致消费者在不知情时依赖错误 crate 的经典陷阱§11.2.4。四、公共表面端口、驱动、退出与 run-profile4.1 十一个 Loop*Port 与组合宿主src/lib.rs将公共表面几乎全部扁平 re-export。十一个端口覆盖循环运行所需的全部宿主能力端口职责LoopCapabilityPort能力可见面、请求批次、provider 工具调用注册与回放LoopModelPort模型网关调用、消息与用法统计LoopPromptPort宿主托管的 prompt bundle 组装PromptMode当前仅TextOnlyLoopTranscriptPort助手草稿起草/更新/定稿、能力结果引用追加LoopContextPort上下文 bundle、窗口截断、压缩元数据LoopInputPort输入批次、游标、取消原因LoopRunInfoPort运行信息与模型路由快照LoopCancellationPort取消信号LoopCompactionPort上下文压缩请求与结果LoopProgressPort进度事件流driver 笔记、迭代、批处理、gate 阻塞、检查点、压缩LoopCheckpointPort检查点写入/加载/暂存此外还有组合性的AgentLoopDriverHostblanket trait与AgentLoopHostError错误词汇。LoopProgressEventprogress.rs值得一提它携带FailureRecovered { sequence }等事件sequence在检查点重放间保持稳定消费方可据此把至少一次的物理追加去重为单一逻辑事件。4.2 AgentLoopDriver用户态循环实现契约driver.rs 定义了驱动契约实现者拥有循环机制向受信 runner 返回LoopExit握手不直接变更 turn 状态、不接收原始权威句柄#[async_trait] pub trait AgentLoopDriver: Send Sync { fn descriptor(self) - AgentLoopDriverDescriptor; async fn run( self, request: AgentLoopDriverRunRequest, host: (dyn AgentLoopDriverHost Send Sync), ) - ResultLoopExit, AgentLoopDriverError; async fn resume( self, request: AgentLoopDriverResumeRequest, host: (dyn AgentLoopDriverHost Send Sync), ) - ResultLoopExit, AgentLoopDriverError; }AgentLoopDriverDescriptor携带idLoopDriverId如reborn:text-only-model-reply、version与可选的 checkpoint schema 信息AgentLoopDriverRunRequest/ResumeRequest以TurnIdTurnRunIdResolvedRunProfileresume 额外带TurnCheckpointId与GateResumeDisposition标识一次运行AgentLoopDriverError分为InvalidRequest/Unavailable/Failed三类其中Failed携带已脱敏、模型可见的原始原因细节供失败解释器描述真实故障。4.3 LoopExit一个声明而非事实loop_exit.rs 的模块注释说得非常清楚LoopExit是驱动返回的声明claim永远不会自动成为事实——turn 内核必须对照宿主铸造的证据校验它之后才允许任何持久转换提交。校验策略、违规分类学与执行转换的应用器都留在内核的ironclaw_turns::loop_exit。LoopExit有四个变体Completed(LoopCompleted)携带completion_kind、reply/result 引用、最终检查点、累计 token 用量。LoopCompletionKind含FinalReply、AskUserReply、NoReply、DelegatedResult、ResultOnly、NothingToReport六种Blocked(LoopBlocked)携带LoopBlockedKindApproval/Auth/Resource/AwaitDependentRun/ExternalTool、gate 引用、凭证要求、检查点。LoopBlockedKind通过FromLoopBlockedKind for GateKind与to_blocked_reason桥接到内核的BlockedReason测试blocked_kind_gate_correspondence_is_exhaustive用编译器强制穷尽匹配锁定了这组对应关系Cancelled(LoopCancelled)HostCancellation/HostInterrupt两种原因Failed(LoopFailed)17 种失败分类见下表。LoopFailureKind#[non_exhaustive]及其持久化线缆字符串变体线缆字符串含义ModelErrormodel_error模型调用错误ContextBuildFailedcontext_build_failed上下文构建失败CapabilityProtocolErrorcapability_protocol_error能力协议错误IterationLimititeration_limit迭代上限InvalidModelOutputinvalid_model_output模型输出非法CheckpointRejectedcheckpoint_rejected检查点被拒CheckpointUnavailablecheckpoint_unavailable检查点不可用TranscriptWriteFailedtranscript_write_failed记录写入失败DriverBugdriver_bug驱动自身缺陷InterruptedUnexpectedlyinterrupted_unexpectedly意外中断NoProgressDetectedno_progress_detected重复或同错误逃逸无进展PolicyDeniedpolicy_denied策略拒绝且无重试可能CompactionUnavailablecompaction_unavailable压缩不可用GateNotSupportedgate_not_supported无法渲染/解析的 gate 触发WallClockLimitwall_clock_limit墙钟预算耗尽ModelCallLimitmodel_call_limit模型调用预算耗尽CapabilityInvocationLimitcapability_invocation_limit能力调用预算耗尽其中WallClockLimit/ModelCallLimit/CapabilityInvocationLimit直接对应 policy.rs 中ResourceBudgetPolicy的三个字段max_wall_clock_seconds/max_model_calls/max_capability_invocations默认值分别为None/2048/4096。此外引用列表有硬上限MAX_LOOP_EXIT_REF_COUNT 64且反序列化器deserialize_bounded_unique_refs会拒绝重复条目LoopFailed的手写Deserialize还兼容读入已退役的diagnostic_ref字段只读兼容不产出任何值。4.4 run-profile 词汇与解析器ResolvedRunProfilesnapshot.rs是一次运行被解析后的完整画像run_class_id、loop_driver描述符、checkpoint schema、模型/能力面/上下文 profile、steering/cancellation/checkpoint/resource-budget 策略、personal_context_policy默认Excluded、运行时约束、runner 池、调度/并发类别、解析指纹与脱敏后的 provenance。InMemoryRunProfileResolverresolver.rs内置了默认交互 profile——从测试 run_profile_contract.rs 可以看到它的解析结果profile_id interactive_default、loop_driver lightweight_loop、run_class_id interactive_coding、model_profile_id interactive_model等且序列化后的线缆不包含secret/api_key/raw_config/RuntimeDispatcher等敏感串。ResolvedRunProfile::legacy_compatibility还承担了 turn 内核投影旧行时的兼容职责。五、新增代码的规则新增字段仅当每个宿主实现都能兑现中性契约时才添加当具体宿主尚未实现新能力时默认值保持fail-closed如CheckpointPolicy::require_final_checkpoint的缺失字段默认即要求最终检查点。新增端口当循环需要新的宿主能力时添加 port trait 的同时必须在同一次变更中把该端口登记进LOOP_PORT_OWNERS位于 reborn_loop_port_location_scan.rs。该扫描对未登记的端口故意失败。当前登记表包含 11 个属于ironclaw_loop_contracts的端口另有 2 个刻意例外LoopExitEvidencePort属于内核ironclaw_turns只读持久证据端口供内核校验LoopExit声明LoopAttachmentReadPort是ironclaw_loop_host内部接缝从不跨过 loop/kernel 膜。新增文件当契约有独立生命周期或校验模型时新建文件禁止common、misc、helpers这类杂物模块。端口不密封它们存在的意义就是被上层 crate 实现密封 trait 模式属于ironclaw_agent_loop的策略槽crates/loop/ironclaw_agent_loop/src/planner.rs留在那里。六、常见错误清单Common mistakes原文档点名的四个高频错误也是架构测试重点防范的对象为图方便导入更底层的运行时 crate——例如为了让某个契约好用而直接拉入ironclaw_turns在 profile 解析中混入生产适配器接线——解析只负责把请求解析成ResolvedRunProfile接线属于宿主从其他 crate re-exportLoop*Port——这是 §11.2.4 的 re-export 路径陷阱reborn_loop_port_location_scan会直接失败因为那会让消费者在不指名的情况下依赖错误 crate让 safe-summary 字段按惯例携带原始内容——安全摘要必须是有界、脱敏的。七、已知债务记录在案而非接受instruction_bundle.rs通过include_str!嵌入了两个 prompt 资产prompts/capability_surface_usage_policy.md与prompts/delivery.md这违反了契约 crate 不持有 prompt 内容的族规则PROPOSAL §6.1.4。从源码看实际嵌入的是三个文件还包括prompts/memory_recall_framing.md用于给跨会话的记忆片段加这是回忆而非当前状态的框架提示见 instruction_bundle.rs 中MEMORY_RECALL_FRAMING的注释。模块之所以仍移到这里是因为ironclaw_hooks消费InstructionMaterializationStore若留在 turn 内核会继续保留hooks → turns的例外。指向的解决方案§6.7.2是把行为InstructionBundleBuilder上提到ironclaw_loop_host只把 bundle/store类型留在这里——这是 CHECKLISTloop_hostre-charter 行上的 WS4 事项。八、验证与门禁如何证明契约没有越界原文档给出两层验证命令# 快速本地检查crate 自身单测 集成测试 cargo test -p ironclaw_loop_contracts # 边界/扫描/上限门禁架构测试套件 cargo test -p ironclaw_architecture_tests后者是全部硬性约束的实际执行者主要门禁包括依赖白名单reborn_dependency_boundaries.rsloop_contracts_allowed只允许上述 4 个 contracts 族 crate其他工作区 crate 一律拒绝框架拒绝reborn_contracts_crates_hold_no_framework_dependencies对全部 6 个 contracts crate 扫描禁止清单tokio rt例外除外大小上限reborn_contracts_crates_carry_a_checked_size_ceiling为每个契约 crate 钉死生产行数上限当前ironclaw_loop_contracts为 13_608 行只许通过显式评审上浮下浮到低于银行余量同样失败——防止什么都不导入、什么都自己实现的空壳 crate 逃过依赖检查端口位置扫描reborn_loop_port_location_scan.rs强制每个端口一个定义位置、一条导入路径同层边清单reborn_same_layer_edge_inventory.rs钉死loop_contracts → {host_api, extension_contracts}等仅有的族内边新增一条即失败。九、谁在消费这个契约README 记录该 crate 被13 个 manifest消费可用grep -rl ^ironclaw_loop_contracts --includeCargo.toml crates Cargo.toml | wc -l复现。从源码直接确认的消费方覆盖了循环层全部四个 crateironclaw_agent_loop、ironclaw_loop_host、ironclaw_turn_runner、ironclaw_hooks以及内核ironclaw_turns——它们各自的角色正好对应装饰器链的四个环节可替换的用户态循环实现、基座端口适配器、运行组合层、最外层的策略/审计钩子层。这也再次印证了方向性同一份契约同时被可替换的上层与权威的内核双向引用而契约本身不依赖其中任何一方。结语ironclaw_loop_contracts是 IronClaw 循环层架构的类型化膜它用十一个端口把宿主能力暴露给可替换的循环实现用LoopExit声明把终结语义的裁决权留给内核用 run-profile 词汇把一次运行的所有策略与画像固化下来。它的每条规则白名单、框架拒绝、端口登记、大小上限都不是纸面约定而是由cargo test -p ironclaw_architecture_tests逐条执行的机器约束。理解这个 crate就理解了 IronClaw可替换循环而不触碰特权代码这一核心架构承诺是如何落地的。【免费下载链接】ironclawIronClaw is an Agent OS focused on privacy, security and extensibility项目地址: https://gitcode.com/gh_mirrors/iro/ironclaw创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考