SYSTEMS / FIELDNOTES
Architecture study · 01 / ZeroClaw

ZeroClaw模型如何成为
可运行的 Agent

从 Rust 的模块边界,到一次工具调用的完整旅程。深入源码,理解这套本地优先 Agent 运行时的设计取舍,以及它真正能保证什么。

运行时位于模型、工具与记忆之间三个外部能力通过接口接入核心运行时,安全策略围绕工具执行形成边界。这是概念示意,不代表网络拓扑。ZEROCLAWRUNTIMEMODELTOOLSMEMORYPOLICY BOUNDARY
核心判断 / TL;DR

ZeroClaw 的架构价值,在于把模型推理、行动权限、状态归属与外部接入拆成可管理的边界。Rust 提供实现基础,运行时负责让这些边界在每一轮任务中真正生效。

00

先建立四个判断

ZeroClaw 是一套可自行运行的 Agent 基础设施:接消息、调用模型、执行工具、管理上下文,并支持后台任务。理解它,最有效的方法是追踪一次行动如何发生。[03] [07]

01 / 本地运行,模型可远程

运行时和数据存储可由自己掌管;使用云端模型时,组装后的上下文仍会发往模型服务。本地优先不等于全离线。

02 / 共用执行引擎,适配不同入口

Channel、CLI、WebSocket、ACP 等路径复用工具循环;入口的身份处理、流式呈现与投递仍有各自生命周期。

03 / 行动由运行时约束

模型提出工具调用,审批、路径与命令策略、执行机制决定它是否能落地。自然语言承诺不足以建立权限边界。

04 / 微内核是持续演进方向

公共 traits、编译特性与 Wasm 插件已经存在;runtime 仍承载较多子系统,不能理解成已经完成的纯插件内核。

判断依据:官方架构、请求与通道生命周期,以及 runtime 的过渡性定位。[03] [07] [23] [24]

本文的证据范围

基于官方仓库 v0.8.5,固定到 cb2b20a9fe59004b7ef03e123942d31f29236d15。核对设计文档、公共接口和关键实现;本地只验证过 CLI 版本与帮助,没有进行模型端到端运行、性能压测或安全审计。文中“建议”是作者推导。

01

设计的出发点:控制权在谁手里

官方设计哲学有明确的优先顺序:所有权、安全、精简、模型供应商可替换。这组顺序解释了许多技术选择:为什么要有本地配置和存储,为什么需要模型之外的权限系统,以及为什么把外部能力放在接口之后。[01]

  1. 1You own it
    自己掌管

    程序可以运行在自己的机器上。配置、会话、工作区拥有可定位的存储位置,用户能够备份、迁移与停止运行。

  2. 2Security first
    行动有边界

    一旦 Agent 能执行命令、访问网络或写文件,它就是有实际权限的进程。执行前的策略判断与执行时的隔离要各负其责。

  3. 3Minimal
    控制能力成本

    通过 Cargo features 和模块拆分控制构建范围。精简也包括模型看到的工具集合,以及运行时需要长期维护的依赖面。

  4. 4Provider agnostic
    保留模型选择

    模型接入经由统一接口和配置组合。业务行为仍受模型的工具调用、流式协议和推理能力差异影响。

Rust 带来了什么,又没有带来什么

Rust 的所有权与类型系统适合承载并发任务、连接与共享状态;原生编译和可选特性有利于分发及依赖管理。与此同时,模型是否准确、工具是否授权、网络是否可靠,都需要运行时的显式设计。这是从架构得到的判断,不能由语言选择直接推导为可靠性结论。

Cargo.toml 的默认特性已经包括 agent runtime、部分 channels、ACP bridge、gateway 等。--no-default-features 会改变能力集合;源码中存在的能力,也可能没有编进你手上的发行包。本文不沿用跨版本流传的“几 MB、几毫秒”性能标签。[02]

02

拆开系统:边缘接入,核心编排

可以把系统看成三部分:入口适配、统一回合引擎、可调用能力。配置、安全与可观测性贯穿其中。

FIG. 01 / LOGICAL ARCHITECTURE点选模块,查看职责
Agent Runtime · 一轮任务的编排者

组装上下文,调用模型,解析工具请求,执行权限判断,再把结果送回模型。cron、SOP、subagents 等也由相关运行时子系统承载。

crates/zeroclaw-runtime/

这是职责图,箭头表示主要调用方向,不是完整 Cargo 依赖图。v0.8.5 的 runtime 仍直接依赖若干具体实现 crate。[03] [23]

Rust 如何把规则变成接口约束

一个具体例子是 webhook 接入:VerifiedWebhookIngress 保存已经验证过的请求体,parse_messages(self, …) 消费这个对象,只解析同一份 bytes,再产出 VerifiedWebhookMessages 交给 dispatch。私有字段与消费式 API 共同限制调用者跳过验证、另塞一份消息的空间。这比一句“Rust 更安全”更能解释语言与架构怎样配合;该漏斗目前用于对应的 webhook 适配路径,不代表所有外层入口已经统一。[26]

真正的扩展点,是公共契约

zeroclaw-api 把核心能力抽象为 traits。运行时通过接口调用能力,工厂与注册逻辑把配置解析成具体对象。这样可以替换一个模型适配器或记忆后端,而不必重写整个工具循环。

契约它负责什么仍需由上层解决
ModelProvider对话请求、响应、流式能力等模型协议选择哪个 profile、何时回退、真实服务模型归因
Channel收消息、发消息、可选草稿与交互能力身份绑定、去重、会话路由、最终投递
Tool名称、参数 schema、执行与结构化结果是否允许执行、超时、审批、审计与回传
Memory写入、检索、删除以及范围约束接口什么值得记、何时记、如何处理冲突与遗忘

公共接口源码:ModelProvider、Channel、Tool;记忆范围由后端与 scoped wrapper 共同实施。[04] [05] [06] [12]

“kernel ABI”要分两层理解

原生 Rust traits 是源码层契约;它们不意味着任意 Rust 动态库都拥有稳定二进制 ABI。面向外部 Wasm 插件的跨语言契约,另由版本化 WIT 接口与 Component Model 承担。

03

一次请求,怎样变成行动

假设用户说:“读一下项目说明,整理出待办。”它会触发一轮或多轮模型调用。真正重要的是中间的状态转移:谁能提供上下文、谁能批准动作、工具结果如何再次进入模型。

FIG. 02 / ONE TURN, MULTIPLE MODEL CALLS
INGRESS / 身份先于行动

接收、识别与路由

通道适配器将平台消息转换为内部消息。按入口处理配对、授权、去重、工作区和会话绑定,再交给回合执行链路。

输出:带来源与会话范围的输入,而不是一段失去身份的纯文本。
INPUT→CONTEXT→MODEL→POLICY→TOOL↺ MODEL / FINAL

交互示意依据统一 turn engine、工具生命周期与入口处理。不同入口不保证完全相同的外层行为。[07] [24]

共享工具循环,减少入口之间的语义分叉

回合执行的大致结构是:准备历史与工具 schema → 调用 provider → 消费响应 → 解析工具请求 → 审批与执行 → 收集结果 → 更新历史 → 判断是否继续。入口可以配置不同的流式输出、超时和审批行为,但共用执行循环有助于让修复覆盖多条路径。[07]

// 概念伪代码,帮助阅读;不是可复制运行的 API
while turn.within_budget() {
    response = model.call(history, visible_tools)
    if response.is_final() { return response.text }

    for call in response.tool_calls {
        decision = policy.check(call, caller_scope)
        result = execute_if_allowed(call, decision)
        history.append_paired(call, result)
    }
}

重试的关键,是“已经对外承诺了什么”

Provider 层负责协议与连接;运行时决定整次调用能否重放。未向外发布不可变输出时,部分故障还有重试空间;一旦文本等不可变事件已经发布,回合重放可能制造重复内容,因此该中断分支停止自动整轮重放。草稿可更新与最终事件不可撤回,也不能简单等同。[08] [09] [25]

尚无可见输出部分错误可以有界恢复
只有可变草稿可覆盖,区别于追加事件
已经提交输出中断后不再整轮重放
重试是否安全,取决于已提交的输出与已发生的副作用。

同理,工具调用发生过网络超时,不能直接推断业务操作失败。若某个工具负责提交订单或发通知,幂等键、业务回执与回读仍需由工具及上层流程设计。统一 Agent 循环并不自动提供跨外部系统的 exactly-once 语义。这是架构层面的推论。

模型可替换,但能力差异仍需显式处理

路由选择、重试策略、profile 构造与“请求模型 / 实际服务模型”归因分属不同层。工具调用协议、图像支持、上下文上限与流式结束标记都可能不同;统一接口减少适配成本,并不抹平这些差异。排障时应追踪实际 provider 和模型,而不能只看用户最初选择的别名。[08]

典型组合是 Router → Reliable → ModelProvider:Router 选择目标路由,Reliable 管理有界尝试与候选,具体 provider 处理协议。流式路径会选择一个合适且未冷却的候选,启动后直接返回其 stream;不能把普通 fallback 配置理解为“流到一半自动无缝换模型”。[29]

04

“记住了”,究竟存在哪里

上下文里出现过一条信息,并不代表它进入了长期记忆。理解存储归属,比讨论“有没有向量库”更重要。

当前 Prompt这次模型请求看见什么
临时组装
会话历史这段对话如何继续
保持消息结构
长期记忆哪些信息跨会话保留
明确写入
文件与日志产物、诊断与执行证据
各有生命周期

ZeroClaw 在这些表面之间传递数据,但它们有不同的 owner、保留策略和权限范围。读过文件只是得到工具结果;回忆被注入 prompt 只是提供上下文;二者都不能直接被算作一次长期记忆写入。历史压缩也可能丢失细节,必须维持 tool call 与 tool result 配对。[10]

记忆注入由执行引擎统一决定

记忆后端负责召回;引擎按 TurnOrigin 决定哪些内容可以进 prompt。当前实现中,Cron / Daemon 排除 Conversation 类记忆,SubTurn 不重复注入,缺少安全 session 范围的用户入口也排除 Conversation 类。统一 renderer 再处理相关性、预算和噪声过滤。这让“后台任务能否带入聊天内容”成为可检查的策略,而不是散落在各通道里的字符串拼接。[30]

SQLite 的意义:先把单机闭环做实

内建 SQLite 后端把结构化记录、FTS5 关键词检索与向量检索放在同一实现中。向量路径会扫描候选 embedding 并计算相似度,在混合模式且两路有结果时与关键词结果合并;实现也支持仅关键词或仅向量等路径。它没有因此获得大规模近似最近邻索引。这个选择易于本地运行与备份,规模增长时的查询成本应通过真实数据评估。[11]

AgentScopedMemory 将写入绑定到当前 agent,并在跨 agent recall 时与允许集合求交。范围收紧依赖真实字段和调用链,不只是 prompt 中的一句“不要看别人的资料”。但 agent 范围也不等同于完整的家庭成员或租户权限体系,后者还需要入口身份映射和应用策略。[12]

更深的议题:谁是最终权威 PROPOSED

ADR-010 提出进一步区分会话连续性、经过筛选的长期知识和可重建的外部检索增强:原始会话由 session store 管,精选记忆由配置的权威后端管,增强连接器可以失效而不影响权威写入。在本次固定版本中,这份 ADR 仍标为 proposed。不能把其中全部验收条件写成已经完成的产品能力。[13]

对上下文系统的启发

先决定每类数据在哪里生效、由谁纠正,再接入搜索或向量增强。检索命中、模型实际引用、任务结果正确是三个不同状态,应该分别验证。

05

安全边界,一层解决一类问题

Agent 的风险来自它能做什么,以及外部内容如何影响它。ZeroClaw 把策略检查、执行隔离和证据机制分开;这些机制相互补充,效果仍取决于入口、配置和实际后端。

身份与范围:这是谁的请求

通道配对、来源授权、agent 与工作区绑定,决定输入进入哪个执行环境。接入一个新通道,也意味着新增一个信任边界。[24]

执行策略:这个动作是否允许

工具在执行前接受审批与风险判断;路径和命令策略限制可用能力。模型生成工具参数,不会因此获得决定自身权限的权力。[07]

系统沙箱:运行以后能触达哪里

文件、进程、网络和环境隔离取决于平台及后端。配置中选了 sandbox,不代表所有平台上都取得同等约束。[14]

结果证据:执行结果来自哪里

成功工具结果可附带 HMAC 回执;运行日志与 observer 提供其他诊断信息。每种证据的覆盖范围和持久性都应单独判断。[15] [16]

读实现,才能知道沙箱有没有生效

在 detect.rs 中,后端不可用时存在回退路径:native runtime 可能落到 none,Docker runtime 则可保留自己的容器边界。native 的 auto 也不会因为主机安装了 Docker 就一概套上 Docker 沙箱。操作时应核验实际选择的 backend,而不能照搬文档中的通用优先级表。[14]

工具回执能证明的事情,要说准确

实现使用随机生成的临时密钥,对工具名、序列化参数、结果与时间戳计算 HMAC-SHA256。模型拿不到密钥,因此难以伪造一个新的有效回执。但校验需要相应运行时的密钥;它不是第三方可独立验证的公证,也不是永久证据链。[15]

问题当前边界
所有调用都有回执?成功执行的结果可签发;阻断、拒绝、超时和失败路径主要依赖其他事件证据。
跨重启还能验证?密钥保存在活跃作用域的内存中;作用域结束后,不具备持久的跨作用域验证能力。
是串起来的审计链?回执覆盖单次调用,不与前序会话哈希串联。永久回执数据库仍列为计划项。
能证明业务已完成?只能证明对应结果由持有密钥的运行时签发。结果是否准确、外部服务是否真正完成,仍要看工具协议和业务回读。

回执的精确覆盖范围以专门文档与实现为准;概览中的“cryptographically-linked audit log”描述不宜直接照搬。[15] [16]

剩余问题仍在:恶意内容可能影响模型的行动选择

Rust 内存安全、策略检查、沙箱都不能单独消除 prompt injection。需要把外部检索内容作为不可信数据处理,并限制工具真实能力。本文没有验证所有攻击路径,不作“安全无漏洞”的结论。

06

两条扩展路线:原生 Rust 与 Wasm

原生能力

实现 Rust trait,通过 registry、factory 与 Cargo feature 接入。适合与运行时深度协作、对性能或系统能力要求较高的模块。

  • 通常随主程序重新构建。
  • 能力进入宿主进程的信任范围。
  • 需要控制依赖方向和编译成本。

外部组件

WIT 定义 tool、channel、memory 等组件契约;Wasmtime 执行组件,bridge 再把它们呈现为原生 trait 实现。

  • 接口显式、可版本化、可跨语言。
  • host imports 按权限提供。
  • 增加 ABI、资源限额与分发管理成本。
Wasm 组件tool / channel / memory
WIT 契约versioned worlds
Wasmtime Hostpermissions · limits
Rust Traitruntime capability

ADR-009 已接受 WIT + 直接 Wasmtime 的执行架构,取代早期 Extism 桥接方案。引用旧教程时尤其要注意这一代际变化。[17]

host 通过权限决定是否提供 HTTP 与配置访问,限制内存、表、实例、fuel 和单次调用时间。这里的 fuel 是执行预算,不是模型 token。签名可以验证插件来源与完整性,但无法替代对插件行为和授权范围的判断。[17] [18]

微内核的收益与代价

将可变能力移到边缘,可以缩小核心变更面;同时必须维护接口版本、组件生命周期、权限和错误传播。v0.8.5 已具备这些基础,runtime 的进一步拆分仍在进行。

07

长期运行,难在状态与恢复

一个 Agent 能启动,并不表示它能在进程退出、模型超时或网络中断后正确继续。不同后台任务有不同的恢复合同。

机制适合表达什么需要看清的边界
Cron到时触发的一次工作计划和运行记录持久化,与正在执行的任务能否恢复是两回事。
SOP有步骤、状态和审批的工作流程v0.8.5 的 persist_runs 默认已为 true;仍需检查具体恢复策略和步骤副作用。
spawn_subagent由父任务等待子任务结果该工具直接 await 子运行;名称中的 spawn 不代表必然在后台独立运行。
后台 delegate脱离当前回合的委派执行任务记录可留存,旧进程任务可标 lost;这不等于重启后自动恢复执行。

后台文档用于理解职责;SOP 默认值以 schema 实现为准,避免沿用该文档仍残留的旧默认描述。同步子任务以实际 await 调用为准。[19] [20] [28] [32]

恢复能力,需要分层看

SOP 默认选择持久后端,但初始化失败时会告警并退回内存,以保住 daemon 启动。后台 delegate 的旧进程任务可以被 reaper 标记为 lost,这提供中断可见性,却不等于重新创建执行栈。记录留存 → 中断可发现 → 流程状态可恢复 → 副作用可安全续跑,是逐层增强的四种能力。[27] [28]

临时 subagent 也有实际的继承约束:策略 override 不能超出父级权限,记忆允许集合只能收紧,动作 tracker 复用父级。它防止“委派一次就重置权限和预算”的放大;仍不能把它当作完整的多人身份治理。[31]

配置也有生命周期

配置通过同一 typed schema 加载,环境覆盖和磁盘持久化有区别;gateway 中保存成功,也不必然意味着 daemon 所有子系统已采用新值。reload 可能在同一 PID 内重建监听器与调度等子系统,独立 gateway 路径还可能需要重启。排查时应分别确认“已保存”和“已应用”。[21]

可观测性首先要能回答:谁做的

日志架构把 attribution(agent、channel、model、session 等归属)与事件属性分开,通过 span 传播上下文。异步 spawn 需要保留这种归因,否则后台日志很容易变成无主事件。直播事件、JSONL 持久化和可选 observer 投影也有不同完整性保证;看到仪表盘事件不等于已经可靠落盘。[22]

如果实际试用,优先验证哪几条链路?

先用一项低风险真实任务验证:消息身份正确 → 工具范围正确 → 结果可回读 → 最终回复送达。随后再验证一次拒绝执行、一次模型或网络失败、一次重启恢复。记录任务结果与恢复行为,比单独数测试数量更能帮助判断是否适合长期运行。

08

放进个人上下文系统,应该站在哪一层

在“飞书 + 豆包 + OpenViking”这样的组合中,可以把 ZeroClaw 候选定位为执行与编排层:接入消息、调用模型、使用受约束的工具,再把结果送回入口。以下是从职责边界推导的组合建议,不是已完成的集成或部署。

DESIGN INFERENCE / 组合建议

让每个系统承担清晰的职责

飞书 · 交互ZeroClaw · 执行模型 / 上下文服务

豆包模型 API 提供推理;OpenViking 可作为上下文检索服务候选。通过经过验证的适配层连接,明确来源、身份范围、写入权威和结果回执。

组件建议职责接入前应验证
飞书成员入口、确认交互、通知呈现账号与 agent 的映射、私聊/群聊范围、消息真实送达。
豆包 API语言理解、整理、推理及工具调用候选实际 endpoint 与认证、工具和流式兼容性、费用与限额。豆包 App 的登录状态不等于 API 凭据。
OpenViking带来源和权限范围的上下文服务候选检索引用、召回范围、删除与更新传播、失败降级;不能假定已有原生适配。
ZeroClaw统一执行回合、工具约束与后台流程发行包 feature、真实 sandbox 后端、恢复语义及端到端结果。

从一个闭环开始,才能判断是否值得接入

一个合适的试验是:“从一份获准资料生成摘要,在指定会话中返回带来源的结果。”先验证资料访问、检索、回答与投递,再增加后台提醒或共享写入。家庭多人场景还需要成员授权、事项账本、冲突处理和撤回传播;这些应用语义不会因为装上 Agent runtime 自动出现。

值得借鉴的是一套边界设计:
入口负责身份,模型负责推理,
运行时负责行动,数据系统负责权威。
09

继续读源码:一条短路径

  1. 先看 Cargo.toml 与 zeroclaw-api。建立构建范围和公共接口地图。
  2. 再追 agent/turn。从一次 provider call 跟到审批、工具结果与下一轮。
  3. 对照 memory 与 security。确认数据范围和行动权限在哪里落实。
  4. 最后看 plugins、cron / SOP 与 config reload。理解扩展及持续运行的成本。

以下 32 处引用全部固定到本文 commit。线上 master 文档会继续变化;重跑或部署时,应以目标版本的源码、配置和实际行为为准。文章是技术分析,未与项目官方建立关联。