配置生命周期
配置既是操作界面,也是运行时契约。将其视为具有明确所有者的状态,而不是复制到任何需要它们的子系统中的松散设置。
规范来源是 zeroclaw_config::schema::Config,从 config.toml 加载。面向用户的配置界面、生成的配置参考、网关配置编辑器、环境变量覆盖、zeroclaw config set、zeroclaw config patch、快速开始以及 RPC 配置方法都通过同一个类型化 schema 路由。
有关将类型化 schema 转换为配置参考的构建顺序、追踪输出规则和漂移检查,请参阅生成文档管道。
谁拥有什么
| Surface | 所有者 | 持久化边界 | 运行时应用边界 |
|---|---|---|---|
| 配置模式 | crates/zeroclaw-config/src/schema.rs 以及 Configurable 派生 | 代码,而不是生成的文档 | 新的二进制构建 |
| 已生成的参考 | cargo mdbook refs / markdown-schema | docs/book/src/reference/config.md 在构建时 | 仅文档说明 |
| 引导位置 | ZEROCLAW_CONFIG_DIR, ZEROCLAW_DATA_DIR, deprecated ZEROCLAW_WORKSPACE | 仅限环境 | 在 Config 存在之前 |
| Schema-mirror 覆盖项 | ZEROCLAW_<lowercase_path> 使用 __ 表示点号 | 仅内存中 | 每个 Config::load_or_init() |
| CLI 配置写入 | zeroclaw config set, config patch, 别名,模型辅助函数 | save_dirty() 到 config.toml | 下一次加载/重新加载,除非当前命令使用新的内存值 |
| RPC 和 TUI 配置写入 | config/* RPC 方法由 zerocode 使用 | 已准入的配置提交:save_dirty(),然后发布 | 已发布的配对更新会立即生效;由守护进程管理的子系统需要重新加载 |
| 快速开始应用 | 共享 web、CLI 和 zerocode 应用路径 | 阶段性应用已完成,并作为配置提交落地(受监管的接口) | Web 和 RPC 可以触发 daemon 重载;独立 CLI 会在下次 load/reload 时应用 |
| Gateway 配置写入 | 通过 persist_and_swap() 处理配置 API 请求 | 已准入的配置提交:save_dirty(),然后发布 | 发布的配对更新会立即生效;守护进程子系统在重新加载后应用。 |
| 守护进程重新加载 | /admin/reload、RPC config/reload 或进程内重载通道 | 重新读取 config.toml | 在同一 PID 中重新创建守护进程子系统 |
不要手动编辑生成的 config reference。如果那里有字段、枚举、alias section、secret marker 或描述错误,请修正 schema 或生成器并重新生成 reference。
加载顺序
Config 加载有几个不同的阶段:
- 从 bootstrap 环境变量解析安装根目录。这发生在任何
Config存在之前,因此 bootstrap 名称保持其大写形式,并且不使用 schema-mirror 语法。 - 读取
config.toml,在内存中运行 schema 迁移,解密已配置的密钥,并将任何格式错误的安全关键部分记录为降级安全。 - 将 schema-mirror 覆盖应用到内存配置中。在环境变量中,
__映射为.,因此ZEROCLAW_providers__models__openai__api_key指向providers.models.openai.api_key。 - 在不将操作员锁定在网关编辑器外的情况下进行验证并发出警告。
在全新安装时,默认值会在应用环境覆盖之前保存。这样可以避免将通过环境注入的密钥和本地 CI 值写入新文件。
环境覆盖项不会被保存
Schema-mirror env vars 是运行时注入。它们在加载时进入内存中的 Config,并在 env_overridden_paths 中被跟踪,因此 CLI、dashboard 和 quickstart 可以显示覆盖标记。
保存时,必须将这些路径在加密前还原为其覆盖前的磁盘值或默认值。对于机密信息,这一点尤为重要:如果操作员在磁盘上有一个已加密的 API 密钥,并且临时为同一路径通过环境变量覆盖启动,那么一次无关的配置保存绝不能用环境变量值或掩码显示字符串替换真实凭据。
在牢记此不变式的前提下审查配置更改:
ZEROCLAW_*schema-mirror 值在加载后会影响运行中的进程。- 它们不会变成持久配置。
- 保存路径必须保留加密密钥和外部密钥引用,除非同一路径被有意编辑。
凭据输入保持类型化
凭据类运行时值仍然属于配置值。API 密钥、OAuth 令牌、端点 URL 以及其他提供商/渠道凭据,在运行时构造函数获取它们之前,应通过类型化配置模式、配置机密处理机制或与模式镜像同步的 ZEROCLAW_* 覆盖项传递。
请勿在 provider、channel、tool、transcription、TTS、memory 或 gateway 构造函数内部添加临时的 std::env::var("PROVIDER_API_KEY") 读取。这会在 Config 之外创建第二个凭据来源,绕过 env-override 可见性,并可能导致 CLI、gateway、RPC/TUI、quickstart 和 reload 行为不一致。
如果 ZeroClaw 有意为某个集成系列提供原生环境桥接,请在集成边界处记录该桥接,并在构造前将其映射到相同的类型化配置值中。否则,诸如 ANTHROPIC_API_KEY、OPENROUTER_API_KEY 或 QDRANT_URL 等生态系统默认的 shell 名称应由操作员桥接到对应的 ZEROCLAW_* 架构镜像变量;参见 Environment variables。
脏路径和增量写入
大多数编辑界面使用 Config::mark_dirty() 加上 save_dirty(),而不是完整重写。save_dirty() 只写入已更改的点路径,尽可能保留未标记为脏的条目和注释,写入当前的 schema_version,并通过原子性的临时文件替换进行写入。
该路径也负责 map-key 区段。创建别名,例如模型提供方、MCP server、skill bundle 或 knowledge bundle,必须将正确的区段标记为已修改,这样别名才能在保存和重新加载后保留下来。只更新内存中的 dashboard 状态的配置编辑是不完整的。
在审查 config 写入时,请检查:
- 在持久化之前,编辑后的路径会被标记为脏状态;
- map-key 会创建、重命名和删除,使父部分或自然键变为脏状态;
- secret 和 env-overridden 路径保持它们的保存掩码行为;
schema_version在增量写入后仍保持为最新;- 更改后的值会在
save_dirty()后再重新加载时保留。
完整保存需要加载溯源信息
Config::save() 会拒绝覆盖已存在的 config.toml,除非内存中的值是从该确切文件填充而来(loaded_from,由 load_or_init 在已存在文件和全新初始化两个分支中设置,并由在修改前重新读取配置文件的加载器设置)。因此,默认构造、程序化构建或重新指向的 Config 无法用近乎空白的快照替换运维人员已填充的配置。
创建缺失的文件(首次运行)仍然会成功:从未读取过文件的值只会被创建一次,之后必须重新加载或强制保存。force_save() 是经过验证的、有意覆盖文件时的显式途径。在审查构建新 Config 并期望将其写入已存在文件的代码路径时,应先加载,或说明使用 force_save() 调用的理由。
force_save() 仅跳过加载来源(load-provenance)检查,目标路径检查仍然生效。若要替换已存在的文件,请将 config_path 设置为目标路径,且该路径需包含非空的父目录。若仅提供裸文件名(如 config.toml),而其运行时解析出的目标路径已存在,则会被拒绝,从而保留对推断目标路径上已有文件进行覆盖的现有保护机制。
已保存 vs 已应用
成功保存表示文件已更改。但这并不总意味着每个运行时组件都已采用了该更改。
守护进程拥有长期运行的子系统图:gateway、channel listeners、scheduler、MQTT listener、session wiring、memory backend、provider factories 和 cost wiring。POST /admin/reload 会向守护进程循环发送信号,守护进程重新读取 config.toml,并在同一进程中重新实例化这些子系统。PID 保持不变,但 listeners 会短暂重新绑定。
Gateway 配置写入会调用 persist_and_swap():在已准入且串行化的配置提交过程中写入磁盘,然后将已保存的配置发布为新的已发布配对,并设置 pending_reload。这使配置编辑器能够立即反映此次写入,同时重新加载横幅会告知操作者,通道、提供方、调度器或其他由守护进程管理的组件可能仍在运行上一个子系统实例。
独立的 zeroclaw gateway start 没有守护进程监督器。它的重载端点会返回需要重启的响应,因为没有外层守护进程循环可以发出信号。
Published 配对与作者连载
监督进程保留一个规范的已发布配对:Config 及其修订版本,外加一个不透明的权威纪元(authority epoch)和一个带检查的序列号。读取方通过只读的实时句柄(zeroclaw_config::live::LiveConfigHandle)获取配置,并将配置与其修订版本作为一个整体进行观察;该句柄既不暴露修改能力,也不维护第二份配置副本。
每个参与的 HTTP、RPC、TUI、Quickstart、配对和频道身份写入方,都必须通过守护进程代际的 LiveConfigAuthority 准入(begin_config_commit)。提交操作会一次性获取进程级配置写入互斥锁,然后在发布完成前持有代际配置写入租约。等待互斥锁的过程不持有配置写入租约。独立的代理别名预留(例如 RPC Quickstart 在准入前进行的预留)仍可计入代际排空。不可逆阶段(先持久化,再在任何磁盘 I/O 之前分配修订号的情况下发布)以保留租约的方式运行,因此被取消的请求无法在原子文件替换与发布之间放弃已派发的提交。一旦代际进入关闭状态,提交即失败关闭;完整重载会启动一个带有新纪元的全新权威对象(序列号仅在同一纪元内可比较)。
派生的已接受授权策略拥有独立于配置对的发布与修订计数器。HTTP 配置写入先发布该策略,再发布配置对;RPC 配置写入则在之后发布该策略。现有的这些顺序无法为两次发布提供统一的原子可见性边界,并且该策略计数器也不是 ConfigRevision 序列。
Other boundaries worth naming:
- 已提交的发布不会因为后续的副作用失败而回滚。即使安装 personality 文件随后失败,Quickstart 仍会发布已提交的配置。CLI 会报告已保存的别名,并指引用户通过工作区文件进行恢复,而不是重新运行 Quickstart。Web 和 RPC 仍然返回与拒绝相同的错误结果,没有单独的已提交结果,尽管它们会在这种部分成功之后发出重新加载信号。
- config migrate 端点会在 before 替换文件之前,先准备一个完整加载的候选配置(解密机密信息、应用环境变量覆盖、执行严格校验)。因此,无法完成加载或校验的候选配置会被拒绝,原始文件保持不变。
model_routing_config和proxy_config工具仍会保存独立加载的磁盘快照,既未经过配置提交准入,也未进行发布。model_routing_config upsert_agent使用脏路径持久化,因此无关的值和注释得以保留,但其其他操作以及proxy_config仍会写入完整的类型化快照。这些非参与式写入者可能丢失同一路径上的并发编辑,或使已发布的读取者保持过期状态。将它们迁移到参与式写入者契约中仍是单独的工作。- 发布本身不会将配置应用到正在运行的子系统。应用账本记录的是现有 RPC 模型提供方刷新所返回的确认信息;其他由守护进程管理的组件仍需要
/admin/reload。新的安全覆盖层和通道交接行为仍受 ADR-012 约束。
应用状态
GET /api/config/reload-status 保留 pending_reload,并从实时配置权威源添加 application 视图。RPC config/status 保留入门就绪状态,并添加相同的视图。已发布的修订包含一个不透明的 epoch 和 sequence。每条记录标识实际处理的修订版本、以字段/键组件表示的语义路径,以及目标实例。带点号的映射键仍作为一个组件保留。
daemon 目标报告 queued_for_reload,并附带 daemon_reload_required。只有已存在且被选中的 RPC model-provider 刷新,才能为其所捕获的会话实例报告 applied_live。在所属消费者确认之前,其记录状态为 pending;被拒绝的实例更新则为 rejected。Schema 应用元数据仅表明可能存在的既有刷新能力,而非运行中实例已采纳某个值的证据。未知路径需要重新加载。若无法提取变更路径,则空路径表示需要整体配置重新加载,并附带 change_scope_unavailable。
HTTP 视图仅包含 daemon 记录。RPC 会话记录还需要满足以下条件:当前的会话读取授权、agent 选择器、已认证的主体归属、连接归属以及当前活动的 incarnation。管理员和共享操作员保留现有的主体归属绕过机制。这些记录中不包含任何配置值、凭据、转录内容或 provider 错误文本。移除或替换会话时,会在受保护的状态读取边界处使其记录失效;旧的回调无法重新创建该记录,也无法确认其后继会话。
账本只保留每个路径和目标最近一次观察到的变更或尝试记录,不是持久化的事件历史,也不是所有子系统的清单。保留数量受 record_limit 限制;truncated 表示发生了记录淘汰。缺失的记录不能作为应用层证据。无关的编辑不会把先前的确认记录改标为最新版本。完整重新加载后出现新的权限源时,会开启新的纪元,且不包含任何已记录的实时变更确认。清除 pending_reload 或返回健康响应,都不会改变应用结果。本次变更不增加任何文件监视器的投递或监视器健康状态的声明。
重新加载访问
允许从回环地址进行本地重载。远程重载需要同时满足以下条件:
gateway.allow_remote_admin = true- 已启用配对并拥有有效的配对持有者令牌
在配对已禁用时选择启用远程管理会被拒绝,而不会被视为匿名远程重新加载访问。
安全关键的格式错误配置段仅在操作员明确选择启用降级服务时才允许降级。否则,进程会拒绝提供服务,因为重置为默认的安全态势可能比文件原本意图更弱。
回滚并修复
Config 写入采用原子临时文件替换,并设置仅所有者可访问的权限。替换已有文件时,写入器会在替换过程中于同一目录下创建 config.toml.bak,写入成功后将其删除。Gateway 写入还会对写入前的文件做快照,并在发布新文件对之前的持久化失败时尽力将其恢复。
对于已保存并应用的有效但不期望的配置更改,没有通用的事务性回滚。请从备份中恢复之前的 config.toml,然后通过 CLI 或仪表板将该字段改回,接着根据上面的运行时边界重新加载或重启。
Config-visible 并不总是运行时支持的
在每个运行时路径使用它之前,一个字段可以先对 schema 可见。只有当文档和审查说明明确说明这一点时,这才是可以接受的。
例如,knowledge_bundles 在 schema 中可见,并出现在配置节 API 中。添加或更改此类表面的 PR 必须明确说明它仅存储配置、连接运行时行为,还是两者都完成。
在审查一个涉及 schema 可见但尚未被运行时消费的字段的 PR 时,要求 PR 描述说明运行时接线是延后、超出范围,还是由同一项更改完成。
审阅者清单
对于 config-schema、env-var、default 或 reload 的更改,请询问:
- 新值的权威来源是什么?
- 这是在创建重复状态,还是在使用时从
Config解析? - 生成的参考文档是否来自代码,而不是人工维护的说明文字?
- 环境覆盖仅在加载时生效并在保存时被屏蔽吗?
- CLI、gateway、RPC/TUI 和 quickstart 各界面在带点路径上是否一致?
- 凭据是否通过类型化配置或有文档记录的 schema-mirror 桥接来解析,而非临时的 provider 原生环境变量读取?
- 保存能否在进程重新加载后仍然保留,而不仅仅是立即的内存中渲染?
- PR 里有没有说明用户是否需要重新加载、重启、迁移或手动回滚?
- 如果该字段仅在配置中可见,PR 是否避免声称运行时支持?
源指针
- 配置模式和持久化:
crates/zeroclaw-config/src/schema.rs - Published pair storage and read-only handle:
crates/zeroclaw-config/src/live.rs - Live-config 权威、提交与生命周期:
crates/zeroclaw-runtime/src/live_config_authority.rs - 环境覆盖语法:
crates/zeroclaw-config/src/env_overrides.rs - Config CLI 命令:
src/main.rs - RPC 和 TUI 配置方法:
crates/zeroclaw-runtime/src/rpc/dispatch.rs - 共享快速入门应用路径:
crates/zeroclaw-runtime/src/quickstart/mod.rs - Web 快速入门重载信号:
crates/zeroclaw-gateway/src/api_quickstart.rs - Gateway 配置 API 和重新加载横幅:
crates/zeroclaw-gateway/src/api_config.rs - 重新加载端点和访问闸门:
crates/zeroclaw-gateway/src/lib.rs - Gateway bearer auth helper:
crates/zeroclaw-gateway/src/api.rs - 生成的引用流水线:
xtask/src/cmd/mdbook/refs.rs