后台工作生命周期
ZeroClaw 有几种在启动它的入站请求结束后继续工作的方式。Cron 作业、SOP 运行、委托任务和运行时生成的子代理共享部分执行机制,但它们不共享同一个生命周期或同一个持久化存储。目标模式定义了一个相关的目标契约,但尚未实现端到端的连接。
当变更添加计划任务或自主运行的工作、引入等待或审批状态、改变取消或重启行为,或将子工作连接到所属任务时,请使用此页面。首要的设计问题不是“它如何在后台运行”,而是“哪个子系统负责管理其生命周期?”
所有权映射
| 工作类型 | 当前所有者或状态界面 | 持久化记录 |
|---|---|---|
| 定时任务 | Cron 调度器和存储 | data/cron/jobs.db |
| SOP 运行 | SopEngine 和 SopRunStore | 默认使用进程内存;当持久化 SQLite 初始化成功时使用 data/sop/runs.db |
| 后台委派 | 持久化任务控制平面 | 必需的任务行位于 data/control_plane.db 中;输出内容位于 <workspace>/delegate_results/<task-id>.json 中 |
| 运行时生成的子代理 | 可用时通过控制平面监管生成站点 | 启动的守护进程下 data/control_plane.db 中的一条尽力而为任务记录 |
持久元数据并不等同于持久化执行。结果文件或任务行可以保留已知信息,并让恢复流程将工作标记为丢失、超时或终态,而无需保留负责执行该工作的进程本地 future。
Cron 作业
Cron 将声明式成员关系与 SQLite 执行存储相结合。运行时创建的作业和经过协调的配置作业都带有其所属的 agent_alias;执行时会解析该 agent 的安全策略,而不是在隐式的守护进程身份下运行。
调度器会轮询已到期、已启用且未认领的行。认领一行可防止其在处理期间被重复选取。完成后会记录受限输出,然后重新调度周期性任务,删除成功的自动删除一次性任务,或禁用其他一次性任务。如果进程在释放认领之前退出,调度器下次启动时会清除过期锁。
启动行为是显式的。启用追赶功能时,会考虑执行逾期作业。否则,逾期的一次性作业会被禁用,并产生跳过结果;而周期性作业会推进到下一个未来触发时间,不记录运行结果。调度器会在轮询迭代之间检查其取消令牌,因此关闭过程会等待当前到期作业批次完成后,循环才会退出。取消调度器并不保证已经分派的外部副作用可以回滚。
SOP 运行
SOP 定义位于配置的 sops 目录下。SopEngine 负责运行推进、审批等待、检查点、终止转换以及进程内状态呈现。当 SopRunStore 接纳并认领某个运行时,它是并发的唯一可信来源。
运行持久化为可选功能。在默认的 sop.persist_runs = false 配置下,引擎使用内存存储。启用持久化后,默认的 SQLite 后端会将 runs.db 写入 <data_dir>/sop 目录,除非通过 run_state_dir 覆盖该路径。存储初始化成功后,活跃快照、终态记录、事件、修订版本及并发声明均可支持重启恢复。若存储初始化失败,守护进程会记录警告并回退至内存存储。
Memory 后端中的 SOP 审计记录是一个独立的可观测性层面。它们不会取代 run store,也不得用作判断某次运行是活动、已暂停、已批准还是终态的权威依据。
仅当运行存储持久化时,审批和检查点状态才是持久化的控制状态。超时策略默认保持故障关闭:超时的审批会升级并继续等待,除非配置明确选择取消或旧版自动审批行为。
委派和子代理
子代理会继承其父代理的有效安全边界。策略和内存覆盖可以收窄父级范围,但无法将其扩大;同时,子代理的操作计数使用父代理的跟踪器,因此生成子代理无法绕过父代理的操作预算。
spawn_subagent路径是同步的:父运行会等待子运行完成,并且此路径没有本地超时或后台取消句柄。
delegate 工具可以同步运行,也可以启动后台任务并返回一个 UUID。后台任务只有在能够打开持久化控制平面、解析其所属的 agent 别名并注册其任务行之后才会启动。该任务行是运行、终止、错误和恢复状态的唯一生命周期权威来源。已完成的输出内容会以原子方式写入传给该工具的工作区下,任务行仅存储其 artifact 引用,而不重复保存内容。
当前版本的读取器仍兼容旧版带状态的产物以及旧版内联任务输出。反向兼容无法保证:以当前仅输出格式写入的产物,无法被需要内嵌生命周期字段的旧版二进制文件或脚本读取。回滚时,请同时回滚数据目录和工作区产物,或在降级前完成所有未完成的后台工作。
实时取消注册表将任务 ID 映射到进程内的令牌。当令牌仍然可用时,取消操作会向正在运行的任务发送信号,并在任务存储中使用比较并交换(CAS)的终态转换。因此,一旦某个终态结果胜出,完成、失败、取消和恢复操作便不会相互覆盖。
在后台 delegate 发布终端输出产物之前,它会序列化精确字节,计算其 SHA-256 摘要,并持久化一个终态结算意图(terminal-settlement intent),其中包含任务 ID、当前所有者 PID 和启动标识(boot identity)、期望的终态、产物路径/引用、摘要以及终态错误。该意图是一个尚未应用的状态转换;TaskRecord.status 仍然是唯一的持久化生命周期权威来源。
The delegate then atomically writes and syncs those exact bytes. 委托随后原子地写入并同步这些完全相同的字节。A single SQLite transaction compares the still-nonterminal, owner-matched task row to the intent, records the desired terminal status and payload, and deletes the unchanged intent. 单个 SQLite 事务将仍处于非终态、所有者匹配的任务行与意图进行比对,记录目标终态状态和负载,并删除未变更的意图。Store failures before intent persistence retry without publishing the artifact; failures after publication retry promotion in-process. 在意图持久化之前发生的存储故障会重试,且不发布产物;发布之后发生的故障则在进程内重试提升。A competing cancellation or other terminal winner is preserved and removes the stale intent. 竞争的取消操作或其他终态胜出者会被保留,并删除过期的意图。
独立的监督器会等待该后台工作器精确对应的 Tokio 任务句柄。如果该工作器 panic 或被中止,而父进程仍然存活,监督器只恢复其任务和已记录的所有者。它会使用与进程恢复相同的产物检查来校验已持久化的结算意图;产物缺失或损坏时,结果为 Failed;如果在结算意图持久化之前退出,则产生绑定所有者的 Failed 结果。存储错误会以有上限的退避方式重试。运行中的工作器永远不会被回收,已存在的终态胜出者或已转移的所有者也不会被覆盖。此机制依据工作器退出的直接证据,而非心跳超时或新的租约。
启动时以及周期性恢复流程会在普通的 Lost 对账之前处理结算意图。恢复操作仅在证明记录的所有者已不存在,或其 PID 已被复用之后才会执行;它会根据记录的摘要校验产物字节;对于缺失或损坏的产物,它会保守地将其解析为 Failed 并附带持久化/恢复错误,而不是生成 Completed。随机进程 nonce 无法通过操作系统进程 API 观察到,因此 PID 加操作系统启动时间对于同一秒内的 PID 复用仍然是保守的做法:恢复时会将这种情况视为同一个所有者。
当前的 delegate 行和 subagent 行会填充 agent、status、所属进程 PID 和进程标识、depth 以及时间戳。delegate 行还会记录创建它的 agent 别名、位于其上方的调用方别名有序链,以及作为 principal 的工具循环会话键。result、wait、list 和取消操作接受创建该行的别名,或出现在该调用链中任意位置的别名。会话键仅用于取证,所有者检查从不使用它。subagent 行仍不填写 route、chain 和 principal。两者都不填写父任务。
后台 delegate 行在运行期间携带心跳:每次进度写入都会刷新心跳,且至少每分钟一次,因此 reaper 的心跳超时对它们生效。subagent 行仍不填写 heartbeat。
启动恢复只有在所记录的所有者进程被确认已不存在,或其 PID 已被复用时,才会将运行中的行标记为 lost;所有者不确定时则失败关闭。Windows 恢复会区分 PID 缺失与拒绝访问或其他不明确的探测结果,并且在无法证明进程已不存在时保留该行。
timed_out 仅适用于会发出心跳的生产者:后台 delegate 会发出心跳,subagent 则不会。后台 delegate 的心跳来自其进度刷新器,该刷新器在自己的任务上至少每分钟重写一次行。因此它表明所有者进程仍存活并正在刷新,而不表明 delegate 正在推进。卡在某一次工具调用内的 delegate 仍会保持新鲜的心跳,此时由其 agentic 超时进行约束。只有当刷新器停止,或在整个时间窗口内每次进度写入都失败时,该行才会超过 reaper 的限制。所有者进程已终止的行由启动恢复回收,而不是通过心跳年龄回收。任务行会让中断的子进程可见,但不会重新创建其执行过程。
遗留的委派结果文件若包含 status,在不存在任务记录时仍可读取。在记录 originator_route 之前创建的终态委派记录,仍可通过精确的任务 ID 读取,包括内联输出。这些记录不能通过遗留例外被取消或以其他方式修改,而非终态且 origin 为 NULL 的记录仍然保持隐藏。写入于发起者链存在之前的记录保留空链,并通过 originator_route 仅对创建者可见。新记录允许创建别名以及其链中记录的每个祖先别名进行读取和修改。
目标模式目标合约
ADR-008 认可任务控制平面将作为目标生命周期、所有权、路由、主体、父级关系和恢复资格的未来权威。存储库中包含目标存储和控制平面 API,但生产环境中的目标准入和执行尚未实现端到端打通。
后台路径只有在保留了所属目标关系,并将终端状态和模型使用情况回报给该目标之后,才能参与目标模式。在此之前,该路径只是普通的后台工作,而非目标模式执行。
变更检查清单
对于后台工作变更,请在评审者批准前回答以下问题:
- 哪个子系统负责生命周期管理,哪个存储是权威数据源?
- 这项工作是进程本地的、具备持久化监督的,还是确实能够在重启后恢复的?
- 哪个令牌或控制平面操作可以取消它,哪些内容可能仍在传输中?
- 此路径实际会填充哪些父任务、代理、路由、主体、递归深度和用量字段?
- 等待、审批、检查点、丢失、超时和终止状态是否可区分?
- 启动恢复是否会重复执行副作用,或静默地遗留一个未处理的 claim?
- 结果交付在重启后观察到完成时是否仍保持幂等性?
源指针
- Cron 调度器与持久化:
crates/zeroclaw-runtime/src/cron/scheduler.rs、crates/zeroclaw-runtime/src/cron/store.rs - SOP 引擎和运行存储:
crates/zeroclaw-runtime/src/sop/engine.rs、crates/zeroclaw-runtime/src/sop/store/ - 委派和子代理行为:委派与子代理、
crates/zeroclaw-runtime/src/tools/delegate.rs、crates/zeroclaw-runtime/src/tools/spawn_subagent.rs、crates/zeroclaw-runtime/src/subagent/mod.rs - 持久化任务控制平面与恢复:
crates/zeroclaw-runtime/src/control_plane/ - 目标模式决策:ADR-008
- SOP 操作员指南:SOPs 如何运行